1/* 2 * Site header behaviour, 1:1 with zvox.ai (components/New/ui/NewHeader.js). 3 * The markup is in header.php and every visual state is a class styled in 4 * assets/css/header.css: 5 * 6 * .is-raised past 8px of scroll, or while the menu is open 7 * .is-menu-open mobile panel + scrim (below 1280px) 8 * .is-closing the panel's short exit before it is hidden 9 * .zx-group.is-open, .zx-drawer.is-open 10 * 11 * While the menu is open the page stops scrolling and `app:lock-scroll` is 12 * dispatched, which the overlay scroll indicator listens for - the same event 13 * the site uses. 14 */ 15(function () { 16 'use strict'; 17 18 function init() { 19 var header = document.getElementById('zxHeader'); 20 if (!header) return; 21 22 var burger = document.getElementById('zxBurger'); 23 var scrim = document.getElementById('zxScrim'); 24 var menuOpen = false; 25 var closeTimer = null; 26 var previousOverflow = ''; 27 28 // ---- Scroll: lift into the strip ------------------------------- 29 function syncRaised() { 30 header.classList.toggle('is-raised', menuOpen || window.scrollY > 8); 31 } 32 window.addEventListener('scroll', syncRaised, { passive: true }); 33 syncRaised(); 34 35 // ---- Desktop dropdowns: hover, plus keyboard/focus ------------- 36 var groups = header.querySelectorAll('[data-zx-group]'); 37 groups.forEach(function (group) { 38 var trigger = group.querySelector('button'); 39 40 function setOpen(open) { 41 group.classList.toggle('is-open', open); 42 if (trigger) trigger.setAttribute('aria-expanded', open ? 'true' : 'false'); 43 } 44 45 group.addEventListener('mouseenter', function () { setOpen(true); }); 46 group.addEventListener('mouseleave', function () { setOpen(false); }); 47 group.addEventListener('focusin', function () { setOpen(true); }); 48 group.addEventListener('focusout', function (event) { 49 if (!group.contains(event.relatedTarget)) setOpen(false); 50 }); 51 if (trigger) { 52 trigger.addEventListener('click', function () { 53 setOpen(!group.classList.contains('is-open')); 54 }); 55 } 56 }); 57 58 // ---- Mobile menu ------------------------------------------------- 59 function onKey(event) { 60 if (event.key === 'Escape') setMenu(false); 61 } 62 63 function setMenu(open) { 64 if (open === menuOpen) return; 65 menuOpen = open; 66 window.clearTimeout(closeTimer); 67 68 if (open) { 69 header.classList.remove('is-closing'); 70 header.classList.add('is-menu-open'); 71 previousOverflow = document.body.style.overflow; 72 document.body.style.overflow = 'hidden'; 73 if (window.lenis && window.lenis.stop) window.lenis.stop(); 74 window.addEventListener('keydown', onKey); 75 window.dispatchEvent(new CustomEvent('app:lock-scroll')); 76 } else { 77 header.classList.remove('is-menu-open'); 78 // Everything leaves at once and fast, as on the site. 79 header.classList.add('is-closing'); 80 closeTimer = window.setTimeout(function () { 81 header.classList.remove('is-closing'); 82 }, 160); 83 document.body.style.overflow = previousOverflow; 84 if (window.lenis && window.lenis.start) window.lenis.start(); 85 window.removeEventListener('keydown', onKey); 86 window.dispatchEvent(new CustomEvent('app:unlock-scroll')); 87 } 88 89 if (burger) { 90 burger.setAttribute('aria-expanded', open ? 'true' : 'false'); 91 burger.setAttribute('aria-label', open ? 'Close menu' : 'Open menu'); 92 } 93 syncRaised(); 94 } 95 96 if (burger) { 97 burger.addEventListener('click', function () { setMenu(!menuOpen); }); 98 } 99 if (scrim) { 100 scrim.addEventListener('click', function () { setMenu(false); }); 101 } 102 103 // Growing past the breakpoint with the menu open would leave the page 104 // locked behind a panel that CSS has hidden. 105 if (window.matchMedia) { 106 var desktop = window.matchMedia('(min-width: 1280px)'); 107 var onBreakpoint = function () { if (desktop.matches) setMenu(false); }; 108 if (desktop.addEventListener) desktop.addEventListener('change', onBreakpoint); 109 else if (desktop.addListener) desktop.addListener(onBreakpoint); 110 } 111 112 // Leaving the page from inside the menu. 113 header.querySelectorAll('.zx-panel a').forEach(function (link) { 114 link.addEventListener('click', function () { setMenu(false); }); 115 }); 116 117 // ---- Drawers (Solutions / Company) ------------------------------ 118 header.querySelectorAll('.zx-drawer-toggle').forEach(function (toggle) { 119 var drawer = document.getElementById(toggle.getAttribute('aria-controls')); 120 if (!drawer) return; 121 toggle.addEventListener('click', function () { 122 var open = !drawer.classList.contains('is-open');
123 drawer.classList.toggle('is-open', open); 124 toggle.setAttribute('aria-expanded', open ? 'true' : 'false'); 125 }); 126 }); 127 } 128 129 if (document.readyState === 'loading') { 130 document.addEventListener('DOMContentLoaded', init); 131 } else { 132 init(); 133 } 134})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.