/** * Vanilla scroll-scrubbed video (no GSAP). * Pins via CSS sticky; maps scroll progress through spacer to video.currentTime. */ export function initScrollScrub() { const root = document.querySelector("[data-scroll-scrub]"); if (!root) return; const video = root.querySelector("video"); const spacer = root.querySelector(".scrub-spacer"); if (!(video instanceof HTMLVideoElement) || !spacer) return; if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) { video.removeAttribute("autoplay"); video.controls = true; spacer.style.display = "none"; return; } let ready = false; let seeking = false; let target = 0; const onReady = () => { ready = true; video.pause(); }; if (video.readyState >= 2) onReady(); else video.addEventListener("loadeddata", onReady, { once: true }); video.addEventListener("seeked", () => { seeking = false; if (Math.abs(video.currentTime - target) > 0.04) { seeking = true; video.currentTime = target; } }); function update() { if (!ready || !video.duration) return; const rect = spacer.getBoundingClientRect(); const total = spacer.offsetHeight - window.innerHeight; const scrolled = -rect.top; const progress = total > 0 ? Math.min(1, Math.max(0, scrolled / total)) : 0; target = progress * video.duration; if (!seeking && Math.abs(video.currentTime - target) > 0.04) { seeking = true; video.currentTime = target; } } window.addEventListener("scroll", update, { passive: true }); window.addEventListener("resize", update); update(); }