/* ========================================================= SAFE GSAP HELPERS ========================================================= */ const safeSet = (targets, vars) => { if (!targets || !window.gsap) return; if (typeof targets === "string") { const found = document.querySelectorAll(targets); if (found && found.length) gsap.set(found, vars); return; } if (targets.nodeType === 1) { gsap.set(targets, vars); return; } if (targets.length) { gsap.set(targets, vars); } }; const killScrollTriggersFor = (elements) => { if (!window.ScrollTrigger || !ScrollTrigger.getAll) return; const triggerElements = new Set(Array.from(elements || [])); ScrollTrigger.getAll().forEach((trigger) => { if (triggerElements.has(trigger.trigger)) { trigger.kill(); } }); }; const refreshScrollTrigger = () => { if (!window.ScrollTrigger) return; ScrollTrigger.refresh(); }; /* ========================================================= HERO ANIMATION ========================================================= */ let hasInitializedHeroAnimations = false; function initHeroAnimations() { if (hasInitializedHeroAnimations) return; hasInitializedHeroAnimations = true; const firstElsHero = document.querySelectorAll("[data-first-el-appear-hero]"); const secondElsHero = document.querySelectorAll( "[data-second-el-appear-hero]" ); const heroBottomEls = document.querySelectorAll("[data-hero-from-bottom]"); const opacityElsHero = document.querySelectorAll( "[data-opacity-el-appear-hero]" ); safeSet(firstElsHero, { y: -40, opacity: 0 }); safeSet(secondElsHero, { y: 40, opacity: 0 }); safeSet(heroBottomEls, { y: 20, opacity: 0, filter: "blur(10px)", }); const tlHero = gsap.timeline(); if (firstElsHero.length) { tlHero.to(firstElsHero, { y: 0, opacity: 1, duration: 1, ease: "power2.out", }); } if (opacityElsHero.length) { tlHero.to( opacityElsHero, { opacity: 1, duration: 0, }, 0.1 ); } if (secondElsHero.length) { tlHero.to( secondElsHero, { y: 0, opacity: 1, duration: 1, ease: "power2.out", stagger: 0.1, }, 0 ); } if (heroBottomEls.length) { tlHero.to( heroBottomEls, { y: 0, opacity: 1, filter: "blur(0px)", duration: 1, ease: "power4.out", stagger: 0.1, }, 0 ); } } /* ========================================================= MAIN SCROLL ANIMATIONS ========================================================= */ function initMainScrollAnimations() { const wraps = document.querySelectorAll("[data-animation-wrap]"); if (!wraps.length) return; killScrollTriggersFor(wraps); wraps.forEach((wrap) => { const firstEls = wrap.querySelectorAll("[data-first-el-appear]"); const secondEls = wrap.querySelectorAll("[data-second-el-appear]"); const bottomEls = wrap.querySelectorAll("[data-animate-from-bottom]"); const centerLineEls = wrap.querySelectorAll("[data-scroll-line-center]"); if ( !firstEls.length && !secondEls.length && !bottomEls.length && !centerLineEls.length ) { return; } safeSet(firstEls, { y: -40, opacity: 0 }); safeSet(secondEls, { y: 40, opacity: 0 }); safeSet(bottomEls, { y: 40, opacity: 0, filter: "blur(8px)", }); safeSet(centerLineEls, { scaleX: 0, transformOrigin: "50% 50%", }); const startPercent = wrap.getAttribute("data-animation-wrap"); const startValue = startPercent ? `top ${startPercent}` : "top 90%"; const tl = gsap.timeline({ scrollTrigger: { trigger: wrap, start: startValue, toggleActions: "play none none none", invalidateOnRefresh: true, }, }); if (firstEls.length) { tl.to(firstEls, { y: 0, opacity: 1, duration: 1, ease: "power2.out", }); } if (secondEls.length) { tl.to( secondEls, { y: 0, opacity: 1, duration: 1, ease: "power2.out", stagger: 0.04, }, 0 ); } if (bottomEls.length) { tl.to( bottomEls, { y: 0, opacity: 1, filter: "blur(0px)", duration: 1, ease: "power4.out", stagger: 0.05, }, 0 ); } if (centerLineEls.length) { centerLineEls.forEach((el) => { const delayValue = el.getAttribute("data-scroll-line-center"); const delay = delayValue ? parseFloat(delayValue) : 0; tl.to( el, { scaleX: 1, duration: 0.8, ease: "power3.out", }, delay ); }); } }); } /* ========================================================= GROUP SCROLL ANIMATIONS ========================================================= */ function initGroupScrollAnimations() { const wraps = document.querySelectorAll("[data-animation-wrap-groups]"); if (!wraps.length) return; killScrollTriggersFor(wraps); wraps.forEach((wrap) => { const groups = wrap.querySelectorAll("[data-navbar-group]"); const allItems = wrap.querySelectorAll("[data-navbar-from-bottom]"); if (!allItems.length) return; safeSet(allItems, { y: 40, opacity: 0, filter: "blur(8px)", }); const startPercent = wrap.getAttribute("data-animation-wrap-groups"); const startValue = startPercent ? `top ${startPercent}` : "top 90%"; const tl = gsap.timeline({ scrollTrigger: { trigger: wrap, start: startValue, toggleActions: "play none none none", invalidateOnRefresh: true, }, }); groups.forEach((group, index) => { const items = group.querySelectorAll("[data-navbar-from-bottom]"); if (!items.length) return; tl.to( items, { y: 0, opacity: 1, filter: "blur(0px)", duration: 0.4, ease: "power3.out", stagger: 0.04, }, index * 0.15 ); }); }); } /* ========================================================= MAIN INIT ========================================================= */ function initGSAPAnimations() { if (!window.gsap || !window.ScrollTrigger) return; gsap.registerPlugin(ScrollTrigger); ScrollTrigger.config({ ignoreMobileResize: true, }); initHeroAnimations(); initMainScrollAnimations(); initGroupScrollAnimations(); setTimeout(refreshScrollTrigger, 200); setTimeout(refreshScrollTrigger, 700); } /* ========================================================= WEBFLOW-SAFE INIT FLOW ========================================================= */ let hasBootedGSAPAnimations = false; function bootGSAPAnimations() { if (hasBootedGSAPAnimations) return; hasBootedGSAPAnimations = true; setTimeout(() => { initGSAPAnimations(); }, 300); } function queueGSAPAnimationsAfterWebflow() { window.Webflow ||= []; window.Webflow.push(() => { if (document.readyState === "complete") { bootGSAPAnimations(); return; } window.addEventListener("load", bootGSAPAnimations, { once: true }); }); window.addEventListener( "load", () => { setTimeout(bootGSAPAnimations, 900); }, { once: true } ); } queueGSAPAnimationsAfterWebflow(); /* ========================================================= NAVBAR TRIGGER ANIMATION START ========================================================= */ /* ========================================================= NAVBAR GROUP ANIMATION ========================================================= */ document.addEventListener("DOMContentLoaded", () => { const navbar = document.querySelector(".block-navbar-animation"); if (!navbar) return; const groups = document.querySelectorAll("[data-navbar-group]"); const allItems = document.querySelectorAll("[data-navbar-from-bottom]"); if (!allItems.length) return; const isMobile = window.innerWidth < 768; function setInitialState() { safeSet(allItems, { y: 30, opacity: 0, filter: "blur(6px)", }); } setInitialState(); function runMobileAnimation() { gsap.killTweensOf(allItems); setInitialState(); const sortedItems = Array.from(allItems).sort((a, b) => { const rectA = a.getBoundingClientRect(); const rectB = b.getBoundingClientRect(); if (Math.abs(rectA.top - rectB.top) > 5) { return rectA.top - rectB.top; } return rectA.left - rectB.left; }); gsap.fromTo( sortedItems, { y: 30, opacity: 0, filter: "blur(6px)", }, { y: 0, opacity: 1, filter: "blur(0px)", duration: 0.4, ease: "power2.out", stagger: 0.03, overwrite: "auto", } ); } function runDesktopAnimation() { gsap.killTweensOf(allItems); setInitialState(); const tl = gsap.timeline(); groups.forEach((group, index) => { const items = group.querySelectorAll("[data-navbar-from-bottom]"); if (!items.length) return; tl.to( items, { y: 0, opacity: 1, filter: "blur(0px)", duration: 0.45, ease: "power3.out", stagger: { each: 0.03, from: "start", }, }, index * 0.12 ); }); } let wasActive = false; const observer = new MutationObserver(() => { const isActive = navbar.classList.contains("is-active"); if (isActive && !wasActive) { if (isMobile) { runMobileAnimation(); } else { runDesktopAnimation(); } } wasActive = isActive; }); observer.observe(navbar, { attributes: true, attributeFilter: ["class"], }); }); /* ========================================================= NAVBAR TRIGGER ANIMATION END ========================================================= */