/* ============================================================================= Interactive Grid Tabs (ULTRA LAZY Wistia BACKGROUND, tabs + hidden content) ============================================================================= */ window.__DF_UTILS__ = window.__DF_UTILS__ || { onReady(fn) { if (document.readyState !== "loading") fn(); else document.addEventListener("DOMContentLoaded", fn); }, debounce(fn, delay) { let t; return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), delay); }; }, }; (() => { const { onReady, debounce } = window.__DF_UTILS__; const VARIANT_ATTR = "data-wf--item-interactive-tab-gradient--variant"; const VIDEO_VARIANT_VALUE = "is-video"; const raf2 = (fn) => requestAnimationFrame(() => requestAnimationFrame(fn)); function isVideoTab(el) { return (el.getAttribute(VARIANT_ATTR) || "") === VIDEO_VARIANT_VALUE; } function getWistiaIdFromTab(tabEl) { const idEl = tabEl.querySelector(".video-data-id"); return (idEl?.textContent || "").trim(); } function findWistiaMount(tabEl) { return tabEl.querySelector(".tab-wistia-mount"); } function findPosterImg(tabEl) { return tabEl.querySelector("img.video-data-poster"); } /* --------------------------- Wistia core + media jsonp loader ---------------------------- */ const WISTIA = { corePromise: null, mediaPromises: new Map(), // id -> Promise posterPromises: new Map(), // id -> Promise(url) }; function loadScriptOnce(src) { return new Promise((resolve, reject) => { const existing = Array.from(document.scripts).find((s) => s.src === src); if (existing) return resolve(true); const s = document.createElement("script"); s.src = src; s.async = true; s.onload = () => resolve(true); s.onerror = () => reject(new Error("Failed to load script: " + src)); document.head.appendChild(s); }); } function ensureWistiaCore() { if (WISTIA.corePromise) return WISTIA.corePromise; WISTIA.corePromise = loadScriptOnce( "https://fast.wistia.com/assets/external/E-v1.js" ); return WISTIA.corePromise; } function ensureWistiaMediaJsonp(hashedId) { if (!hashedId) return Promise.resolve(false); if (WISTIA.mediaPromises.has(hashedId)) return WISTIA.mediaPromises.get(hashedId); const p = loadScriptOnce( `https://fast.wistia.com/embed/medias/${hashedId}.jsonp` ); WISTIA.mediaPromises.set(hashedId, p); return p; } /* --------------------------- Poster from oEmbed (thumbnail_url) ---------------------------- */ async function getPosterUrlFromOembed(hashedId) { if (!hashedId) return ""; if (WISTIA.posterPromises.has(hashedId)) return WISTIA.posterPromises.get(hashedId); const p = (async () => { const oembedUrl = "https://fast.wistia.net/oembed?url=" + encodeURIComponent( `https://home.wistia.com/medias/${hashedId}?embedType=async` ); const res = await fetch(oembedUrl, { credentials: "omit" }); if (!res.ok) return ""; const data = await res.json(); return data && data.thumbnail_url ? data.thumbnail_url : ""; })().catch(() => ""); WISTIA.posterPromises.set(hashedId, p); return p; } /* --------------------------- Build background-like embed ---------------------------- */ function buildEmbedEl(hashedId) { const el = document.createElement("div"); el.className = `wistia_embed wistia_async_${hashedId}` + ` videoFoam=true` + ` autoPlay=true` + ` muted=true` + ` silentAutoPlay=allow` + ` controlsVisibleOnLoad=false`; el.style.width = "100%"; el.style.height = "100%"; return el; } async function attachActive(tabObj) { if (!tabObj.isVideo || !tabObj.wistiaId || !tabObj.mountEl) return; if (tabObj.mountEl.dataset.wistiaAttached === "true") return; tabObj.mountEl.dataset.wistiaAttached = "true"; await ensureWistiaCore(); await ensureWistiaMediaJsonp(tabObj.wistiaId); tabObj.mountEl.innerHTML = ""; tabObj.mountEl.appendChild(buildEmbedEl(tabObj.wistiaId)); } function detach(tabObj) { if (!tabObj.mountEl) return; tabObj.mountEl.innerHTML = ""; tabObj.mountEl.dataset.wistiaAttached = "false"; } class InteractiveGridLazyBg { constructor(wrapper) { this.wrapper = wrapper; this.stepDuration = parseFloat(wrapper.getAttribute("data-step-duration")) || 6; this.switchDelay = parseFloat(wrapper.getAttribute("data-switch-delay")) || 1; this.fadeDuration = parseFloat(wrapper.dataset.fadeDuration) || 0.5; this.autoMediaHeight = wrapper.dataset.mediaWrapperAuto === "true"; this.tabs = []; this.activeIndex = 0; this.heightCache = []; this.state = { inView: false, isRunning: false }; this.playTimer = null; this.progressTween = null; this._timeouts = []; this._onResize = debounce(() => this.reMeasure(), 120); this.init(); } init() { this.collectTabs(); if (!this.tabs.length) return; this.measureHeights(); const initial = this.tabs.findIndex((t) => t.container.classList.contains("is-interactive-active") ); this.activeIndex = initial >= 0 ? initial : 0; this.bindClicks(); this.bindIntersectionObserver(); window.addEventListener("resize", this._onResize, { passive: true }); // Posters from Wistia (best effort, does not block) this.initPosters(); // Clean base + open initial tab like before this.resetAllTabStyles(); this.applyInitialOpenState(this.activeIndex); } collectTabs() { const tabEls = Array.from( this.wrapper.querySelectorAll(".interactive-tab") ); this.tabs = tabEls .map((el) => { const wistiaId = isVideoTab(el) ? getWistiaIdFromTab(el) : ""; return { container: el, clickArea: el.querySelector(".interactive-tab_content_wrap") || el, hidden: el.querySelector(".interactive-tab_content_hidden"), content: el.querySelector(".interactive-tab_content"), mediaWrapper: el.querySelector(".interactive-tab_media_wrap"), progressBar: el.querySelector(".interactive-progress"), isVideo: isVideoTab(el), wistiaId, mountEl: isVideoTab(el) ? findWistiaMount(el) : null, posterImg: isVideoTab(el) ? findPosterImg(el) : null, duration: parseFloat(el.getAttribute("data-video-duration")) || parseFloat(el.dataset.videoDuration) || this.stepDuration, }; }) .filter(Boolean); } async initPosters() { this.tabs.forEach(async (t) => { if (!t.isVideo || !t.wistiaId || !t.posterImg) return; const currentSrc = (t.posterImg.getAttribute("src") || "").trim(); const isPlaceholder = !currentSrc || currentSrc.includes("placeholder"); if (!isPlaceholder) return; const url = await getPosterUrlFromOembed(t.wistiaId); if (url) t.posterImg.setAttribute("src", url); }); } bindIntersectionObserver() { const margin = this.wrapper.getAttribute("data-io-root-margin") || "0px 0px -10% 0px"; const threshold = parseFloat(this.wrapper.getAttribute("data-io-threshold")) || 0.25; const io = new IntersectionObserver( async (entries) => { const entry = entries[0]; if (!entry) return; if (entry.isIntersecting) { this.state.inView = true; await this.ensureActiveAttached(); this.start(); } else { this.state.inView = false; this.stop({ detachAll: true }); } }, { root: null, rootMargin: margin, threshold } ); io.observe(this.wrapper); this._io = io; } bindClicks() { this.tabs.forEach((t, i) => { t.clickArea.addEventListener("click", async () => { if (this.activeIndex === i) return; await this.activateTab(i, { userClicked: true, startNow: true }); }); }); } measureHeights() { if (typeof gsap === "undefined") return; this.heightCache = this.tabs.map((t) => { if (!t.hidden) return 0; gsap.set(t.hidden, { height: "auto", opacity: 1, position: "absolute", visibility: "hidden", }); const h = t.hidden.scrollHeight; gsap.set(t.hidden, { clearProps: "all" }); return h || 0; }); } reMeasure() { this.measureHeights(); const t = this.tabs[this.activeIndex]; if (!t) return; if (typeof gsap !== "undefined") { if (t.hidden) gsap.set(t.hidden, { height: "auto", opacity: 1 }); if (t.mediaWrapper) { if (window.innerWidth < 991) { gsap.set(t.mediaWrapper, { height: this.autoMediaHeight ? "auto" : "80vw", overflow: "hidden", opacity: 1, }); } else { gsap.set(t.mediaWrapper, { clearProps: "height", opacity: 1 }); } } } } clearTimers() { if (this.progressTween && this.progressTween.kill) this.progressTween.kill(); this.progressTween = null; if (this.playTimer && this.playTimer.kill) this.playTimer.kill(); this.playTimer = null; this._timeouts.forEach((id) => clearTimeout(id)); this._timeouts = []; } // --- Reset all progress bars so old inline width/opacity do not linger resetProgressAll() { this.tabs.forEach((t) => { if (!t.progressBar) return; if (typeof gsap !== "undefined") { gsap.killTweensOf(t.progressBar); gsap.set(t.progressBar, { clearProps: "all" }); } else { t.progressBar.removeAttribute("style"); } }); } resetAllTabStyles() { this.tabs.forEach((t) => { t.container.classList.remove("is-interactive-active"); if (typeof gsap !== "undefined") { if (t.hidden) gsap.set(t.hidden, { clearProps: "all" }); if (t.content) gsap.set(t.content, { clearProps: "all" }); if (t.mediaWrapper) gsap.set(t.mediaWrapper, { clearProps: "all" }); if (t.progressBar) gsap.set(t.progressBar, { clearProps: "all" }); } else { if (t.hidden) t.hidden.removeAttribute("style"); if (t.content) t.content.removeAttribute("style"); if (t.mediaWrapper) t.mediaWrapper.removeAttribute("style"); if (t.progressBar) t.progressBar.removeAttribute("style"); } }); // Also hard reset any lingering progress inline styles this.resetProgressAll(); } applyActiveUI(index) { const t = this.tabs[index]; if (!t) return; t.container.classList.add("is-interactive-active"); // Reset all progress bars first this.resetProgressAll(); // Init active progress bar if (t.progressBar && typeof gsap !== "undefined") { gsap.set(t.progressBar, { width: "0%", opacity: 1, clearProps: "transform", }); } else if (t.progressBar) { t.progressBar.style.width = "0%"; t.progressBar.style.opacity = "1"; } } applyInitialOpenState(index) { const t = this.tabs[index]; if (!t) return; this.applyActiveUI(index); const targetHeight = this.heightCache[index] || 0; if (typeof gsap !== "undefined") { if (t.hidden) { // Force opened state, then switch to height:auto gsap.set(t.hidden, { height: targetHeight, opacity: 1 }); raf2(() => { if (!t.container.classList.contains("is-interactive-active")) return; gsap.set(t.hidden, { height: "auto", opacity: 1 }); }); } if (t.content) gsap.set(t.content, { opacity: 1, y: 0, clearProps: "transform" }); if (t.mediaWrapper) gsap.set(t.mediaWrapper, { opacity: 1, y: 0, clearProps: "transform", }); } else { if (t.hidden) { t.hidden.style.opacity = "1"; t.hidden.style.height = targetHeight ? `${targetHeight}px` : "auto"; raf2(() => { if (!t.container.classList.contains("is-interactive-active")) return; t.hidden.style.height = "auto"; }); } if (t.content) t.content.style.opacity = "1"; if (t.mediaWrapper) t.mediaWrapper.style.opacity = "1"; } } async ensureActiveAttached() { const t = this.tabs[this.activeIndex]; if (!t) return; // Detach all other videos (ultra-lazy) this.tabs.forEach((x, ix) => { if (ix !== this.activeIndex && x.isVideo) detach(x); }); // Attach active if (t.isVideo) await attachActive(t); } async activateTab(index, { userClicked, startNow }) { this.activeIndex = index; this.clearTimers(); this.state.isRunning = false; // Stop and detach all other videos (ultra-lazy) this.tabs.forEach((x, ix) => { if (ix !== index && x.isVideo) detach(x); }); // Reset and apply UI this.resetAllTabStyles(); this.applyActiveUI(index); const t = this.tabs[index]; if (!t) return; // Animate content like before if (typeof gsap !== "undefined") { if (t.content) { gsap.fromTo( t.content, { opacity: 0, y: 6 }, { opacity: 1, y: 0, duration: this.fadeDuration, ease: "power2.out", } ); } if (t.hidden) { gsap.fromTo( t.hidden, { height: 0, opacity: 0 }, { height: this.heightCache[index] || 0, opacity: 1, duration: 0.5, ease: "power2.out", onComplete: () => gsap.set(t.hidden, { height: "auto" }), } ); } if (t.mediaWrapper) { gsap.fromTo( t.mediaWrapper, { opacity: 0, y: 10 }, { opacity: 1, y: 0, duration: 0.6, ease: "power3.out", clearProps: "transform", } ); } } else { if (t.hidden) { t.hidden.style.opacity = "1"; t.hidden.style.height = "auto"; } if (t.content) t.content.style.opacity = "1"; if (t.mediaWrapper) t.mediaWrapper.style.opacity = "1"; } // Attach active video only if wrapper is in view (or user clicked) if (userClicked) this.state.inView = true; if (this.state.inView) await this.ensureActiveAttached(); if (startNow && this.state.inView) this.start(); } start() { if (!this.state.inView) return; this.clearTimers(); this.state.isRunning = true; this.runCycle(); } stop({ detachAll } = {}) { this.clearTimers(); this.state.isRunning = false; if (detachAll) { this.tabs.forEach((t) => { if (t.isVideo) detach(t); }); } } runCycle() { if (!this.state.inView || !this.state.isRunning) return; const t = this.tabs[this.activeIndex]; if (!t) return; const duration = Number.isFinite(t.duration) && t.duration > 0 ? t.duration : this.stepDuration; // Ensure active video is attached (best-effort, do not block cycle) if (t.isVideo) this.ensureActiveAttached(); // Progress animation if (t.progressBar && typeof gsap !== "undefined") { gsap.set(t.progressBar, { width: "0%", opacity: 1 }); this.progressTween = gsap.to(t.progressBar, { width: "100%", duration, ease: "none", overwrite: true, }); } const afterProgress = () => { if (!this.state.isRunning) return; const id = setTimeout(async () => { if (!this.state.isRunning) return; const next = (this.activeIndex + 1) % this.tabs.length; await this.activateTab(next, { userClicked: false, startNow: true }); }, this.switchDelay * 1000); this._timeouts.push(id); }; if (typeof gsap !== "undefined") { this.playTimer = gsap.delayedCall(duration, afterProgress); } else { const id = setTimeout(afterProgress, duration * 1000); this._timeouts.push(id); } } } function initAll() { const wrappers = Array.from( document.querySelectorAll(".interactive-grid_wrapper") ); wrappers.forEach((w) => { if (w.dataset.dfInteractiveLazyInited === "true") return; w.dataset.dfInteractiveLazyInited = "true"; new InteractiveGridLazyBg(w); }); } onReady(initAll); })(); /* ============================================================================= Interactive Grid Tabs END ============================================================================= */