1document.addEventListener('DOMContentLoaded', function () { 2 3 /*** KONFIG ***/ 4 const BREAKPOINT = 992; // desktop >= 992px 5 6 // matchMedia odpowiada dokÅadnie breakpointowi Bootstrap: max-width: 991.98px 7 const MQ = window.matchMedia(`(max-width: ${BREAKPOINT - 0.02}px)`); 8 9 let currentMode = null; // ustawiane przez setMode() przy inicjalizacji 10 11 12 /*** RESET ***/ 13 // Usuwa stany UI i event listenery (przez cloneNode) przed reinicjalizacjÄ trybu 14 function resetAllHandlers() { 15 clearTimeout(closeHeightTimer); 16 17 document.querySelectorAll( 18 '.submenu-container, .submenu, .navbar-nav, .submenu-content-container' 19 ).forEach(el => { 20 el.classList.remove('show', 'no-transition', 'transition', 'tilt'); 21 el.removeAttribute('style'); 22 }); 23 24 document.body.classList.remove('menu-opened'); 25 26 const hamburger = document.querySelector('#hamburger'); 27 if (hamburger) hamburger.setAttribute('data-state', 'closed'); 28 29 // cloneNode usuwa event listenery bez potrzeby Åledzenia referencji 30 [ 31 '#main-menu', 32 '#hamburger', 33 '#back', 34 '.submenu-container', 35 '.submenu-content-container', 36 '.navbar-nav .nav-item' 37 ].forEach(selector => { 38 document.querySelectorAll(selector).forEach(el => { 39 el.parentNode?.replaceChild(el.cloneNode(true), el); 40 }); 41 }); 42 } 43 44 45 /*** DESKTOP ***/ 46 function initDesktopMenuHandlers() { 47 const submenuContainer = document.querySelector('.submenu-container'); 48 const navbar = document.querySelector('.navbar'); 49 const mainMenu = document.querySelector('#main-menu'); 50 51 if (!submenuContainer || !navbar || !mainMenu) return; 52 53 // ZapamiÄtana wysokoÅÄ navbar â unikamy reflow przy każdym scroll 54 const navbarHeight = navbar.offsetHeight; 55 56 let activeSubmenu = null; 57 let closeHeightTimer = null; 58 59 // --- Aktualizacja wysokoÅci kontenera --- 60 // Potrzebne gdy submenu-container ma transition na height â
61 // bez jawnego ustawienia height przeglÄ darka nie animuje zmiany miÄdzy submenu. 62 function updateContainerHeight() { 63 if (!activeSubmenu) return; 64 requestAnimationFrame(() => { 65 submenuContainer.style.height = `${activeSubmenu.offsetHeight}px`; 66 }); 67 } 68 69 // --- Otwieranie submenu --- 70 function openSubmenu(submenuId) { 71 const next = document.getElementById(submenuId); 72 if (!next) return; 73 74 // Anuluj reset height jeÅli otwarcie nastÄ pi przed upÅywem 250ms od zamkniÄcia 75 clearTimeout(closeHeightTimer); 76 77 if (activeSubmenu && activeSubmenu !== next) { 78 activeSubmenu.classList.remove('show'); 79 } 80 81 activeSubmenu = next; 82 activeSubmenu.classList.add('show'); 83 submenuContainer.classList.add('show'); 84 document.body.classList.add('menu-opened'); 85 updateContainerHeight(); 86 } 87 88 // --- Zamykanie submenu --- 89 function closeSubmenu() { 90 submenuContainer.classList.remove('show'); 91 // WyczyÅÄ height dopiero po zakoÅczeniu animacji zamykania (opacity: 250ms) 92 // żeby nie wywoÅaÄ skoku wysokoÅci podczas znikania 93 closeHeightTimer = setTimeout(() => { 94 submenuContainer.style.height = ''; 95 }, 250); 96 document.body.classList.remove('menu-opened'); 97 if (activeSubmenu) { 98 activeSubmenu.classList.remove('show'); 99 activeSubmenu = null; 100 } 101 } 102 103 // --- ZarzÄ dzanie klasÄ hover na linkach gÅównego menu --- 104 // (potrzebna do stylowania aktywnego linku gdy kursor jest nad submenu) 105 function bindHoverClasses() { 106 document.querySelectorAll('#main-menu > li').forEach(item => { 107 const link = item.querySelector('[show-submenu]'); 108 if (!link) return; 109 110 const submenuId = link.getAttribute('show-submenu'); 111 const relatedSub = document.getElementById(submenuId); 112 113 const add = () => link.classList.add('hover'); 114 const remove = () => link.classList.remove('hover'); 115 116 item.addEventListener('mouseenter', add); 117 item.addEventListener('mouseleave', remove); 118 relatedSub?.addEventListener('mouseenter', add); 119 relatedSub?.addEventListener('mouseleave', remove); 120 }); 121 } 122 123 // --- PrzeÅÄ czanie klas transition/no-transition --- 124 // no-transition: gdy kursor przesuwa siÄ miÄdzy linkami w navbar (brak animacji wysokoÅci) 125 // transition: gdy kursor wchodzi w obszar submenu (animacja wysokoÅci przy zmianie sekcji) 126 function setTransition(enable) { 127 requestAnimationFrame(() => { 128 submenuContainer.classList.toggle('transition', enable); 129 submenuContainer.classList.toggle('no-transition', !enable); 130 }); 131 } 132 133 // --- Event listenery --- 134 function bindEvents() { 135 136 // Każdy nav-item w gÅównym menu â otwórz submenu lub zamknij 137 document.querySelectorAll('#main-menu > li').forEach(item => { 138 const link = item.querySelector('a'); 139 if (!link) return; 140 141 const submenuId = link.getAttribute('show-submenu'); 142 143 item.addEventListener('mouseenter', () => { 144 if (submenuId) { 145 openSubmenu(submenuId); // link z submenu â otwórz 146 } else { 147 closeSubmenu(); // link bez submenu â zamknij 148 } 149 }); 150 151 item.addEventListener('mouseleave', e => { 152 const toEl = e.relatedTarget; 153 if (!submenuContainer.contains(toEl) && !navbar.contains(toEl)) { 154 closeSubmenu(); 155 } 156 }); 157 }); 158
159 // Submenu container â utrzymaj otwarcie gdy kursor nad nim 160 submenuContainer.addEventListener('mouseenter', () => { 161 submenuContainer.classList.add('show'); 162 setTransition(true); 163 }); 164 submenuContainer.addEventListener('mouseleave', e => { 165 const toEl = e.relatedTarget; 166 if (!navbar.contains(toEl)) { 167 closeSubmenu(); 168 } else { 169 setTransition(false); 170 } 171 }); 172 173 // navbar wejÅcie â wyÅÄ cz transition żeby zmiana submenu byÅa natychmiastowa 174 navbar.addEventListener('mouseenter', () => setTransition(false)); 175 176 // mainMenu wejÅcie/wyjÅcie 177 mainMenu.addEventListener('mouseenter', () => setTransition(true)); 178 mainMenu.addEventListener('mouseleave', e => { 179 if (navbar.contains(e.relatedTarget)) setTransition(false); 180 }); 181 } 182 183 bindHoverClasses(); 184 bindEvents(); 185 } 186 187 188 /*** MOBILE ***/ 189 function initMobileMenuHandlers() { 190 const hamburger = document.querySelector('#hamburger'); 191 const mainMenu = document.querySelector('#main-menu'); 192 const backButton = document.querySelector('#back'); 193 const navbarLogo = document.querySelector('.navbar-logo'); 194 const submenuContainer = document.querySelector('.submenu-container'); 195 196 // Stos kontenerów z klasÄ 'tilt' â potrzebny do cofania (back button) 197 const tiltStack = []; 198 199 // --- Hamburger --- 200 hamburger?.addEventListener('click', () => { 201 const isOpen = hamburger.getAttribute('data-state') === 'open'; 202 const nextState = isOpen ? 'closed' : 'open'; 203 204 hamburger.setAttribute('data-state', nextState); 205 mainMenu?.classList.toggle('show', nextState === 'open'); 206 document.body.classList.toggle('menu-opened', nextState === 'open'); 207 208 if (nextState === 'closed') { 209 // Zamknij wszystkie submenu z opóźnieniem (czas trwania animacji CSS) 210 submenuContainer?.classList.remove('show'); 211 setTimeout(() => { 212 document.querySelectorAll( 213 '.submenu-container > *, .submenu-content-container > *' 214 ).forEach(el => el.classList.remove('show')); 215 }, 400); 216 217 document.querySelectorAll('.submenu-container, .navbar-nav').forEach(el => { 218 el.classList.remove('tilt'); 219 }); 220 221 tiltStack.length = 0; 222 } 223 }); 224 225 // --- Klik w linki otwierajÄ ce submenu / submenu wewnÄtrzne --- 226 document.querySelectorAll('[show-submenu], [show-submenu-inner]').forEach(el => { 227 el.addEventListener('click', function (e) { 228 // Blokuj nawigacjÄ tylko na mobile 229 if (MQ.matches) e.preventDefault(); 230 231 const attr = el.getAttribute('show-submenu') || el.getAttribute('show-submenu-inner'); 232 const target = document.getElementById(attr); 233 if (!target) return; 234 235 // Pokaż submenu-container i docelowe submenu 236 if (submenuContainer) submenuContainer.classList.add('show'); 237 target.classList.add('show'); 238 239 // ZapamiÄtaj rodzica na stosie i dodaj tilt (efekt cofania) 240 const parent = el.closest('.submenu-container') || el.closest('.navbar-nav'); 241 if (parent && !parent.classList.contains('tilt')) { 242 parent.classList.add('tilt'); 243 tiltStack.push(parent); 244 } 245 246 // Submenu wewnÄtrzne â zarzÄ dzaj widocznoÅciÄ rodzeÅstwa 247 if (el.hasAttribute('show-submenu-inner')) { 248 const container = target.closest('.submenu-content-container'); 249 if (container) { 250 container.querySelector('.submenu-content.show')?.classList.remove('show'); 251 target.classList.add('show'); 252 container.classList.add('show'); 253 } 254 } 255 }); 256 }); 257 258 // --- Przycisk WrÃ³Ä --- 259 backButton?.addEventListener('click', () => { 260 const openContentContainer = document.querySelector('.submenu-content-container.show'); 261 262 if (openContentContainer) { 263 // Krok 2: zamknij submenu wewnÄtrzne 264 openContentContainer.classList.remove('show'); 265 setTimeout(() => { 266 openContentContainer.querySelectorAll(':scope > *').forEach(el => { 267 el.classList.remove('show'); 268 }); 269 }, 400); 270 } else if (submenuContainer?.classList.contains('show')) { 271 // Krok 1: zamknij gÅówne submenu 272 submenuContainer.classList.remove('show'); 273 setTimeout(() => { 274 submenuContainer.querySelectorAll(':scope > *').forEach(el => { 275 el.classList.remove('show'); 276 }); 277 }, 400); 278 } 279 280 // PrzywrÃ³Ä poprzedni element ze stosu (usuÅ tilt) 281 tiltStack.pop()?.classList.remove('tilt'); 282 }); 283
284 // --- WidocznoÅÄ przycisku WrÃ³Ä (obserwuj submenu-container) --- 285 if (submenuContainer && navbarLogo) { 286 new MutationObserver(() => { 287 navbarLogo.classList.toggle( 288 'show-back', 289 submenuContainer.classList.contains('show') 290 ); 291 }).observe(submenuContainer, { attributes: true, attributeFilter: ['class'] }); 292 } 293 294 // --- Linki do kotwic (np. #omnichannel) â zamknij menu i scrolluj --- 295 document.querySelectorAll('#main-menu a[href^="#"]').forEach(link => { 296 link.addEventListener('click', function (e) { 297 const targetId = this.getAttribute('href'); 298 if (targetId === '#' || targetId === '#!') return; 299 300 const target = document.querySelector(targetId); 301 if (!target) return; 302 303 e.preventDefault(); 304 305 // Zamknij menu 306 hamburger?.setAttribute('data-state', 'closed'); 307 mainMenu?.classList.remove('show'); 308 document.body.classList.remove('menu-opened'); 309 submenuContainer?.classList.remove('show'); 310 tiltStack.length = 0; 311 312 // Scrolluj po animacji zamkniÄcia menu 313 setTimeout(() => { 314 target.scrollIntoView({ behavior: 'smooth' }); 315 }, 300); 316 }); 317 }); 318 } 319 320 /*** STICKY NAVBAR ***/ 321 // Throttle przez requestAnimationFrame â bezpieczne dla wydajnoÅci przy scroll 322 let stickyTicking = false;
323 let closeHeightTimer = null; // deklaracja w scope globalnym moduÅu dla resetAllHandlers 324 325 const navbarEl = document.querySelector('.navbar'); 326 const navbarHeight = navbarEl?.offsetHeight ?? 0; 327 328 function handleStickyNavbar() { 329 if (stickyTicking) return; 330 stickyTicking = true; 331 332 requestAnimationFrame(() => { 333 if (navbarEl) { 334 navbarEl.classList.toggle('sticky', window.scrollY > navbarHeight); 335 } 336 stickyTicking = false; 337 }); 338 } 339 340 /*** ZARZÄDZANIE TRYBEM ***/ 341 function setMode(nextMode) { 342 if (nextMode === currentMode) return; 343 344 document.body.classList.toggle('mobile', nextMode === 'mobile'); 345 document.body.classList.toggle('desktop', nextMode === 'desktop'); 346 347 resetAllHandlers(); 348 349 if (nextMode === 'mobile') { 350 initMobileMenuHandlers(); 351 } else { 352 initDesktopMenuHandlers(); 353 } 354 355 currentMode = nextMode; 356 } 357 358 /*** INICJALIZACJA ***/ 359 setMode(MQ.matches ? 'mobile' : 'desktop'); 360 handleStickyNavbar(); 361 362 // Zmiana trybu przy zmianie breakpointa (niezawodne, reaguje na devtools) 363 MQ.addEventListener('change', () => { 364 setMode(MQ.matches ? 'mobile' : 'desktop'); 365 }); 366 367 // Sticky przy scroll â throttlowany przez rAF 368 document.addEventListener('scroll', handleStickyNavbar, { passive: true }); 369 370 // Sticky przy resize (na wypadek zmiany wysokoÅci navbar) 371 window.addEventListener('resize', handleStickyNavbar, { passive: true }); 372 373});
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.