/** * KV-Stream - Hero Billboard Component * Modern Apple TV+ / Netflix inspired hero section */ /** * Create a modern hero section with featured content * @param {Object|Array} featuredItems - Featured video object or array * @param {Function} onPlay - Callback when play is clicked * @param {Function} onInfo - Callback when more info is clicked * @param {string} modifier - Optional CSS class for variants * @returns {HTMLElement} Hero section element */ export function createHeroSection(featuredItems, onPlay, onInfo, modifier = '') { const hero = document.createElement('section'); hero.className = `hero-billboard ${modifier}`; hero.id = 'heroSection'; // Normalize input to array const items = Array.isArray(featuredItems) ? featuredItems : [featuredItems]; if (items.length === 0 || !items[0]) { return hero; } // Get first featured item for display const featured = items[0]; const backdropUrl = featured.backdrop || featured.thumbnail || featured.poster_url || ''; const year = featured.year || new Date().getFullYear(); const rating = featured.rating ? `${featured.rating}★` : ''; const quality = featured.resolution || featured.quality || 'HD'; const genre = featured.genre || featured.category || ''; const duration = featured.duration || ''; // Build meta items const metaItems = [quality, year, genre, duration, rating].filter(Boolean); hero.innerHTML = `
${featured.title}

${featured.title}

${metaItems.map((item, i) => ` ${item} ${i < metaItems.length - 1 ? '' : ''} `).join('')}

${featured.description || ''}

${items.length > 1 ? createSliderDots(items) : ''} `; // Add styles addHeroStyles(); // Setup slider if multiple items if (items.length > 1) { setupSlider(hero, items, onPlay, onInfo); } else { // Single item events setupSingleItemEvents(hero, featured, onPlay, onInfo); } return hero; } function createSliderDots(items) { return `
${items.map((_, i) => ` `).join('')}
`; } function setupSingleItemEvents(hero, featured, onPlay, onInfo) { const playBtn = hero.querySelector('[data-action="play"]'); const infoBtn = hero.querySelector('[data-action="info"]'); if (playBtn) { playBtn.addEventListener('click', () => onPlay && onPlay(featured)); } if (infoBtn) { infoBtn.addEventListener('click', () => onInfo && onInfo(featured)); } } function setupSlider(hero, items, onPlay, onInfo) { let currentIndex = 0; let interval; const dots = hero.querySelectorAll('.hero-billboard__dot'); const showSlide = (index) => { if (index < 0) index = items.length - 1; if (index >= items.length) index = 0; currentIndex = index; const featured = items[index]; const backdropUrl = featured.backdrop || featured.thumbnail || featured.poster_url || ''; // Update content const backdrop = hero.querySelector('.hero-billboard__backdrop img'); const title = hero.querySelector('.hero-billboard__title'); const description = hero.querySelector('.hero-billboard__description'); const playBtn = hero.querySelector('[data-action="play"]'); const infoBtn = hero.querySelector('[data-action="info"]'); if (backdrop) backdrop.src = backdropUrl; if (title) title.textContent = featured.title; if (description) description.textContent = featured.description || ''; if (playBtn) playBtn.dataset.id = featured.id; if (infoBtn) infoBtn.dataset.id = featured.id; // Update dots dots.forEach((dot, i) => dot.classList.toggle('active', i === index)); }; const startAutoPlay = () => { if (interval) clearInterval(interval); interval = setInterval(() => showSlide(currentIndex + 1), 8000); }; startAutoPlay(); // Dot click events dots.forEach(dot => { dot.addEventListener('click', () => { const idx = parseInt(dot.dataset.index); showSlide(idx); startAutoPlay(); }); }); // Button events hero.addEventListener('click', (e) => { const playBtn = e.target.closest('[data-action="play"]'); const infoBtn = e.target.closest('[data-action="info"]'); if (playBtn && onPlay) { onPlay(items[currentIndex]); } else if (infoBtn && onInfo) { onInfo(items[currentIndex]); } }); } function addHeroStyles() { if (document.getElementById('hero-billboard-styles')) return; const styles = document.createElement('style'); styles.id = 'hero-billboard-styles'; styles.textContent = ` .hero-billboard { position: relative; width: 100%; /* Fluid height: scales with viewport, min 300px, max 85vh */ height: clamp(300px, 60vh, 85vh); overflow: hidden; margin-bottom: clamp(10px, 2vw, 30px); } .hero-billboard__backdrop { position: absolute; inset: 0; } .hero-billboard__backdrop img { width: 100%; height: 100%; object-fit: cover; object-position: center 20%; } .hero-billboard__gradient { position: absolute; inset: 0; background: linear-gradient( to right, rgba(0, 0, 0, 0.95) 0%, rgba(0, 0, 0, 0.7) 25%, rgba(0, 0, 0, 0.3) 50%, transparent 75% ), linear-gradient( to top, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.6) 15%, transparent 50% ); } .hero-billboard__content { position: absolute; inset: 0; display: flex; align-items: center; /* Fluid padding: scales with viewport */ padding: 0 clamp(20px, 5vw, 80px); } .hero-billboard__info { /* Fluid max-width: scales between 280px and 600px based on viewport */ max-width: clamp(280px, 40vw, 600px); z-index: 2; } .hero-billboard__title { /* Fluid font-size: scales dynamically with screen */ font-size: clamp(1.5rem, 4vw, 3.5rem); font-weight: 700; color: #fff; margin: 0 0 clamp(8px, 1.5vw, 20px) 0; line-height: 1.15; text-shadow: 0 2px 10px rgba(0,0,0,0.6); } .hero-billboard__meta { display: flex; align-items: center; flex-wrap: wrap; gap: clamp(4px, 0.8vw, 10px); margin-bottom: clamp(8px, 1.5vw, 20px); } .hero-billboard__meta-item { font-size: clamp(0.7rem, 1vw, 1rem); color: rgba(255,255,255,0.9); font-weight: 500; } .hero-billboard__meta-item:first-child { background: #0071e3; padding: clamp(2px, 0.4vw, 6px) clamp(6px, 0.8vw, 12px); border-radius: 4px; font-size: clamp(0.65rem, 0.9vw, 0.85rem); font-weight: 600; } .hero-billboard__meta-dot { color: rgba(255,255,255,0.5); font-size: clamp(0.6rem, 0.8vw, 0.85rem); } .hero-billboard__description { font-size: clamp(0.85rem, 1.1vw, 1.1rem); color: rgba(255,255,255,0.8); line-height: 1.5; margin: 0 0 clamp(12px, 2vw, 28px) 0; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; } .hero-billboard__actions { display: flex; gap: clamp(8px, 1vw, 14px); flex-wrap: wrap; } .hero-billboard__btn { display: inline-flex; align-items: center; gap: clamp(6px, 0.6vw, 10px); padding: clamp(10px, 1.2vw, 16px) clamp(16px, 2vw, 32px); border: none; border-radius: clamp(6px, 0.6vw, 10px); font-size: clamp(0.85rem, 1vw, 1.1rem); font-weight: 600; cursor: pointer; transition: all 0.2s ease; white-space: nowrap; } .hero-billboard__btn--primary { background: #fff; color: #000; } .hero-billboard__btn--primary:hover { background: rgba(255,255,255,0.9); transform: scale(1.02); } .hero-billboard__btn--secondary { background: rgba(255,255,255,0.15); color: #fff; backdrop-filter: blur(10px); } .hero-billboard__btn--secondary:hover { background: rgba(255,255,255,0.25); } .hero-billboard__btn svg { flex-shrink: 0; width: clamp(18px, 1.5vw, 24px); height: clamp(18px, 1.5vw, 24px); } .hero-billboard__dots { position: absolute; bottom: clamp(15px, 3vh, 35px); left: 50%; transform: translateX(-50%); display: flex; gap: clamp(5px, 0.6vw, 10px); z-index: 10; } .hero-billboard__dot { width: clamp(6px, 0.6vw, 10px); height: clamp(6px, 0.6vw, 10px); border-radius: 50%; border: none; background: rgba(255,255,255,0.4); cursor: pointer; transition: all 0.3s ease; padding: 0; } .hero-billboard__dot.active { background: #fff; width: clamp(16px, 2vw, 28px); border-radius: 4px; } .hero-billboard__dot:hover { background: rgba(255,255,255,0.7); } /* Small variant for category pages */ .hero-billboard.hero--small { height: clamp(200px, 35vh, 400px); } /* Mobile-specific adjustments (small screens need content at bottom) */ @media (max-width: 600px) { .hero-billboard__content { align-items: flex-end; padding-bottom: clamp(50px, 10vh, 80px); } .hero-billboard__info { max-width: 100%; } .hero-billboard__description { -webkit-line-clamp: 2; } .hero-billboard__actions { width: 100%; } .hero-billboard__btn { flex: 1; justify-content: center; } } `; document.head.appendChild(styles); } export function initHeroCarousel(container, featuredVideos, onPlay, onInfo) { if (!featuredVideos || featuredVideos.length === 0) return; const hero = createHeroSection(featuredVideos, onPlay, onInfo); container.innerHTML = ''; container.appendChild(hero); }