if ("scrollRestoration" in history) { history.scrollRestoration = "manual"; } let transitionDuration = 1.6; let btnSpeed = 0.4; CustomEase.create("primary-ease", "M0,0 C0.15,0 0.15,1 1,1"); CustomEase.create("loaderEase", "M0,0 C0.87,0 0.13,1 1,1"); CustomEase.create("smooth", "0.38, 0.005, 0.215, 1"); CustomEase.create("fade", "0.25, 0.1, 0.25, 1"); CustomEase.create("energy", "0.32, 0.72, 0, 1"); CustomEase.create("stick", "0.16, 1, 0.3, 1"); CustomEase.create("osmo", "M0,0 C0.625,0.05 0,1 1,1"); CustomEase.create( "loading", "M0,0 C0.05,0.18 0.1,0.32 0.22,0.5 0.34,0.68 0.5,0.78 0.68,0.88 0.82,0.95 0.92,0.99 1,1" ); gsap.defaults({ ease: "stick", duration: btnSpeed, }); if (typeof window.ScrollTrigger !== "undefined") { ScrollTrigger.config({ autoRefreshEvents: "visibilitychange,DOMContentLoaded,load", ignoreMobileResize: true, }); } const onDesktop = (fn) => gsap.matchMedia().add("(min-width: 992px) and (pointer: fine)", fn); const onMobile = (fn) => gsap.matchMedia().add("(max-width: 991px) and (pointer: coarse)", fn); const hasLenis = typeof window.Lenis !== "undefined"; const hasScrollTrigger = typeof window.ScrollTrigger !== "undefined"; const rmMQ = window.matchMedia("(prefers-reduced-motion: reduce)"); let reducedMotion = rmMQ.matches; rmMQ.addEventListener?.("change", (e) => (reducedMotion = e.matches)); rmMQ.addListener?.((e) => (reducedMotion = e.matches)); let onceFunctionsInitialized = false; function enterTlAnimations() { let textEnter = gsap.timeline({ defaults: { duration: transitionDuration, }, }); textEnter.set("[intro-stagger-text]", { opacity: 1, }); textEnter.from("[intro-stagger-text]", { y: "-10rem", }); textEnter.from( "[intro-stagger-text] .line", { rotateX: 90, y: "-5rem", opacity: 0, }, "<" ); textEnter.from( "[intro-fade]", { y: "-10rem", }, "<" ); textEnter.to( "[intro-fade]", { opacity: 1, duration: transitionDuration / 2, ease: "sine.in", }, "<" ); textEnter.fromTo( "[intro-clip]", { clipPath: "inset(0% 0% 100% 0% round .3125rem)", }, { clipPath: "inset(0% 0% 0% 0% round .3125rem)", }, "<" ); textEnter.to( "[intro-opacity]", { opacity: 1, duration: transitionDuration / 2, ease: "sine.in", }, "<" ); } let nextPage = document; const has = (s) => !!nextPage.querySelector(s); let previousUrl = document.referrer || "/"; let globalMouseX = -9999; let globalMouseY = -9999; function positionCursors() { onDesktop(() => { $("[cursor-track]").each(function () { let cursor = $(this).find("[cursor-el]"); if (cursor.length) { let rect = this.getBoundingClientRect(); gsap.set(cursor, { x: globalMouseX - rect.left, y: globalMouseY - rect.top, }); cursor.css("visibility", "visible"); } }); }); } let cursorsRevealed = false; onDesktop(() => { window.addEventListener("mousemove", function (e) { globalMouseX = e.clientX; globalMouseY = e.clientY; if (!cursorsRevealed) { cursorsRevealed = true; positionCursors(); } }); }); let lenis = null; function initLenis() { if (lenis) return; if (!hasLenis) return; lenis = new Lenis({ syncTouch: true, // syncTouchLerp: 0.075, // touchInertiaExponent: 1.7, // touchMultiplier: 1, wheelMultiplier: 1.25, }); if (hasScrollTrigger) { lenis.on("scroll", ScrollTrigger.update); } gsap.ticker.add((time) => { lenis.raf(time * 1000); }); gsap.ticker.lagSmoothing(0); } let lineSplit; let charSplit; function runSplit() { lineSplit = new SplitType("[stagger-text], [intro-stagger-text]", { types: "lines", }); charSplit = new SplitType("[stagger-char], [contact-flip], [h-split]", { types: "chars, words, lines", }); } runSplit(); // ----------------------------------------- // INIT FUNCTIONS // ----------------------------------------- function initCmsNumbers() { $(".interaction_cms_wrap .interaction_cms_order").each(function () { let number = $(this).text().trim(); if (parseInt(number) < 10 && number.length === 1) { $(this).text("0" + number); } }); } function initPanels() { onDesktop(() => { let sections = gsap.utils.toArray($("[panel]").toArray()); sections.forEach((section, index) => { let tl = gsap.timeline({}); let scrollingImg = $(section).find("img"); let tScaling = gsap.timeline({ scrollTrigger: { trigger: section, start: "top bottom", end: "bottom top", scrub: true, onEnter: () => { $(".sticky-card.is-first").addClass("white-bg"); }, onLeaveBack: () => { $(".sticky-card.is-first").removeClass("white-bg"); }, }, }); if (index !== sections.length - 1) { tl.to(section, { ease: "none", scrollTrigger: { trigger: section, start: "top top", pin: section, pinSpacing: false, end: "bottom top", scrub: true, }, }); } tScaling.to(scrollingImg, { scale: 1.1, ease: "none" }); tScaling.to(scrollingImg, { scale: 1, ease: "none" }); }); }); } function initBackButtons() { $(".back-btn").each(function () { let fallback = $(this).attr("href") || "/"; $(this).attr("href", previousUrl || fallback); }); } function initCopyUrl() { $("[copy-url]").each(function () { let btn = $(this); let text1 = btn.find("[btn-text1]"); let text2 = btn.find("[btn-text2]"); let originalText1 = text1.text(); let originalText2 = text2.text(); btn.on("click", function () { navigator.clipboard.writeText(window.location.href); text1.text("Link Copied!"); text2.text("Link Copied!"); setTimeout(function () { text1.text(originalText1); text2.text(originalText2); }, 2000); }); }); } function initAccordions() { $("[accordion-item]").each(function () { let accordionItem = $(this); accordionItem.on("click", function () { let wasActive = accordionItem.hasClass("is-active"); $("[accordion-item]").removeClass("is-active"); if (!wasActive) { accordionItem.addClass("is-active"); } setTimeout(() => { ScrollTrigger.refresh(); lenis.resize(); }, 300); }); }); } function initNavHover() { onDesktop(() => { $(".navigation") .on("mouseenter", function () { $("body").addClass("nav-hover"); }) .on("mouseleave", function () { $("body").removeClass("nav-hover"); }); }); } function initCursorTracking() { onDesktop(() => { let cursorTrackers = []; $("[cursor-track]").each(function () { let block = this; let cursor = $(this).find("[cursor-el]"); let fadeEl = $(this).find("[cursor-fade]"); let hasGradient = $(this).attr("cursor-track") === "gradient"; if (cursor.length) { gsap.set(cursor, { xPercent: -50, yPercent: -50, x: -9999, y: -9999 }); let xTo = gsap.quickTo(cursor, "x", { duration: 0.6, ease: "power3" }); let yTo = gsap.quickTo(cursor, "y", { duration: 0.6, ease: "power3" }); cursorTrackers.push({ block, xTo, yTo }); if (fadeEl.length) { $(this).on("mouseenter", function () { gsap.to(fadeEl, { opacity: 0, duration: 0.3 }); }); $(this).on("mouseleave", function () { gsap.to(fadeEl, { opacity: 1, duration: 0.3 }); }); } } if (hasGradient) { $(this).on("mousemove", function (e) { let rect = block.getBoundingClientRect(); let x = ((e.clientX - rect.left) / rect.width) * 100; let y = ((e.clientY - rect.top) / rect.height) * 100; gsap.to(block, { "--mouse-x": x + "%", "--mouse-y": y + "%", duration: 0.5, overwrite: true, }); }); $(this).on("mouseleave", function () { gsap.to(block, { "--mouse-x": "50%", "--mouse-y": "50%", duration: 0.5, }); }); } }); window.addEventListener("mousemove", function () { cursorTrackers.forEach(({ block, xTo, yTo }) => { let rect = block.getBoundingClientRect(); xTo(globalMouseX - rect.left); yTo(globalMouseY - rect.top); }); }); lenis.on("scroll", function () { cursorTrackers.forEach(({ block, xTo, yTo }) => { let rect = block.getBoundingClientRect(); xTo(globalMouseX - rect.left); yTo(globalMouseY - rect.top); }); }); }); } function initButtonHovers() { onDesktop(() => { $("[btn-el]").each(function (index) { let enterAnim = gsap.timeline({ paused: true }); if ($(this).find("[btn-el_hover]").length) { enterAnim.to($(this).find("[btn-el_hover]"), { top: "0%", duration: btnSpeed, ease: "expo.out", }); } if ($(this).find("[btn-text1]").length) { enterAnim.to( $(this).find("[btn-text1]"), { yPercent: 100, duration: btnSpeed, ease: "primary-ease" }, "<" ); } if ($(this).find("[btn-text2]").length) { enterAnim.to( $(this).find("[btn-text2]"), { top: "0%", duration: btnSpeed, ease: "expo.out" }, "<25%" ); } if ($(this).find("[btn-text1]").length) { enterAnim.set($(this).find("[btn-text1]"), { yPercent: -100 }); } let leaveAnim = gsap.timeline({ paused: true }); if ($(this).find("[btn-el_hover]").length) { leaveAnim.to($(this).find("[btn-el_hover]"), { top: "100%", duration: btnSpeed, ease: "power3.inOut", }); } if ($(this).find("[btn-text2]").length) { leaveAnim.to( $(this).find("[btn-text2]"), { top: "100%", duration: btnSpeed, ease: "primary-ease" }, "<" ); } if ($(this).find("[btn-text1]").length) { leaveAnim.to( $(this).find("[btn-text1]"), { yPercent: 0, duration: btnSpeed, ease: "power3.out" }, "<25%" ); } if ($(this).find("[btn-text2]").length) { leaveAnim.set($(this).find("[btn-text2]"), { top: "-100%" }); } $(this).on("mouseover", function () { leaveAnim.pause(); enterAnim.restart(); }); $(this).on("mouseleave", function () { if (enterAnim.progress() < 0.75) { enterAnim.reverse(); } else { leaveAnim.restart(); } }); }); }); } function initLightSections() { let navEl = nextPage.querySelector(".navigation"); if (!navEl) return; $("[light]").each(function () { ScrollTrigger.create({ trigger: this, start: "top 50px", end: "bottom 50px", onEnter: () => { $(navEl).addClass("light"); }, onEnterBack: () => { $(navEl).addClass("light"); }, onLeave: () => { $(navEl).removeClass("light"); }, onLeaveBack: () => { $(navEl).removeClass("light"); }, }); }); } function initStaggerText() { $("[stagger-text]").each(function (index) { let tl = gsap.timeline({ scrollTrigger: { trigger: $(this), start: "top 90%", toggleActions: "play none none none", }, }); tl.fromTo( $(this).find(".line"), { yPercent: -100, clipPath: "polygon(0% 100%, 100% 100%, 100% 100%, 0% 100%)", }, { yPercent: 0, clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)", ease: "osmo", duration: 0.8, stagger: 0.1, } ); }); } function initStaggerChar() { $("[stagger-char]").each(function () { let tl = gsap.timeline({ scrollTrigger: { trigger: $(this), start: "top 70%", }, }); tl.from($(this).find(".char"), { yPercent: 110, rotateX: -70, duration: 0.9, stagger: { each: 0.1 }, ease: "power4.out", }); }); } function initFadeIn() { $("[fade-in]").each(function (index) { let tl = gsap.timeline({ scrollTrigger: { trigger: $(this), start: "top 90%", }, }); tl.to($(this), { opacity: 1, duration: 0.8, ease: "sine.in", }); }); } function initBorderGradient() { onDesktop(() => { $(".bordered-btn:not([cursor-track]), .border-el:not([cursor-track])").each( function () { let btn = this; $(this).on("mousemove", function (e) { let rect = btn.getBoundingClientRect(); let x = ((e.clientX - rect.left) / rect.width) * 100; let y = ((e.clientY - rect.top) / rect.height) * 100; gsap.to(btn, { "--mouse-x": x + "%", "--mouse-y": y + "%", duration: 0.3, overwrite: true, }); }); $(this).on("mouseleave", function () { gsap.to(btn, { "--mouse-x": "50%", "--mouse-y": "50%", duration: 0.5, }); }); } ); }); } function initCultureText() { $(".culture-type_layout").each(function () { let layout = this; let text = $(this).find(".culture-mask_text")[0]; if (!text) return; let mouseX = 0, mouseY = 0; let maskReady = false; const baseColor = "#0B2525"; // resting text colour; must match gradient base let split = new SplitText(text, { type: "lines", linesClass: "culture-line", }); // h3 is only a perspective container – no gradient, no clip. gsap.set(text, { perspective: 500 }); // During the intro the lines are plain solid text: no background-clip, // so the transform/opacity tween has nothing to ghost. gsap.set(split.lines, { color: baseColor, webkitTextFillColor: baseColor, background: "none", transformOrigin: "50% 0%", }); function applyMask() { split.lines.forEach((line) => { line.style.background = "radial-gradient(circle 2em at var(--mouse-x, -100%) var(--mouse-y, -100%), #49F7F7 0%, rgba(73,247,247,0) 100%), " + baseColor; line.style.webkitBackgroundClip = "text"; line.style.backgroundClip = "text"; line.style.webkitTextFillColor = "transparent"; line.style.color = "transparent"; }); maskReady = true; } function updateGradient() { if (!maskReady) return; split.lines.forEach((line) => { let rect = line.getBoundingClientRect(); let x = ((mouseX - rect.left) / rect.width) * 100; let y = ((mouseY - rect.top) / rect.height) * 100; gsap.to(line, { "--mouse-x": x + "%", "--mouse-y": y + "%", duration: 0.6, ease: "power3", overwrite: "auto", }); }); } gsap.from(split.lines, { rotateX: -80, yPercent: 120, opacity: 0, stagger: 0.175, duration: 1.4, ease: "osmo", scrollTrigger: { trigger: text, start: "top 80%" }, onComplete: () => { // Drop GSAP's inline transform/opacity first so the clip paints on a // plain, non-composited box, then switch the mask on. gsap.set(split.lines, { clearProps: "transform,opacity" }); gsap.set(text, { clearProps: "perspective" }); applyMask(); if (layout.matches(":hover")) updateGradient(); }, }); onDesktop(() => { $(layout).on("mousemove", function (e) { mouseX = e.clientX; mouseY = e.clientY; updateGradient(); }); lenis.on("scroll", function () { if (maskReady && layout.matches(":hover")) updateGradient(); }); }); }); } function initAwardsScroll() { $(".awards-section").each(function () { let section = $(this); let left = section.find(".awards-left"); let shiftDown = left.find(".awards-shift_down"); let right = section.find(".awards-wrap, .awards-right"); if (!shiftDown.length || !right.length) return; onDesktop(() => { const getTravel = () => Math.max(0, right.outerHeight() - shiftDown.outerHeight()); gsap.fromTo( shiftDown, { marginTop: 0 }, { marginTop: () => getTravel(), ease: "none", scrollTrigger: { trigger: section, start: "top bottom", end: "bottom 50%", endTrigger: section, scrub: true, }, } ); }); }); } function initSubscribeSubmit() { $(".subscribe-submit").on("click", function (e) { if ($(e.target).is(".submit-btn")) return; $(this).find(".submit-btn").click(); }); } function initAdditiveItems() { $(".additive-item").each(function () { let mediaWrap = $(this).find(".additive-media_wrap"); gsap.fromTo( mediaWrap, { width: "100%" }, { width: "90%", scrollTrigger: { trigger: $(this), start: "top center", end: "bottom top", scrub: 0.4, }, ease: "sine.in", } ); }); } function initDarkStickyCards() { $(".dark-sticky_card").each(function () { let container = $(this).find(".dark-sticky_card-container"); gsap.fromTo( container, { scale: 1 }, { scale: 0.9, scrollTrigger: { trigger: this, start: "top top", end: "bottom top", scrub: 0.4, }, transformOrigin: "center bottom", ease: "sine.in", } ); }); } function initWireframes() { $("[wireframe-trigger]").each(function () { let trigger = $(this); let holder = trigger.find(".wireframe-holder"); let wire = trigger.find(".wireframe"); onDesktop(() => { gsap .timeline({ scrollTrigger: { trigger: trigger, start: "top bottom", end: "bottom top", scrub: 0.4, }, }) .fromTo(holder, { yPercent: -20 }, { yPercent: 20, ease: "linear" }); }); onMobile(() => { gsap .timeline({ scrollTrigger: { trigger: trigger, start: "top bottom", end: "bottom top", scrub: true, }, }) .fromTo(wire, { y: "10rem" }, { y: "-20rem", ease: "none" }); }); }); } window.addEventListener( "resize", debounceOnWidthChange(() => { lineSplit.revert(); charSplit.revert(); runSplit(); if (hasScrollTrigger) ScrollTrigger.refresh(); }, 1000) ); function homeFunctions() { onDesktop(() => { $(".color-card").each(function () { let hoverEl = $(this).find(".color-card_hover"); function getDirection(e, el) { let rect = el.getBoundingClientRect(); let y = e.clientY - rect.top - rect.height / 2; return y < 0 ? -1 : 1; } $(this).on("mouseenter", function (e) { let dir = getDirection(e, this); gsap.killTweensOf(hoverEl); gsap.fromTo( hoverEl, { top: dir === -1 ? "-100%" : "100%" }, { top: "0%", duration: 0.75, delay: 0.1, ease: "stick" } ); }); $(this).on("mouseleave", function (e) { let dir = getDirection(e, this); gsap.killTweensOf(hoverEl); gsap.to(hoverEl, { top: dir === -1 ? "-100%" : "100%", duration: 0.75, ease: "stick", }); }); }); }); onDesktop(() => { $(".timeline-wrapper").each(function (index) { let childTriggers = $(this).find(".client-copy_item"); let childTargets = $(this).find(".client-logo_item"); let nameTargets = $(this).find(".client-name_item"); function makeItemActive(index) { childTriggers.removeClass("is-active"); childTargets.removeClass("is-active"); nameTargets.removeClass("is-active"); childTriggers.eq(index).addClass("is-active"); childTargets.eq(index).addClass("is-active"); nameTargets.eq(index).addClass("is-active"); } makeItemActive(0); nameTargets.each(function (index) { ScrollTrigger.create({ trigger: $(this), start: "top center", end: "bottom center", onToggle: (isActive) => { if (isActive) { makeItemActive(index); } }, }); }); }); }); onMobile(() => { $(".timeline-wrapper").each(function (index) { let childTriggers = $(this).find(".m-client-copy_item"); let childTargets = $(this).find(".m-client-logo_item"); let nameTargets = $(this).find(".m-client_name"); function makeItemActive(index) { childTriggers.removeClass("is-active"); childTargets.removeClass("is-active"); nameTargets.removeClass("is-active"); childTriggers.eq(index).addClass("is-active"); childTargets.eq(index).addClass("is-active"); nameTargets.eq(index).addClass("is-active"); } makeItemActive(0); nameTargets.each(function (index) { $(this).on("click", function () { makeItemActive(index); }); }); }); }); onDesktop(() => { $(".sticky-holder").each(function (index) { let clipTimeline = gsap.timeline({ scrollTrigger: { trigger: ".sticky-holder", start: "top top", end: "bottom top", pin: ".sticky-img_mask", // pinType: "transform", scrub: true, }, defaults: { ease: "none", }, }); clipTimeline.fromTo( ".sticky-img_mask", { backgroundSize: "150%", }, { backgroundSize: "100%", scrollTrigger: { trigger: ".sticky-holder", start: "top bottom", endTrigger: ".sticky-track", end: "bottom top", scrub: 0.4, }, } ); clipTimeline.fromTo( ".sticky-img_mask", { maskSize: "25vw", }, { maskSize: "200vw", scrollTrigger: { trigger: ".sticky-track", start: "top top", endTrigger: ".sticky-content", end: "top top", scrub: 0.4, }, } ); }); }); onMobile(() => { $(".color-card").each(function (index) { let clipTimeline = gsap.timeline({ scrollTrigger: { trigger: $(this), start: "top center", end: "bottom center", toggleClass: "in-view", scrub: true, }, }); }); $(".sticky-holder").each(function (index) { let clipTimeline = gsap.timeline({ scrollTrigger: { trigger: ".sticky-holder", start: "top top", end: "bottom top", pin: ".sticky-img_mask", scrub: true, }, defaults: { ease: "none", }, }); clipTimeline.fromTo( ".sticky-img_mask", { maskSize: "45vw", }, { maskSize: "550vw", scrollTrigger: { trigger: ".sticky-holder", start: "top top", endTrigger: ".sticky-content", end: "top center", scrub: 0.4, }, } ); }); }); onDesktop(() => { let navlogoShrink = gsap.timeline({ scrollTrigger: { trigger: ".home-hero_section", start: "top top", end: "+=500px", scrub: true, }, }); navlogoShrink.to(".nav-logo_holder", { width: "4.75em", ease: "linear", }); }); onMobile(() => { let navlogoShrink = gsap.timeline({ scrollTrigger: { trigger: ".home-hero_section", start: "top top", end: "+=200px", scrub: true, }, }); navlogoShrink.to(".nav-logo_holder", { width: "3.5rem", ease: "linear", }); }); let st = ScrollTrigger.create({ trigger: ".section.client", pin: ".section.client", pinSpacing: true, scrub: true, start: "top top", end: "bottom 50%+=12.5px", endTrigger: ".client-name_list", }); onDesktop(() => { $("[clip-wide]").each(function (index) { let clipWide = gsap.timeline({ scrollTrigger: { trigger: $(this), start: "clamp(top 80%)", end: "clamp(top center)", scrub: 0.4, }, }); clipWide.to($(this), { clipPath: "inset(0px 0px round 0px)", ease: "smooth", }); }); }); onMobile(() => { $("[clip-wide]").each(function (index) { let clipWide = gsap.timeline({ scrollTrigger: { trigger: $(this), start: "clamp(top 60%)", end: "top top", scrub: true, }, }); clipWide.to($(this), { clipPath: "inset(0px 0px)", }); }); }); let heroPin = gsap.timeline({ scrollTrigger: { trigger: ".home-hero_section", start: "top top", end: "bottom top", scrub: true, ease: "none", pin: true, pinSpacing: false, }, }); awardsPopup(); } function awardsPopup() { let awardsEl = $("[popup-el]"); let awardsPopupEl = awardsEl.find(".awards-popup"); let awardsBg = awardsEl.find(".awards-bg"); let awardsCloseBtn = awardsEl.find(".close-holder.is-white"); let awardsScroller = awardsEl.find(".awards-popup_layout")[0]; let awardsCounter = awardsEl.find("[awards-counter]"); let awardsItems = awardsEl.find(".awards-item"); let totalAwards = awardsItems.length; function pad(n) { return n < 10 ? "0" + n : "" + n; } function updateCounter(index) { awardsCounter.text(pad(index + 1) + "/" + pad(totalAwards)); } let observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { let index = awardsItems.index(entry.target); updateCounter(index); } }); }, { root: awardsScroller, threshold: 0.5, } ); awardsItems.each(function () { observer.observe(this); }); let openAwardsTl = gsap.timeline({ paused: true, defaults: { ease: "osmo", duration: transitionDuration, }, onStart: () => { awardsPopupEl.addClass("open"); onDesktop(() => { $(".block-cursor").css("display", "none"); }); awardsScroller.scrollTop = 0; updateCounter(0); }, }); openAwardsTl.from( awardsBg, { width: "80%", height: "0%", top: "10%", clearProps: "all", }, "<" ); openAwardsTl.to( awardsBg, { borderRadius: "0px", }, "<" ); openAwardsTl.from( "[awards-fade]", { opacity: 0, duration: transitionDuration / 2, ease: "sine.in", }, "<50%" ); let closeAwardsTl = gsap.timeline({ paused: true, defaults: { ease: "osmo", duration: transitionDuration / 2, }, onComplete: () => { awardsPopupEl.removeClass("open"); gsap.set(awardsBg, { clearProps: "borderColor" }); onDesktop(() => { $(".block-cursor").css("display", ""); }); }, }); closeAwardsTl.to("[awards-fade]", { opacity: 0, ease: "expo.out", }); closeAwardsTl.to( awardsBg, { borderColor: "transparent", }, "<" ); closeAwardsTl.to( awardsBg, { borderRadius: "3px", width: "80%", height: "0%", top: "10%", }, "<" ); awardsEl.on("click", function (e) { if ($(e.target).closest(".awards-popup").length) return; onDesktop(() => { let trigger = $(this).closest("[cursor-track]"); if (trigger.length) { gsap.to(trigger.find("[cursor-el]"), { opacity: 0, duration: 0.3 }); gsap.to(trigger.find("[cursor-fade]"), { opacity: 1, duration: 0.3 }); } }); openAwardsTl.restart(); $(".navigation").addClass("muted"); lenis.stop(); }); awardsCloseBtn.on("click", function () { onDesktop(() => { awardsEl.each(function () { let trigger = $(this).closest("[cursor-track]"); if (trigger.length) { gsap.to(trigger.find("[cursor-el]"), { opacity: 1, duration: 0.3 }); } }); }); closeAwardsTl.restart(); $(".navigation").removeClass("muted"); lenis.start(); }); } function initParallax() { $("[parallax-component]").each(function (index) { let parallaxComp = $(this); let parallaxEl = parallaxComp.find("[parallax-el]"); let parallaxTl = gsap.timeline({ scrollTrigger: { trigger: parallaxComp, start: "top bottom", end: "bottom top", scrub: true, }, }); onDesktop(() => { parallaxTl.fromTo( parallaxEl, { y: "20svh" }, { y: "-20svh", ease: "linear" } ); }); }); } function initFooterClip() { onDesktop(() => { $("[clip-footer]").each(function (index) { let clippedImg = $(this).find("img"); let clipWide = gsap.timeline({ scrollTrigger: { trigger: $(this), start: "clamp(top 20%)", end: "bottom center", scrub: true, onEnter: () => { // lenis.resize(); $("[footer-fadeout]").addClass("hide"); $(".page_content").addClass("transform-bottom"); }, onLeaveBack: () => { $("[footer-fadeout]").removeClass("hide"); $(".page_content").removeClass("transform-bottom"); }, }, }); clipWide.to($(this), { clipPath: "inset(0px 20px round 3px)", ease: "none", }); clipWide.fromTo( clippedImg, { scale: "1.2" }, { scale: "1", ease: "none", scrollTrigger: { trigger: $(this), start: "top bottom", end: "bottom center", scrub: true, }, }, "<" ); }); }); onMobile(() => { $("[clip-footer]").each(function (index) { let clipWide = gsap.timeline({ scrollTrigger: { trigger: $(this), start: "bottom bottom", scrub: true, onEnter: () => { $("[footer-fadeout]").addClass("hide"); }, onLeaveBack: () => { $("[footer-fadeout]").removeClass("hide"); }, }, }); }); }); } function initOverlays() { $(".overlay").each(function (index) { let overlay = gsap.timeline({ scrollTrigger: { trigger: $(this), start: "top center", end: "bottom center", scrub: true, ease: "none", }, }); overlay.fromTo($(this), { opacity: 0 }, { opacity: 1 }); }); } function initHLines() { $(".h-line").each(function (index) { let hLine = gsap.timeline({ scrollTrigger: { trigger: $(this), start: "top 90%", toggleActions: "play none none none", }, }); hLine.fromTo( $(this), { width: "0%" }, { duration: 1.2, ease: "power4.out", width: "100%" } ); }); } function initVLines() { $(".v-line").each(function (index) { let vLine = gsap.timeline({ scrollTrigger: { trigger: $(this), start: "top center", end: "top 80%", toggleActions: "play none reverse none", duration: 2, ease: "power2.in", }, }); vLine.fromTo($(this), { height: "0%" }, { height: "100%" }); }); } function initContactPopup() { let openContactTl = gsap.timeline({ paused: true, defaults: { ease: "osmo", duration: transitionDuration, }, onStart: () => { $(".contact-component").addClass("open"); requestAnimationFrame(() => { $(".contact-popup_info").scrollTop(0); }); let contactAnim = $(".contact-component [lottie-holder]").data( "contactLottie" ); if (contactAnim) { contactAnim.setSpeed(1); contactAnim.setDirection(1); contactAnim.loop = true; contactAnim.play(); } }, }); openContactTl.from( ".contact-blur", { width: "80%", height: "0%", top: "10%", clearProps: "all", }, "<" ); openContactTl.to(".contact-blur", { borderRadius: "0px" }, "<"); openContactTl.from( "[contact-fade]", { opacity: 0, duration: transitionDuration / 2, ease: "sine.in", }, "<50%" ); let subscribePopup = $(".subscribe-popup"); let contactPopupInfo = $(".contact-popup_info"); let contactPopupLayout = $(".contact-popup_layout"); let subscribeOpenedFromOutside = false; let closeContactAfterSubscribe = false; let closeContactTl = gsap.timeline({ paused: true, defaults: { ease: "osmo", duration: transitionDuration / 2, }, onComplete: () => { $(".contact-component").removeClass("open"); subscribePopup.css("display", "none"); gsap.set(subscribePopup, { clearProps: "transform" }); gsap.set(contactPopupInfo, { clearProps: "backgroundColor" }); gsap.set(contactPopupLayout, { clearProps: "opacity" }); gsap.set(".contact-blur", { clearProps: "borderColor" }); }, }); closeContactTl.to("[contact-fade]", { opacity: 0, ease: "expo.out", onComplete: () => { let contactAnim = $(".contact-component [lottie-holder]").data( "contactLottie" ); if (contactAnim) contactAnim.goToAndStop(0, true); $(".navigation").removeClass("muted"); }, }); closeContactTl.to( ".contact-blur", { borderColor: "transparent", }, "<" ); closeContactTl.to( ".contact-blur", { borderRadius: "3px", width: "80%", height: "0%", top: "10%", }, "<" ); let openSubscribeTl = gsap.timeline({ paused: true, defaults: { ease: "osmo", duration: transitionDuration / 2, }, onStart: () => { subscribePopup.css("display", "flex"); subscribePopup.scrollTop(0); }, onComplete: () => { gsap.set(subscribePopup, { clearProps: "transform" }); }, }); openSubscribeTl.from(subscribePopup, { yPercent: 100, }); openSubscribeTl.to( contactPopupInfo, { backgroundColor: "#F2F2F3", duration: transitionDuration / 2, }, "<" ); openSubscribeTl.to( contactPopupLayout, { opacity: 0, duration: transitionDuration / 2, }, "<" ); let closeSubscribeTl = gsap.timeline({ paused: true, defaults: { ease: "osmo", duration: transitionDuration / 2, }, onComplete: () => { subscribePopup.css("display", "none"); gsap.set(subscribePopup, { clearProps: "transform" }); if (closeContactAfterSubscribe) { closeContactAfterSubscribe = false; closeContactTl.restart(); closeMenu(); lenis.start(); } }, }); closeSubscribeTl.to(subscribePopup, { yPercent: 100, }); closeSubscribeTl.to( contactPopupInfo, { backgroundColor: "", clearProps: "backgroundColor", duration: transitionDuration / 2, }, "<" ); closeSubscribeTl.to( contactPopupLayout, { opacity: 1, duration: transitionDuration / 2, }, "<" ); $("[subscribe-btn]").on("click", function () { let contactOpen = $(".contact-component").hasClass("open"); if (contactOpen) { subscribeOpenedFromOutside = false; openSubscribeTl.restart(); } else { subscribeOpenedFromOutside = true; gsap.set(subscribePopup, { yPercent: 0 }); gsap.set(contactPopupInfo, { backgroundColor: "#F2F2F3" }); gsap.set(contactPopupLayout, { opacity: 0 }); subscribePopup.css("display", "flex"); subscribePopup.scrollTop(0); openContactTl.restart(); $(".navigation").addClass("muted"); lenis.stop(); } }); $(".close-subscribe_btn").on("click", function () { closeSubscribeTl.restart(); }); $("[contact-trigger]").on("click", function () { onDesktop(() => { let trigger = $(this).closest("[cursor-track]"); if (trigger.length) { gsap.to(trigger.find("[cursor-el]"), { opacity: 0, duration: 0.3 }); gsap.to(trigger.find("[cursor-fade]"), { opacity: 1, duration: 0.3 }); } }); openContactTl.restart(); $(".navigation").addClass("muted"); lenis.stop(); }); $(".close-holder:not(.is-white)").on("click", function () { onDesktop(() => { $("[contact-trigger]").each(function () { let trigger = $(this).closest("[cursor-track]"); if (trigger.length) { gsap.to(trigger.find("[cursor-el]"), { opacity: 1, duration: 0.3 }); } }); }); closeContactTl.restart(); closeMenu(); lenis.start(); }); } function initMobileMenu() { let wasLightClassPresent = false; let menuComponent = $(".mobile-menu-component"); let openMenuTl = gsap.timeline({ paused: true, defaults: { ease: "osmo", duration: transitionDuration, }, onStart: () => { wasLightClassPresent = $(".navigation").hasClass("light"); $(".navigation").removeClass("light"); gsap.set(menuComponent, { opacity: 1, pointerEvents: "auto" }); gsap.set(".menu-holder", { opacity: 0 }); menuComponent.addClass("open"); $("body").addClass("menu-open"); lenis.stop(); }, }); openMenuTl.from( ".menu-blur_bg", { width: "80%", height: "0%", top: "10%", clearProps: "all", }, "<" ); openMenuTl.to(".menu-blur_bg", { borderRadius: "0px" }, "<"); openMenuTl.from( "[menu-fade]", { opacity: 0, duration: transitionDuration / 2, ease: "sine.in", }, "<50%" ); openMenuTl.to( ".menu-holder", { opacity: 1, duration: transitionDuration / 2, ease: "sine.in", }, "<" ); let closeMenuTl = gsap.timeline({ paused: true, defaults: { ease: "osmo", duration: transitionDuration / 2, }, onStart: () => { gsap.set(".menu-holder", { opacity: 0 }); $("body").removeClass("menu-open"); }, onComplete: () => { gsap.set(menuComponent, { opacity: 0, pointerEvents: "none" }); menuComponent.removeClass("open"); gsap.set(".menu-blur_bg", { clearProps: "borderColor" }); if (wasLightClassPresent) { $(".navigation").addClass("light"); } lenis.start(); }, }); closeMenuTl.to("[menu-fade]", { opacity: 0, ease: "expo.out", }); closeMenuTl.to( ".menu-blur_bg", { borderColor: "transparent", }, "<" ); closeMenuTl.to( ".menu-blur_bg", { borderRadius: "3px", width: "80%", height: "0%", top: "10%", ease: "expo.out", }, "<" ); closeMenuTl.to(".menu-holder", { opacity: 1, duration: transitionDuration / 3, ease: "sine.out", }); window.closeMenu = function () { if (!menuComponent.hasClass("open")) return; closeMenuTl.restart(); }; $(".menu-holder").on("click", function () { if (!menuComponent.hasClass("open")) { openMenuTl.restart(); } else { closeMenuTl.restart(); } }); } function initNavScrollCollapse() { onDesktop(() => { let menuOpenedByClick = false; let menuOpenScrollPos = 0; let navScrollTl = gsap.timeline({ paused: true, defaults: { easeReverse: true, ease: "osmo", }, }); navScrollTl.set(".menu-btn", { display: "flex", pointerEvents: "auto", }); navScrollTl.to(".nav-flex .nav-link", { opacity: 0, pointerEvents: "none", ease: "energy", easeReverse: "power1.in", }); navScrollTl.to( ".nav-flex .v-line-nav", { height: "0%", ease: "energy", easeReverse: "osmo", }, "<" ); navScrollTl.fromTo( ".nav-flex", { width: "32em" }, { width: "3.125em" }, "<70%" ); navScrollTl.from( ".menu-btn .menu-btn_line", { opacity: 0, width: "0em", ease: "energy", easeReverse: "power1.in", }, "<60%" ); ScrollTrigger.create({ trigger: "body", start: "50px top", onEnter: () => { if (!menuOpenedByClick) { navScrollTl.play(); } }, onLeaveBack: () => { menuOpenedByClick = false; navScrollTl.reverse(); }, }); $(".menu-btn").on("click", function () { menuOpenedByClick = true; menuOpenScrollPos = window.scrollY; navScrollTl.reverse(); }); lenis.on("scroll", ({ scroll }) => { if (menuOpenedByClick && Math.abs(scroll - menuOpenScrollPos) > 100) { menuOpenedByClick = false; navScrollTl.play(); } }); }); } function initFloatingNavToggle() { onDesktop(() => { $(".culture-section").each(function () { ScrollTrigger.create({ trigger: $(this), start: "clamp(bottom bottom)", end: "clamp(bottom top)", onEnter: () => { $("[floating-nav]").addClass("hide"); }, onLeaveBack: () => { $("[floating-nav]").removeClass("hide"); }, }); }); }); } function initializeMobileExecDropdowns() { onMobile(() => { let execTimelines = []; let execCurrentlyOpen = -1; $(".m-exec-cms_item").each(function (index) { let dropTrigger = $(this).find(".m-exec_top"); let dropContent = $(this).find(".m-exec_dropdown"); let dropContentFade = $(this).find(".m-exec_dropdown_row"); let plusVert = $(this).find(".m-plus_vert"); let execItem = $(this); let dropdownTl = gsap.timeline({ paused: true }); dropdownTl.to(dropContent, { height: "auto", duration: 0.6, ease: "power2.inOut", }); dropdownTl.to( dropContentFade, { opacity: 1, duration: 0, ease: "power2.inOut", }, "<" ); dropdownTl.to( plusVert, { rotateX: 90, duration: 0.6, ease: "power3.inOut", }, "<" ); execTimelines.push(dropdownTl); dropTrigger.on("click", function () { if (execCurrentlyOpen === index) { dropdownTl.reverse(); execItem.removeClass("is-open"); execCurrentlyOpen = -1; } else { if (execCurrentlyOpen !== -1) { execTimelines[execCurrentlyOpen].reverse(); $(".m-exec-cms_item").eq(execCurrentlyOpen).removeClass("is-open"); } dropdownTl.play(); execItem.addClass("is-open"); execCurrentlyOpen = index; } }); }); }); } const GRAINIENT_VERTEX = `#version 300 es in vec2 position; void main() { gl_Position = vec4(position, 0.0, 1.0); }`; const GRAINIENT_FRAGMENT = `#version 300 es precision highp float; uniform vec2 iResolution; uniform float iTime; uniform float uTimeSpeed; uniform float uColorBalance; uniform float uWarpStrength; uniform float uWarpFrequency; uniform float uWarpSpeed; uniform float uWarpAmplitude; uniform float uBlendAngle; uniform float uBlendSoftness; uniform float uRotationAmount; uniform float uNoiseScale; uniform float uGrainAmount; uniform float uGrainScale; uniform float uGrainAnimated; uniform float uContrast; uniform float uGamma; uniform float uSaturation; uniform vec2 uCenterOffset; uniform float uZoom; uniform vec3 uColor1; uniform vec3 uColor2; uniform vec3 uColor3; out vec4 fragColor; #define S(a,b,t) smoothstep(a,b,t) mat2 Rot(float a){float s=sin(a),c=cos(a);return mat2(c,-s,s,c);} vec2 hash(vec2 p){p=vec2(dot(p,vec2(2127.1,81.17)),dot(p,vec2(1269.5,283.37)));return fract(sin(p)*43758.5453);} float noise(vec2 p){vec2 i=floor(p),f=fract(p),u=f*f*(3.0-2.0*f);float n=mix(mix(dot(-1.0+2.0*hash(i+vec2(0.0,0.0)),f-vec2(0.0,0.0)),dot(-1.0+2.0*hash(i+vec2(1.0,0.0)),f-vec2(1.0,0.0)),u.x),mix(dot(-1.0+2.0*hash(i+vec2(0.0,1.0)),f-vec2(0.0,1.0)),dot(-1.0+2.0*hash(i+vec2(1.0,1.0)),f-vec2(1.0,1.0)),u.x),u.y);return 0.5+0.5*n;} void main(){ float t = iTime * uTimeSpeed; vec2 uv = gl_FragCoord.xy / iResolution.xy; float ratio = iResolution.x / iResolution.y; vec2 tuv = uv - 0.5 + uCenterOffset; tuv /= max(uZoom, 0.001); float degree = noise(vec2(t * 0.1, tuv.x * tuv.y) * uNoiseScale); tuv.y *= 1.0 / ratio; tuv *= Rot(radians((degree - 0.5) * uRotationAmount + 180.0)); tuv.y *= ratio; float frequency = uWarpFrequency; float ws = max(uWarpStrength, 0.001); float amplitude = uWarpAmplitude / ws; float warpTime = t * uWarpSpeed; tuv.x += sin(tuv.y * frequency + warpTime) / amplitude; tuv.y += sin(tuv.x * (frequency * 1.5) + warpTime) / (amplitude * 0.5); float b = uColorBalance; float s = max(uBlendSoftness, 0.0); mat2 blendRot = Rot(radians(uBlendAngle)); float blendX = (tuv * blendRot).x; float edge0 = -0.3 - b - s; float edge1 = 0.2 - b + s; float v0 = 0.5 - b + s; float v1 = -0.3 - b - s; vec3 layer1 = mix(uColor3, uColor2, S(edge0, edge1, blendX)); vec3 layer2 = mix(uColor2, uColor1, S(edge0, edge1, blendX)); vec3 col = mix(layer1, layer2, S(v0, v1, tuv.y)); vec2 grainUv = uv * max(uGrainScale, 0.001); if (uGrainAnimated > 0.5) { grainUv += vec2(iTime * 0.05); } float grain = fract(sin(dot(grainUv, vec2(12.9898, 78.233))) * 43758.5453); col += (grain - 0.5) * uGrainAmount; col = (col - 0.5) * uContrast + 0.5; float luma = dot(col, vec3(0.2126, 0.7152, 0.0722)); col = mix(vec3(luma), col, uSaturation); col = pow(max(col, 0.0), vec3(1.0 / max(uGamma, 0.001))); col = clamp(col, 0.0, 1.0); fragColor = vec4(col, 1.0); }`; function hexToRgbFloat(hex) { const r = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); if (!r) return [1, 1, 1]; return [ parseInt(r[1], 16) / 255, parseInt(r[2], 16) / 255, parseInt(r[3], 16) / 255, ]; } function initGrainient(scope) { if (typeof window.ogl === "undefined") { console.warn("[Grainient] OGL library not loaded — skipping"); return; } const { Renderer, Program, Mesh, Triangle } = window.ogl; const root = scope || document; const containers = root.querySelectorAll(".bg-gradient"); if (!containers.length) return; containers.forEach((container) => { if (container.__grainientInited) return; container.__grainientInited = true; // Clear inline CSS gradient + filter so the canvas shows cleanly container.style.backgroundImage = "none"; container.style.filter = "none"; const opts = { color1: "#49F7F7", color2: "#104E4E", color3: "#CBBFFC", timeSpeed: 1.3, colorBalance: -0.17, warpStrength: 0.45, warpFrequency: 4.4, warpSpeed: 1.5, warpAmplitude: 40, blendAngle: -106, blendSoftness: 0.24, rotationAmount: 450, noiseScale: 1, grainAmount: 0.1, grainScale: 2, grainAnimated: false, contrast: 1.35, gamma: 1, saturation: 0.9, centerX: 0, centerY: 0, zoom: 1.05, }; const renderer = new Renderer({ webgl: 2, alpha: true, antialias: false, dpr: Math.min(window.devicePixelRatio || 1, 2), }); const gl = renderer.gl; const canvas = gl.canvas; canvas.style.width = "100%"; canvas.style.height = "100%"; canvas.style.display = "block"; canvas.style.position = "absolute"; canvas.style.inset = "0"; // Ensure container can position the canvas const computedPos = getComputedStyle(container).position; if (computedPos === "static") container.style.position = "relative"; container.prepend(canvas); const geometry = new Triangle(gl); const program = new Program(gl, { vertex: GRAINIENT_VERTEX, fragment: GRAINIENT_FRAGMENT, uniforms: { iTime: { value: 0 }, iResolution: { value: new Float32Array([1, 1]) }, uTimeSpeed: { value: opts.timeSpeed }, uColorBalance: { value: opts.colorBalance }, uWarpStrength: { value: opts.warpStrength }, uWarpFrequency: { value: opts.warpFrequency }, uWarpSpeed: { value: opts.warpSpeed }, uWarpAmplitude: { value: opts.warpAmplitude }, uBlendAngle: { value: opts.blendAngle }, uBlendSoftness: { value: opts.blendSoftness }, uRotationAmount: { value: opts.rotationAmount }, uNoiseScale: { value: opts.noiseScale }, uGrainAmount: { value: opts.grainAmount }, uGrainScale: { value: opts.grainScale }, uGrainAnimated: { value: opts.grainAnimated ? 1 : 0 }, uContrast: { value: opts.contrast }, uGamma: { value: opts.gamma }, uSaturation: { value: opts.saturation }, uCenterOffset: { value: new Float32Array([opts.centerX, opts.centerY]), }, uZoom: { value: opts.zoom }, uColor1: { value: new Float32Array(hexToRgbFloat(opts.color1)) }, uColor2: { value: new Float32Array(hexToRgbFloat(opts.color2)) }, uColor3: { value: new Float32Array(hexToRgbFloat(opts.color3)) }, }, }); const mesh = new Mesh(gl, { geometry, program }); let lastW = 0; let lastH = 0; const setSize = () => { const rect = container.getBoundingClientRect(); const w = Math.max(1, Math.floor(rect.width)); const h = Math.max(1, Math.floor(rect.height)); // Skip if the size hasn't actually changed (ResizeObserver can fire on // sub-pixel / scroll-driven layout shifts that don't affect our buffer). if (w === lastW && h === lastH) return; lastW = w; lastH = h; renderer.setSize(w, h); const res = program.uniforms.iResolution.value; res[0] = gl.drawingBufferWidth; res[1] = gl.drawingBufferHeight; // No explicit render here — the rAF loop already renders every frame, // so we avoid a second draw on top of the GPU buffer reallocation. }; // Coalesce the continuous ResizeObserver callbacks down to one resize per // animation frame so dragging the window doesn't thrash the WebGL buffer. let resizeRaf = 0; const ro = new ResizeObserver(() => { if (resizeRaf) return; resizeRaf = requestAnimationFrame(() => { resizeRaf = 0; setSize(); }); }); ro.observe(container); setSize(); let raf = 0; let isVisible = true; let isPageVisible = !document.hidden; const t0 = performance.now(); const loop = (t) => { if (!canvas.isConnected) { cleanup(); return; } program.uniforms.iTime.value = (t - t0) * 0.001; renderer.render({ scene: mesh }); raf = requestAnimationFrame(loop); }; const tryStart = () => { if (isVisible && isPageVisible && raf === 0) raf = requestAnimationFrame(loop); }; const tryStop = () => { if (raf !== 0) { cancelAnimationFrame(raf); raf = 0; } }; const io = new IntersectionObserver( ([entry]) => { isVisible = entry.isIntersecting; isVisible ? tryStart() : tryStop(); }, { threshold: 0 } ); io.observe(container); const onVisibility = () => { isPageVisible = !document.hidden; isPageVisible ? tryStart() : tryStop(); }; document.addEventListener("visibilitychange", onVisibility); const cleanup = () => { tryStop(); if (resizeRaf) { cancelAnimationFrame(resizeRaf); resizeRaf = 0; } ro.disconnect(); io.disconnect(); document.removeEventListener("visibilitychange", onVisibility); }; tryStart(); }); } function initializeScrollCubeAnimation() { onDesktop(() => { $("[cube-flip-component]").each(function () { let component = $(this); let perspective = component.find("[cube-perspective]"); if (!perspective.length) return; let track = component.find(".exec-wrap_r"); let items = component.find("[cube-flip-item]"); let sides = perspective.find("[cube-flip-side]"); let box = perspective.find("[cube-component]"); let halfDepth = box.outerHeight() / 2; // Position each face on the cube (order matters: rotateX THEN translateZ) sides.each(function (i) { $(this).css( "transform", "rotateX(" + i * -90 + "deg) translateZ(" + halfDepth + "px)" ); }); gsap.set(box, { rotateX: 0 }); let totalRotation = (sides.length - 1) * 90; const getTravel = () => track.length ? Math.max(0, track.outerHeight() - perspective.outerHeight()) : 0; let cubeTl = gsap.timeline({ scrollTrigger: { trigger: track, start: "top bottom", end: "bottom top", scrub: true, }, }); if (track.length) { cubeTl.fromTo( perspective, { marginTop: 0 }, { marginTop: "auto", ease: "none" }, 0 ); } let firstItem = items.first(); let lastItem = items.last(); gsap.fromTo( box, { rotateX: 0 }, { rotateX: totalRotation, ease: "none", scrollTrigger: { trigger: firstItem, start: "top bottom", endTrigger: lastItem, end: "bottom top", scrub: true, }, } ); }); }); onMobile(() => { $("[mobile-cube-perspective]").each(function () { let perspective = $(this); let sides = perspective.find("[cube-flip-side]"); let box = perspective.find("[cube-component]"); if (!box.length) return; let halfDepth = box.outerHeight() / 2; sides.each(function (i) { $(this).css( "transform", "rotateX(" + i * -90 + "deg) translateZ(" + halfDepth + "px)" ); }); let totalRotation = (sides.length - 1) * 90; gsap.fromTo( box, { rotateX: 0 }, { scrollTrigger: { trigger: perspective, start: "top bottom", end: "bottom top", scrub: true, }, rotateX: totalRotation, ease: "power2.inOut", } ); }); // Pinned cube-flip components (e.g. .culture-cube) on mobile $("[cube-flip-component]").each(function () { let component = $(this); let sticky = component.find(".m-sticky_cube-outer"); if (!sticky.length) return; let perspective = component.find("[cube-perspective]"); let sides = perspective.find("[cube-flip-side]"); let box = perspective.find("[cube-component]"); let items = component.find("[cube-flip-item]"); if (!box.length || items.length < 2) return; let halfDepth = box.outerHeight() / 2; sides.each(function (i) { $(this).css( "transform", "rotateX(" + i * -90 + "deg) translateZ(" + halfDepth + "px)" ); }); gsap.set(box, { rotateX: 0 }); // Pin only — no rotation driven here ScrollTrigger.create({ trigger: sticky, start: () => "bottom " + window.innerHeight, endTrigger: component, end: () => "bottom " + window.innerHeight, pin: sticky, pinSpacing: false, }); let totalRotation = (sides.length - 1) * 90; let firstItem = items.first(); let lastItem = items.last(); gsap.fromTo( box, { rotateX: 0 }, { rotateX: totalRotation, ease: "none", scrollTrigger: { trigger: firstItem, start: () => "top " + window.innerHeight, endTrigger: lastItem, end: () => "bottom " + window.innerHeight, scrub: 0.4, }, } ); }); }); } let tlContainer; let tlHeaderStagger = []; let tlHeaderStaggerTwo = []; let tlContentStagger = []; function videoPopup() { $("[video-player]").each(function (index) { let playVidBtn = $(this).find(".video-popup_btn"); let videoPopupEl = $(this).find(".video-popup"); let closeVidBtn = $(this).find(".close-vid"); let videoItem = $(this).find(".plyr_component"); let videoScale = $(this).find(".plyr_embed"); let videoBg = $(this).find(".video-popup_bg"); let navVideo = $("[pt-nav]"); let thisComponent = $(this); const controls = `
Sound On Sound Off
`; let player = new Plyr(thisComponent.find(".plyr_video")[0], { controls, resetOnEnd: true, }); let videoPopupTl = gsap.timeline({ paused: true, defaults: { ease: "expo.inOut", duration: transitionDuration, }, onStart: () => { lenis.stop(); }, onReverseComplete: () => { lenis.start(); }, }); videoPopupTl.set( videoPopupEl, { display: "flex", }, "<" ); videoPopupTl.from(videoBg, { opacity: 0, ease: "sine.out", }); videoPopupTl.fromTo( videoItem, { clipPath: "inset(10% 10% 90% 10% round 3px)", }, { opacity: 1, clipPath: "inset(0% 0% 0% 0% round 0px)", }, "<" ); videoPopupTl.fromTo( videoScale, { scale: 1.2, }, { scale: 1, }, "<" ); videoPopupTl.fromTo( closeVidBtn, { opacity: 0, duration: 0.6, }, { opacity: 1, }, "<" ); videoPopupTl.set(videoBg, { pointerEvents: "auto", }); playVidBtn.on("click", function () { player.play(); videoPopupTl.restart(); }); closeVidBtn.on("click", function () { videoPopupTl.reverse().then( () => { navVideo.css("display", "block"); }, null, "-=0.1" ); player.pause(); }); player.on("ended", (event) => { videoPopupTl.reverse().then( () => { navVideo.css("display", "block"); }, null, "-=0.1" ); }); }); } function resetWebflow(data) { $(".w--current").removeClass("w--current"); $("a").each(function () { let href = $(this).attr("href"); if ( href && !href.startsWith("#") && this.pathname === window.location.pathname ) { $(this).addClass("w--current"); } }); $(".menu-wrapper .menu-link").first().addClass("w--current"); // Reinitialize Webflow so the in-page anchor scrollspy re-binds to the // new page's sections (otherwise w--current won't update on scroll after a // Barba transition). if (window.Webflow) { window.Webflow.destroy(); window.Webflow.ready(); const ix2 = window.Webflow.require && window.Webflow.require("ix2"); if (ix2 && ix2.init) ix2.init(); } } const runPageLeaveAnimation = (current, next) => { if (hasLenis && lenis) lenis.stop(); $(current).addClass("muted"); // Reset mobile menu state so the next page loads with a clean menu const leaveTl = gsap.timeline({ onComplete: () => { current.remove(); }, defaults: { ease: "expo.inOut", duration: transitionDuration, }, }); leaveTl.set($(current).find(".transition-overlay"), { display: "block", }); leaveTl.to($(current).find("[pt-nav]"), { y: "3rem", ease: "power3.inOut", duration: transitionDuration, }); leaveTl.fromTo( $(current).find(".page_content"), { y: "0rem", scale: 1, }, { y: "3rem", scale: 0.97, ease: "power3.inOut", duration: transitionDuration, }, "<" ); leaveTl.fromTo( $(current).find(".transition-overlay"), { clipPath: "polygon(0% 0%, 100% 0%, 100% 0%, 0% 0%)", }, { clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)", duration: transitionDuration, }, "<" ); leaveTl.add("pageReady"); return new Promise((resolve) => { leaveTl.call(resolve, null, "pageReady"); }); }; let counter = { value: 0, }; let loaderDuration = 2; function updateLoaderText() { let progress = Math.round(counter.value); $(".progress-number").text(String(progress).padStart(2, "0")); } function initNavLottie(scope) { let navHolder = $(scope).find(".navigation [lottie-holder]"); if (!navHolder.length) return; let navLottieEl = navHolder.find("[lottie-element]"); let navLottieUrl = navLottieEl.attr("lottie-url"); if (!navLottieEl.length || !navLottieUrl) return; let navLottieAnim = bodymovin.loadAnimation({ container: navLottieEl[0], renderer: "svg", autoplay: false, loop: true, path: navLottieUrl, rendererSettings: { idPrefix: "navLottie" }, }); navHolder.data("lottie", navLottieAnim); navLottieAnim.addEventListener("DOMLoaded", function () { navLottieAnim.goToAndStop(0, true); onDesktop(() => { navHolder.on("mouseenter", function () { navLottieAnim.setDirection(1); navLottieAnim.loop = true; navLottieAnim.play(); navLottieAnim.setSpeed(1); }); navHolder.on("mouseleave", function () { navLottieAnim.setDirection(-1); navLottieAnim.setSpeed(3); navLottieAnim.loop = false; }); }); }); return navLottieAnim; } function initContactLottie(scope) { let contactHolder = $(scope).find(".contact-component [lottie-holder]"); if (!contactHolder.length) return; let contactLottieEl = contactHolder.find("[lottie-element]"); let contactLottieUrl = contactLottieEl.attr("lottie-url"); if (!contactLottieEl.length || !contactLottieUrl) return; let contactLottieAnim = bodymovin.loadAnimation({ container: contactLottieEl[0], renderer: "canvas", autoplay: false, loop: true, path: contactLottieUrl, rendererSettings: { idPrefix: "contactLottie" }, }); contactHolder.data("contactLottie", contactLottieAnim); contactLottieAnim.addEventListener("DOMLoaded", function () { contactLottieAnim.goToAndStop(0, true); }); return contactLottieAnim; } function initFooterLottie(scope) { let footerHolder = $(scope).find("[footer-lottie_component]"); if (!footerHolder.length) return; let footerLottieEl = footerHolder.find("[footer-lottie]"); let footerLottieUrl = footerLottieEl.attr("footer-lottie"); if (!footerLottieEl.length || !footerLottieUrl) return; let footerLottieAnim = bodymovin.loadAnimation({ container: footerLottieEl[0], renderer: "svg", autoplay: false, loop: false, path: footerLottieUrl, rendererSettings: { idPrefix: "footerLottie" }, }); footerHolder.data("footerLottie", footerLottieAnim); footerLottieAnim.addEventListener("DOMLoaded", function () { footerLottieAnim.goToAndStop(0, true); let totalFrames = footerLottieAnim.totalFrames - 1; ScrollTrigger.create({ trigger: "[clip-footer]", start: "bottom 40%", end: "max", scrub: true, onUpdate: (self) => { footerLottieAnim.goToAndStop(self.progress * totalFrames, true); }, }); }); } function initMailchimpForms(scope = document) { const root = scope || document; root.querySelectorAll("form[data-mailchimp-form]").forEach((form) => { if (form.dataset.mcInit === "true") return; form.dataset.mcInit = "true"; form.addEventListener( "submit", function (e) { e.preventDefault(); e.stopImmediatePropagation(); const submitBtn = form.querySelector('[type="submit"]'); if (submitBtn) submitBtn.disabled = true; hideWebflowFormMessages(form); submitMailchimpJsonp(form) .then((res) => { const msg = res?.msg || ""; const isSuccess = res?.result === "success" || msg.toLowerCase().includes("already subscribed"); if (isSuccess) { form.reset(); showWebflowFormSuccess(form); } else { console.warn("Mailchimp error:", res); showWebflowFormError(form); } }) .catch((err) => { console.warn("Mailchimp submit failed:", err); showWebflowFormError(form); }) .finally(() => { if (submitBtn) submitBtn.disabled = false; }); }, true ); }); } function submitMailchimpJsonp(form) { return new Promise((resolve, reject) => { let action = form.getAttribute("action"); if (!action) { reject("No Mailchimp form action found."); return; } action = action.replace(/&/g, "&"); action = action.replace("/subscribe/post?", "/subscribe/post-json?"); action = action.replace(/([?&])c=[^&]*/g, ""); const callbackName = "mailchimpCallback_" + Date.now() + "_" + Math.floor(Math.random() * 100000); const formData = new FormData(form); const params = new URLSearchParams(); formData.forEach((value, key) => { if (key) params.append(key, value); }); params.append("c", callbackName); const script = document.createElement("script"); const separator = action.includes("?") ? "&" : "?"; window[callbackName] = function (response) { cleanup(); resolve(response); }; function cleanup() { delete window[callbackName]; if (script.parentNode) script.parentNode.removeChild(script); clearTimeout(timeout); } const timeout = setTimeout(() => { cleanup(); reject("Mailchimp request timed out."); }, 10000); script.onerror = function () { cleanup(); reject("Mailchimp script failed to load."); }; script.src = action + separator + params.toString(); document.body.appendChild(script); }); } function hideWebflowFormMessages(form) { const wrapper = form.closest(".w-form"); if (!wrapper) return; const success = wrapper.querySelector(".w-form-done"); const error = wrapper.querySelector(".w-form-fail"); if (success) success.style.display = "none"; if (error) error.style.display = "none"; } function showWebflowFormSuccess(form) { const wrapper = form.closest(".w-form"); if (!wrapper) return; const success = wrapper.querySelector(".w-form-done"); const error = wrapper.querySelector(".w-form-fail"); form.style.display = "none"; if (success) success.style.display = "block"; if (error) error.style.display = "none"; } function showWebflowFormError(form) { const wrapper = form.closest(".w-form"); if (!wrapper) return; const success = wrapper.querySelector(".w-form-done"); const error = wrapper.querySelector(".w-form-fail"); form.style.display = ""; if (success) success.style.display = "none"; if (error) error.style.display = "block"; } function whenWebflowLottiesReady(fallbackMs = 4000) { let containers = Array.from( document.querySelectorAll('[data-animation-type="lottie"]') ); let ready = Promise.all( containers.map( (el) => new Promise((resolve) => { // Webflow injects the rendered / only once the // animation JSON is loaded and the first frame is built. if (el.querySelector("svg, canvas")) { resolve(); return; } let obs = new MutationObserver(() => { if (el.querySelector("svg, canvas")) { obs.disconnect(); resolve(); } }); obs.observe(el, { childList: true, subtree: true }); }) ) ); let fallback = new Promise((resolve) => setTimeout(resolve, fallbackMs)); return Promise.race([ready, fallback]); } const runPageOnceAnimation = (container) => { // Keep scroll disabled during the intro so ScrollTriggers can be // created/refreshed before the user can scroll. afterEnter will start Lenis. if (hasLenis && lenis) lenis.stop(); gsap.set("[intro-stagger-text]", { opacity: 1, }); let pageReloadTl = gsap.timeline({ paused: true, defaults: { duration: transitionDuration, }, onComplete: () => { $("body").removeClass("show-preloader"); }, }); pageReloadTl.to($(".preloader-logo"), { opacity: 1, duration: transitionDuration, ease: "fade", }); pageReloadTl.to( counter, { value: 100, onUpdate: updateLoaderText, duration: loaderDuration, ease: "loaderEase", }, "<" ); pageReloadTl.to( $(".preloader-content"), { marginBottom: "0px", duration: loaderDuration * 0.9, ease: "loaderEase", }, "<10%" ); pageReloadTl.fromTo( $(".preloader"), { clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)", }, { clipPath: "polygon(0% 100%, 100% 100%, 100% 100%, 0% 100%)", ease: "expo.inOut", } ); pageReloadTl.to( $(".preloader-content"), { y: "15rem", ease: "expo.inOut", }, "<" ); pageReloadTl.from( $(container).find("[pt-nav]"), { y: "-15rem", ease: "expo.out", }, "<50%" ); pageReloadTl.from( $(container).find("[intro-stagger-text]"), { y: "-10rem", }, "<" ); pageReloadTl.from( $(container).find("[intro-stagger-text] .line"), { rotateX: 90, y: "-5rem", opacity: 0, }, "<" ); if ($(container).find("[intro-fade]").length) { pageReloadTl.from( $(container).find("[intro-fade]"), { y: "-10rem", }, "<" ); pageReloadTl.to( $(container).find("[intro-fade]"), { opacity: 1, duration: transitionDuration / 2, ease: "sine.in", }, "<" ); } if ($(container).find("[intro-clip]").length) { pageReloadTl.fromTo( $(container).find("[intro-clip]"), { clipPath: "inset(0% 0% 100% 0% round .3125rem)", }, { clipPath: "inset(0% 0% 0% 0% round .3125rem)", stagger: 0.1, }, "<" ); } if ($(container).find("[intro-opacity]").length) { pageReloadTl.to( $(container).find("[intro-opacity]"), { opacity: 1, duration: transitionDuration / 2, ease: "sine.in", }, "<" ); } whenWebflowLottiesReady().then(() => { pageReloadTl.play(); }); return pageReloadTl; }; const runPageEnterAnimation = (next) => { $(next).find("[footer-fadeout]").removeClass("hide"); $(next).removeClass("transform-bottom"); const enterTl = gsap.timeline({ defaults: { ease: "expo.inOut", duration: transitionDuration, }, }); enterTl.set(next, { opacity: 1, pointerEvents: "auto", }); enterTl.set($(next).find("[intro-stagger-text] "), { opacity: 0, }); enterTl.fromTo( next, { clipPath: "inset(10% 10% 90% 10% round 0.3rem)", }, { clipPath: "inset(0% 0% 0% 0% round 0rem)", }, 0.2 ); enterTl.from( $(next).find("[pt-nav]"), { y: "-10rem", ease: "expo.out", duration: transitionDuration, onStart: () => { runSplit(); enterTlAnimations(); }, }, "<50%" ); enterTl.add("pageReady"); enterTl.call(resetPage, [next], "pageReady"); return new Promise((resolve) => { enterTl.call(resolve, null, "pageReady"); }); }; // ----------------------------------------- // FUNCTION REGISTRY // ----------------------------------------- function initOnceFunctions() { initLenis(); if (onceFunctionsInitialized) return; onceFunctionsInitialized = true; } function initBeforeEnterFunctions(next) { nextPage = next || document; $("body").removeClass("menu-open"); $(".mobile-menu-component").removeClass("open"); gsap.set(".mobile-menu-component", { opacity: 0, pointerEvents: "none" }); gsap.set(".menu-holder", { clearProps: "opacity" }); if (has("[btn-el]")) initButtonHovers(); // Namespace-specific pre-enter setup let ns = next?.dataset?.barbaNamespace || ""; if (ns === "additive" || ns === "team") { $(next).find(".navigation").addClass("light"); } } function initAfterEnterFunctions(next) { nextPage = next || document; // Text + scroll animations if (has("[stagger-text]")) initStaggerText(); if (has("[stagger-char]")) initStaggerChar(); if (has("[fade-in]")) initFadeIn(); if (has("[parallax-component]")) initParallax(); if (has(".overlay")) initOverlays(); if (has(".h-line")) initHLines(); if (has(".v-line")) initVLines(); if (has("[light]")) initLightSections(); if (has("[clip-footer]")) initFooterClip(); // Scroll-driven layout if (has("[panel]")) initPanels(); if (has(".awards-section")) initAwardsScroll(); if (has(".additive-item")) initAdditiveItems(); if (has(".dark-sticky_card")) initDarkStickyCards(); if (has("[wireframe-trigger]")) initWireframes(); // Interactions if (has("[cursor-track]")) initCursorTracking(); if (has("[accordion-item]")) initAccordions(); if (has("[copy-url]")) initCopyUrl(); if (has(".bordered-btn, .border-el")) initBorderGradient(); if (has(".culture-type_layout")) initCultureText(); if (has(".subscribe-submit")) initSubscribeSubmit(); if (has(".back-btn")) initBackButtons(); if (has(".interaction_cms_wrap")) initCmsNumbers(); // Navigation if (has(".navigation")) initNavHover(); if (has(".nav-flex")) initNavScrollCollapse(); if (has(".mobile-menu-component")) initMobileMenu(); if (has(".contact-component")) initContactPopup(); // Page-specific let ns = next?.dataset?.barbaNamespace || ""; if (ns === "home") homeFunctions(); if (ns === "team") { initGrainient(next); initializeScrollCubeAnimation(); videoPopup(); initializeMobileExecDropdowns(); initFloatingNavToggle(); } initMailchimpForms(next); positionCursors(); resetWebflow(); initFooterLottie(next); if (hasLenis && lenis) { lenis.resize(); } if (hasScrollTrigger) { ScrollTrigger.refresh(); } } // ----------------------------------------- // GENERIC + HELPERS // ----------------------------------------- function resetPage(container) { window.scrollTo(0, 0); gsap.set(container, { clearProps: "position,top,left,right,width,height,zIndex,pointerEvents", }); if (hasLenis && lenis) { lenis.resize(); lenis.start(); } } function debounceOnWidthChange(fn, ms) { let last = innerWidth, timer; return function (...args) { clearTimeout(timer); timer = setTimeout(() => { if (innerWidth !== last) { last = innerWidth; fn.apply(this, args); } }, ms); }; } // ----------------------------------------- // BARBA HOOKS + INIT // ----------------------------------------- barba.hooks.before((data) => { previousUrl = data.current.url.href; }); barba.hooks.beforeEnter((data) => { if (onceFunctionsInitialized) { gsap.set(data.next.container, { position: "fixed", top: 0, left: 0, right: 0, width: "100%", height: "100vh", zIndex: 3000, pointerEvents: "none", }); } if (hasLenis && lenis) { lenis.stop(); } initBeforeEnterFunctions(data.next.container); }); barba.hooks.afterLeave(() => { if (hasScrollTrigger) { ScrollTrigger.getAll().forEach((t) => t.kill()); } }); barba.hooks.afterEnter((data) => { let container = data.next.container; lineSplit.revert(); charSplit.revert(); runSplit(); initAfterEnterFunctions(container); if (hasLenis && lenis) { lenis.resize(); lenis.start(); } if (hasScrollTrigger) { ScrollTrigger.refresh(); } }); barba.init({ timeout: 50000, preventRunning: true, transitions: [ { name: "default", sync: true, once(data) { initOnceFunctions(); let container = data.next.container; initNavLottie(container); initContactLottie(container); return runPageOnceAnimation(container); }, leave: (data) => runPageLeaveAnimation(data.current.container, data.next.container), enter: (data) => { let next = data.next.container; initNavLottie(next); initContactLottie(next); return runPageEnterAnimation(next); }, }, ], });