/* RFC-1175: capability-gated autoplay for decorative background Do not handle feature-video playback — that is video-player.ts (Plyr + hls.js). Do not handle living photos — live-photos.ts owns [data-live-photo]. RFC-1175: initial implementation — saveData/reduced-motion/viewport-gated background autoplay. RFC-1175: close Lighthouse gaps — directive-allowlist robots (Schemamap retired, ROBOTS-SURFACE-01), capability-gated video (preload=none, data-bg-video, saveData/reduced-motion), single fetchpriority=high per page (IMG-DELIVERY-05), inlined shared texture token, 24px QR touch targets; RFC-1177 security-headers draft */ export interface BackgroundVideosOptions { prefersReducedMotion?: boolean; } function mayAutoplay(prefersReducedMotion: boolean): boolean { if (prefersReducedMotion) return false; const connection = typeof navigator === "undefined" ? undefined : (navigator as Navigator & { connection?: { saveData?: boolean } }).connection; return !connection?.saveData; } function play(video: HTMLVideoElement): void { // iOS Safari respects the autoplay attribute more than programmatic play() // (live-photos.ts precedent) — set it before calling play(). video.setAttribute("autoplay", ""); void video.play().catch(() => { /* Autoplay may still be rejected by browser policy — the poster stays. */ }); } function pause(video: HTMLVideoElement): void { video.removeAttribute("autoplay"); video.pause(); } export function initBackgroundVideos(options: BackgroundVideosOptions = {}): void { const videos = Array.from(document.querySelectorAll("video[data-bg-video]")); if (videos.length === 0) return; const prefersReducedMotion = options.prefersReducedMotion ?? window.matchMedia("(prefers-reduced-motion: reduce)").matches; if (!mayAutoplay(prefersReducedMotion)) return; if (!("IntersectionObserver" in window)) { for (const video of videos) play(video); return; } const observer = new IntersectionObserver( (entries) => { for (const entry of entries) { const video = entry.target as HTMLVideoElement; if (entry.isIntersecting) { play(video); } else { pause(video); } } }, { threshold: 0.1 }, ); for (const video of videos) observer.observe(video); document.addEventListener("visibilitychange", () => { if (!document.hidden) return; for (const video of videos) pause(video); }); // RFC-1175: smoke-test init marker (SMOKE-INIT-01) — set after the last // addEventListener so component-smoke can synchronize on init completion. document.documentElement.setAttribute("data-bg-video-initialized", "true"); }