diff --git a/css/main.css b/css/main.css index 3f79739..d52352b 100644 --- a/css/main.css +++ b/css/main.css @@ -463,7 +463,6 @@ body.light-theme::-webkit-scrollbar-thumb:hover, body.light-theme .item-details: ============================================== */ -/* Styles to manage hero loading state and content section visibility */ .hero.loading .hero-content { opacity: 0; } @@ -472,9 +471,8 @@ body.light-theme::-webkit-scrollbar-thumb:hover, body.light-theme .item-details: opacity: 1; } -/* Custom styles for settings modal descriptions */ #settingsModal .text-muted { - color: var(--text-primary); /* MODIFIED: Replaced 'white !important' for theme consistency */ + color: var(--text-primary); } #main-view { @@ -904,8 +902,10 @@ body.sidebar-collapsed #main-container { .item-poster { display: block; width: 100%; - height: 0; - padding-bottom: 150%; + height: auto; + aspect-ratio: 2 / 3; + object-fit: cover; + background-color: var(--secondary); background-size: cover; background-position: center; transition: transform 0.6s cubic-bezier(0.23, 1, 0.32, 1); @@ -915,12 +915,6 @@ body.sidebar-collapsed #main-container { transform: scale(1.08); } -.item-poster img { - display: block; - width: 100%; - height: auto; -} - .item-overlay { position: absolute; top: 0; @@ -3043,9 +3037,8 @@ body.miniplayer-active #musicPlayerContainer { #volumeSlider { -webkit-appearance: none; appearance: none; - /* Removed writing-mode: bt-lr; */ - width: 100px; /* Adjusted for horizontal orientation */ - height: 8px; /* Adjusted for horizontal orientation */ + width: 100px; + height: 8px; background: var(--glass); border-radius: 4px; } @@ -3408,8 +3401,6 @@ body.miniplayer-active { padding-bottom: 85px; } /* --- m3u-generator.css --- */ -/* M3U Generator Section */ - .m3u-animated-item { opacity: 0; transform: translateY(20px); @@ -3516,7 +3507,7 @@ body.miniplayer-active { padding-bottom: 85px; } margin-right: 0.8rem; background-color: var(--secondary); border: 1px solid var(--glass-border); - flex-shrink: 0; /* Prevent shrinking */ + flex-shrink: 0; } #m3u-libraries-container .form-check-label { @@ -3524,7 +3515,7 @@ body.miniplayer-active { padding-bottom: 85px; } cursor: pointer; color: var(--text-secondary); font-size: 0.9rem; - line-height: 1.2; /* Ensure consistent line height */ + line-height: 1.2; } #m3u-libraries-container .form-check-input:checked { @@ -3559,7 +3550,7 @@ body.miniplayer-active { padding-bottom: 85px; } font-family: 'Orbitron', sans-serif; font-size: 1.4rem; margin-bottom: 1.5rem; - color: white; /* Changed to white for better visibility */ + color: white; } .m3u-instructions { @@ -3579,19 +3570,18 @@ body.miniplayer-active { padding-bottom: 85px; } padding: 1rem; font-size: 1rem; border-radius: var(--border-radius-md); - transition: all 0.3s ease; /* Added for animation */ + transition: all 0.3s ease; } #download-m3u-btn:hover { - transform: translateY(-3px); /* Added for hover effect */ - box-shadow: var(--shadow-lg); /* Added for hover effect */ + transform: translateY(-3px); + box-shadow: var(--shadow-lg); } #download-m3u-btn span { margin-left: 0.5rem; } -/* Loading state for libraries */ #m3u-libraries-loader { display: flex; flex-direction: column; @@ -3607,7 +3597,6 @@ body.miniplayer-active { padding-bottom: 85px; } margin-bottom: 1rem; } -/* Light Theme */ .light-theme .m3u-config-panel { background: var(--secondary); } @@ -3638,7 +3627,6 @@ body.miniplayer-active { padding-bottom: 85px; } color: var(--accent-dark); } -/* Responsive */ @media (max-width: 992px) { .m3u-container { grid-template-columns: 1fr; @@ -3920,4 +3908,4 @@ body.miniplayer-active { padding-bottom: 85px; } .provider-card:hover .provider-tooltip { opacity: 1; transform: translateY(0); -} +} \ No newline at end of file diff --git a/js/state.js b/js/state.js index 8c7bde9..9a70c46 100644 --- a/js/state.js +++ b/js/state.js @@ -29,6 +29,7 @@ export const state = { localSeries: [], localArtists: [], localPhotos: [], + localContentLookup: new Set(), db: null, lastScrollPosition: 0, currentItemId: null, diff --git a/js/ui.js b/js/ui.js index 0c5b5f4..e2a0c89 100644 --- a/js/ui.js +++ b/js/ui.js @@ -64,6 +64,29 @@ export async function loadLocalContent() { state.jellyfinMovies = jfMovies; state.jellyfinSeries = jfSeries; + state.localContentLookup.clear(); + const normalize = (str) => str ? str.toLowerCase().trim().replace(/\s+/g, ' ') : ''; + + const processSource = (source) => { + if (!Array.isArray(source)) return; + source.forEach(server => { + if (server && Array.isArray(server.titulos)) { + server.titulos.forEach(t => { + if (t && t.title) { + const year = t.year ? String(t.year).slice(0, 4) : 'any'; + const lookupKey = `${normalize(t.title)}|${year}`; + state.localContentLookup.add(lookupKey); + } + }); + } + }); + }; + + processSource(state.localMovies); + processSource(state.localSeries); + processSource(state.jellyfinMovies); + processSource(state.jellyfinSeries); + } catch (error) { showNotification(_("errorLoadingLocalContent"), "error"); } @@ -79,7 +102,6 @@ export function resetView() { const heroBg1 = document.querySelector('.hero-background-1'); const heroBg2 = document.querySelector('.hero-background-2'); - // Hide all main content sections if (mainContent) { mainContent.style.display = 'none'; } @@ -93,12 +115,10 @@ export function resetView() { document.getElementById('providers-section').style.display = 'none'; document.getElementById('m3u-generator-section').style.display = 'none'; - // Show hero if enabled if (heroSection) { if (state.settings.showHero) { heroSection.style.display = 'flex'; - // Clear dynamic hero content and reset to default if (state.heroIntervalId) { clearInterval(state.heroIntervalId); state.heroIntervalId = null; @@ -148,7 +168,7 @@ export function switchView(viewType) { } if (mainContent) { - mainContent.style.display = 'block'; // Ensure main content is visible when switching views + mainContent.style.display = 'block'; } const sidebar = document.getElementById('sidebar-nav'); @@ -182,7 +202,6 @@ export function switchView(viewType) { if (el) el.style.display = 'none'; }); - // Explicitly reset the Providers section to its initial state before view switch const providersSection = document.getElementById('providers-section'); if (providersSection) { const detailsContainer = document.getElementById('provider-details-container'); @@ -271,7 +290,6 @@ export function switchView(viewType) { loadProviders(); break; case 'm3u-generator': - // The m3u-generator.js file handles its own initialization break; } @@ -453,31 +471,22 @@ function isContentAvailableLocally(title, type, year) { const normalize = (str) => str.toLowerCase().trim().replace(/\s+/g, ' '); const normalizedTitle = normalize(title); + const yearKey = year ? String(year).slice(0, 4) : 'any'; - const checkSource = (source, itemType) => { - if (!Array.isArray(source)) return false; - return source.some(server => - server && Array.isArray(server.titulos) && - server.titulos.some(t => { - if (!t || typeof t.title !== 'string') return false; - const localTitle = normalize(t.title); - if (localTitle !== normalizedTitle) return false; - if (year && t.year && Math.abs(parseInt(t.year) - parseInt(year)) > 1) return false; - if (itemType && t.type && t.type.toLowerCase() !== itemType.toLowerCase()) return false; - return true; - }) - ); - }; - - if (checkSource(state.localMovies, 'movie')) return true; - if (checkSource(state.localSeries, 'series')) return true; - if (checkSource(state.jellyfinMovies, 'movie')) return true; - if (checkSource(state.jellyfinSeries, 'series')) return true; + const lookupKey = `${normalizedTitle}|${yearKey}`; + + if (state.localContentLookup.has(lookupKey)) { + return true; + } + const lookupKeyAnyYear = `${normalizedTitle}|any`; + if (yearKey !== 'any' && state.localContentLookup.has(lookupKeyAnyYear)) { + return true; + } + return false; } - export function renderGrid(items, append = false, gridId = 'content-grid') { const grid = document.getElementById(gridId); if (!append) grid.innerHTML = ''; @@ -513,7 +522,7 @@ export function renderGrid(items, append = false, gridId = 'content-grid') { card.dataset.id = item.id; card.dataset.type = itemType; card.innerHTML = ` -
+ ${title} ${voteAvg >= 7.8 ? 'TOP' : ''} ${isAvailable ? ` ${_('local')}` : ''}
@@ -1274,7 +1283,6 @@ export async function initializeHeroSection() { const heroSection = document.getElementById('hero-section'); if (heroSection.style.display === 'none' || !state.settings.showHero) return; - // Clear existing timers for slide changes and initial load if (state.heroIntervalId) { clearInterval(state.heroIntervalId); state.heroIntervalId = null; @@ -1289,7 +1297,6 @@ export async function initializeHeroSection() { const content = document.querySelector('.hero-content'); const heroButtons = content.querySelector('.hero-buttons'); - // Set static background and show default content content.querySelector('.hero-title').textContent = _('heroWelcome'); content.querySelector('.hero-subtitle').textContent = _('heroSubtitle'); content.querySelector('#hero-rating').innerHTML = ''; @@ -1303,7 +1310,6 @@ export async function initializeHeroSection() { gsap.set(content, { autoAlpha: 1 }); heroSection.classList.remove('loading'); - // After 5 seconds, load the dynamic content if we are still on the home view state.heroLoadTimeoutId = setTimeout(() => { if (state.currentView === 'home') { loadTmdbHeroContent(); @@ -1318,7 +1324,7 @@ export async function initializeHeroSection() { const popularItems = data.results.filter(i => i.backdrop_path && i.overview).slice(0, 8); if (popularItems.length === 0) { - return; // Keep static image if no items + return; } let currentBg = bg1; @@ -1966,4 +1972,4 @@ export function showPrevPhoto() { export async function loadProviders() { const providers = await fetchAllProviders(state.settings.watchRegion); renderProviders(providers); -} +} \ No newline at end of file