/*Disable all dev mods for complex components if the user forgot to disable them in the admin area*/ document.addEventListener("DOMContentLoaded", () => { document.querySelectorAll(".dev-edite-mode").forEach((el) => { if (el.classList.contains("is-on")) { el.classList.remove("is-on"); } }); }); /* ============================================================================= 6. Marquee (no jank safeguards) START ============================================================================= */ (() => { function initMarquees(selector, defaultSpeed) { const marquees = document.querySelectorAll(selector); if (!marquees.length) return; marquees.forEach((parent) => { const track = parent.querySelector(".marquee_track") || parent; const original = track.innerHTML; track.insertAdjacentHTML("beforeend", original); track.insertAdjacentHTML("beforeend", original); const speedAttr = parseFloat(parent.getAttribute("data-speed")); const speed = !isNaN(speedAttr) ? speedAttr : defaultSpeed; const direction = parent.dataset.direction === "right" ? 1 : -1; let offset = 0; let paused = false; const cycleWidth = Array.from(track.children) .slice(0, track.children.length / 3) .reduce((sum, el) => sum + el.clientWidth, 0); const animate = () => { if (!paused) { offset += speed * direction; track.style.transform = `translateX(${offset}px)`; if (direction === -1 && Math.abs(offset) > cycleWidth) { offset += cycleWidth; } if (direction === 1 && offset > 0) { offset -= cycleWidth; } } requestAnimationFrame(animate); }; animate(); }); } document.addEventListener("DOMContentLoaded", () => { initMarquees(".marquee", 0.6); }); })(); /* ============================================================================= 6. Marquee (no jank safeguards) END ============================================================================= */ /* ============================================================================= TAB START ============================================================================= */ document.addEventListener("DOMContentLoaded", () => { const tabs = document.querySelectorAll(".switch-tab"); const items = document.querySelectorAll(".item-tab-wrapper"); if (!tabs.length || !items.length) return; tabs.forEach((tab, index) => { tab.addEventListener("click", () => { /* -------------------- Reset all states -------------------- */ tabs.forEach((t) => t.classList.remove("is-active")); items.forEach((item) => { item.classList.remove("is-active"); item.classList.remove("is-visible"); }); /* -------------------- Activate tab -------------------- */ tab.classList.add("is-active"); const currentItem = items[index]; if (!currentItem) return; /* -------------------- Step-by-step activation -------------------- */ currentItem.classList.add("is-active"); // 🔥 allow CSS transition to trigger requestAnimationFrame(() => { currentItem.classList.add("is-visible"); }); }); }); }); /* ============================================================================= TAB END ============================================================================= */ /* ============================================================================= SLIDER START ============================================================================= */ window.Webflow ||= []; window.Webflow.push(() => { const sliderEl = document.querySelector(".swiper-slider-testimonial"); const prevArrow = document.querySelector(".arrow-slider-wrapper.is-prev"); const nextArrow = document.querySelector(".arrow-slider-wrapper.is-next"); if (!sliderEl || !prevArrow || !nextArrow) { console.warn("Testimonial slider elements not found"); return; } const swiper = new Swiper(sliderEl, { slidesPerView: "auto", spaceBetween: 90, speed: 600, watchOverflow: true, loop: false, autoplay: { delay: 5000, disableOnInteraction: false, pauseOnMouseEnter: false, }, navigation: { prevEl: prevArrow, nextEl: nextArrow, }, breakpoints: { 480: { slidesPerView: "auto", spaceBetween: 20, }, 768: { slidesPerView: "auto", spaceBetween: 32, }, 992: { slidesPerView: "auto", spaceBetween: 40, }, 1200: { slidesPerView: "auto", spaceBetween: 90, }, }, on: { init(swiper) { updateArrows(swiper); }, slideChange(swiper) { updateArrows(swiper); }, resize(swiper) { updateArrows(swiper); }, }, }); function updateArrows(swiper) { prevArrow.classList.toggle("is-off", swiper.isBeginning); nextArrow.classList.toggle("is-off", swiper.isEnd); } }); /* ============================================================================= SLIDER END ============================================================================= */ /* ------------------------------ FAQ OPEN / CLOSE - START ------------------------------ */ $(document).ready(function () { const DURATION = 350; const EASING = "ease"; $(".faq_question").on("click", function () { const $accordion = $(this).closest(".faq_accordion"); const $answer = $accordion.find(".faq_answer"); const $icons = $accordion.find(".line-icon"); const $lineFaq = $accordion.find(".line-faq"); const isOpen = $accordion.hasClass("is-bg-faq"); /* ----------------------------- CLOSE ALL (one open only) ----------------------------- */ $(".faq_accordion.is-bg-faq") .not($accordion) .each(function () { closeFaq($(this)); }); /* ----------------------------- TOGGLE CURRENT ----------------------------- */ if (isOpen) { closeFaq($accordion); } else { openFaq($accordion); } }); /* ============================= FUNCTIONS ============================= */ function openFaq($accordion) { const $answer = $accordion.find(".faq_answer"); const $icons = $accordion.find(".line-icon"); const $lineFaq = $accordion.find(".line-faq"); const targetHeight = $answer[0].scrollHeight; $accordion.addClass("is-bg-faq"); $answer.addClass("is-off-border"); $icons.addClass("is-on"); $lineFaq.addClass("is-visible"); $answer .css({ height: 0, overflow: "hidden", transition: `height ${DURATION}ms ${EASING}`, }) .outerHeight(targetHeight); setTimeout(() => { $answer.css({ height: "auto", overflow: "visible", }); }, DURATION); } function closeFaq($accordion) { const $answer = $accordion.find(".faq_answer"); const $icons = $accordion.find(".line-icon"); const $lineFaq = $accordion.find(".line-faq"); const currentHeight = $answer.outerHeight(); $accordion.removeClass("is-bg-faq"); $answer.removeClass("is-off-border"); $icons.removeClass("is-on"); $lineFaq.removeClass("is-visible"); $answer .css({ height: currentHeight, overflow: "hidden", }) .outerHeight(0); setTimeout(() => { $answer.css({ height: "", overflow: "", }); }, DURATION); } }); /* ------------------------------ FAQ OPEN / CLOSE - END ------------------------------ */ /* ============================================================================= SLIDER HERO START ============================================================================= */ window.Webflow ||= []; window.Webflow.push(() => { const sliderEl = document.querySelector(".slider-hero-wrapper"); if (!sliderEl) return; initHeroSwiper(sliderEl); }); function initHeroSwiper(sliderEl) { const swiper = new Swiper(sliderEl, { slidesPerView: "auto", centeredSlides: window.innerWidth >= 768, loop: true, speed: 700, grabCursor: true, watchSlidesProgress: true, breakpoints: { 0: { spaceBetween: 16, centeredSlides: false, }, 768: { spaceBetween: 20, centeredSlides: true, }, 992: { spaceBetween: 32, centeredSlides: true, }, }, autoplay: { delay: 6000, disableOnInteraction: false, pauseOnMouseEnter: true, }, on: { setTranslate(swiper) { const w = window.innerWidth; const isDesktop = w >= 992; const isTablet = w >= 768 && w < 992; const MIN_SCALE = isDesktop ? 0.82 : isTablet ? 0.9 : 0.94; const MIN_OPACITY = isDesktop ? 0.15 : isTablet ? 0.4 : 0.6; swiper.slides.forEach((slide) => { const progress = Math.min(Math.abs(slide.progress), 2); const scale = Math.max(MIN_SCALE, 1 - progress * 0.12); const opacity = Math.max(MIN_OPACITY, 1 - progress * 0.45); slide.style.transform = `scale(${scale})`; slide.style.opacity = opacity; slide.style.zIndex = String(Math.round(100 - progress)); }); }, init(swiper) { requestAnimationFrame(() => { swiper.update(); window.syncSliderCardVideos?.(sliderEl); }); }, slideChangeTransitionStart() { window.syncSliderCardVideos?.(sliderEl); }, resize(swiper) { swiper.update(); window.syncSliderCardVideos?.(sliderEl); }, }, }); const observer = new IntersectionObserver( ([entry]) => { const inView = entry.isIntersecting && entry.intersectionRatio > 0.2; if (inView) { swiper.autoplay?.start(); } else { swiper.autoplay?.stop(); } window.setSliderVideoVisibility?.(sliderEl, inView); }, { threshold: [0, 0.2, 0.35], } ); observer.observe(sliderEl); } /* ============================================================================= SLIDER HERO END ============================================================================= */ /* ============================================================================= GSAP ANIMATION Reveal text on scroll - START ============================================================================= */ // --- Reveal text on scroll --- const splitTypes = document.querySelectorAll("[reveal-type]"); if (splitTypes.length > 0) { splitTypes.forEach((el) => { const text = new SplitType(el, { types: ["chars", "words"], }); gsap.fromTo( text.chars, { color: "#797979" }, { color: "#FFFFFF", scrollTrigger: { trigger: el, start: "top 70%", end: "bottom 40%", scrub: true, markers: false, }, stagger: 0.05, ease: "power2.out", } ); }); } /* ============================================================================= GSAP ANIMATION Reveal text on scroll - END ============================================================================= */ /* ========================== Vertical Slider — Desktop / Tablet ONLY - START ========================== */ document.addEventListener("DOMContentLoaded", () => { const ENABLE_FROM = 768; // tablet + const DUPLICATION_TIMES = 5; const sliderEl = document.querySelector(".vertical-slider-carousel"); if (!sliderEl || typeof Swiper === "undefined") return; const wrapper = sliderEl.querySelector(".swiper-wrapper"); if (!wrapper) return; const originalSlides = Array.from(wrapper.children) .filter((sl) => sl.classList.contains("swiper-slide")) .map((sl) => sl.cloneNode(true)); let swiper = null; let isEnabled = false; function buildSlides() { wrapper.innerHTML = ""; for (let i = 0; i < DUPLICATION_TIMES; i++) { originalSlides.forEach((slide) => { wrapper.appendChild(slide.cloneNode(true)); }); } } function restoreSlides() { wrapper.innerHTML = ""; originalSlides.forEach((slide) => { wrapper.appendChild(slide.cloneNode(true)); }); } function updateOpacity(swiper) { swiper.slides.forEach((slide) => { const card = slide.querySelector(".card-slider-vertical"); if (!card) return; card.classList.remove("is-active"); slide.style.opacity = "0.7"; }); const active = swiper.slides[swiper.activeIndex]; if (!active) return; const activeCard = active.querySelector(".card-slider-vertical"); if (activeCard) activeCard.classList.add("is-active"); active.style.opacity = "1"; } function init() { buildSlides(); swiper = new Swiper(sliderEl, { direction: "vertical", slidesPerView: "auto", centeredSlides: true, loop: true, spaceBetween: 24, speed: 900, grabCursor: true, watchSlidesProgress: true, autoplay: { delay: 2000, disableOnInteraction: false, }, on: { init(sw) { updateOpacity(sw); }, slideChange(sw) { updateOpacity(sw); }, }, }); } function destroy() { if (swiper) { swiper.destroy(true, true); swiper = null; } restoreSlides(); } function update() { const shouldEnable = window.innerWidth >= ENABLE_FROM; if (shouldEnable === isEnabled) return; isEnabled = shouldEnable; destroy(); if (isEnabled) init(); } update(); let r; window.addEventListener("resize", () => { clearTimeout(r); r = setTimeout(update, 200); }); }); /* ========================== Vertical Slider — Desktop / Tablet ONLY - END ========================== */ /* ========================== Slider testimonial v-2 START ========================== */ /* ========================== Slider testimonial v-2 START ========================== */ window.Webflow ||= []; window.Webflow.push(() => { // 🔒 SAFETY CHECKS if (typeof Swiper === "undefined") { console.warn("Swiper is not loaded"); return; } const sliders = document.querySelectorAll(".swiper-testimonial-wrapper"); if (!sliders.length) return; sliders.forEach((sliderEl) => { try { // ✅ COUNT SLIDES const slides = sliderEl.querySelectorAll(".swiper-slide"); // If 1 or less slides → do NOT init Swiper if (slides.length <= 1) { sliderEl.classList.add("is-single-slide"); // optional helper class return; } new Swiper(sliderEl, { slidesPerView: 1, spaceBetween: 32, speed: 900, centeredSlides: true, loop: true, grabCursor: true, watchSlidesProgress: true, autoplay: { delay: 5000, disableOnInteraction: false, pauseOnMouseEnter: true, }, breakpoints: { 0: { spaceBetween: 20, }, 768: { spaceBetween: 28, }, 992: { spaceBetween: 32, }, }, }); } catch (err) { console.error("Swiper init failed:", err); } }); }); /* ========================== Slider testimonial v-2 END ========================== */ /* ========================== Slider testimonial v-2 END ========================== */ /* ============================================================================= Interactive Grid Tabs (ULTRA LAZY video, start-on-scroll) START ============================================================================= */ 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 prefersReducedMotion = window.matchMedia( "(prefers-reduced-motion: reduce)" ).matches; const saveData = !!(navigator.connection && navigator.connection.saveData); const SHOULD_AUTOPLAY = !(prefersReducedMotion || saveData); const raf2 = (fn) => requestAnimationFrame(() => requestAnimationFrame(fn)); function isVideoTab(el) { return (el.getAttribute(VARIANT_ATTR) || "") === VIDEO_VARIANT_VALUE; } function findVideoEl(tabEl) { return ( tabEl.querySelector("video.tab-video-here") || tabEl.querySelector("video.cloud-video-element") || tabEl.querySelector("video") ); } function getUrlFromTab(tabEl) { const urlEl = tabEl.querySelector(".video-data-url"); return (urlEl?.textContent || "").trim(); } function hardDetachVideo(videoEl) { if (!videoEl) return; try { videoEl.pause(); } catch (e) {} const sourceEl = videoEl.querySelector("source"); if (sourceEl) sourceEl.removeAttribute("src"); videoEl.removeAttribute("src"); videoEl.preload = "none"; videoEl.setAttribute("preload", "none"); try { videoEl.load(); } catch (e) {} } function hardAttachVideo(videoEl, url) { if (!videoEl || !url) return false; const sourceEl = videoEl.querySelector("source"); const current = sourceEl ? (sourceEl.getAttribute("src") || "").trim() : (videoEl.getAttribute("src") || videoEl.currentSrc || "").trim(); if (current === url) return false; try { videoEl.muted = true; videoEl.playsInline = true; videoEl.setAttribute("muted", ""); videoEl.setAttribute("playsinline", ""); } catch (e) {} if (sourceEl) sourceEl.setAttribute("src", url); else videoEl.setAttribute("src", url); videoEl.preload = "auto"; videoEl.setAttribute("preload", "auto"); try { videoEl.load(); } catch (e) {} return true; } function safePlayWithToken(state, videoEl, { resetTime }) { if (!videoEl) return; const token = ++state.playToken; let started = false; let fallbackTimer = null; const stillValid = () => state.playToken === token && state.isRunning; const doPlay = () => { if (!stillValid()) return; if (started) return; started = true; if (fallbackTimer) { clearTimeout(fallbackTimer); fallbackTimer = null; } if (resetTime) { try { videoEl.currentTime = 0; } catch (e) {} } const p = videoEl.play?.(); if (p && typeof p.catch === "function") p.catch(() => {}); }; if (videoEl.readyState >= 2) { raf2(doPlay); return; } const onReady = () => raf2(doPlay); videoEl.addEventListener("loadeddata", onReady, { once: true }); videoEl.addEventListener("canplay", onReady, { once: true }); fallbackTimer = setTimeout(() => { if (started) return; if (!stillValid()) return; if (videoEl.readyState >= 2) raf2(doPlay); }, 900); } class InteractiveGridLazy { 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, playToken: 0, }; 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 }); // Ensure clean base and open the initial tab UI immediately (no autoplay) this.resetAllTabStyles(); this.applyInitialOpenState(this.activeIndex); } collectTabs() { const tabEls = Array.from( this.wrapper.querySelectorAll(".interactive-tab") ); this.tabs = tabEls .map((el) => { const videoEl = isVideoTab(el) ? findVideoEl(el) : null; const url = isVideoTab(el) ? getUrlFromTab(el) : ""; const duration = parseFloat(el.getAttribute("data-video-duration")) || parseFloat(el.dataset.videoDuration) || this.stepDuration; if (videoEl) { try { videoEl.muted = true; videoEl.playsInline = true; videoEl.setAttribute("muted", ""); videoEl.setAttribute("playsinline", ""); } catch (e) {} videoEl.autoplay = false; videoEl.removeAttribute("autoplay"); videoEl.removeAttribute("loop"); // Ultra-lazy baseline: ensure nothing downloads on load hardDetachVideo(videoEl); } 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), videoEl, url, duration, }; }) .filter(Boolean); } 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( (entries) => { const entry = entries[0]; if (!entry) return; if (entry.isIntersecting) { this.state.inView = true; this.start(); } else { this.state.inView = false; this.stop(); } }, { root: null, rootMargin: margin, threshold } ); io.observe(this.wrapper); this._io = io; } bindClicks() { this.tabs.forEach((t, i) => { t.clickArea.addEventListener("click", () => { if (this.activeIndex === i) return; 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 = []; } stopAllVideos({ resetTime, detach }) { this.tabs.forEach((t) => { if (!t.videoEl) return; try { t.videoEl.pause(); } catch (e) {} if (resetTime) { try { t.videoEl.currentTime = 0; } catch (e) {} } if (detach) hardDetachVideo(t.videoEl); }); } 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"); } }); } applyActiveUI(index) { const t = this.tabs[index]; if (!t) return; t.container.classList.add("is-interactive-active"); 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) { // Open initial tab's hidden content immediately to avoid "missing height" on first render 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 an opened state, then switch to height:auto (stable for responsive) 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"; } } activateTab(index, { userClicked, startNow }) { this.activeIndex = index; this.clearTimers(); this.state.isRunning = false; this.state.playToken++; this.stopAllVideos({ resetTime: true, detach: true }); this.resetAllTabStyles(); this.applyActiveUI(index); const t = this.tabs[index]; if (!t) return; 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", } ); } } if (userClicked) this.state.inView = true; if (startNow && this.state.inView) this.start(); } start() { if (!this.state.inView) return; this.clearTimers(); this.state.isRunning = true; this.runCycle(); } stop() { this.clearTimers(); this.state.isRunning = false; this.state.playToken++; const t = this.tabs[this.activeIndex]; if (t?.videoEl) { try { t.videoEl.pause(); } catch (e) {} } } freezeLastFrame(videoEl) { if (!videoEl) return; try { videoEl.pause(); const d = videoEl.duration; if (Number.isFinite(d) && d > 0.1) { videoEl.currentTime = Math.max(0, d - 0.05); } } catch (e) {} } 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; if (t.isVideo && t.videoEl && t.url) { hardAttachVideo(t.videoEl, t.url); if (SHOULD_AUTOPLAY) { safePlayWithToken(this.state, t.videoEl, { resetTime: true }); } } 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; if (t.isVideo && t.videoEl) this.freezeLastFrame(t.videoEl); const id = setTimeout(() => { if (!this.state.isRunning) return; const next = (this.activeIndex + 1) % this.tabs.length; 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 InteractiveGridLazy(w); }); } onReady(initAll); })(); /* ============================================================================= Interactive Grid Tabs END ============================================================================= */ /* ============================================================================= Timeline START ============================================================================= */ $(document).ready(function () { const ACTIVE_CLASS = "is-active"; const ROTATE_CLASS = "is-rotate"; const OFF_CLASS = "is-off"; const VISIBLE_CLASS = "is-visible"; const OPACITY_CLASS = "is-opacity"; // ← NEW const VISIBLE_DELAY = 100; function clearVisibleTimer($content) { const timer = $content.data("visibleTimer"); if (timer) { clearTimeout(timer); $content.removeData("visibleTimer"); } } function closeItem($item) { const $content = $item.find(".lower-opening-block-wrapper"); const $plus = $item.find(".vertical-plus-timeline"); const $badge = $item.find(".badge-timeline-week"); clearVisibleTimer($content); $content.removeClass(VISIBLE_CLASS).css("max-height", 0); $item.removeClass(ACTIVE_CLASS); $plus.removeClass(ROTATE_CLASS); $badge.removeClass(OFF_CLASS); $badge.removeClass(OPACITY_CLASS); // ← REMOVE HERE } function openItem($item) { const $content = $item.find(".lower-opening-block-wrapper"); const $plus = $item.find(".vertical-plus-timeline"); const $badge = $item.find(".badge-timeline-week"); clearVisibleTimer($content); const height = $content[0].scrollHeight; $content.css("max-height", height + "px"); $item.addClass(ACTIVE_CLASS); $plus.addClass(ROTATE_CLASS); $badge.addClass(OFF_CLASS); $badge.addClass(OPACITY_CLASS); // ← ADD HERE const timer = setTimeout(() => { $content.addClass(VISIBLE_CLASS); }, VISIBLE_DELAY); $content.data("visibleTimer", timer); } /* ========================= CLICK ON HEADER ========================= */ $(".upper-opening-unit-wrapper").on("click", function (e) { e.stopPropagation(); const $currentItem = $(this).closest(".content-hero-timeline-item"); const isOpen = $currentItem.hasClass(ACTIVE_CLASS); $(".content-hero-timeline-item") .not($currentItem) .each(function () { closeItem($(this)); }); if (isOpen) { closeItem($currentItem); } else { openItem($currentItem); } }); $(".lower-opening-block-wrapper").on("click", function (e) { e.stopPropagation(); }); const $firstItem = $(".content-hero-timeline-item").first(); if ($firstItem.length) { openItem($firstItem); } }); /* ============================================================================= Timeline END ============================================================================= */ /* ============================================================================= NAVBAR START ============================================================================= */ document.addEventListener("DOMContentLoaded", () => { const navbar = document.querySelector(".navbar_component"); const menuButton = document.querySelector(".navbar_menu-button"); const textEl = document.querySelector(".text-open"); if (!navbar || !menuButton || !textEl) return; const OPEN_CLASS = "w--open"; const DEFAULT_TEXT = textEl.textContent.trim(); const OPEN_TEXT = "Close"; function updateState() { const isOpen = menuButton.classList.contains(OPEN_CLASS); // toggle navbar state navbar.classList.toggle("is-on", isOpen); // toggle text textEl.textContent = isOpen ? OPEN_TEXT : DEFAULT_TEXT; } // initial state (на випадок якщо меню вже відкрите) updateState(); const observer = new MutationObserver(updateState); observer.observe(menuButton, { attributes: true, attributeFilter: ["class"], }); }); /* ============================================================================= NAVBAR END ============================================================================= */ /* ============================================================================= Custom DD Filter START ============================================================================= */ $(document).ready(function () { const BREAKPOINT = 991; const $ddWrapper = $(".switch-dd-wrapper"); const $listWrapper = $(".list-dd-wrapper"); const $list = $(".list-dd"); const $formWrapper = $(".form-b2b-wrapper"); const $originalParent = $formWrapper.parent(); let isMoved = false; let isOpen = false; /* =============================== RESPONSIVE MOVE =============================== */ function moveFormIfNeeded() { if (window.innerWidth <= BREAKPOINT && !isMoved) { $formWrapper.appendTo($list); isMoved = true; } if (window.innerWidth > BREAKPOINT && isMoved) { $formWrapper.appendTo($originalParent); isMoved = false; closeDropdown(); } } /* =============================== OPEN =============================== */ function openDropdown() { const fullHeight = $list[0].scrollHeight; $listWrapper.css("max-height", fullHeight + "px"); $ddWrapper.addClass("is-open"); isOpen = true; } /* =============================== CLOSE =============================== */ function closeDropdown() { $listWrapper.css("max-height", "0px"); $ddWrapper.removeClass("is-open"); isOpen = false; } /* =============================== UPDATE LABEL SAFE =============================== */ function updateDropdownLabel(text) { let $currentLabel = $ddWrapper.find(".dd-current-label"); // якщо label не існує — створюємо if (!$currentLabel.length) { $currentLabel = $("
"); $ddWrapper.prepend($currentLabel); } $currentLabel.text(text); } /* =============================== TOGGLE =============================== */ $ddWrapper.on("click", function (e) { if ($(e.target).closest(".form-b2b-wrapper").length) return; if (window.innerWidth > BREAKPOINT) return; isOpen ? closeDropdown() : openDropdown(); }); /* =============================== RADIO CLICK =============================== */ $formWrapper.on("click", ".switch_b2b", function () { const activeText = $(this).find(".text-size-18").text().trim(); updateDropdownLabel(activeText); if (window.innerWidth <= BREAKPOINT) { closeDropdown(); } }); /* =============================== CLICK OUTSIDE =============================== */ $(document).on("click", function (e) { if ( window.innerWidth <= BREAKPOINT && !$(e.target).closest(".switch-dd-wrapper").length ) { closeDropdown(); } }); /* =============================== RESIZE (debounced) =============================== */ let resizeTimer; $(window).on("resize", function () { clearTimeout(resizeTimer); resizeTimer = setTimeout(moveFormIfNeeded, 150); }); /* =============================== INIT =============================== */ moveFormIfNeeded(); // initial active sync const initialText = $formWrapper .find(".switch_b2b.is-active .text-size-18") .first() .text() .trim(); if (initialText) { updateDropdownLabel(initialText); } }); /* ============================================================================= Custom DD Filter END ============================================================================= */ /* ============================================================================= NAVBAR INTERACTION START ============================================================================= */ window.Webflow ||= []; window.Webflow.push(() => { const navbar = document.querySelector(".navbar_component"); if (!navbar) return; // Safety check const SCROLL_OFFSET = 200; let isActive = false; function handleScroll() { const scrollTop = window.scrollY || window.pageYOffset; if (scrollTop > SCROLL_OFFSET) { if (!isActive) { navbar.classList.add("is-active"); isActive = true; } } else { if (isActive) { navbar.classList.remove("is-active"); isActive = false; } } } // Run once on load (if page refreshed while scrolled) handleScroll(); // Add scroll listener window.addEventListener("scroll", handleScroll, { passive: true }); }); /* part nav 2 */ window.Webflow ||= []; window.Webflow.push(() => { const navbar = document.querySelector(".navbar_component"); if (!navbar) return; const SCROLL_OFFSET = 200; const SHOW_DELAY = 120; // delay before showing on scroll up let lastScroll = window.scrollY; let ticking = false; let showTimeout = null; function updateNavbar() { const currentScroll = window.scrollY; // Only start behavior after 200px if (currentScroll > SCROLL_OFFSET) { // Scrolling down if (currentScroll > lastScroll) { navbar.classList.add("is-collapsed"); clearTimeout(showTimeout); } // Scrolling up else { clearTimeout(showTimeout); showTimeout = setTimeout(() => { navbar.classList.remove("is-collapsed"); }, SHOW_DELAY); } } else { navbar.classList.remove("is-collapsed"); } lastScroll = currentScroll; ticking = false; } function onScroll() { if (!ticking) { window.requestAnimationFrame(updateNavbar); ticking = true; } } window.addEventListener("scroll", onScroll, { passive: true }); }); /* ============================================================================= NAVBAR INTERACTION END ============================================================================= */ /* ============================================================================= Looped movement of words START ============================================================================= */ document.addEventListener("DOMContentLoaded", () => { const track = document.querySelector(".text-track"); const container = document.querySelector(".treck-text-block"); if (!track || !container) return; const SPEED = 600; const DELAY = 2500; const originalItems = Array.from(track.children); originalItems.forEach((item) => { track.appendChild(item.cloneNode(true)); }); const itemHeight = originalItems[0].offsetHeight; container.style.height = itemHeight + "px"; let index = 0; const total = originalItems.length; setInterval(() => { index++; track.style.transition = `transform ${SPEED}ms cubic-bezier(.77,0,.18,1)`; track.style.transform = `translateY(-${index * itemHeight}px)`; if (index >= total) { index = 0; track.addEventListener("transitionend", function handler() { track.removeEventListener("transitionend", handler); track.style.transition = "none"; track.style.transform = `translateY(0px)`; }); } }, DELAY); }); /* ============================================================================= Looped movement of words END ============================================================================= */