/** * KIKO-style homepage promo carousel — progress bars, pause, video sync. */ export function initPromoSlider() { const root = document.querySelector("[data-promo-slider]"); if (!(root instanceof HTMLElement)) return; const allSlides = [...root.querySelectorAll("[data-phs-slide]")]; const allBars = [...root.querySelectorAll("[data-phs-goto]")]; const allFills = [...root.querySelectorAll("[data-phs-fill]")]; const isShown = (el) => el instanceof HTMLElement && getComputedStyle(el).display !== "none"; const visibleIdx = allSlides .map((slide, i) => (isShown(slide) ? i : -1)) .filter((i) => i >= 0); const slides = visibleIdx.map((i) => allSlides[i]); const bars = visibleIdx.map((i) => allBars[i]); const fills = visibleIdx.map((i) => allFills[i]); const toggle = root.querySelector("[data-phs-toggle]"); const iconPause = root.querySelector(".phs-icon-pause"); const iconPlay = root.querySelector(".phs-icon-play"); if (!slides.length) return; const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches; let index = 0; let paused = reduce; let raf = 0; let startedAt = 0; let elapsed = 0; const durationOf = (i) => { const raw = Number(slides[i]?.getAttribute("data-phs-duration") || 6500); return Number.isFinite(raw) && raw > 0 ? raw : 6500; }; const setToggleUi = () => { if (toggle instanceof HTMLElement) { toggle.setAttribute("aria-pressed", paused ? "true" : "false"); toggle.setAttribute("aria-label", paused ? "پخش اسلایدر" : "توقف اسلایدر"); } if (iconPause instanceof HTMLElement) iconPause.hidden = paused; if (iconPlay instanceof HTMLElement) iconPlay.hidden = !paused; }; const syncVideo = (activeIndex) => { slides.forEach((slide, i) => { const video = slide.querySelector("[data-phs-video]"); if (!(video instanceof HTMLVideoElement)) return; if (i === activeIndex && !paused) { video.muted = true; const p = video.play(); if (p && typeof p.catch === "function") p.catch(() => {}); } else { try { video.pause(); } catch { /* ignore */ } } }); }; const setProgress = (i, ratio) => { fills.forEach((fill, fi) => { if (!(fill instanceof HTMLElement)) return; let w = 0; if (fi < i) w = 1; else if (fi === i) w = Math.min(1, Math.max(0, ratio)); fill.style.transform = `scaleX(${w})`; }); }; const show = (next) => { const i = ((next % slides.length) + slides.length) % slides.length; index = i; elapsed = 0; startedAt = performance.now(); slides.forEach((slide, si) => { const on = si === i; slide.classList.toggle("is-active", on); slide.setAttribute("aria-hidden", on ? "false" : "true"); }); bars.forEach((bar, bi) => bar.classList.toggle("is-active", bi === i)); setProgress(i, 0); syncVideo(i); }; const tick = (now) => { raf = 0; if (paused) return; elapsed = now - startedAt; const dur = durationOf(index); setProgress(index, elapsed / dur); if (elapsed >= dur) { show(index + 1); } raf = requestAnimationFrame(tick); }; const start = () => { if (paused || reduce) return; if (!raf) { startedAt = performance.now() - elapsed; raf = requestAnimationFrame(tick); } }; const stop = () => { if (raf) { cancelAnimationFrame(raf); raf = 0; } }; bars.forEach((bar, i) => { bar.addEventListener("click", () => { stop(); show(i); start(); }); }); toggle?.addEventListener("click", () => { paused = !paused; setToggleUi(); if (paused) { stop(); elapsed = performance.now() - startedAt; syncVideo(index); } else { start(); syncVideo(index); } }); // Pause autoplay when off-screen const io = new IntersectionObserver( (entries) => { const e = entries[0]; if (!e) return; if (!e.isIntersecting) { stop(); syncVideo(-1); } else if (!paused) { start(); syncVideo(index); } }, { threshold: 0.2 } ); io.observe(root); setToggleUi(); show(0); if (!reduce) start(); }