1/* ============================================================ 2 Mega-Menu Interactions â 2026 Update 3 Handles Offerings and Platform mega-menus. 4 Coexists with Webflow's site.js which manages .w-dropdown. 5 ============================================================ */ 6(function () { 7 'use strict'; 8 9 var DELAY = 200; 10 var triggers = document.querySelectorAll('[data-mega-menu]'); 11 var closeTimers = {}; 12 var desktopMQ = window.matchMedia('(min-width: 992px)'); 13 14 // Add ARIA roles and keyboard attributes to triggers 15 triggers.forEach(function (trigger) { 16 trigger.setAttribute('role', 'button'); 17 trigger.setAttribute('tabindex', '0'); 18 trigger.setAttribute('aria-haspopup', 'true'); 19 if (!trigger.getAttribute('aria-expanded')) { 20 trigger.setAttribute('aria-expanded', 'false'); 21 } 22 }); 23 24 function openPanel(trigger) { 25 var id = trigger.getAttribute('data-mega-menu'); 26 var panel = document.getElementById(id); 27 if (!panel) return; 28 clearTimeout(closeTimers[id]); 29 // Close all other panels first 30 triggers.forEach(function (t) { 31 if (t !== trigger) closePanel(t); 32 }); 33 panel.classList.add('is-open'); 34 trigger.setAttribute('aria-expanded', 'true'); 35 // Nudge scroll so mobile browsers recalculate scrollable area 36 if (!desktopMQ.matches) { 37 requestAnimationFrame(function () { 38 var overlay = document.querySelector('.w-nav-overlay'); 39 if (overlay && overlay.scrollHeight > overlay.clientHeight && overlay.scrollTop === 0) { 40 overlay.scrollTop = 1; 41 overlay.scrollTop = 0; 42 } 43 }); 44 } 45 } 46 47 function closePanel(trigger) { 48 var id = trigger.getAttribute('data-mega-menu'); 49 var panel = document.getElementById(id); 50 if (!panel) return; 51 panel.classList.remove('is-open'); 52 trigger.setAttribute('aria-expanded', 'false'); 53 // Clamp scroll position when panel collapses to avoid blank space 54 if (!desktopMQ.matches) { 55 requestAnimationFrame(function () { 56 var overlay = document.querySelector('.w-nav-overlay'); 57 if (overlay && overlay.scrollTop > overlay.scrollHeight - overlay.clientHeight) { 58 overlay.scrollTop = Math.max(0, overlay.scrollHeight - overlay.clientHeight); 59 } 60 }); 61 } 62 } 63 64 function scheduledClose(trigger) { 65 var id = trigger.getAttribute('data-mega-menu'); 66 closeTimers[id] = setTimeout(function () { 67 closePanel(trigger); 68 }, DELAY); 69 } 70 71 function cancelClose(trigger) { 72 var id = trigger.getAttribute('data-mega-menu'); 73 clearTimeout(closeTimers[id]); 74 } 75 76 triggers.forEach(function (trigger) { 77 var id = trigger.getAttribute('data-mega-menu'); 78 var panel = document.getElementById(id); 79 var wrapper = trigger.closest('.mega-menu-wrapper'); 80 if (!panel) return; 81 82 // Desktop hover â only when above mobile breakpoint 83 if (wrapper) { 84 wrapper.addEventListener('mouseenter', function () { 85 if (desktopMQ.matches) openPanel(trigger); 86 }); 87 wrapper.addEventListener('mouseleave', function () { 88 if (desktopMQ.matches) scheduledClose(trigger); 89 }); 90 } else { 91 trigger.addEventListener('mouseenter', function () { 92 if (desktopMQ.matches) openPanel(trigger); 93 }); 94 trigger.addEventListener('mouseleave', function () { 95 if (desktopMQ.matches) scheduledClose(trigger); 96 }); 97 panel.addEventListener('mouseenter', function () { 98 if (desktopMQ.matches) cancelClose(trigger); 99 }); 100 panel.addEventListener('mouseleave', function () { 101 if (desktopMQ.matches) scheduledClose(trigger); 102 }); 103 } 104 105 // Click toggle (mobile) 106 trigger.addEventListener('click', function (e) { 107 e.preventDefault(); 108 if (panel.classList.contains('is-open')) { 109 closePanel(trigger); 110 } else { 111 openPanel(trigger); 112 } 113 }); 114 115 // Keyboard: Enter/Space to toggle, arrow keys to navigate 116 trigger.addEventListener('keydown', function (e) { 117 if (e.key === 'Enter' || e.key === ' ') { 118 e.preventDefault(); 119 if (panel.classList.contains('is-open')) { 120 closePanel(trigger); 121 } else {
122 openPanel(trigger); 123 // Focus first link in panel 124 var firstLink = panel.querySelector('a'); 125 if (firstLink) firstLink.focus(); 126 } 127 } 128 if (e.key === 'ArrowDown') { 129 e.preventDefault(); 130 if (!panel.classList.contains('is-open')) { 131 openPanel(trigger); 132 } 133 var firstLink = panel.querySelector('a'); 134 if (firstLink) firstLink.focus(); 135 } 136 }); 137 138 // Allow keyboard navigation within panel and return focus 139 panel.addEventListener('keydown', function (e) { 140 var links = panel.querySelectorAll('a'); 141 var idx = Array.prototype.indexOf.call(links, document.activeElement); 142 143 if (e.key === 'ArrowDown') { 144 e.preventDefault(); 145 if (idx < links.length - 1) links[idx + 1].focus(); 146 } 147 if (e.key === 'ArrowUp') { 148 e.preventDefault(); 149 if (idx > 0) { 150 links[idx - 1].focus(); 151 } else { 152 trigger.focus(); 153 } 154 } 155 if (e.key === 'Escape') { 156 closePanel(trigger); 157 trigger.focus(); 158 } 159 if (e.key === 'Tab') { 160 // On last link Tab, or shift+tab on first, close panel 161 if (!e.shiftKey && idx === links.length - 1) { 162 closePanel(trigger); 163 } 164 if (e.shiftKey && idx === 0) { 165 closePanel(trigger); 166 trigger.focus(); 167 e.preventDefault(); 168 } 169 } 170 }); 171 }); 172 173 // Close on outside click 174 document.addEventListener('click', function (e) { 175 triggers.forEach(function (trigger) { 176 var id = trigger.getAttribute('data-mega-menu'); 177 var panel = document.getElementById(id); 178 if (!panel) return; 179 if (!trigger.contains(e.target) && !panel.contains(e.target)) { 180 closePanel(trigger); 181 } 182 }); 183 }); 184 185 // Close on Escape (global) 186 document.addEventListener('keydown', function (e) { 187 if (e.key === 'Escape') { 188 triggers.forEach(function (trigger) { closePanel(trigger); }); 189 } 190 }); 191 192 // Body scroll lock and panel reset for mobile nav 193 var navButton = document.querySelector('.w-nav-button'); 194 if (navButton) { 195 var mobileMQ = window.matchMedia('(max-width: 991px)'); 196 var observer = new MutationObserver(function (mutations) { 197 mutations.forEach(function (mutation) { 198 if (mutation.attributeName !== 'class') return; 199 if (!mobileMQ.matches) return; 200 var isOpen = navButton.classList.contains('w--open'); 201 if (isOpen) { 202 document.body.style.overflow = 'hidden'; 203 document.body.style.position = 'fixed'; 204 document.body.style.width = '100%'; 205 document.body.style.top = '-' + window.scrollY + 'px'; 206 // Fix overlay height â Webflow's $(window).height() can be wrong on mobile 207 var navEl = document.querySelector('.navbar.w-nav'); 208 var animDuration = parseInt(navEl && navEl.getAttribute('data-duration') || '400', 10); 209 setTimeout(function () { 210 var overlay = document.querySelector('.w-nav-overlay'); 211 if (!overlay || overlay.style.display === 'none') return; 212 var navHeight = navEl ? navEl.getBoundingClientRect().height : 0; 213 overlay.style.setProperty('height', (window.innerHeight - navHeight) + 'px', 'important'); 214 }, animDuration + 50); 215 } else { 216 // Close all mega-menu panels 217 triggers.forEach(function (t) { closePanel(t); }); 218 // Restore scroll position 219 var scrollY = document.body.style.top; 220 document.body.style.overflow = ''; 221 document.body.style.position = ''; 222 document.body.style.width = ''; 223 document.body.style.top = ''; 224 window.scrollTo(0, parseInt(scrollY || '0') * -1); 225 } 226 }); 227 }); 228 observer.observe(navButton, { attributes: true, attributeFilter: ['class'] }); 229 } 230 231 // Backup: listen for Webflow's jQuery w-close event 232 var wNav = document.querySelector('.w-nav'); 233 if (wNav && window.jQuery) { 234 window.jQuery(wNav).on('w-close', function () {
235 triggers.forEach(function (t) { closePanel(t); }); 236 }); 237 } 238})();
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.