/* ========================================================= External Video Loader (FIXED VERSION) - Hero: immediate attach + autoplay + loop - Non-hero: lazy load via IntersectionObserver - Non-hero: pause on leave (NO src removal) - Mobile video support via .video-data-url-mob (<= 767px) - Safe resize switching ========================================================= */ window.Webflow ||= []; window.Webflow.push(() => { const wraps = Array.from(document.querySelectorAll(".external-video_wrap")); if (!wraps.length) return; /* ----------------------------------------- Global flags ----------------------------------------- */ const prefersReducedMotion = window.matchMedia( "(prefers-reduced-motion: reduce)" ).matches; const saveData = !!(navigator.connection && navigator.connection.saveData); const MOBILE_BREAKPOINT = 767; const HERO_AUTOPLAY_DEFAULT = true; const IO_ROOT_MARGIN_NON_HERO = "0px 0px"; const IO_THRESHOLD_NON_HERO = 0.25; const raf2 = (fn) => requestAnimationFrame(() => requestAnimationFrame(fn)); /* ----------------------------------------- Helpers ----------------------------------------- */ function isMobileViewport() { return window.innerWidth <= MOBILE_BREAKPOINT; } function getDesktopUrl(wrap) { return (wrap.querySelector(".video-data-url")?.textContent || "").trim(); } function getMobileUrl(wrap) { return ( wrap.querySelector(".video-data-url-mob")?.textContent || "" ).trim(); } function resolveUrl(item) { if (isMobileViewport() && item.mobileUrl) return item.mobileUrl; return item.desktopUrl; } function getVideo(wrap) { return wrap.querySelector("video"); } function getSource(video) { return video?.querySelector("source") || null; } function baseVideoAttrs(video) { if (!video) return; video.muted = true; video.playsInline = true; video.setAttribute("muted", ""); video.setAttribute("playsinline", ""); video.autoplay = false; video.loop = false; video.removeAttribute("autoplay"); video.removeAttribute("loop"); video.preload = "none"; video.setAttribute("preload", "none"); } function attachVideo(video, url, preload = "metadata") { if (!video || !url) return; const source = getSource(video); const current = source?.getAttribute("src") || video.getAttribute("src") || video.currentSrc || ""; if (current !== url) { if (source) source.setAttribute("src", url); else video.setAttribute("src", url); } video.preload = preload; video.setAttribute("preload", preload); try { video.load(); } catch (e) {} } function ensureLoop(video) { if (!video) return; video.loop = true; video.setAttribute("loop", ""); } function shouldAutoplay(wrap) { if (wrap.hasAttribute("data-external-video-force-play")) return true; return !(prefersReducedMotion || saveData); } function pauseSoft(video) { if (!video) return; try { video.pause(); } catch (e) {} } function playSafely(item) { const { wrap, video } = item; if (!video || !shouldAutoplay(wrap)) return; item._playToken = (item._playToken || 0) + 1; const token = item._playToken; const attempt = () => { if (item._playToken !== token) return; try { video.currentTime = 0; } catch (e) {} const p = video.play?.(); if (p?.catch) p.catch(() => {}); }; if (video.readyState >= 2) { raf2(attempt); } else { video.addEventListener("loadeddata", () => raf2(attempt), { once: true }); } } /* ----------------------------------------- Build items ----------------------------------------- */ const items = wraps .map((wrap) => { const video = getVideo(wrap); const desktopUrl = getDesktopUrl(wrap); if (!video || !desktopUrl) return null; const mobileUrl = getMobileUrl(wrap); const isHero = wrap.hasAttribute("data-external-video-hero") || video.classList.contains("external-video-item-hero"); baseVideoAttrs(video); return { wrap, video, desktopUrl, mobileUrl, isHero, isAttached: false, _ioSeen: false, _playToken: 0, }; }) .filter(Boolean); if (!items.length) return; /* ----------------------------------------- HERO: immediate attach ----------------------------------------- */ items.forEach((it) => { if (!it.isHero) return; const url = resolveUrl(it); attachVideo(it.video, url, "auto"); it.isAttached = true; if (HERO_AUTOPLAY_DEFAULT) { ensureLoop(it.video); it.video.autoplay = true; it.video.setAttribute("autoplay", ""); playSafely(it); } }); /* ----------------------------------------- NON-HERO: lazy + pause only ----------------------------------------- */ const nonHero = items.filter((i) => !i.isHero); if (nonHero.length) { const io = new IntersectionObserver( (entries) => { entries.forEach((entry) => { const it = entry.target.__extVideoItem; if (!it) return; if (entry.isIntersecting) { it._ioSeen = true; if (!it.isAttached) { const url = resolveUrl(it); attachVideo(it.video, url, "metadata"); it.isAttached = true; } if (it.wrap.hasAttribute("data-external-video-play-on-view")) { ensureLoop(it.video); playSafely(it); } } else if (it._ioSeen) { pauseSoft(it.video); it._playToken++; } }); }, { root: null, rootMargin: IO_ROOT_MARGIN_NON_HERO, threshold: IO_THRESHOLD_NON_HERO, } ); nonHero.forEach((it) => { it.wrap.__extVideoItem = it; io.observe(it.wrap); }); } /* ----------------------------------------- Resize: switch desktop / mobile source ----------------------------------------- */ let lastIsMobile = isMobileViewport(); window.addEventListener("resize", () => { const nowIsMobile = isMobileViewport(); if (nowIsMobile === lastIsMobile) return; lastIsMobile = nowIsMobile; items.forEach((it) => { if (!it.isAttached) return; const nextUrl = resolveUrl(it); const source = getSource(it.video); const current = source?.getAttribute("src") || ""; if (current === nextUrl) return; pauseSoft(it.video); attachVideo(it.video, nextUrl, it.isHero ? "auto" : "metadata"); if ( it.isHero || it.wrap.hasAttribute("data-external-video-play-on-view") ) { ensureLoop(it.video); playSafely(it); } }); }); /* ----------------------------------------- Visibility CPU saver ----------------------------------------- */ document.addEventListener("visibilitychange", () => { if (document.hidden) { items.forEach((it) => { pauseSoft(it.video); it._playToken++; }); return; } items.forEach((it) => { if (it.isHero && it.isAttached && HERO_AUTOPLAY_DEFAULT) { playSafely(it); } }); }); });