diff --git a/static/css/style.css b/static/css/style.css index ce200fb..5b39747 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -58,6 +58,8 @@ html { font-size: 16px; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + background-color: var(--yt-bg-primary); + /* Fix white bar issue */ } body { @@ -66,6 +68,7 @@ body { color: var(--yt-text-primary); line-height: 1.4; overflow-x: hidden; + min-height: 100vh; } a { @@ -194,7 +197,7 @@ button { .yt-search-input { flex: 1; height: 40px; - background: var(--yt-bg-primary); + background: var(--yt-bg-secondary); border: 1px solid var(--yt-border); border-right: none; border-radius: 20px 0 0 20px; @@ -371,6 +374,7 @@ button { padding: 12px 0 24px; overflow-x: auto; scrollbar-width: none; + flex-wrap: nowrap; -ms-overflow-style: none; } @@ -430,7 +434,12 @@ button { width: 100%; height: 100%; object-fit: cover; - transition: transform 0.3s ease; + opacity: 0; + transition: opacity 0.5s ease, transform 0.3s ease; +} + +.yt-thumbnail.loaded { + opacity: 1; } .yt-video-card:hover .yt-thumbnail { @@ -819,11 +828,28 @@ button { @media (max-width: 768px) { .yt-header-center { - display: none; + display: flex; + /* Show search on mobile */ + margin: 0 8px; + max-width: none; + flex: 1; + justify-content: center; + } + + .yt-search-input { + padding: 0 12px; + font-size: 14px; + border-radius: 18px 0 0 18px; + } + + .yt-search-btn { + width: 48px; + border-radius: 0 18px 18px 0; } .yt-mobile-search { - display: flex; + display: none; + /* Hide icon since bar is visible */ } .yt-signin-text { @@ -831,8 +857,9 @@ button { } .yt-video-grid { - grid-template-columns: 1fr; - gap: 0; + grid-template-columns: repeat(2, 1fr); + gap: 8px; + padding: 0 4px; } .yt-video-card { @@ -851,6 +878,10 @@ button { .yt-categories { padding: 8px 0 16px; gap: 8px; + display: flex; + flex-wrap: nowrap; + overflow-x: auto; + white-space: nowrap; } .yt-category-pill { @@ -1170,4 +1201,131 @@ button { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} + +/* Floating Back Button */ +.yt-floating-back { + position: fixed; + bottom: 24px; + right: 24px; + width: 56px; + height: 56px; + background: var(--yt-accent-blue); + color: white; + border-radius: 50%; + display: none; + /* Hidden on desktop */ + align-items: center; + justify-content: center; + font-size: 20px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); + z-index: 2000; + cursor: pointer; + transition: transform 0.2s, background 0.2s; + border: none; +} + +.yt-floating-back:active { + transform: scale(0.95); + background: #2c95dd; +} + +@media (max-width: 768px) { + .yt-floating-back { + display: flex; + /* Show only on mobile */ + } +} + +/* Mobile V2 Overrides */ +@media (max-width: 768px) { + .yt-video-grid { + grid-template-columns: repeat(2, 1fr) !important; + gap: 8px !important; + padding: 0 4px !important; + } + + .yt-video-card { + padding: 4px !important; + } + + .yt-categories { + display: flex !important; + flex-wrap: nowrap !important; + grid-template-rows: none !important; + padding-right: 16px !important; + white-space: nowrap !important; + } + + .yt-floating-back { + background: var(--yt-accent-red) !important; + } + + .yt-floating-back:active { + background: #cc0000 !important; + } +} + +/* Mobile V4 Overrides - Pixel Perfect Polish */ +@media (max-width: 768px) { + + /* Optimize Categories */ + .yt-categories { + padding: 8px 0 8px 8px !important; + /* Left padding for start, no right padding */ + width: 100% !important; + mask-image: linear-gradient(to right, black 95%, transparent 100%); + -webkit-mask-image: linear-gradient(to right, black 95%, transparent 100%); + } + + .yt-chip { + font-size: 12px !important; + padding: 6px 12px !important; + height: 30px !important; + border-radius: 6px !important; + } + + /* Maximize Thumbnails */ + .yt-main { + padding: 0 !important; + /* Edge to edge */ + } + + .yt-video-grid { + gap: 1px !important; + /* Minimal gap */ + padding: 0 !important; + background: var(--yt-bg-primary); + } + + .yt-video-card, + .skeleton-card { + padding: 0 !important; + margin-bottom: 4px !important; + } + + .yt-thumbnail-container, + .skeleton-thumb { + border-radius: 6px !important; + } + + .yt-video-details { + padding: 6px 8px 12px !important; + } + + .yt-video-title { + font-size: 13px !important; + line-height: 1.2 !important; + } + + /* Reduce header padding to match */ + .yt-header { + padding: 0 12px !important; + } + + /* Filter bar spacing */ + .yt-filter-bar { + padding-left: 0 !important; + padding-right: 0 !important; + } } \ No newline at end of file diff --git a/static/js/main.js b/static/js/main.js index c471786..218e402 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -36,6 +36,31 @@ let currentPage = 1; let isLoading = false; let hasMore = true; // Track if there are more videos to load +// --- Lazy Loading --- +const imageObserver = new IntersectionObserver((entries, observer) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const img = entry.target; + const src = img.getAttribute('data-src'); + if (src) { + img.src = src; + img.onload = () => img.classList.add('loaded'); + img.removeAttribute('data-src'); + } + observer.unobserve(img); + } + }); +}, { + rootMargin: '50px 0px', + threshold: 0.1 +}); + +window.observeImages = function () { + document.querySelectorAll('img[data-src]').forEach(img => { + imageObserver.observe(img); + }); +}; + // --- Infinite Scroll --- function initInfiniteScroll() { const observer = new IntersectionObserver((entries) => { @@ -234,7 +259,7 @@ async function loadTrending(reset = true) { card.innerHTML = `
- ${escapeHtml(video.title)} + ${escapeHtml(video.title)} ${video.duration ? `${video.duration}` : ''}
@@ -255,6 +280,7 @@ async function loadTrending(reset = true) { sectionDiv.appendChild(scrollContainer); resultsArea.appendChild(sectionDiv); }); + if (window.observeImages) window.observeImages(); return; } @@ -299,7 +325,7 @@ function displayResults(videos, append = false) { card.style.width = '100%'; card.style.maxWidth = '200px'; card.innerHTML = ` - +

${escapeHtml(video.title)}

${formatViews(video.view_count)} views

`; @@ -308,7 +334,7 @@ function displayResults(videos, append = false) { card.className = 'yt-video-card'; card.innerHTML = `
- ${escapeHtml(video.title)} + ${escapeHtml(video.title)} ${video.duration ? `${video.duration}` : ''}
@@ -337,6 +363,8 @@ function displayResults(videos, append = false) { }); resultsArea.appendChild(card); }); + + if (window.observeImages) window.observeImages(); } // Format view count (YouTube style) @@ -425,28 +453,51 @@ function initTheme() { let savedTheme = localStorage.getItem('theme'); // If no saved preference, use Time of Day (Auto) - // Approximation: 6 AM to 6 PM is Light (Sunrise/Sunset) if (!savedTheme) { const hour = new Date().getHours(); savedTheme = (hour >= 6 && hour < 18) ? 'light' : 'dark'; } - document.documentElement.setAttribute('data-theme', savedTheme); + setTheme(savedTheme, false); // Initial set without saving (already saved or computed) +} - // Update toggle if exists - const toggle = document.getElementById('themeToggle'); - if (toggle) { - toggle.checked = savedTheme === 'dark'; +function setTheme(theme, save = true) { + document.documentElement.setAttribute('data-theme', theme); + if (save) { + localStorage.setItem('theme', theme); + } + + // Update UI Buttons (if on settings page) + const btnLight = document.getElementById('themeBtnLight'); + const btnDark = document.getElementById('themeBtnDark'); + + if (btnLight && btnDark) { + btnLight.classList.remove('active'); + btnDark.classList.remove('active'); + + if (theme === 'light') btnLight.classList.add('active'); + else btnDark.classList.add('active'); } } -function toggleTheme() { - const current = document.documentElement.getAttribute('data-theme'); - const newTheme = current === 'light' ? 'dark' : 'light'; +// Ensure theme persists on back navigation (BFCache) +window.addEventListener('pageshow', (event) => { + // Re-apply theme from storage to ensure it matches user preference + // even if page was restored from cache with old state + const savedTheme = localStorage.getItem('theme'); + if (savedTheme) { + setTheme(savedTheme, false); + } else { + initTheme(); + } +}); - document.documentElement.setAttribute('data-theme', newTheme); - localStorage.setItem('theme', newTheme); -} +// Sync across tabs +window.addEventListener('storage', (event) => { + if (event.key === 'theme') { + setTheme(event.newValue, false); + } +}); // --- Profile Logic --- async function updateProfile(e) { diff --git a/templates/index.html b/templates/index.html index 2fd71de..d5b9504 100644 --- a/templates/index.html +++ b/templates/index.html @@ -42,26 +42,11 @@ -
-
-

Shorts

-
-
- -
- -
- -
-
+
-
+
@@ -356,39 +341,44 @@ border-radius: 12px; object-fit: cover; background: var(--yt-bg-secondary); + opacity: 0; + transition: opacity 0.5s ease; } - .yt-short-title { - font-size: 14px; - font-weight: 500; - margin-top: 8px; - display: -webkit-box; - -webkit-line-clamp: 2; - line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - } + .yt-short-thumb.loaded { + opacity: 1; - .yt-short-views { - font-size: 12px; - color: var(--yt-text-secondary); - margin-top: 4px; - } - - @media (max-width: 768px) { - .yt-shorts-arrow { - display: none; + .yt-short-title { + font-size: 14px; + font-weight: 500; + margin-top: 8px; + display: -webkit-box; + -webkit-line-clamp: 2; + line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; } - .yt-filter-bar { - padding: 0 10px; - top: 56px; + .yt-short-views { + font-size: 12px; + color: var(--yt-text-secondary); + margin-top: 4px; } - .yt-sort-container { - /* Legacy override if needed */ + @media (max-width: 768px) { + .yt-shorts-arrow { + display: none; + } + + .yt-filter-bar { + padding: 0 10px; + top: 56px; + } + + .yt-sort-container { + /* Legacy override if needed */ + } } - } @@ -428,17 +430,7 @@ } // --- Back Button Logic --- - document.addEventListener('DOMContentLoaded', () => { - const path = window.location.pathname; - const menuBtn = document.querySelector('.yt-menu-btn'); - const backBtnHeader = document.getElementById('headerBackBtn'); - - // If not home, swap menu for back - if (path !== '/' && backBtnHeader) { - if (menuBtn) menuBtn.style.display = 'none'; - backBtnHeader.style.display = 'flex'; - } - }); + // Back Button Logic Removed (Handled Server-Side)