feat: Add comprehensive documentation suite and reorganize project structure
- Created complete documentation in docs/ directory - Added PROJECT_OVERVIEW.md with feature highlights and getting started guide - Added ARCHITECTURE.md with system design and technical details - Added SECURITY.md with comprehensive security implementation guide - Added DEVELOPMENT.md with development workflows and best practices - Added DEPLOYMENT.md with production deployment instructions - Added API.md with complete REST API documentation - Added CONTRIBUTING.md with contribution guidelines - Added CHANGELOG.md with version history and migration notes - Reorganized all documentation files into docs/ directory for better organization - Updated README.md with proper documentation links and quick navigation - Enhanced project structure with professional documentation standards
This commit is contained in:
383
f_templates/tpl_frontend/tpl_index_personalized.tpl
Normal file
383
f_templates/tpl_frontend/tpl_index_personalized.tpl
Normal file
@@ -0,0 +1,383 @@
|
||||
{*
|
||||
/*******************************************************************************************************************
|
||||
| Software Name : EasyStream
|
||||
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
|
||||
| Software Author : (c) Sami Ahmed
|
||||
|*******************************************************************************************************************
|
||||
| This source file is subject to the EasyStream Proprietary License Agreement.
|
||||
|*******************************************************************************************************************
|
||||
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|
||||
|*******************************************************************************************************************/
|
||||
*}
|
||||
|
||||
{* Personalized Homepage Template *}
|
||||
|
||||
<div class="personalized-homepage">
|
||||
{* Hero Section / Featured Content *}
|
||||
<div class="hero-section">
|
||||
{generate_html type="home_layout"}
|
||||
</div>
|
||||
|
||||
{* Personalized Content Sections *}
|
||||
<div class="content-sections">
|
||||
{foreach from=$personalized_sections key=section_key item=section}
|
||||
<section class="content-section section-{$section_key}" data-type="{$section.type}">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">{$section.title}</h2>
|
||||
{if $section_key == 'for_you' && $is_logged_in}
|
||||
<div class="section-info" title="Personalized based on your watch history, likes, and subscriptions">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zM7 4v1h2V4H7zm0 2v6h2V6H7z"/>
|
||||
</svg>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="section-content">
|
||||
{if !empty($section.items)}
|
||||
<div class="content-grid {if $section.type == 'short'}shorts-grid{else}videos-grid{/if}">
|
||||
{foreach from=$section.items item=item}
|
||||
{if $section.type == 'mixed'}
|
||||
{* Continue Watching Items *}
|
||||
<div class="content-item continue-watching-item">
|
||||
<a href="{$main_url}/watch?v={$item.file_key}" class="item-thumbnail">
|
||||
<img src="{$main_url}/f_data/data_userfiles/{$item.usr_key}/t/{$item.file_key}/0.jpg"
|
||||
alt="{$item.file_title|escape}"
|
||||
loading="lazy">
|
||||
|
||||
{* Progress Bar *}
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" style="width: {$item.watch_progress}%"></div>
|
||||
</div>
|
||||
|
||||
{* File Type Badge *}
|
||||
<span class="type-badge">{$item.file_type|upper}</span>
|
||||
</a>
|
||||
<div class="item-info">
|
||||
<h3 class="item-title">
|
||||
<a href="{$main_url}/watch?v={$item.file_key}">{$item.file_title|escape|truncate:60}</a>
|
||||
</h3>
|
||||
<div class="item-meta">
|
||||
<a href="{$main_url}/profile?user={$item.usr_user}" class="channel-name">{$item.usr_dname|escape}</a>
|
||||
<span class="progress-text">{$item.watch_progress}% watched</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{else}
|
||||
{* Regular Video/Short/Live Items *}
|
||||
<div class="content-item {$section.type}-item">
|
||||
<a href="{$main_url}/watch?v={$item.file_key}" class="item-thumbnail">
|
||||
<img src="{$main_url}/f_data/data_userfiles/{$item.usr_key}/t/{$item.file_key}/0.jpg"
|
||||
alt="{$item.file_title|escape}"
|
||||
loading="lazy">
|
||||
|
||||
{if $item.file_duration}
|
||||
<span class="duration">{$item.file_duration|date_format:"%M:%S"}</span>
|
||||
{/if}
|
||||
|
||||
{if $section.type == 'live' && $item.stream_live == 1}
|
||||
<span class="live-badge">LIVE</span>
|
||||
{/if}
|
||||
</a>
|
||||
<div class="item-info">
|
||||
<h3 class="item-title">
|
||||
<a href="{$main_url}/watch?v={$item.file_key}">{$item.file_title|escape|truncate:60}</a>
|
||||
</h3>
|
||||
<div class="item-meta">
|
||||
<a href="{$main_url}/profile?user={$item.usr_user}" class="channel-name">{$item.usr_dname|escape}</a>
|
||||
<div class="stats">
|
||||
<span class="views">{$item.file_views|number_format} views</span>
|
||||
{if $item.upload_date}
|
||||
<span class="separator">•</span>
|
||||
<span class="date">{$item.upload_date|date_format:"%b %d, %Y"}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/foreach}
|
||||
</div>
|
||||
{else}
|
||||
<div class="empty-section">
|
||||
<p>No content available in this section yet.</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
{/foreach}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Personalized Homepage Styles */
|
||||
.personalized-homepage {
|
||||
max-width: 1920px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.content-sections {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.content-section {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #030303;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.section-info {
|
||||
color: #606060;
|
||||
cursor: help;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
/* Video Grid Layout */
|
||||
.videos-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* Shorts Grid Layout */
|
||||
.shorts-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.content-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.item-thumbnail {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-bottom: 56.25%; /* 16:9 aspect ratio */
|
||||
background: #f0f0f0;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.shorts-grid .item-thumbnail {
|
||||
padding-bottom: 177.78%; /* 9:16 aspect ratio for shorts */
|
||||
}
|
||||
|
||||
.item-thumbnail img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.content-item:hover .item-thumbnail img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.duration,
|
||||
.live-badge,
|
||||
.type-badge {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.live-badge {
|
||||
background: #cc0000;
|
||||
}
|
||||
|
||||
.type-badge {
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
bottom: auto;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
font-size: 10px;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
/* Progress Bar for Continue Watching */
|
||||
.progress-bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: #cc0000;
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
.item-info {
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.item-title a {
|
||||
color: #030303;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.item-title a:hover {
|
||||
color: #cc0000;
|
||||
}
|
||||
|
||||
.item-meta {
|
||||
font-size: 13px;
|
||||
color: #606060;
|
||||
}
|
||||
|
||||
.channel-name {
|
||||
color: #606060;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.channel-name:hover {
|
||||
color: #030303;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.separator {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
color: #065fd4;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.empty-section {
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
color: #606060;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 1024px) {
|
||||
.videos-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.videos-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.shorts-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.videos-grid,
|
||||
.shorts-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.personalized-homepage {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark Mode Support */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.section-header {
|
||||
border-bottom-color: #303030;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
color: #f1f1f1;
|
||||
}
|
||||
|
||||
.item-title a {
|
||||
color: #f1f1f1;
|
||||
}
|
||||
|
||||
.item-thumbnail {
|
||||
background: #1f1f1f;
|
||||
}
|
||||
|
||||
.empty-section {
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Lazy loading for images
|
||||
if ('IntersectionObserver' in window) {
|
||||
const imageObserver = new IntersectionObserver((entries, observer) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const img = entry.target;
|
||||
img.src = img.dataset.src || img.src;
|
||||
observer.unobserve(img);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.item-thumbnail img').forEach(img => {
|
||||
imageObserver.observe(img);
|
||||
});
|
||||
}
|
||||
|
||||
// Track video clicks for improving recommendations
|
||||
document.querySelectorAll('.content-item a').forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
const fileKey = this.href.match(/v=([^&]+)/);
|
||||
if (fileKey && fileKey[1]) {
|
||||
// Optional: Send analytics event
|
||||
// fetch('/api/track_click.php?v=' + fileKey[1]);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user