PageSourceSearch

https://www.przelewy24.pl/themes/przelewy24/assets/v2/js/navbar.js?09072026

js przelewy24.pl collected 2026-09-24 08:55:41 UTC 15,243 bytes, 373 lines download raw bytes

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.