// Fixed ping-pong background video. Desktop only (≥768px), paused under reduced motion. // Path is reported on . function HeroVideo({ src, poster }) { const vRef = React.useRef(null), seamRef = React.useRef(null); const [desk, setDesk] = React.useState(() => matchMedia('(min-width:768px)').matches); React.useEffect(() => { const mq = matchMedia('(min-width:768px)'); const on = () => setDesk(mq.matches); mq.addEventListener('change', on); return () => mq.removeEventListener('change', on); }, []); React.useEffect(() => { const v = vRef.current, seam = seamRef.current, root = document.documentElement; const report = (m) => { root.setAttribute('data-hero-video', m); console.info('[hero-video] path: ' + m); }; if (!desk) { report('static (mobile)'); return; } if (matchMedia('(prefers-reduced-motion: reduce)').matches) { report('static (reduced motion)'); return; } let raf, cleanup = []; const listen = (ev, fn) => { v.addEventListener(ev, fn); cleanup.push(() => v.removeEventListener(ev, fn)); }; const pingPong = () => { let fwd = true; report('pingpong'); listen('timeupdate', () => { if (fwd && v.currentTime >= v.duration - 0.05) { fwd = false; v.playbackRate = -1; v.play(); } if (!fwd && v.currentTime <= 0.05) { fwd = true; v.playbackRate = 1; v.play(); } }); listen('ended', () => { if (fwd) { fwd = false; v.playbackRate = -1; v.play(); } }); }; const crossfade = () => { report('crossfade'); try { v.playbackRate = 1; } catch (e) {} let fading = false; const tick = () => { if (v.duration && !fading && v.currentTime >= v.duration - 0.6) { fading = true; seam.classList.add('is-on'); } raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); listen('ended', () => { v.currentTime = 0; const off = () => requestAnimationFrame(() => { seam.classList.remove('is-on'); fading = false; }); v.play().then(off, off); }); }; const test = () => { const t0 = v.currentTime; try { v.playbackRate = -1; } catch (e) { return crossfade(); } if (v.playbackRate !== -1) return crossfade(); setTimeout(() => { const ok = v.currentTime < t0 - 0.05; try { v.playbackRate = 1; } catch (e) {} ok ? pingPong() : crossfade(); }, 300); }; let tested = false; listen('timeupdate', () => { if (!tested && v.currentTime >= 0.8) { tested = true; test(); } }); v.play().catch(() => report('static (autoplay blocked)')); return () => { cancelAnimationFrame(raf); cleanup.forEach(f => f()); }; }, [desk]); if (!desk) return null; return (
); } window.HeroVideo = HeroVideo;