(function () { "use strict"; var VERSION = 122; var LINK_SHIFT_X = "0.8em"; var LINK_SHIFT_EM = 0.8; var NAV_ENTER_DUR = 0.62; // Current-link moves run as CSS transitions: they stay on the compositor while a new page is loading and the main // thread (and every GSAP frame with it) stalls for 100 ms at a time. var NAV_MOVE_EASE_CSS = "cubic-bezier(0.16, 1, 0.3, 1)"; // On click the link is at rest after its hover move; an expo-out start would kick it ~3px on the first frame. var NAV_LEAVE_EASE_CSS = "cubic-bezier(0.33, 0, 0.2, 1)"; var NAV_DIM_OPACITY = "0.55"; var NAV_LEAVE_DUR = 0.5; var NAV_ARRIVE_KEY = "nav-arrive"; var NAV_ARRIVE_MAX_MS = 15000; var NAV_ARRIVE_CLASS = "nav-arrive"; var NAV_ARRIVE_HOVER_CLASS = "nav-arrive-hover"; var LINK_OUT_DUR = 0.34; var LINK_IN_DUR = 0.34; var LINK_OUT_Y = -16; var LINK_IN_Y = -16; var LINK_STAGGER_OUT = { each: 0.032, from: "start" }; var LINK_STAGGER_IN = { each: 0.032, from: "end" }; var MENU_IN_DUR = 0.46; var MENU_OUT_DUR = 0.36; var MENU_TOGGLE_DUR = 0.16; var EXPAND_REVERSE_SPEED = 1.5; var MENU_IN_Y = 10; var MENU_OUT_Y = -8; var MENU_REVEAL_AFTER_LINKS = 2; var EXPAND_LINKS_IN_AT = 0.32; var MENU_LINKS_COLLAPSE_DUR = 0.2; var DESKTOP_EXPAND_AT = 0.22; var DESKTOP_LINK_EASE_OUT = "expo.out"; var DESKTOP_LINK_EASE_IN = "expo.in"; var DESKTOP_MORPH_EASE = "power2.out"; var DESKTOP_HEIGHT_EASE = "power2.inOut"; var BLOCK_COLLAPSE_DUR = 0.5; var BLOCK_EXPAND_DUR = 0.54; var MOBILE_ANIM_DUR = 0.65; var MOBILE_MENU_IN_DUR = MOBILE_ANIM_DUR; var MOBILE_MENU_OUT_DUR = MOBILE_ANIM_DUR; var MOBILE_OVERLAY_IN_DUR = MOBILE_ANIM_DUR; var MOBILE_OVERLAY_OUT_DUR = MOBILE_ANIM_DUR; var MOBILE_BLOCK_COLLAPSE_DUR = MOBILE_ANIM_DUR; var MOBILE_BLOCK_EXPAND_DUR = MOBILE_ANIM_DUR; var SCROLL_LOCK_PAD_MS = 100; var DESKTOP_EASE = "power2.inOut"; var DESKTOP_EASE_OUT = "power2.out"; var DESKTOP_EASE_IN = "power2.in"; var MOBILE_EASE = "power2.inOut"; var MOBILE_EASE_OUT = "power2.out"; var MOBILE_EASE_IN = "power2.in"; var GSAP_URL = (window.MENU_SCROLL_COLLAPSE_CONFIG && window.MENU_SCROLL_COLLAPSE_CONFIG.gsapUrl) || "https://cdn.jsdelivr.net/npm/gsap@3.12.7/dist/gsap.min.js"; if (window.__menuScrollCollapseVersion === VERSION) return; window.__menuScrollCollapseVersion = VERSION; var CFG = window.MENU_SCROLL_COLLAPSE_CONFIG || {}; var DESKTOP_MQ = CFG.desktopMq || CFG.enabledMq || "(min-width: 768px)"; var MOBILE_MQ = CFG.mobileMq || "(max-width: 767px)"; var TOP_EXPAND = CFG.topExpandPx != null ? CFG.topExpandPx : 32; var MODAL_OPEN_SELECTOR = CFG.modalOpenSelector != null ? CFG.modalOpenSelector : ".archive_lightbox.is-open"; var NATIVE_DIR_DELTA = CFG.nativeDirDelta != null ? CFG.nativeDirDelta : 2; var LENIS_VEL_MIN = CFG.lenisVelMin != null ? CFG.lenisVelMin : 0.05; var MOBILE_DIR_THRESHOLD = CFG.mobileDirThreshold != null ? CFG.mobileDirThreshold : 18; var DESKTOP_DIR_THRESHOLD = CFG.desktopDirThreshold != null ? CFG.desktopDirThreshold : 16; var PATH_MATCH = CFG.pathMatch; var DEFAULT_DESKTOP_EXCLUDE = ["/", "/work"]; var DEFAULT_MOBILE_SCROLL_EXCLUDE = ["/", "/work"]; var DESKTOP_EXCLUDE = CFG.excludePaths != null ? CFG.excludePaths : DEFAULT_DESKTOP_EXCLUDE; var MOBILE_SCROLL_EXCLUDE = CFG.mobileScrollExcludePaths != null ? CFG.mobileScrollExcludePaths : CFG.mobileExcludePaths != null ? CFG.mobileExcludePaths : DEFAULT_MOBILE_SCROLL_EXCLUDE; var desktopMq = window.matchMedia(DESKTOP_MQ); var mobileMq = window.matchMedia(MOBILE_MQ); var nav = null; var menuLinks = null; var bottomSide = null; var burger = null; var mobileMenu = null; var mobileOverlay = null; var mobileMenuOpen = false; var mobileMenuTl = null; var mobileTouchHandled = false; var lastY = 0; var collapsed = false; var forceExpanded = false; var heightsCached = false; var ready = false; var bootstrapped = false; var resizeTimer = 0; var desktopClickBound = false; var mobileClickBound = false; var closingMobileMenu = false; var lenisScrollBound = false; var overlayTrackRaf = 0; var scrollAnimLocked = false; var mobileNavAnimLocked = false; var mobileScrollAccum = 0; var desktopScrollAccum = 0; var lastUserScrollAt = 0; var userScrollBound = false; var USER_SCROLL_MS = CFG.userScrollMs != null ? CFG.userScrollMs : 240; var mobilePageScrollLocked = false; var gsapLoadPromise = null; var sizeCache = { bottom: 0, bottomMarginTop: 0, bottomPaddingTop: 0, bottomPaddingBottom: 0, linksExpanded: 0, linksCollapsed: 0, navLinkHeights: [], menuLinksGap: 0, }; function gsapRef() { return window.gsap || null; } // jsDelivr is a single point of failure for GSAP / three.js; fall back to unpkg, share one load per page. function loadScriptWithFallback(urls, isReady) { return new Promise(function (resolve, reject) { var i = 0; (function next() { if (isReady()) return resolve(); if (i >= urls.length) return reject(new Error("script failed: " + urls[0])); var s = document.createElement("script"); s.src = urls[i++]; s.async = true; s.onload = function () { if (isReady()) resolve(); else next(); }; s.onerror = function () { s.remove(); next(); }; document.head.appendChild(s); })(); }); } function fallbackUrl(url) { return url.replace("https://cdn.jsdelivr.net/npm/", "https://unpkg.com/"); } function sharedGsap(url) { if (window.gsap) return Promise.resolve(window.gsap); if (!window.__siteGsapPromise) { window.__siteGsapPromise = loadScriptWithFallback([url, fallbackUrl(url)], function () { return !!window.gsap; }); window.__siteGsapPromise.catch(function () { window.__siteGsapPromise = null; }); } return window.__siteGsapPromise.then(function () { return window.gsap; }); } function loadGsap() { if (gsapRef()) return Promise.resolve(gsapRef()); if (gsapLoadPromise) return gsapLoadPromise; gsapLoadPromise = sharedGsap(GSAP_URL).then(function () { if (gsapRef()) return gsapRef(); throw new Error("GSAP failed to load"); }); gsapLoadPromise.catch(function () { gsapLoadPromise = null; }); return gsapLoadPromise; } function bindDesktopClick() { if (!burger || desktopClickBound || !desktopMq.matches) return; burger.addEventListener("click", onMenuClickDesktop, true); desktopClickBound = true; } function bindMobileMenuClick() { if (!burger || mobileClickBound) return; burger.addEventListener("touchend", onMenuTouchMobile, { passive: false }); burger.addEventListener("click", onMenuClickMobile, true); mobileClickBound = true; } function getScrollY() { if (window.lenis && typeof window.lenis.scroll === "number") { return window.lenis.scroll; } return window.scrollY || window.pageYOffset || 0; } function getMode() { if (desktopMq.matches) return "desktop"; if (mobileMq.matches) return "mobile"; return null; } function isMobileMode() { return getMode() === "mobile"; } function resetScrollDirectionState() { mobileScrollAccum = 0; desktopScrollAccum = 0; } function markUserScroll() { lastUserScrollAt = Date.now(); } function hasUserScrollIntent() { return Date.now() - lastUserScrollAt <= USER_SCROLL_MS; } function hasScrollMotion() { if (hasUserScrollIntent()) return true; if (window.lenis) { var v = Math.abs(window.lenis.velocity || 0); if (v >= LENIS_VEL_MIN) return true; } return false; } function bindUserScrollIntent() { if (userScrollBound) return; userScrollBound = true; window.addEventListener("wheel", markUserScroll, { passive: true, capture: true }); window.addEventListener("touchmove", markUserScroll, { passive: true, capture: true }); window.addEventListener( "keydown", function (e) { var k = e.key; if ( k === "ArrowDown" || k === "ArrowUp" || k === "PageDown" || k === "PageUp" || k === "Home" || k === "End" || k === " " || k === "Spacebar" ) markUserScroll(); }, { passive: true, capture: true }, ); } function getScrollDirection(y) { var dy = y - lastY; if (Math.abs(dy) < NATIVE_DIR_DELTA) { if (Math.abs(desktopScrollAccum) > 0) { desktopScrollAccum *= 0.5; if (Math.abs(desktopScrollAccum) < 1) desktopScrollAccum = 0; } return 0; } if (!hasScrollMotion()) { desktopScrollAccum = 0; return 0; } if (lenisScrollBound && window.lenis && !collapsed) { var vel = Math.abs(window.lenis.velocity || 0); if (vel >= LENIS_VEL_MIN) { var lenisDir = window.lenis.direction || 0; if (lenisDir === 1 && dy < 0) return 0; if (lenisDir === -1 && dy > 0) return 0; } } desktopScrollAccum += dy; if (desktopScrollAccum >= DESKTOP_DIR_THRESHOLD) { desktopScrollAccum = 0; return 1; } if (desktopScrollAccum <= -DESKTOP_DIR_THRESHOLD) { desktopScrollAccum = 0; return -1; } return 0; } function getMobileScrollDirection(y) { mobileScrollAccum += y - lastY; if (mobileScrollAccum >= MOBILE_DIR_THRESHOLD) { mobileScrollAccum = 0; return 1; } if (mobileScrollAccum <= -MOBILE_DIR_THRESHOLD) { mobileScrollAccum = 0; return -1; } return 0; } function lockMobileNavAnim() { if (!isMobileMode()) return; mobileNavAnimLocked = true; } function unlockMobileNavAnim() { mobileNavAnimLocked = false; resetScrollDirectionState(); } function readBottomSideMotion() { if (!bottomSide) { return { height: 0, marginTop: 0, paddingTop: 0, paddingBottom: 0, opacity: 0 }; } var cs = window.getComputedStyle(bottomSide); var opacity = gsapRef() ? gsap.getProperty(bottomSide, "opacity") : parseFloat(cs.opacity); if (typeof opacity !== "number" || isNaN(opacity)) opacity = parseFloat(cs.opacity) || 0; return { height: bottomSide.offsetHeight || 0, marginTop: parseFloat(cs.marginTop) || 0, paddingTop: parseFloat(cs.paddingTop) || 0, paddingBottom: parseFloat(cs.paddingBottom) || 0, opacity: opacity, }; } function isMobileMenuOpen() { if (!isMobileMode()) return false; return mobileMenuOpen; } function neutralizeWebflowMenu(g) { if (!mobileMenu || !g) return; g.set(mobileMenu, { transform: "none", maxHeight: "none", clearProps: "transform,maxHeight,scale,rotate,rotateX,rotateY,rotateZ,skewX,skewY", }); var nodes = mobileMenu.querySelectorAll("*"); if (nodes.length) g.set(nodes, { clearProps: "transform" }); } function measureMobileMenuHeight() { if (!mobileMenu) return 0; var g = gsapRef(); if (!g) return mobileMenu.scrollHeight || 0; neutralizeWebflowMenu(g); g.set(mobileMenu, { height: "auto", display: "flex", overflow: "visible", visibility: "hidden", position: "absolute", opacity: 0, transform: "none", }); var h = mobileMenu.offsetHeight; g.set(mobileMenu, { clearProps: "height,display,overflow,visibility,position,opacity" }); return h; } function killMobileMenuTweens() { var g = gsapRef(); if (!g) return; if (mobileMenuTl) { mobileMenuTl.kill(); mobileMenuTl = null; } if (mobileMenu) { g.killTweensOf(mobileMenu); g.killTweensOf(mobileMenu.children); } if (mobileOverlay) g.killTweensOf(mobileOverlay); } function getNavLinks(excludeBurger) { if (!menuLinks) return []; var all = menuLinks.querySelectorAll(".h_link"); if (!excludeBurger) return Array.prototype.slice.call(all); return Array.prototype.filter.call(all, function (el) { return !el.classList.contains("menu_hamburger"); }); } function isNavLinkCurrent(link) { return ( link.classList.contains("w--current") || link.getAttribute("aria-current") === "page" || link.classList.contains("is-nav-link-current") ); } function navLinkShiftX(link) { return isNavLinkCurrent(link) ? LINK_SHIFT_X : 0; } function prepNavLinksForMotion(g, navLinks, opts) { if (!g || !navLinks.length) return; opts = opts || {}; navLinks.forEach(function (link) { var props = { display: opts.display != null ? opts.display : "flex", autoAlpha: opts.opacity != null ? opts.opacity : 1, x: opts.shift === false ? 0 : navLinkShiftX(link), y: opts.y != null ? opts.y : 0, }; g.set(link, props); }); } function setNavLinksMotion(g, navLinks, props) { if (!g || !navLinks.length) return; g.set(navLinks, props); } function syncCurrentNavLinkMarkers() { if (!menuLinks) return; getNavLinks(true).forEach(function (link) { var isCurrent = link.classList.contains("w--current") || link.getAttribute("aria-current") === "page"; link.classList.toggle("is-nav-link-current", isCurrent); }); } function scheduleNavEnterAnimation() { if (!nav || isMobileMode() || collapsed) return; clearTimeout(nav._navEnterScheduleTimer); nav._navEnterScheduleTimer = setTimeout(function () { nav._navEnterScheduleTimer = null; playNavEnterAnimation(); }, 48); } function normPath(path) { return (path || "/").replace(/\/+$/, "") || "/"; } function linkPath(link) { try { return normPath(new URL(link.href, location.href).pathname); } catch (err) { return ""; } } function navArrow(link) { return link.querySelector(".arrow_menu_nav"); } function setImportant(el, prop, value) { el.style.setProperty(prop, value, "important"); } // Menu clicks write the target path here; the next page then shows its current link already in place instead of // replaying the enter move the previous page has just played. // webflow/nav-arrive-head.html reads the same entry in the head and adds these classes before the first paint. var rootClasses = document.documentElement.classList; var navArrivePosed = rootClasses.contains(NAV_ARRIVE_CLASS); var navArrivalHover = rootClasses.contains(NAV_ARRIVE_HOVER_CLASS); var navArrival = (function () { try { var raw = sessionStorage.getItem(NAV_ARRIVE_KEY); if (!raw) return navArrivePosed; sessionStorage.removeItem(NAV_ARRIVE_KEY); var data = JSON.parse(raw); if (!data || data.path !== normPath(location.pathname) || Date.now() - data.t >= NAV_ARRIVE_MAX_MS) return navArrivePosed; navArrivalHover = navArrivalHover || !!data.hover; return true; } catch (err) { return navArrivePosed; } })(); function dropNavArriveClasses() { rootClasses.remove(NAV_ARRIVE_CLASS); rootClasses.remove(NAV_ARRIVE_HOVER_CLASS); } function writeNavArrival(path, hover) { try { sessionStorage.setItem(NAV_ARRIVE_KEY, JSON.stringify({ path: path, t: Date.now(), hover: hover })); } catch (err) {} } function navTxt(link) { return link.querySelector(".txt"); } function hasPainted() { try { return performance.getEntriesByType("paint").length > 0; } catch (err) { return true; } } // Inline !important poses that override the Webflow hover/current rules (themselves !important) and are removed // again once the page's own CSS gives the same pose. function dropImportant(el, props) { props.forEach(function (p) { if (el.style.getPropertyPriority(p) === "important") el.style.removeProperty(p); }); } // withGsap also resets GSAP's inline transforms; without it only the poses set here are dropped, so a collapse // tween started meanwhile keeps its own values. function clearNavPose(links, withGsap) { var g = withGsap ? gsapRef() : null; links.forEach(function (link) { var arrow = navArrow(link); var txt = navTxt(link); if (g) g.set(link, { clearProps: "transform,x,y" }); dropImportant(link, ["transform", "transition"]); if (txt) dropImportant(txt, ["opacity", "transition"]); if (!arrow) return; if (g) g.set(arrow, { clearProps: "opacity,visibility,transform" }); dropImportant(arrow, ["opacity", "transform", "transition"]); }); } function moveNavLink(link, active, shiftEm, dur, easeCss) { var arrow = navArrow(link); var ease = dur + "s " + (easeCss || NAV_MOVE_EASE_CSS); setImportant(link, "transition", dur ? "transform " + ease : "none"); setImportant(link, "transform", "translateX(" + shiftEm + "em)"); if (!arrow) return; setImportant(arrow, "transition", dur ? "opacity " + ease + ", transform " + ease : "none"); setImportant(arrow, "opacity", active ? "1" : "0"); setImportant(arrow, "transform", active ? "translateY(-50%) translateX(0)" : "translateY(-50%) translateX(-0.35em)"); } function finishNavEnter() { if (!nav || !nav._navEnterTl) return; var tl = nav._navEnterTl; nav._navEnterTl = null; clearTimeout(tl.timer); clearNavPose(tl.links); } function playNavEnterAnimation() { if (!nav || isMobileMode() || collapsed) return; syncCurrentNavLinkMarkers(); var g = gsapRef(); if (!g) return; var currentLinks = getNavLinks(true).filter(isNavLinkCurrent); var arrived = navArrival && !nav._navArrivePainted; navArrival = false; if (nav._navEnterTl) nav._navEnterTl.kill(); if (!currentLinks.length || arrived) { nav.classList.remove("is-nav-pending-enter"); return; } currentLinks.forEach(function (link) { g.killTweensOf(link); g.set(link, { autoAlpha: 1 }); var arrow = navArrow(link); if (arrow) g.killTweensOf(arrow); }); clearNavPose(currentLinks, true); currentLinks.forEach(function (link) { moveNavLink(link, false, 0, 0); }); nav.classList.remove("is-nav-pending-enter"); void nav.offsetWidth; currentLinks.forEach(function (link) { moveNavLink(link, true, LINK_SHIFT_EM, NAV_ENTER_DUR); }); var tl = { links: currentLinks, timer: setTimeout(finishNavEnter, NAV_ENTER_DUR * 1000 + 80), kill: finishNavEnter, }; nav._navEnterTl = tl; } // The clicked link slides into its current pose (and the old current one out of it) while the page transition // fades the content, so the next page opens with the menu exactly as this one left it. function onNavLeaveClick(e) { if (!nav || !menuLinks || isMobileMode() || collapsed || e.defaultPrevented) return; if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return; var a = e.target.closest && e.target.closest("a[href]"); if (!a || a.target === "_blank" || a.hasAttribute("download")) return; var url; try { url = new URL(a.href, location.href); } catch (err) { return; } if (url.origin !== location.origin) return; var path = normPath(url.pathname); if (path === normPath(location.pathname)) return; playNavLeaveAnimation(path); } function playNavLeaveAnimation(path) { var g = gsapRef(); if (nav._navEnterTl) nav._navEnterTl.kill(); nav._navLeaving = true; var links = getNavLinks(true); var current = links.filter(isNavLinkCurrent)[0]; // The target page's current link sits at its w--current margin plus the 0.8em shift. var marginEm = LINK_SHIFT_EM; if (current) { var cs = window.getComputedStyle(current); var fs = parseFloat(cs.fontSize) || 0; if (fs) marginEm = (parseFloat(cs.marginLeft) || 0) / fs; } var moving = links.filter(function (link) { return (linkPath(link) === path) !== isNavLinkCurrent(link); }); moving.forEach(function (link) { if (!g) return; g.killTweensOf(link); var arrow = navArrow(link); if (arrow) g.killTweensOf(arrow); }); // Clicking a hovered menu link keeps the hover look (other links dimmed) through the transition and onto the // next page, where the cursor still is; dropping it here made the other links brighten and then dim again. var hovered = nav.classList.contains("is-nav-link-hover") && menuLinks._hh && linkPath(menuLinks._hh) === path; if (!hovered) clearDesktopLinkHover(); moving.forEach(function (link) { var next = linkPath(link) === path; moveNavLink(link, next, next ? marginEm + LINK_SHIFT_EM : -marginEm, NAV_LEAVE_DUR, NAV_LEAVE_EASE_CSS); var txt = navTxt(link); if (hovered && !next && txt) { setImportant(txt, "transition", "opacity " + NAV_LEAVE_DUR + "s " + NAV_LEAVE_EASE_CSS); setImportant(txt, "opacity", NAV_DIM_OPACITY); } }); nav._navLeaveLinks = moving; nav._navLeavePath = path; writeNavArrival(path, !!hovered); } function onNavLeaveHoverLost() { if (!nav._navLeaving) return; writeNavArrival(nav._navLeavePath, false); (nav._navLeaveLinks || []).forEach(function (link) { var txt = navTxt(link); if (txt) dropImportant(txt, ["opacity"]); }); } function settleArrivedNav() { var links = getNavLinks(true); var currentLinks = links.filter(isNavLinkCurrent); if (!currentLinks.length) return; currentLinks.forEach(function (link) { moveNavLink(link, true, LINK_SHIFT_EM, 0); }); if (navArrivalHover) { links.forEach(function (link) { var txt = navTxt(link); if (txt) setImportant(txt, "transition", "none"); }); } void nav.offsetWidth; // Held until the page's own CSS (current pose, no pending class) is in effect, so no transition can start // from the pre-mount pose. setTimeout(function () { requestAnimationFrame(function () { if (!nav._navEnterTl) clearNavPose(links); }); }, 400); } // Runs after the first syncState(), which resets the hover state while it sets the nav up. function applyArrivalHover() { if (!navArrivalHover) return; navArrivalHover = false; // The hover pose would show the link shifted without its arrow while the enter glide is pending. if (isMobileMode() || collapsed || nav._navArrivePainted) return; var current = getNavLinks(true).filter(isNavLinkCurrent)[0]; if (!current) return; setDesktopLinkHover(current); // The cursor may have left the menu during the transition; the first move tells. document.addEventListener( "mousemove", function (e) { if (!menuLinks.contains(e.target)) clearDesktopLinkHover(); }, { once: true, passive: true }, ); } function resetNavLeave() { if (!nav || !nav._navLeaving) return; nav._navLeaving = false; clearNavPose(nav._navLeaveLinks || []); nav._navLeaveLinks = null; } function getLinkMotionEl(link) { if (!link) return null; return link.querySelector(".txt") || link; } function getNavLinkMotionTargets() { return getNavLinks(true).map(getLinkMotionEl).filter(Boolean); } function getBurgerMotionEl() { return getLinkMotionEl(burger); } function getBottomKids() { return bottomSide ? Array.prototype.slice.call(bottomSide.children) : []; } function clearBottomMotion(g) { if (!bottomSide || !g) return; var kids = getBottomKids(); if (kids.length) g.set(kids, { clearProps: "opacity,transform" }); var txts = bottomSide.querySelectorAll(".txt"); if (txts.length) g.set(txts, { clearProps: "transform,opacity" }); } function isMobileMenuBusy() { return mobileMenuOpen || closingMobileMenu || (nav && nav.classList.contains("is-mobile-menu-animating")); } function lockMobilePageScroll() { if (!isMobileMode() || mobilePageScrollLocked) return; mobilePageScrollLocked = true; document.documentElement.classList.add("menu-mobile-scroll-lock"); document.body.classList.add("menu-mobile-scroll-lock"); if (window.lenis && typeof window.lenis.stop === "function") window.lenis.stop(); } function unlockMobilePageScroll() { if (!mobilePageScrollLocked) return; mobilePageScrollLocked = false; document.documentElement.classList.remove("menu-mobile-scroll-lock"); document.body.classList.remove("menu-mobile-scroll-lock"); if (window.lenis && typeof window.lenis.start === "function") window.lenis.start(); } function lockScrollAnim(ms) { if (!nav || isMobileMode()) return; scrollAnimLocked = true; resetScrollDirectionState(); clearTimeout(nav._scrollAnimLockTimer); nav._scrollAnimLockTimer = setTimeout(function () { scrollAnimLocked = false; lastY = getScrollY(); resetScrollDirectionState(); }, ms); } function unlockScrollAnimEarly() { scrollAnimLocked = false; if (nav) clearTimeout(nav._scrollAnimLockTimer); } function freezeNavAnim() { if (!nav || !gsapRef()) return; var g = gsapRef(); if (nav._menuTl) { nav._menuTl.kill(); nav._menuTl = null; } if (nav._menuContentTl) { nav._menuContentTl.kill(); nav._menuContentTl = null; } var targets = [bottomSide, burger]; if (menuLinks) targets.push(menuLinks); targets = targets.concat(getNavLinks(true)); if (getBurgerMotionEl()) targets.push(getBurgerMotionEl()); if (bottomSide) targets = targets.concat(getBottomKids()); g.killTweensOf(targets); nav.classList.remove("is-menu-animating"); clearTimeout(nav._scrollAnimLockTimer); scrollAnimLocked = false; } function readMenuLinksHeight() { if (!menuLinks) return 0; var h = gsap.getProperty(menuLinks, "height"); if (typeof h === "number") return h; return menuLinks.offsetHeight || 0; } function pinMenuLinksMotionHeight(g, height) { if (!g || !menuLinks || height == null) return; g.set(menuLinks, { height: height, overflow: "hidden" }); } function isDesktopMidCollapse() { if (isMobileMode() || !menuLinks) return false; var navLinks = getNavLinks(true); if (!navLinks.length) return false; var linksVisible = navLinks.some(function (link) { return window.getComputedStyle(link).display !== "none"; }); var height = readMenuLinksHeight(); return linksVisible && height < sizeCache.linksExpanded * 0.98; } function isDesktopMidExpand() { if (isMobileMode() || !menuLinks) return false; var navLinks = getNavLinks(true); if (!navLinks.length) return false; var linksPartial = navLinks.some(function (link) { var opacity = gsap.getProperty(link, "opacity"); return opacity < 0.98; }); var height = readMenuLinksHeight(); return linksPartial && height > sizeCache.linksCollapsed * 1.02; } function getInitialCollapsedState(y) { if (isMobileMode()) return true; return scrollShouldCollapse(y); } function isMobileExpandPage() { return isMobileMode() && pathScrollExcluded(); } function getShouldCollapse(y) { if (isMobileExpandPage()) return false; return scrollCollapseEnabled() && getInitialCollapsedState(y) && !forceExpanded; } function resolveLinkPath(href) { if (!href || href.charAt(0) === "#") return null; try { var url = new URL(href, location.href); if (url.origin !== location.origin) return null; return normalizePath(url.pathname); } catch (err) { return null; } } function isMobileScrollCollapsePath(path) { if (!path) return false; if (!MOBILE_SCROLL_EXCLUDE || !MOBILE_SCROLL_EXCLUDE.length) return true; return !MOBILE_SCROLL_EXCLUDE.some(function (entry) { return pathMatchesExcludeEntry(path, entry); }); } function willDestinationCollapse(href) { if (!isMobileMode()) return false; return isMobileScrollCollapsePath(resolveLinkPath(href)); } function isModifiedLinkClick(e) { return e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || (typeof e.button === "number" && e.button !== 0); } function estimateAnimLockMs(expanding) { if (isMobileMode()) { return Math.round((expanding ? MOBILE_BLOCK_EXPAND_DUR : MOBILE_BLOCK_COLLAPSE_DUR) * 1000 + SCROLL_LOCK_PAD_MS); } var count = getNavLinks(true).length; var linkSpan = getLinksStaggerDuration(count, LINK_OUT_DUR, LINK_STAGGER_OUT.each); var contentEnd = Math.max( linkSpan + 0.02 + MENU_LINKS_COLLAPSE_DUR, linkSpan * 0.55 + getBlockCollapseDur() + 0.28, ); var total = contentEnd + 0.02 + MENU_TOGGLE_DUR; if (expanding) total = total / EXPAND_REVERSE_SPEED; return Math.round(total * 1000 + SCROLL_LOCK_PAD_MS); } function configureGsap() { var g = gsapRef(); if (!g || g._menuScrollCollapseReady) return; g.defaults({ ease: DESKTOP_EASE_OUT, overwrite: "auto" }); g.config({ force3D: true }); g._menuScrollCollapseReady = true; } function readBottomSpacing() { if (!bottomSide) return; var cs = window.getComputedStyle(bottomSide); sizeCache.bottomMarginTop = parseFloat(cs.marginTop) || 0; sizeCache.bottomPaddingTop = parseFloat(cs.paddingTop) || 0; sizeCache.bottomPaddingBottom = parseFloat(cs.paddingBottom) || 0; } function measureCollapsedLinksHeight() { if (!burger) return 0; var prev = burger.style.display; burger.style.display = "inline-flex"; var h = burger.offsetHeight; burger.style.display = prev; return h; } function clearDesktopLinkHover() { if (!nav) return; nav.classList.remove("is-nav-link-hover"); if (!menuLinks) return; menuLinks._hh = null; menuLinks.querySelectorAll(".is-nav-link-hovered").forEach(function (l) { l.classList.remove("is-nav-link-hovered"); }); } function setDesktopLinkHover(link) { if (!nav || !menuLinks || !link || menuLinks._hh === link) return; menuLinks._hh = link; nav.classList.add("is-nav-link-hover"); getNavLinks(true).forEach(function (l) { l.classList.toggle("is-nav-link-hovered", l === link); }); } function bindDesktopLinkHover() { if (!menuLinks || menuLinks._dh) return; menuLinks._dh = true; menuLinks.addEventListener("mouseover", function (e) { if (!isActive() || isMobileMode() || collapsed || nav._navLeaving || nav.classList.contains("is-menu-animating")) return; var link = e.target.closest(".h_link:not(.menu_hamburger)"); if (!link || !menuLinks.contains(link)) return; setDesktopLinkHover(link); }); menuLinks.addEventListener("mouseleave", function (e) { // Starting the navigation fires a mouseleave with no relatedTarget; the cursor has not moved, so the hover // look carries over (the next page drops it on the first move if the cursor is elsewhere). if (nav._navLeaving && !e.relatedTarget) return; clearDesktopLinkHover(); onNavLeaveHoverLost(); }); } function releaseDesktopNavArrows(g) { if (!g || !menuLinks || isMobileMode()) return; var arrows = menuLinks.querySelectorAll(".h_link:not(.menu_hamburger) .arrow_menu_nav"); if (arrows.length) g.set(arrows, { clearProps: "opacity,visibility,transform" }); } function releaseDesktopLinkOpacity(g) { if (!g || !menuLinks || isMobileMode() || collapsed) return; var navLinks = getNavLinks(true); var motionTargets = getNavLinkMotionTargets(); if (navLinks.length) g.set(navLinks, { autoAlpha: 1, y: 0, clearProps: "transform" }); if (motionTargets.length) g.set(motionTargets, { clearProps: "opacity,transform" }); releaseDesktopNavArrows(g); } function restoreLinkDisplay() { if (!menuLinks) return; var g = gsapRef(); var links = menuLinks.querySelectorAll(".h_link"); var txts = menuLinks.querySelectorAll(".h_link .txt"); if (g) { g.set(links, { clearProps: "display,opacity,transform,visibility,height,padding" }); if (txts.length) g.set(txts, { clearProps: "transform,opacity" }); } else { links.forEach(function (el) { el.style.display = ""; el.style.opacity = ""; el.style.transform = ""; el.style.visibility = ""; el.style.height = ""; }); txts.forEach(function (el) { el.style.transform = ""; el.style.opacity = ""; }); } } function ensureMobileOverlay() { if (mobileOverlay) return mobileOverlay; mobileOverlay = document.createElement("div"); mobileOverlay.className = "menu-mobile-overlay"; mobileOverlay.setAttribute("aria-hidden", "true"); mobileOverlay.addEventListener("click", closeMobileMenu); document.body.appendChild(mobileOverlay); return mobileOverlay; } function updateMobileOverlayBounds() { if (!mobileOverlay || !nav) return; var top = Math.max(0, Math.round(nav.getBoundingClientRect().bottom)); mobileOverlay.style.setProperty("--menu-overlay-top", top + "px"); } function stopOverlayBoundsTracking() { if (overlayTrackRaf) cancelAnimationFrame(overlayTrackRaf); overlayTrackRaf = 0; } function startOverlayBoundsTracking() { stopOverlayBoundsTracking(); if (!mobileOverlay || !mobileOverlay.classList.contains("is-visible")) return; function tick() { if (!mobileOverlay || !mobileOverlay.classList.contains("is-visible")) { overlayTrackRaf = 0; return; } updateMobileOverlayBounds(); if (nav.classList.contains("is-menu-animating")) { overlayTrackRaf = requestAnimationFrame(tick); return; } overlayTrackRaf = 0; } overlayTrackRaf = requestAnimationFrame(tick); } function setMobileOverlay(open, immediate, timeline, position) { if (!isMobileMode()) { if (mobileOverlay) { stopOverlayBoundsTracking(); if (!timeline && gsapRef()) gsapRef().killTweensOf(mobileOverlay); mobileOverlay.classList.remove("is-visible"); gsapRef() && gsapRef().set(mobileOverlay, { opacity: 0, visibility: "hidden", pointerEvents: "none" }); } return; } var g = gsapRef(); if (!g) return; var el = ensureMobileOverlay(); if (!timeline) g.killTweensOf(el); if (open) { updateMobileOverlayBounds(); el.classList.add("is-visible"); el.setAttribute("aria-hidden", "false"); g.set(el, { visibility: "visible" }); startOverlayBoundsTracking(); if (immediate) { g.set(el, { opacity: 1, pointerEvents: "auto" }); return; } if (timeline) { timeline.set(el, { opacity: 0, pointerEvents: "none" }, position || 0); timeline.to( el, { opacity: 1, duration: MOBILE_OVERLAY_IN_DUR, ease: "power2.out", onStart: function () { g.set(el, { pointerEvents: "auto" }); }, }, position || 0, ); return; } g.set(el, { opacity: 0, pointerEvents: "none" }); g.to(el, { opacity: 1, duration: MOBILE_OVERLAY_IN_DUR, ease: "power2.out", onStart: function () { g.set(el, { pointerEvents: "auto" }); }, }); return; } el.setAttribute("aria-hidden", "true"); stopOverlayBoundsTracking(); if (immediate) { el.classList.remove("is-visible"); g.set(el, { opacity: 0, visibility: "hidden", pointerEvents: "none" }); return; } if (timeline) { timeline.to( el, { opacity: 0, duration: MOBILE_OVERLAY_OUT_DUR, ease: "power2.in", onComplete: function () { el.classList.remove("is-visible"); g.set(el, { visibility: "hidden", pointerEvents: "none" }); }, }, position || 0, ); return; } g.to(el, { opacity: 0, duration: MOBILE_OVERLAY_OUT_DUR, ease: "power2.in", onComplete: function () { el.classList.remove("is-visible"); g.set(el, { visibility: "hidden", pointerEvents: "none" }); }, }); } function openMobileMenuAnim() { var g = gsapRef(); if (!g || !mobileMenu || !burger || mobileMenuOpen || !isMobileMode()) return; var needsNavExpand = collapsed; if (!heightsCached) measureHeights(false); killMobileMenuTweens(); if (needsNavExpand) freezeNavAnim(); var kids = Array.prototype.slice.call(mobileMenu.children); var motionKids = kids.map(getLinkMotionEl).filter(Boolean); var targetHeight = measureMobileMenuHeight(); var overlay = ensureMobileOverlay(); neutralizeWebflowMenu(g); mobileMenuOpen = true; forceExpanded = true; lockMobilePageScroll(); nav.classList.add("menu-mobile-open", "is-mobile-menu-animating"); burger.classList.add("is-active"); burger.setAttribute("aria-expanded", "true"); if (needsNavExpand) { lockMobileNavAnim(); collapsed = false; nav.setAttribute("data-menu-collapsed", "false"); nav.classList.remove("is-scroll-collapsed"); nav.classList.add("is-scroll-collapse-ready", "is-menu-animating"); g.set(bottomSide, { height: 0, marginTop: 0, paddingTop: 0, paddingBottom: 0, opacity: 0, overflow: "hidden", pointerEvents: "none", }); clearBottomMotion(g); g.set(getBottomKids(), { opacity: 1 }); } g.set(mobileMenu, { display: "flex", flexDirection: "column", overflow: "hidden", height: 0, opacity: 0, pointerEvents: "auto", transform: "none", maxHeight: "none", }); if (motionKids.length) g.set(motionKids, { opacity: 1, clearProps: "transform" }); mobileMenuTl = g.timeline({ defaults: { ease: MOBILE_EASE, overwrite: "auto" }, onComplete: function () { if (needsNavExpand) { applyCollapsedVisual(false); finishMenuAnimation(); } g.set(mobileMenu, { clearProps: "transform" }); if (motionKids.length) g.set(motionKids, { clearProps: "transform" }); nav.classList.remove("is-mobile-menu-animating"); mobileMenuTl = null; updateMobileOverlayBounds(); }, }); updateMobileOverlayBounds(); overlay.classList.add("is-visible"); overlay.setAttribute("aria-hidden", "false"); g.set(overlay, { visibility: "visible", opacity: 0, pointerEvents: "none" }); startOverlayBoundsTracking(); mobileMenuTl.to( overlay, { opacity: 1, duration: MOBILE_OVERLAY_IN_DUR, ease: MOBILE_EASE, onStart: function () { g.set(overlay, { pointerEvents: "auto" }); }, }, 0, ); mobileMenuTl.to( mobileMenu, { height: targetHeight, opacity: 1, duration: MOBILE_MENU_IN_DUR, ease: MOBILE_EASE }, 0, ); if (needsNavExpand) { mobileMenuTl.to( bottomSide, { height: sizeCache.bottom, marginTop: sizeCache.bottomMarginTop, paddingTop: sizeCache.bottomPaddingTop, paddingBottom: sizeCache.bottomPaddingBottom, opacity: 1, pointerEvents: "auto", duration: MOBILE_BLOCK_EXPAND_DUR, ease: MOBILE_EASE, }, 0, ); } } function closeMobileMenuAnim(options) { options = options || {}; var navigateTo = options.navigateTo; var g = gsapRef(); if (!g || !mobileMenu || !burger || !mobileMenuOpen || closingMobileMenu) return false; closingMobileMenu = true; killMobileMenuTweens(); neutralizeWebflowMenu(g); mobileMenuOpen = false; burger.classList.remove("is-active"); burger.setAttribute("aria-expanded", "false"); nav.classList.add("is-mobile-menu-animating"); var needsNavCollapse = options.forceNavCollapse || (scrollCollapseEnabled() && scrollShouldCollapse(getScrollY())); if (needsNavCollapse) { if (nav._menuTl) { nav._menuTl.kill(); nav._menuTl = null; } g.killTweensOf(bottomSide); collapsed = true; forceExpanded = false; nav.setAttribute("data-menu-collapsed", "true"); nav.classList.add("is-scroll-collapse-ready", "is-menu-animating", "is-scroll-collapsed"); lockMobileNavAnim(); var bottomNow = readBottomSideMotion(); g.set(bottomSide, { height: bottomNow.height || sizeCache.bottom, marginTop: bottomNow.marginTop, paddingTop: bottomNow.paddingTop || sizeCache.bottomPaddingTop, paddingBottom: bottomNow.paddingBottom || sizeCache.bottomPaddingBottom, opacity: bottomNow.opacity || 1, overflow: "hidden", pointerEvents: "auto", }); clearBottomMotion(g); g.set(getBottomKids(), { opacity: 1 }); } var kids = Array.prototype.slice.call(mobileMenu.children); var motionKids = kids.map(getLinkMotionEl).filter(Boolean); var overlay = mobileOverlay; mobileMenuTl = g.timeline({ defaults: { ease: MOBILE_EASE, overwrite: "auto" }, onComplete: function () { g.set(mobileMenu, { display: "none", height: 0, opacity: 0, pointerEvents: "none", transform: "none", clearProps: "overflow,flexDirection,maxHeight", }); if (motionKids.length) g.set(motionKids, { clearProps: "opacity,transform" }); if (overlay) { overlay.classList.remove("is-visible"); overlay.setAttribute("aria-hidden", "true"); g.set(overlay, { opacity: 0, visibility: "hidden", pointerEvents: "none" }); } stopOverlayBoundsTracking(); nav.classList.remove("menu-mobile-open", "is-mobile-menu-animating"); mobileMenuTl = null; closingMobileMenu = false; forceExpanded = false; unlockMobilePageScroll(); if (needsNavCollapse) { applyCollapsedVisual(true); finishMenuAnimation(); } if (navigateTo) { window.location.assign(navigateTo); } }, }); if (overlay) { overlay.setAttribute("aria-hidden", "true"); stopOverlayBoundsTracking(); mobileMenuTl.to( overlay, { opacity: 0, duration: MOBILE_OVERLAY_OUT_DUR, ease: MOBILE_EASE, onStart: function () { g.set(overlay, { pointerEvents: "none" }); }, }, 0, ); } mobileMenuTl.to(mobileMenu, { height: 0, opacity: 0, duration: MOBILE_MENU_OUT_DUR, ease: MOBILE_EASE }, 0); if (needsNavCollapse) { mobileMenuTl.to( bottomSide, { height: 0, marginTop: 0, paddingTop: 0, paddingBottom: 0, opacity: 0, pointerEvents: "none", duration: MOBILE_BLOCK_COLLAPSE_DUR, ease: MOBILE_EASE, }, 0, ); } return true; } function closeMobileMenu() { if (!isMobileMode() || !mobileMenuOpen || closingMobileMenu) return; closeMobileMenuAnim(); } function bindMobileOverlayTracking() { if (!nav || nav._menuOverlayResizeObs) return; var onResize = function () { if (mobileOverlay && mobileOverlay.classList.contains("is-visible")) { updateMobileOverlayBounds(); } }; if (typeof ResizeObserver === "function") { var ro = new ResizeObserver(onResize); ro.observe(nav); if (bottomSide) ro.observe(bottomSide); nav._menuOverlayResizeObs = ro; } } function initMobileMenuPanel() { var g = gsapRef(); if (!mobileMenu || !g) return; neutralizeWebflowMenu(g); g.set(mobileMenu, { display: "none", height: 0, opacity: 0, overflow: "hidden", pointerEvents: "none", transform: "none", maxHeight: "none", }); } function bindWebflowMenuStyleGuard() { if (!mobileMenu || mobileMenu._wfStyleGuard) return; var obs = new MutationObserver(function () { if (!mobileMenuOpen && !nav.classList.contains("is-mobile-menu-animating")) return; var g = gsapRef(); if (!g || !mobileMenu.style.transform) return; g.set(mobileMenu, { clearProps: "transform", transform: "none" }); }); obs.observe(mobileMenu, { attributes: true, attributeFilter: ["style", "class"] }); mobileMenu._wfStyleGuard = obs; } function bindMobileMenuLinks() { if (!mobileMenu || mobileMenu._menuLinksBound) return; mobileMenu.querySelectorAll("a").forEach(function (link) { if (link === burger) return; link.addEventListener("click", function (e) { if (!isMobileMode() || !mobileMenuOpen) return; if (isModifiedLinkClick(e)) return; var href = link.getAttribute("href"); if (!href || href.charAt(0) === "#") return; if (!resolveLinkPath(href)) { closeMobileMenuAnim(); return; } e.preventDefault(); var collapseDest = willDestinationCollapse(href); if (!closeMobileMenuAnim({ navigateTo: href, forceNavCollapse: collapseDest })) { window.location.assign(href); } }); }); mobileMenu._menuLinksBound = true; } function bindMobileMenuSync() { mobileMenu = document.querySelector(".menu_mobile"); if (!mobileMenu) return; initMobileMenuPanel(); bindMobileMenuLinks(); bindWebflowMenuStyleGuard(); if (burger) { burger.setAttribute("role", "button"); burger.setAttribute("aria-expanded", "false"); if (!mobileMenu.id) mobileMenu.id = "menu_mobile"; burger.setAttribute("aria-controls", mobileMenu.id); } if (!document._menuMobileEscBound) { document.addEventListener("keydown", function (e) { if (e.key === "Escape" && isMobileMode() && mobileMenuOpen) closeMobileMenuAnim(); }); document._menuMobileEscBound = true; } } function normalizePath(pathname) { var path = pathname || "/"; if (path.length > 1 && path.charAt(path.length - 1) === "/") { path = path.slice(0, -1); } return path; } function getScrollExcludeList() { return isMobileMode() ? MOBILE_SCROLL_EXCLUDE : DESKTOP_EXCLUDE; } function pathScrollExcluded() { var excludeList = getScrollExcludeList(); if (!excludeList || !excludeList.length) return false; var path = normalizePath(location.pathname); return excludeList.some(function (entry) { return pathMatchesExcludeEntry(path, entry); }); } function pathMatchAllowed() { if (!PATH_MATCH) return true; if (typeof PATH_MATCH === "function") return PATH_MATCH(location.pathname); if (PATH_MATCH instanceof RegExp) return PATH_MATCH.test(location.pathname); if (Array.isArray(PATH_MATCH)) { return PATH_MATCH.some(function (part) { return location.pathname.indexOf(part) === 0; }); } return true; } function scrollCollapseEnabled() { return isActive() && pathMatchAllowed() && !pathScrollExcluded(); } function pathMatchesExcludeEntry(path, entry) { if (typeof entry === "function") return entry(path, location.pathname); if (entry instanceof RegExp) return entry.test(location.pathname); var normalized = normalizePath(entry); if (normalized === "/") return path === "/"; return path === normalized; } function isActive() { return !!getMode() && !!nav; } function scrollShouldCollapse(y) { return y > TOP_EXPAND; } function getBlockCollapseDur() { return isMobileMode() ? MOBILE_BLOCK_COLLAPSE_DUR : BLOCK_COLLAPSE_DUR; } function getBlockExpandDur() { return isMobileMode() ? MOBILE_BLOCK_EXPAND_DUR : BLOCK_EXPAND_DUR; } function killMenuTweens() { var g = gsapRef(); if (!g || !nav) return; if (nav._menuTl) { nav._menuTl.kill(); nav._menuTl = null; } if (nav._menuContentTl) { nav._menuContentTl.kill(); nav._menuContentTl = null; } var targets = [bottomSide, burger]; if (menuLinks) targets.push(menuLinks); targets = targets.concat(getNavLinks(true)); targets = targets.concat(getNavLinkMotionTargets()); if (getBurgerMotionEl()) targets.push(getBurgerMotionEl()); if (bottomSide) targets = targets.concat(Array.prototype.slice.call(bottomSide.children)); if (!mobileMenuOpen && !nav.classList.contains("is-mobile-menu-animating")) { if (mobileOverlay) targets.push(mobileOverlay); if (mobileMenu) { targets.push(mobileMenu); targets = targets.concat(Array.prototype.slice.call(mobileMenu.children)); } if (mobileMenuTl) { mobileMenuTl.kill(); mobileMenuTl = null; } } g.killTweensOf(targets); } function cancelMenuAnimation() { if (!nav) return; clearTimeout(nav._navEnterScheduleTimer); nav._navEnterScheduleTimer = null; if (nav._navEnterTl) { nav._navEnterTl.kill(); nav._navEnterTl = null; } clearTimeout(nav._menuAnimTimer); killMenuTweens(); nav.classList.remove("is-menu-animating"); nav._menuAnimTimer = null; unlockMobileNavAnim(); } function finishMenuAnimation() { if (!nav) return; nav.classList.remove("is-menu-animating"); nav._menuTl = null; nav._menuContentTl = null; clearTimeout(nav._menuAnimTimer); nav._menuAnimTimer = null; unlockMobileNavAnim(); if (!collapsed && gsapRef()) { restoreLinkDisplay(); if (isMobileMode()) clearBottomMotion(gsapRef()); else releaseDesktopLinkOpacity(gsapRef()); } syncCurrentNavLinkMarkers(); if (mobileOverlay && mobileOverlay.classList.contains("is-visible")) { updateMobileOverlayBounds(); } } function readElementHeight(el) { if (!el) return 0; el.style.height = "auto"; var h = el.offsetHeight; el.style.height = ""; return h; } function measureBottomSideExpandedSilent() { if (!bottomSide) return; nav.classList.add("is-menu-measuring"); var style = bottomSide.style; var saved = { position: style.position, visibility: style.visibility, left: style.left, height: style.height, marginTop: style.marginTop, paddingTop: style.paddingTop, paddingBottom: style.paddingBottom, opacity: style.opacity, overflow: style.overflow, pointerEvents: style.pointerEvents, }; style.position = "absolute"; style.visibility = "hidden"; style.left = "-9999px"; style.height = "auto"; style.marginTop = ""; style.paddingTop = ""; style.paddingBottom = ""; style.opacity = "1"; style.overflow = "visible"; style.pointerEvents = "none"; sizeCache.bottom = readElementHeight(bottomSide); readBottomSpacing(); style.position = saved.position; style.visibility = saved.visibility; style.left = saved.left; style.height = saved.height; style.marginTop = saved.marginTop; style.paddingTop = saved.paddingTop; style.paddingBottom = saved.paddingBottom; style.opacity = saved.opacity; style.overflow = saved.overflow; style.pointerEvents = saved.pointerEvents; nav.classList.remove("is-menu-measuring"); } function measureHeights(force, opts) { if (!nav || !bottomSide) return; if (heightsCached && !force) return; opts = opts || {}; if (opts.silentExpanded && isMobileMode()) { killMenuTweens(); var gSilent = gsapRef(); if (gSilent) gSilent.killTweensOf(bottomSide); measureBottomSideExpandedSilent(); heightsCached = true; return; } var g = gsapRef(); var wasCollapsed = nav.classList.contains("is-scroll-collapsed"); killMenuTweens(); nav.classList.add("is-menu-measuring"); nav.classList.remove("is-scroll-collapsed", "is-menu-animating"); if (g) { g.set([bottomSide, menuLinks], { clearProps: "height,opacity,transform,margin,padding" }); restoreLinkDisplay(); clearBottomMotion(g); } else { bottomSide.style.height = ""; bottomSide.style.marginTop = ""; bottomSide.style.paddingTop = ""; bottomSide.style.paddingBottom = ""; if (menuLinks) menuLinks.style.height = ""; restoreLinkDisplay(); } sizeCache.bottom = readElementHeight(bottomSide); readBottomSpacing(); if (!isMobileMode() && menuLinks) { cacheExpandedNavLinkMetrics(g); if (g) { g.set(getNavLinks(true), { clearProps: "display,opacity,transform,visibility" }); g.set(menuLinks, { clearProps: "height,overflow" }); if (burger) g.set(burger, { clearProps: "display" }); } } if (wasCollapsed) nav.classList.add("is-scroll-collapsed"); nav.classList.remove("is-menu-measuring"); heightsCached = true; } function applyCollapsedVisual(state) { var g = gsapRef(); if (!g || !nav) return; var navLinks = getNavLinks(true); var bottomKids = getBottomKids(); var motionTargets = getNavLinkMotionTargets(); if (state) { nav.classList.add("is-scroll-collapsed"); g.set(bottomSide, { height: 0, marginTop: 0, paddingTop: 0, paddingBottom: 0, opacity: 0, overflow: "hidden", pointerEvents: "none", }); if (isMobileMode()) { clearBottomMotion(g); g.set(bottomKids, { opacity: 1 }); } else { g.set(bottomKids, { opacity: 0 }); } if (!isMobileMode() && menuLinks) { g.set(menuLinks, { height: sizeCache.linksCollapsed, overflow: "hidden" }); g.set(navLinks, { display: "none", opacity: 1, clearProps: "transform" }); if (motionTargets.length) g.set(motionTargets, { clearProps: "opacity,transform" }); if (burger) { g.set(burger, { display: "inline-flex", opacity: 1, position: "absolute", top: 0, left: 0, margin: 0, pointerEvents: "auto", }); var burgerMotion = getBurgerMotionEl(); if (burgerMotion) g.set(burgerMotion, { clearProps: "transform" }); } } return; } nav.classList.remove("is-scroll-collapsed"); restoreLinkDisplay(); clearBottomMotion(g); g.set(bottomSide, { height: sizeCache.bottom, marginTop: sizeCache.bottomMarginTop, paddingTop: sizeCache.bottomPaddingTop, paddingBottom: sizeCache.bottomPaddingBottom, opacity: 1, overflow: "hidden", pointerEvents: "auto", }); g.set(bottomKids, { opacity: 1 }); if (!isMobileMode() && menuLinks) { g.set(menuLinks, { height: sizeCache.linksExpanded, overflow: "hidden" }); g.set(navLinks, { display: "flex", autoAlpha: 1, clearProps: "transform" }); if (motionTargets.length) g.set(motionTargets, { clearProps: "opacity,transform" }); releaseDesktopNavArrows(g); } syncCurrentNavLinkMarkers(); } function applyCollapsedInstant(state, opts) { if (!nav) return; opts = opts || {}; cancelMenuAnimation(); collapsed = !!state; nav.classList.add("is-scroll-collapse-ready", "is-menu-instant"); nav.setAttribute("data-menu-collapsed", collapsed ? "true" : "false"); applyCollapsedVisual(collapsed); requestAnimationFrame(function () { nav.classList.remove("is-menu-instant"); if (collapsed || isMobileMode()) { nav.classList.remove("is-nav-pending-enter"); return; } releaseDesktopLinkOpacity(gsapRef()); if (opts.replayEnter === false && !nav._navEnterTl) { nav.classList.remove("is-nav-pending-enter"); return; } scheduleNavEnterAnimation(); }); } function getLinksStaggerDuration(count, itemDur, stagger) { if (!count) return 0; return itemDur + Math.max(0, count - 1) * stagger; } function getBurgerRevealAt(linkCount) { if (!linkCount) return 0; var revealAfter = Math.min(MENU_REVEAL_AFTER_LINKS, linkCount); return (revealAfter - 1) * LINK_STAGGER_OUT.each + LINK_OUT_DUR * 0.82; } function ensureExpandedMetrics(g, preserveLinksHeight) { if ( !menuLinks || sizeCache.linksExpanded <= sizeCache.linksCollapsed + 1 || !sizeCache.navLinkHeights.length || !sizeCache.navLinkHeights.some(function (h) { return h > 0; }) ) { cacheExpandedNavLinkMetrics(g, preserveLinksHeight); } } function cacheExpandedNavLinkMetrics(g, preserveMenuLinksHeight) { if (!menuLinks || isMobileMode()) return; var navLinksMeasured = getNavLinks(true); if (!navLinksMeasured.length) return; if (g) { if (burger) g.set(burger, { display: "none" }); g.set(navLinksMeasured, { display: "flex", autoAlpha: 1, clearProps: "transform" }); g.set(menuLinks, { height: "auto", overflow: "visible" }); } else { navLinksMeasured.forEach(function (link) { link.style.display = "flex"; }); menuLinks.style.height = "auto"; menuLinks.style.overflow = "visible"; } sizeCache.linksExpanded = readElementHeight(menuLinks); sizeCache.navLinkHeights = measureNavLinkHeights(navLinksMeasured); sizeCache.menuLinksGap = getMenuLinksGap(); sizeCache.linksCollapsed = measureCollapsedLinksHeight(); if (g && preserveMenuLinksHeight != null) { pinMenuLinksMotionHeight(g, preserveMenuLinksHeight); } } function getMenuLinksGap() { if (!menuLinks) return 0; var cs = window.getComputedStyle(menuLinks); var gap = parseFloat(cs.rowGap); if (isNaN(gap) || gap <= 0) gap = parseFloat(cs.gap) || 0; return isNaN(gap) ? 0 : gap; } function measureNavLinkHeights(navLinks) { return navLinks.map(function (link) { return link.offsetHeight || 0; }); } function prepLinkTextsForCollapse(g, navLinks) { prepNavLinksForMotion(g, navLinks, { display: "flex", opacity: 1, y: 0, shift: true }); var texts = getNavLinkMotionTargets(); if (texts.length) { g.set(texts, { y: 0, autoAlpha: 1, scaleY: 1, transformOrigin: "50% 0%" }); } return texts; } function prepLinkTextsForExpand(g, navLinks) { prepNavLinksForMotion(g, navLinks, { display: "flex", opacity: 1, y: 0, shift: true }); var texts = getNavLinkMotionTargets(); if (texts.length) { g.set(texts, { y: LINK_OUT_Y, autoAlpha: 0, scaleY: 0.84, transformOrigin: "50% 0%" }); } return texts; } function addDesktopLinkMotion(g, navLinks, tl, direction, startAt) { startAt = startAt || 0; var entering = direction === "in"; var gap; var heights; var linkSpan = getLinksStaggerDuration(navLinks.length, LINK_OUT_DUR, LINK_STAGGER_OUT.each); var texts; var arrows = []; var remaining; var linksStartH; var i; if (entering) { linksStartH = readMenuLinksHeight() || sizeCache.linksCollapsed; ensureExpandedMetrics(g, linksStartH); prepLinkTextsForExpand(g, navLinks); pinMenuLinksMotionHeight(g, linksStartH); heights = measureNavLinkHeights(navLinks); if ( !heights.some(function (h) { return h > 0; }) ) { cacheExpandedNavLinkMetrics(g, linksStartH); prepLinkTextsForExpand(g, navLinks); pinMenuLinksMotionHeight(g, linksStartH); heights = sizeCache.navLinkHeights.slice(); } else { sizeCache.navLinkHeights = heights; } } else { prepLinkTextsForCollapse(g, navLinks); heights = measureNavLinkHeights(navLinks); } gap = entering ? sizeCache.menuLinksGap || getMenuLinksGap() : getMenuLinksGap(); var linkStagger = entering ? LINK_STAGGER_IN : LINK_STAGGER_OUT; texts = getNavLinkMotionTargets(); navLinks.forEach(function (link) { var arrow = link.querySelector(".arrow_menu_nav"); if (arrow) arrows.push(arrow); }); remaining = entering ? linksStartH : menuLinks.offsetHeight || sizeCache.linksExpanded; if (texts.length) { tl.to( texts, { y: entering ? 0 : LINK_OUT_Y, autoAlpha: entering ? 1 : 0, scaleY: entering ? 1 : 0.84, duration: LINK_OUT_DUR, stagger: linkStagger, ease: DESKTOP_MORPH_EASE, }, startAt, ); } if (arrows.length) { if (entering) { g.set(arrows, { autoAlpha: 0 }); tl.to( arrows, { autoAlpha: function (index, target) { var link = target.closest(".h_link"); return link && isNavLinkCurrent(link) ? 1 : 0; }, duration: LINK_OUT_DUR * 0.5, stagger: linkStagger, ease: DESKTOP_LINK_EASE_OUT, }, startAt, ); } else { tl.to( arrows, { autoAlpha: 0, duration: LINK_OUT_DUR * 0.5, stagger: linkStagger, ease: DESKTOP_LINK_EASE_IN }, startAt, ); } } var heightStart = startAt + LINK_OUT_DUR * 0.3; var heightDur = Math.max(linkSpan - LINK_OUT_DUR * 0.3, 0.12) + MENU_LINKS_COLLAPSE_DUR; tl.to( menuLinks, { height: entering ? sizeCache.linksExpanded : sizeCache.linksCollapsed, duration: heightDur, ease: DESKTOP_HEIGHT_EASE, }, heightStart, ); if (entering) { tl.call( function () { if (texts.length) g.set(texts, { clearProps: "transform,opacity,visibility" }); if (arrows.length) g.set(arrows, { clearProps: "opacity,visibility,transform" }); }, null, startAt + linkSpan, ); } else { tl.set(navLinks, { display: "none" }, startAt + linkSpan); } return linkSpan; } function addDesktopBurgerIn(g, tl, position) { if (!burger) return; var burgerMotion = getBurgerMotionEl(); tl.to( burger, { autoAlpha: 1, duration: MENU_TOGGLE_DUR, ease: DESKTOP_LINK_EASE_OUT, pointerEvents: "auto" }, position, ); if (burgerMotion) { tl.fromTo( burgerMotion, { y: MENU_IN_Y, autoAlpha: 0 }, { y: 0, autoAlpha: 1, duration: MENU_TOGGLE_DUR, ease: DESKTOP_LINK_EASE_OUT }, position, ); } } function addDesktopBurgerOut(g, tl, position) { if (!burger) return; var burgerMotion = getBurgerMotionEl(); g.set(burger, { display: "inline-flex", autoAlpha: 1, pointerEvents: "auto", position: "absolute", top: 0, left: 0, margin: 0, }); if (burgerMotion) g.set(burgerMotion, { y: 0, autoAlpha: 1, clearProps: "transform" }); tl.to( burger, { autoAlpha: 0, duration: MENU_OUT_DUR, ease: DESKTOP_LINK_EASE_IN, pointerEvents: "none" }, position, ); if (burgerMotion) { tl.to( burgerMotion, { y: MENU_OUT_Y, autoAlpha: 0, duration: MENU_OUT_DUR, ease: DESKTOP_LINK_EASE_IN }, position, ); } tl.call( function () { hideBurger(g); }, null, position + MENU_OUT_DUR, ); } function buildDesktopCollapseTimeline(g, mid) { var navLinks = getNavLinks(true); prepDesktopMenuMotion(g, false, mid); var tl = g.timeline({ paused: true, defaults: { ease: DESKTOP_EASE, overwrite: "auto" }, onComplete: function () { applyCollapsedVisual(true); finishMenuAnimation(); }, onReverseComplete: function () { applyCollapsedVisual(false); finishMenuAnimation(); }, }); var linkSpan = addDesktopLinkMotion(g, navLinks, tl, "out", 0); addDesktopBottomBlockMotion(tl, linkSpan, false, 0); addDesktopBurgerIn(g, tl, tl.duration() + 0.02); return tl; } function addDesktopBottomBlockMotion(tl, linkSpan, expanding, phaseStart) { phaseStart = phaseStart || 0; var bottomKids = getBottomKids(); var blockAt = phaseStart + linkSpan * 0.55; var blockDur = expanding ? getBlockExpandDur() : getBlockCollapseDur(); if (bottomKids.length) { tl.to( bottomKids, expanding ? { opacity: 1, y: 0, duration: 0.32, stagger: { each: 0.045, from: "start" }, ease: DESKTOP_LINK_EASE_OUT } : { opacity: 0, y: -8, duration: 0.32, stagger: 0.045, ease: DESKTOP_LINK_EASE_IN }, blockAt * 0.35, ); } tl.to( bottomSide, expanding ? { height: sizeCache.bottom, marginTop: sizeCache.bottomMarginTop, paddingTop: sizeCache.bottomPaddingTop, paddingBottom: sizeCache.bottomPaddingBottom, duration: blockDur, ease: DESKTOP_MORPH_EASE, } : { height: 0, marginTop: 0, paddingTop: 0, paddingBottom: 0, duration: blockDur, ease: DESKTOP_MORPH_EASE }, blockAt, ); tl.to( bottomSide, expanding ? { opacity: 1, pointerEvents: "auto", duration: 0.28, ease: DESKTOP_LINK_EASE_OUT } : { opacity: 0, duration: 0.28, ease: DESKTOP_LINK_EASE_IN }, blockAt + blockDur * 0.35, ); } function prepDesktopMenuMotion(g, expanding, mid) { var navLinks = getNavLinks(true); var bottomKids = getBottomKids(); var currentBottom = bottomSide.offsetHeight || sizeCache.bottom; var currentLinks = menuLinks ? menuLinks.offsetHeight || sizeCache.linksExpanded : 0; var currentMargin = parseFloat(window.getComputedStyle(bottomSide).marginTop) || sizeCache.bottomMarginTop; if (mid && menuLinks) currentLinks = readMenuLinksHeight(); if (mid) { currentBottom = bottomSide.offsetHeight || 0; currentMargin = parseFloat(window.getComputedStyle(bottomSide).marginTop) || 0; } if (expanding) { nav.classList.remove("is-scroll-collapsed"); if (mid) { g.set(bottomSide, { height: currentBottom, marginTop: currentMargin, paddingTop: sizeCache.bottomPaddingTop, paddingBottom: sizeCache.bottomPaddingBottom, opacity: Math.max(gsap.getProperty(bottomSide, "opacity") || 0, 0.01), overflow: "hidden", pointerEvents: "auto", }); g.set(bottomKids, { opacity: 1, clearProps: "transform" }); g.set(menuLinks, { height: currentLinks, overflow: "hidden" }); hideBurger(g); } else { g.set(bottomSide, { height: 0, marginTop: 0, paddingTop: 0, paddingBottom: 0, opacity: 0, overflow: "hidden", pointerEvents: "none", }); g.set(bottomKids, { opacity: 0, y: -8 }); g.set(menuLinks, { height: sizeCache.linksCollapsed, overflow: "hidden" }); if (burger) { g.set(burger, { display: "inline-flex", autoAlpha: 1, pointerEvents: "auto", position: "absolute", top: 0, left: 0, margin: 0, }); var burgerMotion = getBurgerMotionEl(); if (burgerMotion) g.set(burgerMotion, { y: 0, autoAlpha: 1 }); } pinMenuLinksMotionHeight(g, sizeCache.linksCollapsed); } return; } g.set(bottomSide, { height: currentBottom, marginTop: currentMargin, paddingTop: sizeCache.bottomPaddingTop, paddingBottom: sizeCache.bottomPaddingBottom, opacity: 1, overflow: "hidden", }); g.set(menuLinks, { height: currentLinks, overflow: "hidden" }); prepareBurgerHidden(g); } function animateDesktopMenu(expanding) { var g = gsapRef(); if (!g || !bottomSide) return; if (expanding) { reverseDesktopCollapse(g); return; } nav._menuTl = buildDesktopCollapseTimeline(g, isDesktopMidExpand()); nav._menuTl.play(); } function reverseDesktopCollapse(g) { var tl = nav._menuTl; var live = tl && nav.classList.contains("is-menu-animating") && tl.isActive(); if (!live) { if (tl) tl.kill(); applyCollapsedVisual(false); tl = buildDesktopCollapseTimeline(g, false); nav._menuTl = tl; tl.progress(1, true); } nav.classList.remove("is-scroll-collapsed"); clearDesktopLinkHover(); nav.classList.add("is-scroll-collapse-ready", "is-menu-animating"); g.set(getNavLinks(true), { display: "flex" }); tl.timeScale(EXPAND_REVERSE_SPEED); tl.reverse(); } function prepareBurgerHidden(g) { if (!burger) return; g.set(burger, { display: "inline-flex", position: "absolute", top: 0, left: 0, margin: 0, autoAlpha: 0, pointerEvents: "none", }); var burgerMotion = getBurgerMotionEl(); if (burgerMotion) g.set(burgerMotion, { y: MENU_IN_Y, autoAlpha: 0 }); } function hideBurger(g) { if (!burger) return; g.set(burger, { display: "none", opacity: 0, pointerEvents: "none" }); } function showBurger(g) { if (!burger) return; g.set(burger, { display: "inline-flex", opacity: 1, pointerEvents: "auto", position: "absolute", top: 0, left: 0, margin: 0, }); var burgerMotion = getBurgerMotionEl(); if (burgerMotion) g.set(burgerMotion, { clearProps: "transform" }); } function animateCollapse() { var g = gsapRef(); if (!g || !bottomSide) return; var bottomKids = getBottomKids(); if (isMobileMode()) { clearBottomMotion(g); g.set(bottomKids, { opacity: 1 }); var bottomNow = readBottomSideMotion(); g.set(bottomSide, { height: bottomNow.height, marginTop: bottomNow.marginTop, paddingTop: bottomNow.paddingTop, paddingBottom: bottomNow.paddingBottom, opacity: bottomNow.opacity, overflow: "hidden", }); nav._menuTl = g.timeline({ defaults: { ease: MOBILE_EASE, overwrite: "auto" }, onComplete: function () { applyCollapsedVisual(true); finishMenuAnimation(); }, }); var collapseDur = getBlockCollapseDur(); nav._menuTl.to( bottomSide, { height: 0, marginTop: 0, paddingTop: 0, paddingBottom: 0, opacity: 0, duration: collapseDur, ease: MOBILE_EASE, }, 0, ); return; } animateDesktopMenu(false); } function animateExpand() { var g = gsapRef(); if (!g || !bottomSide) return; var bottomKids = getBottomKids(); if (!isMobileMode()) { animateDesktopMenu(true); return; } var bottomNow = readBottomSideMotion(); g.set(bottomSide, { height: bottomNow.height, marginTop: bottomNow.marginTop, paddingTop: bottomNow.paddingTop, paddingBottom: bottomNow.paddingBottom, opacity: bottomNow.opacity, overflow: "hidden", pointerEvents: bottomNow.height > 0 ? "auto" : "none", }); clearBottomMotion(g); g.set(bottomKids, { opacity: 1 }); nav._menuTl = g.timeline({ defaults: { ease: MOBILE_EASE, overwrite: "auto" }, onComplete: function () { applyCollapsedVisual(false); finishMenuAnimation(); }, }); var expandDur = getBlockExpandDur(); nav._menuTl.to( bottomSide, { height: sizeCache.bottom, marginTop: sizeCache.bottomMarginTop, paddingTop: sizeCache.bottomPaddingTop, paddingBottom: sizeCache.bottomPaddingBottom, opacity: 1, pointerEvents: "auto", duration: expandDur, ease: MOBILE_EASE, }, 0, ); } function setCollapsed(state, options) { options = options || {}; if (!nav || !gsapRef()) return; if (forceExpanded && state && !options.fromMenuOpen && !options.fromPageEnter) return; if (isMobileMode() && isMobileMenuBusy() && !options.fromMenuOpen && !options.fromPageEnter) return; var next = !!state; var reversing = !isMobileMode() && !!options.reverse && nav.classList.contains("is-menu-animating"); if (!next && reversing && nav._menuTl) { collapsed = false; nav.setAttribute("data-menu-collapsed", "false"); animateDesktopMenu(true); if (!isMobileMode()) lockScrollAnim(estimateAnimLockMs(true)); if (mobileOverlay && mobileOverlay.classList.contains("is-visible")) { startOverlayBoundsTracking(); } return; } if (!reversing && !options.fromMenuOpen && !options.fromPageEnter && !options.fromScroll && !forceExpanded) { if (!isMobileMode() && scrollAnimLocked) return; if (!isMobileMode() && nav.classList.contains("is-menu-animating")) return; if (isMobileMode() && (mobileNavAnimLocked || nav.classList.contains("is-menu-animating"))) return; } if (reversing && !nav._menuTl) freezeNavAnim(); if (next === collapsed && !reversing) return; if (!heightsCached) measureHeights(false); if (!reversing) killMenuTweens(); else if (nav._menuTl) { nav._menuTl.kill(); nav._menuTl = null; } clearTimeout(nav._menuAnimTimer); collapsed = next; nav.setAttribute("data-menu-collapsed", next ? "true" : "false"); clearDesktopLinkHover(); nav.classList.add("is-scroll-collapse-ready", "is-menu-animating"); if (next) { nav.classList.add("is-scroll-collapsed"); animateCollapse(); } else { nav.classList.remove("is-scroll-collapsed"); animateExpand(); } if (!isMobileMode()) lockScrollAnim(estimateAnimLockMs(!next)); else if (!options.fromMenuOpen) lockMobileNavAnim(); if (mobileOverlay && mobileOverlay.classList.contains("is-visible")) { startOverlayBoundsTracking(); } } function requestDesktopExpand() { unlockScrollAnimEarly(); setCollapsed(false, { fromScroll: true, reverse: nav.classList.contains("is-menu-animating") }); } function expandMenu() { forceExpanded = true; requestDesktopExpand(); } function onMenuTouchMobile(e) { if (!isActive() || !isMobileMode()) return; mobileTouchHandled = true; onMenuClickMobile(e); } function onMenuClickMobile(e) { if (!isActive() || !isMobileMode()) return; if (e && e.type === "click" && mobileTouchHandled) { mobileTouchHandled = false; return; } if (e) { e.preventDefault(); e.stopPropagation(); if (typeof e.stopImmediatePropagation === "function") e.stopImmediatePropagation(); } if (mobileMenuOpen) closeMobileMenuAnim(); else openMobileMenuAnim(); } function onMenuClickDesktop(e) { if (!isActive() || isMobileMode() || !collapsed) return; if (e) { e.preventDefault(); e.stopPropagation(); } expandMenu(); } function isModalOpen() { if (!MODAL_OPEN_SELECTOR) return false; return !!document.querySelector(MODAL_OPEN_SELECTOR); } function onScrollPosition(y) { if (!isActive()) return; if (isModalOpen()) return; if (!bootstrapped) { lastY = y; return; } if (isMobileMode() && (isMobileMenuBusy() || mobileNavAnimLocked || nav.classList.contains("is-menu-animating"))) { lastY = y; return; } if (!scrollCollapseEnabled()) { lastY = y; return; } if (!isMobileMode() && y <= TOP_EXPAND) { forceExpanded = false; if (collapsed || nav.classList.contains("is-menu-animating")) { requestDesktopExpand(); } lastY = y; return; } if (!isMobileMode() && (scrollAnimLocked || nav.classList.contains("is-menu-animating"))) { var dirDuringAnim = getScrollDirection(y); if (collapsed && dirDuringAnim === -1) { requestDesktopExpand(); lastY = y; return; } lastY = y; resetScrollDirectionState(); return; } var dir = isMobileMode() ? getMobileScrollDirection(y) : getScrollDirection(y); if (!dir) { lastY = y; return; } if (!isMobileMode() && !hasScrollMotion()) { lastY = y; resetScrollDirectionState(); return; } forceExpanded = false; if (dir === 1) { if (!collapsed) setCollapsed(true); } else if (dir === -1) { if (collapsed) requestDesktopExpand(); } lastY = y; } function resetNav() { collapsed = false; forceExpanded = false; bootstrapped = false; heightsCached = false; mobileMenuOpen = false; closingMobileMenu = false; scrollAnimLocked = false; unlockMobileNavAnim(); resetScrollDirectionState(); unlockMobilePageScroll(); killMobileMenuTweens(); stopOverlayBoundsTracking(); if (nav) clearTimeout(nav._scrollAnimLockTimer); setMobileOverlay(false, true); if (!nav) return; cancelMenuAnimation(); clearDesktopLinkHover(); nav.classList.remove( "is-scroll-collapsed", "is-scroll-collapse-ready", "is-menu-measuring", "is-menu-instant", "is-menu-animating", ); nav.setAttribute("data-menu-collapsed", "false"); clearTimeout(nav._menuAnimTimer); if (gsapRef()) { gsapRef().set([bottomSide, menuLinks], { clearProps: "all" }); restoreLinkDisplay(); if (bottomSide) gsapRef().set(bottomSide.children, { clearProps: "all" }); if (mobileMenu) { gsapRef().set(mobileMenu, { clearProps: "all" }); if (mobileMenu.children.length) gsapRef().set(mobileMenu.children, { clearProps: "all" }); mobileMenu.classList.remove("is-open", "is-ready"); } } if (nav) nav.classList.remove("menu-mobile-open"); if (burger) { burger.classList.remove("is-active"); burger.setAttribute("aria-expanded", "false"); } } function syncScrollAnchor() { lastY = getScrollY(); resetScrollDirectionState(); } function syncState() { if (!isActive()) { resetNav(); lastY = getScrollY(); return; } lastY = getScrollY(); resetScrollDirectionState(); if (!bootstrapped && isMobileMode()) { collapsed = true; nav.classList.add("is-scroll-collapse-ready", "is-scroll-collapsed", "is-menu-instant"); nav.setAttribute("data-menu-collapsed", "true"); if (gsapRef()) applyCollapsedVisual(true); measureHeights(false, { silentExpanded: true }); bootstrapped = true; nav.classList.remove("is-menu-instant"); if (isMobileExpandPage()) { setCollapsed(false, { fromPageEnter: true }); } else { applyCollapsedInstant(true); } return; } if (isMobileExpandPage() && (nav.classList.contains("is-menu-animating") || !collapsed)) { return; } var hadHeights = heightsCached; measureHeights(false, { silentExpanded: false }); // measureHeights clears the inline collapsed styles; without re-applying them a collapsed header popped open // (fonts.ready / pageshow) and later snapped shut on the first resize. if (bootstrapped && !hadHeights && !nav.classList.contains("is-menu-animating")) { applyCollapsedVisual(collapsed); } if (!bootstrapped) { lastY = getScrollY(); resetScrollDirectionState(); applyCollapsedInstant(getShouldCollapse(lastY)); bootstrapped = true; if (!isMobileMode() && lastY <= TOP_EXPAND && collapsed) { applyCollapsedInstant(false); } } } function syncMobileMenuHeight() { if (!isMobileMode() || !mobileMenuOpen || !mobileMenu || !gsapRef()) return; gsapRef().set(mobileMenu, { height: measureMobileMenuHeight() }); } var lastResizeWidth = window.innerWidth; // Re-measuring kills running tweens, so wait until the nav is not animating. function whenNavIdle(fn) { var tries = 0; (function check() { var busy = nav && (nav._navEnterTl || nav._navEnterScheduleTimer || nav.classList.contains("is-menu-animating") || closingMobileMenu); if (busy && ++tries < 40) { setTimeout(check, 100); return; } fn(); })(); } function onResize() { clearTimeout(resizeTimer); resizeTimer = setTimeout(function () { if (!isActive()) { resetNav(); return; } var width = window.innerWidth; var widthChanged = Math.abs(width - lastResizeWidth) > 1; lastResizeWidth = width; // Mobile toolbars show/hide on scroll and fire height-only resizes; nav heights depend on width only. if (!widthChanged) { syncMobileMenuHeight(); if (mobileOverlay && mobileOverlay.classList.contains("is-visible")) { updateMobileOverlayBounds(); } return; } heightsCached = false; measureHeights(true); applyCollapsedInstant(collapsed, { replayEnter: false }); lastY = getScrollY(); resetScrollDirectionState(); syncMobileMenuHeight(); if (mobileOverlay && mobileOverlay.classList.contains("is-visible")) { updateMobileOverlayBounds(); } }, 160); } function bindLenisScroll() { if (!window.lenis || typeof window.lenis.on !== "function" || lenisScrollBound) return false; lenisScrollBound = true; window.lenis.on("scroll", function (payload) { var y = payload && typeof payload.scroll === "number" ? payload.scroll : getScrollY(); onScrollPosition(y); }); requestAnimationFrame(function () { if (isActive()) syncState(); }); return true; } function bindScroll() { if (bindLenisScroll()) return; var n = 0; var wait = setInterval(function () { if (bindLenisScroll() || ++n > 80) clearInterval(wait); }, 50); window.addEventListener( "scroll", function () { if (lenisScrollBound) return; onScrollPosition(getScrollY()); }, { passive: true }, ); } function bind() { nav = document.querySelector(".fixed_menu"); menuLinks = nav ? nav.querySelector(".menu_links") : null; bottomSide = nav ? nav.querySelector(".bottom_side") : null; burger = nav ? nav.querySelector(".h_link.menu_hamburger") : null; if (!nav || !bottomSide || ready) return false; ready = true; // If the pre-mount pose (link unshifted, arrow hidden) has already been on screen, glide from it; otherwise the // current link is shown in place from the first frame. With the head snippet the page painted in place already. nav._navArrivePainted = navArrival && hasPainted() && !navArrivePosed; nav.classList.add("is-scroll-collapse-mounted"); if (navArrival && !nav._navArrivePainted) { settleArrivedNav(); } else { nav.classList.add("is-nav-pending-enter"); } window.addEventListener("click", onNavLeaveClick, true); bindMobileMenuClick(); bindDesktopClick(); bindDesktopLinkHover(); syncCurrentNavLinkMarkers(); bindMobileMenuSync(); bindMobileOverlayTracking(); desktopMq.addEventListener("change", function () { bindDesktopClick(); syncState(); }); mobileMq.addEventListener("change", syncState); window.addEventListener("resize", onResize, { passive: true }); window.addEventListener("pageshow", function (e) { if (!e.persisted) { whenNavIdle(function () { heightsCached = false; syncState(); }); return; } resetNavLeave(); heightsCached = false; syncState(); if (nav && !isMobileMode() && !collapsed) scheduleNavEnterAnimation(); }); if (document.fonts && document.fonts.ready) { document.fonts.ready.then(function () { if (!isActive()) return; whenNavIdle(function () { heightsCached = false; syncState(); }); }); } bindUserScrollIntent(); bindScroll(); syncState(); applyArrivalHover(); dropNavArriveClasses(); console.info("[menu-scroll-collapse v" + VERSION + " gsap] ready"); return true; } function init() { loadGsap() .then(function () { configureGsap(); if (bind()) return; var tries = 0; var poll = setInterval(function () { if (bind() || ++tries > 40) clearInterval(poll); }, 100); }) .catch(function (err) { console.error("[menu-scroll-collapse] GSAP load failed", err); }); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } if (window.Webflow) { window.Webflow.push(function () { loadGsap().then(function () { configureGsap(); syncState(); }); }); } window.menuScrollCollapse = { version: VERSION, expand: expandMenu, collapse: function () { forceExpanded = false; setCollapsed(true); }, refresh: syncState, syncScroll: syncScrollAnchor, measure: function () { heightsCached = false; measureHeights(true); applyCollapsedInstant(collapsed); }, }; })();