PageSourceSearch

https://www.accademiamoda.it/wp-content/themes/iuad-theme/src/js/custom/secondary_menu.js?ver=1.0.0

js accademiamoda.it collected 2026-10-04 13:23:44 UTC 56,816 bytes, 1,343 lines download raw bytes

1/**
2 * Secondary Menu Offcanvas
3 * Gestisce l'apertura e chiusura di 4 offcanvas tramite bottoni
4 */
5
6(function() {
7    'use strict';
8
9    document.addEventListener('DOMContentLoaded', function() {
10        //console.log('Secondary Menu: DOMContentLoaded - inizializzazione');
11        
12        if (typeof gsap === 'undefined') {
13            console.error('GSAP non è caricato!');
14            return;
15        }
16
17        // Seleziona solo i BUTTON del secondary menu (non i tag <a>), ESCLUSI i bottoni filtro portfolio e docenti
18        const buttons = document.querySelectorAll('button.secondary-menu__btn:not(.portfolio-filter-btn):not(.docenti-filter-btn)');
19        
20        //console.log('Secondary Menu: bottoni trovati', buttons.length);
21        buttons.forEach((btn, index) => {
22            //console.log(`Secondary Menu: bottone ${index}:`, {
23                //element: btn,
24                //inHeader: btn.closest('header') !== null,
25                //inFooter: btn.closest('footer') !== null,
26                //inMobileTemplate: btn.closest('.responsive-template-mobile') !== null,
27                //inDesktopTemplate: btn.closest('.responsive-template-desktop') !== null,
28                //dataOffcanvas: btn.getAttribute('data-offcanvas')
29            //});
30        });
31        
32        // Seleziona tutti gli offcanvas
33        const allOffcanvases = document.querySelectorAll('.secondary-offcanvas');
34        
35        //console.log('Secondary Menu: offcanvas trovati', allOffcanvases.length);
36        allOffcanvases.forEach((oc, index) => {
37            //console.log(`Secondary Menu: offcanvas ${index}:`, {
38                //id: oc.id,
39                //inHeader: oc.closest('header') !== null,
40                //inFooter: oc.closest('footer') !== null,
41                //inMobileTemplate: oc.closest('.responsive-template-mobile') !== null,
42                //inDesktopTemplate: oc.closest('.responsive-template-desktop') !== null,
43                //display: window.getComputedStyle(oc).display
44            //});
45        });
46        
47        // Filtra solo gli offcanvas visibili (per template responsive)
48        // Preferisce quelli dentro template mobile visibili, altrimenti quelli direttamente visibili
49        const offcanvases = Array.from(allOffcanvases).filter(offcanvas => {
50            const computedStyle = window.getComputedStyle(offcanvas);
51            const mobileWrapper = offcanvas.closest('.responsive-template-mobile');
52            
53            // Se è dentro un template mobile visibile, includilo
54            if (mobileWrapper && window.getComputedStyle(mobileWrapper).display !== 'none') {
55                return true;
56            }
57            
58            // Se è direttamente visibile (non dentro template responsive o dentro template desktop visibile), includilo
59            if (computedStyle.display !== 'none' && computedStyle.visibility !== 'hidden') {
60                // Verifica che non sia dentro un template mobile nascosto
61                if (!mobileWrapper || window.getComputedStyle(mobileWrapper).display === 'none') {
62                    return true;
63                }
64            }
65            
66            return false;
67        });
68
69        //console.log('Secondary Menu: offcanvas visibili filtrati', offcanvases.length);
70
71        if (buttons.length === 0) {
72            console.warn('Secondary Menu: nessun bottone trovato!');
73            return;
74        }
75        
76        if (offcanvases.length === 0) {
77            console.warn('Secondary Menu: nessun offcanvas visibile trovato!');
78            // Non ritornare, continua comunque per attaccare i listener ai bottoni
79        }
80
81        // Stato
82        let currentOpenOffcanvas = null;
83        let customScrollbars = {};
84        let sublevelScrollbars = {};
85
86        /**
87         * Funzione helper per verificare se un elemento è visibile
88         * Nota: gli offcanvas possono avere display:none inizialmente, quindi controlliamo solo i wrapper
89         */
90        function isElementVisible(element, checkDisplay) {
91            if (!element) return false;
92            
93            // Se checkDisplay è true, verifica anche display (per bottoni, ecc.)
94            if (checkDisplay) {
95                const computedStyle = window.getComputedStyle(element);
96                if (computedStyle.display === 'none' || computedStyle.visibility === 'hidden') {
97                    return false;
98                }
99            }
100            
101            // Verifica se è dentro un template responsive nascosto
102            const mobileWrapper = element.closest('.responsive-template-mobile');
103            if (mobileWrapper && window.getComputedStyle(mobileWrapper).display === 'none') {
104                return false;
105            }
106            const desktopWrapper = element.closest('.responsive-template-desktop');
107            if (desktopWrapper && window.getComputedStyle(desktopWrapper).display === 'none') {
108                return false;
109            }
110            return true;
111        }
112
113        /**
114         * Funzione helper per trovare l'offcanvas visibile
115         * Quando ci sono template responsive, ci possono essere più offcanvas con lo stesso ID
116         * Questa funzione trova quello visibile corrispondente al template del bottone cliccato
117         * IMPORTANTE: cerca l'offcanvas nello stesso contesto DOM del bottone (header vs footer)
118         */
119        function findVisibleOffcanvas(offcanvasId, buttonElement) {
120            // Cerca tutti gli offcanvas con l'ID (potrebbero essercene più di uno)
121            const allOffcanvas = document.querySelectorAll(`#${offcanvasId}`);
122            
123            if (allOffcanvas.length === 0) {
124                return null;
125            }
126            
127            // Se ce n'è solo uno, usalo
128            if (allOffcanvas.length === 1) {
129                return allOffcanvas[0];
130            }
131            
132            // Se abbiamo il bottone, trova l'offcanvas nello stesso contesto DOM
133            if (buttonElement) {
134                // Verifica se il bottone è nell'header o nel footer
135                const isInHeader = buttonElement.closest('header') !== null;
136                const isInFooter = buttonElement.closest('footer') !== null;
137                const buttonMobileWrapper = buttonElement.closest('.responsive-template-mobile');
138                const buttonDesktopWrapper = buttonElement.closest('.responsive-template-desktop');
139                
140                //console.log('Secondary Menu - findVisibleOffcanvas:', {
141                    //offcanvasId: offcanvasId,
142                    //buttonElement: buttonElement,
143                    //isInHeader: isInHeader,
144                    //isInFooter: isInFooter,
145                    //buttonInMobile: buttonMobileWrapper,
146                    //buttonInDesktop: buttonDesktopWrapper,
147                    //allOffcanvasesCount: allOffcanvas.length
148                //});
149                
150                // Se il bottone è nell'header (desktop)
151                if (isInHeader && !buttonMobileWrapper && !buttonDesktopWrapper) {
152                    // Cerca l'offcanvas che è nell'header (non nel footer)
153                    for (let i = 0; i < allOffcanvas.length; i++) {
154                        const oc = allOffcanvas[i];
155                        const ocInHeader = oc.closest('header') !== null;
156                        const ocInFooter = oc.closest('footer') !== null;
157                        const ocMobileWrapper = oc.closest('.responsive-template-mobile');
158                        const ocDesktopWrapper = oc.closest('.responsive-template-desktop');
159                        
160                        // Preferisce l'offcanvas nell'header che non è in un template responsive
161                        if (ocInHeader && !ocInFooter && !ocMobileWrapper && !ocDesktopWrapper) {
162                            //console.log('Secondary Menu: trovato offcanvas desktop nell\'header', oc);
163                            return oc;
164                        }
165                    }
166                    // Fallback: offcanvas nell'header anche se in template responsive
167                    for (let i = 0; i < allOffcanvas.length; i++) {
168                        const oc = allOffcanvas[i];
169                        const ocInHeader = oc.closest('header') !== null;
170                        if (ocInHeader) {
171                            //console.log('Secondary Menu: trovato offcanvas nell\'header (fallback)', oc);
172                            return oc;
173                        }
174                    }
175                }
176                
177                // Se il bottone è nel footer (mobile)
178                if (isInFooter || buttonMobileWrapper) {
179                    // Cerca l'offcanvas nel footer o nel template mobile
180                    for (let i = 0; i < allOffcanvas.length; i++) {
181                        const oc = allOffcanvas[i];
182                        const ocInFooter = oc.closest('footer') !== null;
183                        const ocMobileWrapper = oc.closest('.responsive-template-mobile');
184                        
185                        // Preferisce l'offcanvas nel footer nello stesso template mobile
186                        if (buttonMobileWrapper && ocMobileWrapper === buttonMobileWrapper) {
187                            //console.log('Secondary Menu: trovato offcanvas mobile nello stesso template', oc);
188                            return oc;
189                        }
190                        
191                        // O l'offcanvas nel footer
192                        if (ocInFooter && ocMobileWrapper && window.getComputedStyle(ocMobileWrapper).display !== 'none') {
193                            //console.log('Secondary Menu: trovato offcanvas mobile nel footer', oc);
194                            return oc;
195                        }
196                    }
197                }
198                
199                // Se il bottone è nel template desktop
200                if (buttonDesktopWrapper) {
201                    const desktopDisplay = window.getComputedStyle(buttonDesktopWrapper).display;
202                    if (desktopDisplay !== 'none') {
203                        // Cerca l'offcanvas nello stesso template desktop
204                        for (let i = 0; i < allOffcanvas.length; i++) {
205                            const oc = allOffcanvas[i];
206                            const ocDesktopWrapper = oc.closest('.responsive-template-desktop');
207                            if (ocDesktopWrapper === buttonDesktopWrapper) {
208                                //console.log('Secondary Menu: trovato offcanvas desktop (in template)', oc);
209                                return oc;
210                            }
211                        }
212                    }
213                }
214            }
215            
216            // Fallback: trova quello visibile (preferisce mobile se visibile, altrimenti desktop, altrimenti quello non in template)
217            for (let i = 0; i < allOffcanvas.length; i++) {
218                const oc = allOffcanvas[i];
219                const mobileWrapper = oc.closest('.responsive-template-mobile');
220                const desktopWrapper = oc.closest('.responsive-template-desktop');
221                
222                // Preferisce mobile se visibile
223                if (mobileWrapper && window.getComputedStyle(mobileWrapper).display !== 'none') {
224                    //console.log('Secondary Menu: fallback - offcanvas mobile', oc);
225                    return oc;
226                }
227                
228                // Poi desktop se visibile
229                if (desktopWrapper && window.getComputedStyle(desktopWrapper).display !== 'none') {
230                    //console.log('Secondary Menu: fallback - offcanvas desktop (in template)', oc);
231                    return oc;
232                }
233                
234                // Infine quello non in template responsive (probabilmente nell'header)
235                if (!mobileWrapper && !desktopWrapper) {
236                    //console.log('Secondary Menu: fallback - offcanvas desktop (non 
236in template)', oc);
237                    return oc;
238                }
239            }
240            
241            // Se nessuno è visibile, usa il primo
242            //console.log('Secondary Menu: usando il primo offcanvas trovato', allOffcanvas[0]);
243            return allOffcanvas[0];
244        }
245
246        // Handler per bloccare il touch scroll fuori dall'offcanvas
247        let preventTouchMoveHandler = null;
248        
249        /**
250         * Blocca lo scroll della pagina senza causare salti visivi
251         */
252        function disablePageScroll() {
253            // Se c'è Lenis, fermalo
254            if (window.lenis) {
255                window.lenis.stop();
256            }
257            
258            // Blocca lo scroll su html e body
259            document.documentElement.style.overflow = 'hidden';
260            document.body.style.overflow = 'hidden';
261            
262            // Su mobile, blocca il touchmove sul document ma permetti lo scroll nel content dell'offcanvas
263            preventTouchMoveHandler = function(e) {
264                // Permetti lo scroll se l'evento proviene dal contenuto dell'offcanvas
265                const offcanvasContent = e.target.closest('.secondary-offcanvas__content');
266                if (offcanvasContent) {
267                    return;
268                }
269                // Blocca lo scroll fuori dall'offcanvas
270                e.preventDefault();
271            };
272            
273            document.addEventListener('touchmove', preventTouchMoveHandler, { passive: false });
274        }
275
276        /**
277         * Riabilita lo scroll della pagina
278         */
279        function enablePageScroll() {
280            // Rimuovi il blocco dello scroll
281            document.documentElement.style.removeProperty('overflow');
282            document.body.style.removeProperty('overflow');
283            
284            // Rimuovi il listener touchmove
285            if (preventTouchMoveHandler) {
286                document.removeEventListener('touchmove', preventTouchMoveHandler);
287                preventTouchMoveHandler = null;
288            }
289            
290            // Se c'è Lenis, riavvialo
291            if (window.lenis) {
292                window.lenis.start();
293            }
294        }
295
296        /**
297         * Apre un offcanvas specifico
298         */
299        function openOffcanvas(offcanvasId, label, buttonElement) {
300            // Usa findVisibleOffcanvas per trovare l'offcanvas visibile corrispondente al bottone cliccato
301            const offcanvas = findVisibleOffcanvas(offcanvasId, buttonElement);
302            
303            //console.log('Secondary Menu - openOffcanvas:', {
304                //offcanvasId: offcanvasId,
305                //label: label,
306                //buttonElement: buttonElement,
307                //foundOffcanvas: offcanvas,
308                //buttonInMobile: buttonElement ? buttonElement.closest('.responsive-template-mobile') : null,
309                //buttonInDesktop: buttonElement ? buttonElement.closest('.responsive-template-desktop') : null,
310                //allOffcanvases: document.querySelectorAll(`#${offcanvasId}`).length
311            //});
312            
313            if (!offcanvas) {
314                console.error('Secondary Menu: offcanvas non trovato per', offcanvasId);
315                return;
316            }
317            
318            if (currentOpenOffcanvas === offcanvas) {
319                //console.log('Secondary Menu: offcanvas già aperto');
320                return;
321            }
322
323            // Chiudi eventuale offcanvas aperto
324            if (currentOpenOffcanvas) {
325                closeOffcanvas(currentOpenOffcanvas);
326            }
327
328            const overlay = offcanvas.querySelector('.secondary-offcanvas__overlay');
329            const animation = offcanvas.querySelector('.secondary-offcanvas__animation');
330            const panel = offcanvas.querySelector('.secondary-offcanvas__panel');
331            const cutoutLabel = offcanvas.querySelector('.secondary-offcanvas__cutout-label');
332
333            // Aggiorna la label del cutout con il titolo della sezione
334            if (cutoutLabel && label) {
335                cutoutLabel.textContent = label;
336            }
337
338            // Blocca lo scroll della pagina PRIMA di mostrare l'offcanvas
339            disablePageScroll();
340
341            // Forza un reflow per assicurarsi che gli stili siano applicati
342            void document.body.offsetHeight;
343
344            // Mostra l'offcanvas
345            gsap.set(offcanvas, { display: 'block' });
346
347            // Timeline di apertura
348            const tl = gsap.timeline();
349
350            // 1. Fade in overlay
351            tl.fromTo(overlay,
352                { opacity: 0 },
353                { opacity: 1, duration: 0.3, ease: 'power2.out' },
354                0
355            );
356
357            // 2. Slide in panel da destra con bounce
358            tl.fromTo(animation,
359                { x: '100%' },
360                { x: '0%', duration: 0.6, ease: 'back.out(0.9)' },
361                0.1
362            );
363
364            // 3. Fade in contenuto
365            tl.fromTo(panel,
366                { opacity: 0 },
367                { opacity: 1, duration: 0.3, ease: 'power2.out' },
368                0.3
369            );
370            
371            // Dopo l'animazione completa, assicura che il contenuto possa scrollare su mobile
372            tl.call(() => {
373                const content = offcanvas.querySelector('.secondary-offcanvas__content');
374                if (content && window.innerWidth <= 1024) {
375                    // Forza le proprietà CSS necessarie per lo scroll touch
376                    content.style.touchAction = 'pan-y';
377                    content.style.webkitOverflowScrolling = 'touch';
378                    content.style.overflowY = 'auto';
379                    content.style.pointerEvents = 'auto';
380                    // Rimuovi qualsiasi trasformazione che GSAP potrebbe aver applicato al contenuto
381                    gsap.set(content, { clearProps: 'transform,will-change' });
382                    // Assicura che anche il panel non blocchi lo scroll
383                    panel.style.touchAction = 'pan-y';
384                }
385            }, null, '+=0.1');
386
387            currentOpenOffcanvas = offcanvas;
388
389            // Attacca gli event listener per la chiusura all'offcanvas appena aperto
390            // (importante per assicurarsi che siano attaccati all'offcanvas corretto)
391            attachCloseListeners(offcanvas);
392
393            // Se è l'offcanvas-2, inizializza il vertical carousel
394            if (offcanvasId === 'offcanvas-2') {
395                setTimeout(() => {
396                    initVerticalCarousel();
397                }, 100);
398            }
399
400            // Inizializza la scrollbar custom dopo l'apertura
401            // Stesso approccio di docente-offcanvas.js
402            setTimeout(() => {
403                if (!customScrollbars[offcanvasId]) {
404                    customScrollbars[offcanvasId] = initCustomScrollbar(offcanvas);
405                } else {
406                    // Aggiorna la scrollbar se già esiste
407                    customScrollbars[offcanvasId].update();
408                }
409            }, 100);
410        }
411
412        /**
413         * Variante di openOffcanvas che apre un elemento specifico (bypass findVisibleOffcanvas).
414         * Usata solo in casi mirati dove esistono ID duplicati e serve determinismo.
415         */
416        function openOffcanvasElement(offcanvas, label) {
417            if (!offcanvas) return;
418
419            if (currentOpenOffcanvas === offcanvas) {
420                return;
421            }
422
423            // Chiudi eventuale offcanvas aperto
424            if (currentOpenOffcanvas) {
425                closeOffcanvas(currentOpenOffcanvas);
426            }
427
428            const overlay = offcanvas.querySelector('.secondary-offcanvas__overlay');
429            const animation = offcanvas.querySelector('.secondary-offcanvas__animation');
430            const panel = offcanvas.querySelector('.secondary-offcanvas__panel');
431            const cutoutLabel = offcanvas.querySelector('.secondary-offcanvas__cutout-label');
432
433            if (cutoutLabel && label) {
434                cutoutLabel.textContent = label;
435            }
436
437            disablePageScroll();
438            void document.body.offsetHeight;
439            gsap.set(offcanvas, { display: 'block' });
440
441            const tl = gsap.timeline();
442            tl.fromTo(overlay,
443                { opacity: 0 },
444                { opacity: 1, duration: 0.3, ease: 'power2.out' },
445                0
446            );
447            tl.fromTo(animation,
448                { x: '100%' },
449                { x: '0%', duration: 0.6, ease: 'back.out(0.9)' },
450                0.1
451            );
452            tl.fromTo(panel,
453                { opacity: 0 },
454                { opacity: 1, duration: 0.3, ease: 'power2.out' },
455                0.3
456            );
457
458            tl.call(() => {
459                const content = offcanvas.querySelector('.secondary-offcanvas__content');
460                if (content && window.innerWidth <= 1024) {
461                    content.style.touchAction = 'pan-y';
462                    content.style.webkitOverflowScrolling = 'touch';
463                    content.style.overflowY = 'auto';
464                    content.style.pointerEvents = 'auto';
465                    gsap.set(content, { clearProps: 'transform,will-change' });
466                    panel.style.touchAction = 'pan-y';
467                }
468            }, null, '+=0.1');
469
470            currentOpenOffcanvas = offcanvas;
471            attachCloseListeners(offcanvas);
472
473            const offcanvasId = offcanvas.id;
474            if (offcanvasId === 'offcanvas-2') {
475                setTimeout(() => {
476                    initVerticalCarousel();
477                }, 100);
478            }
479
480            setTimeout(() => {
481                if (!customScrollbars[offcanvasId]) {
482                    customScrollbars[offcanvasId] = initCustomScrollbar(offcanvas);
483                } else {
484                    customScrollbars[offcanvasId].update();
485                }
486            }, 100);
487        }
488
489        /**
490         * Chiude un offcanvas
491         */
492        function closeOffcanvas(offcanvas) {
493            if (!offcanvas) return;
494
495            const overlay = offcanvas.querySelector('.secondary-offcanvas__overlay');
496            const animation = offcanvas.querySelector('.secondary-offcanvas__animation');
497            const panel = offcanvas.querySelector('.secondary-offcanvas__panel');
498
499            // Timeline di chiusura
500            const tl = gsap.timeline({
501                onComplete: () => {
502                    gsap.set(offcanvas, { display: 'none' });
503                    currentOpenOffcanvas = null;
504                    // Riabilita lo scroll della pagina SOLO dopo che l'offcanvas è completamente chiuso
505                    enablePageScroll();
506                }
507            });
508
509            // 1. Fade out contenuto
510            tl.to(panel,
511                { opacity: 0, duration: 0.2, ease: 'power2.in' },
512                0
513            );
514
515            // 2. Slide out panel a destra
516            tl.to(animation,
517                { x: '100%', duration: 0.4, ease: 'power3.in' },
518                0.1
519            );
520
521            // 3. Fade out overlay
522            tl.to(overlay,
523                { opacity: 0, duration: 0.3, ease: 'power2.in' },
524                0.2
525            );
526        }
527
528        /**
529         * Event listener per i bottoni
530         */
531        //console.log('Secondary Menu: attacco event listener ai bottoni', buttons.length);
532        buttons.forEach((button, index) => {
533            //console.log(`Secondary Menu: attacco listener al bottone ${index}:`, {
534                //button: button,
535                //inHeader: button.closest('header') !== null,
536                //inFooter: button.closest('footer') !== null,
537                //dataOffcanvas: button.getAttribute('data-offcanvas'),
538                //dataLabel: button.getAttribute('data-label')
539            //});
540            
541            button.addEventListener('click', function(e) {
542                //console.log('Secondary Menu: CLICK RILEVATO!', {
543                    //button: this,
544                    //index: index,
545                    //offcanvasId: this.getAttribute('data-offcanvas'),
546                    //label: this.getAttribute('data-label'),
547                    //event: e,
548                    //target: e.target,
549                    //currentTarget: e.currentTarget
550                //});
551                
552                e.preventDefault();
553                e.stopPropagation();
554                
555                const offcanvasId = this.getAttribute('data-offcanvas');
556                const label = this.getAttribute('data-label');
557                
558                //console.log('Secondary Menu: valori estratti', { offcanvasId, label });
559                
560                if (offcanvasId) {
561                    // Fix mirata: bottone banner "iscriviti_block_button"
562                    // - <= 1024px: apri l'offcanvas "mobile" (ultima istanza nel DOM)
563                    // - > 1024px: apri l'offcanvas "desktop" (prima istanza nel DOM)
564                    if (this.classList.contains('iscriviti_block_button')) {
565                        const allSameId = document.querySelectorAll(`#${offcanvasId}`);
566                        if (allSameId.length > 1) {
567                            const isMobileViewport = window.innerWidth <= 1024;
568                            let target = null;
569
570                            if (isMobileViewport) {
571                                // Preferisci l'istanza NON nel desktop header
572                                target = Array.from(allSameId).find(oc => oc.closest('.nd-desktop-header') === null) || null;
573                            } else {
574                                // Preferisci l'istanza nel desktop header
575                                target = Array.from(allSameId).find(oc => oc.closest('.nd-desktop-header') !== null) || null;
576                            }
577
578                            // Fallback: prima/ultima
579                            if (!target) {
580                                target = isMobileViewport ? allSameId[allSameId.length - 1] : allSameId[0];
581                            }
582
583                            openOffcanvasElement(target, label);
584                            return;
585                        }
586                    }
587
588                    //console.log('Secondary Menu: chiamata openOffcanvas', { offcanvasId, label, button: this });
589                    // Passa il bottone cliccato per trovare l'offcanvas nello stesso template
590                    openOffcanvas(offcanvasId, label, this);
591                } else {
592                    console.error('Secondary Menu: bottone senza data-offcanvas!', this);
593                }
594            }, { capture: false, passive: false });
595        });
596        
597        //console.log('Secondary Menu: event listener attaccati a', buttons.length, 'bottoni');
598
599        /**
600         * Attacca gli event listener per la chiusura a un offcanvas specifico
601         * Viene chiamato quando l'offcanvas viene aperto per assicurarsi che i listener
602         * siano attaccati all'offcanvas corretto
603         */
604        function attachCloseListeners(offcanvas) {
605            if (!offcanvas) return;
606            
607            const closeBtn = offcanvas.querySelector('.secondary-offcanvas__close');
608            const overlay = offcanvas.querySelector('.secondary-offcanvas__overlay');
609            
610            // Usa un flag per evitare listener duplicati
611            if (closeBtn && !closeBtn.dataset.closeListenerAttached) {
612                closeBtn.dataset.closeListenerAttached = 'true';
613                closeBtn.addEventListener('click', function(e) {
614                    //console.log('Secondary Menu: click su close button', offcanvas);
615                    e.preventDefault();
616                    e.stopPropagation();
617                    closeOffcanvas(offcanvas);
618                });
619            }
620
621            if (overlay && !overlay.dataset.overlayListenerAttached) {
622                overlay.dataset.overlayListenerAttached = 'true';
623                overlay.addEventListener('click', function(e) {
624                    //console.log('Secondary Menu: click su overlay', offcanvas);
625                    e.preventDefault();
626                    e.stopPropagation();
627                    closeOffcanvas(offcanvas);
628                });
629            }
630        }
631
632        /**
633         * Event listener per chiusura - attacca a tutti gli offcanvas trovati
634         * (anche quelli non visibili all'inizializzazione)
635         */
636        //console.log('Secondary Menu: attacco event listener per chiusura a', allOffcanvases.length, 'offcanvas');
637        allOffcanvases.forEach((offcanvas, index) => {
638            //console.log(`Secondary Menu: attacco listener chiusura all'offcanvas ${index}:`, {
639                //id: offcanvas.id,
640                //inHeader: offcanvas.closest('header') !== null,
641                //inFooter: offcanvas.closest('footer') !== null
642            //});
643            attachCloseListeners(offcanvas);
644        });
645
646        /**
647         * Chiusura con tasto ESC
648         */
649        document.addEventListener('keydown', function(e) {
650            if (e.key === 'Escape' && currentOpenOffcanvas) {
651                closeOffcanvas(currentOpenOffcanvas);
652            }
653        });
654
655        /**
656         * Vertical Carousel
657         * Inizializza il carousel solo sull'offcanvas-2 visibile (per template responsive)
658         */
659        function initVerticalCarousel() {
660            // Trova l'offcanvas-2 visibile (usa currentOpenOffcanvas se disponibile)
661            const offcanvas2 = currentOpenOffcanvas && currentOpenOffcanvas.id === 'offcanvas-2' 
662                ? currentOpenOffcanvas 
663                : findVisibleOffcanvas('offcanvas-2', null);
664            if (!offcanvas2) {
665                // Se non è ancora disponibile, riprova quando viene aperto
666                return;
667            }
668            
669            // Cerca il carousel dentro l'offcanvas visibile
670            const verticalCarousel = offcanvas2.querySelector('.vertical-carousel');
671            if (!verticalCarousel) {
672                return;
673            }
674            
675            // Se il carousel è già stato inizializzato, non reinizializzarlo
676            if (verticalCarousel.dataset.initialized === 'true') {
677                return;
678            }
679            
680            const slides = verticalCarousel.querySelectorAll('.vertical-carousel__slide');
681            const dots = verticalCarousel.querySelectorAll('.vertical-carousel__dot');
682            
683            if (slides.length === 0 || dots.length === 0) {
684                return;
685            }
686            
687            let currentSlide = 0;
688            let isAnimating = false;
689            
690            // Marca come inizializzato
691            verticalCarousel.dataset.initialized = 'true';
692
693            /**
694             * Cambia slide con animazione verticale
695             */
696            function goToSlide(index) {
697                if (isAnimating || index === currentSlide) return;
698                if (index < 0 || index >= slides.length) return;
699
700                isAnimating = true;
701
702                const oldSlide = slides[currentSlide];
703                const newSlide = slides[index];
704                const direction = index > currentSlide ? 1 : -1;
705
706                // Timeline GSAP per animazione verticale
707                const tl = gsap.timeline({
708                    onComplete: () => {
709                        isAnimating = false;
710                        currentSlide = index;
711                    }
712                });
713
714                // Slide uscente: slide verso l'alto o il basso
715                tl.to(oldSlide, {
716                    y: direction * -50,
717                    opacity: 0,
718                    duration: 0.4,
719                    ease: 'power2.in'
720                }, 0);
721
722                // Prepara la nuova slide
723                gsap.set(newSlide, {
724                    y: direction * 50,
725                    opacity: 0,
726                    display: 'flex',
727                    visibility: 'visible',
728                    position: 'absolute'
729                });
730
731                // Slide entrante: slide dal basso o dall'alto
732                tl.to(newSlide, {
733                    y: 0,
734                    opacity: 1,
735                    duration: 0.5,
736                    ease: 'power2.out',
737                    onComplete: () => {
738                        // Rimuovi la classe active dalla slide vecchia
739                        oldSlide.classList.remove('active');
740                        gsap.set(oldSlide, { display: 'none', position: 'absolute', y: 0 });
741                        
742                        // Aggiungi la classe active alla nuova slide
743                        newSlide.classList.add('active');
744                        gsap.set(newSlide, { position: 'relative', clearProps: 'y' });
745                        
746                        // Aggiorna la scrollbar quando cambia la slide
747                        const offcanvasId = currentOpenOffcanvas ? currentOpenOffcanvas.id : null;
748                        if (offcanvasId && customScrollbars[offcanvasId]) {
749                            customScrollbars[offcanvasId].update();
750                        }
751                    }
752                }, 0.2);
753
754                // Aggiorna i dots
755                dots.forEach((dot, i) => {
756                    if (i === index) {
757                        dot.classList.add('active');
758                    } else {
759                        dot.classList.remove('active');
760                    }
761                });
762            }
763
764            /**
765             * Event listener per i dots
766             */
767            dots.forEach((dot, index) => {
768                dot.addEventListener('click', function() {
769                    goToSlide(index);
770                });
771            });
772
773            /**
774             * Navigazione con frecce tastiera (opzionale)
775             */
776            document.addEventListener('keydown', function(e) {
777                if (!currentOpenOffcanvas) return;
778                if (currentOpenOffcanvas.id !== 'offcanvas-2') return;
779
780                if (e.key === 'ArrowDown') {
781                    e.preventDefault();
782                    goToSlide(currentSlide + 1);
783                } else if (e.key === 'ArrowUp') {
784                    e.preventDefault();
785                    goToSlide(currentSlide - 1);
786                }
787            });
788
789            /**
790             * Navigazione con scroll del mouse:
791             * - Arrivo a fondo/inizio contenuto: stop (blocco lo scroll).
792             * - Se scrollo di nuovo nella stessa direzione: cambio slide.
793             */
794            const contentEl = offcanvas2.querySelector('.secondary-offcanvas__content');
795            let atBottomStopped = false;
796            let atTopStopped = false;
797            if (contentEl) {
798                contentEl.addEventListener('wheel', function(e) {
799                    if (!e.target.closest('.vertical-carousel')) {
800                        return;
801                    }
802
803                    const scrollHeight = contentEl.scrollHeight;
804                    const clientHeight = contentEl.clientHeight;
805
806                    if (scrollHeight <= clientHeight) {
807                        atBottomStopped = false;
808                        atTopStopped = false;
809                        if (e.deltaY > 0 && currentSlide < slides.length - 1) {
810                            e.preventDefault();
811                            goToSlide(currentSlide + 1);
812                        } else if (e.deltaY < 0 && currentSlide > 0) {
813                            e.preventDefault();
814                            goToSlide(currentSlide - 1);
815                        }
816                        return;
817                    }
818
819                    const scrollTop = contentEl.scrollTop;
820                    const threshold = 2;
821                    const isAtTop = scrollTop <= threshold;
822                    const isAtBottom = scrollTop + clientHeight >= scrollHeight - threshold;
823
824                    if (!isAtBottom) atBottomStopped = false;
825                    if (!isAtTop) atTopStopped = false;
826
827                    if (e.deltaY > 0 && isAtBottom && currentSlide < slides.length - 1) {
828                        e.preventDefault();
829                        if (atBottomStopped) {
830                            goToSlide(currentSlide + 1);
831                            atBottomStopped = false;
832                            requestAnimationFrame(() => { contentEl.scrollTop = 0; });
833                        } else {
834                            atBottomStopped = true;
835                        }
836                    } else if (e.deltaY < 0 && isAtTop && currentSlide > 0) {
837                        e.preventDefault();
838                        if (atTopStopped) {
839                            goToSlide(currentSlide - 1);
840                            atTopStopped = false;
841                            requestAnimationFrame(() => { contentEl.scrollTop = contentEl.scrollHeight; });
842                        } else {
843                            atTopStopped = true;
844                        }
845                    }
846                }, { passive: false });
847            }
848        }
849        
850        // Inizializza il carousel se l'offcanvas-2 è già disponibile
851        initVerticalCarousel();
852
853        /**
854         * Gestisce lo scroll wheel sulle liste insegnamenti
855         * Permette lo scroll interno della lista senza propagare all'offcanvas
856         
857        function initInsegnamentiListScroll() {
858            // Usa event delegation sul document per catturare anche liste aggiunte dinamicamente
859            document.addEventListener('wheel', function(e) {
860                const insegnamentiList = e.target.closest('.insegnamenti-list ul');
861                if (!insegnamentiList) return;
862                
863                const scrollHeight = insegnamentiList.scrollHeight;
864                const clientHeight = insegnamentiList.clientHeight;
865                
866                // Se la lista non è scrollabile, lascia passare l'evento
867                if (scrollHeight <= clientHeight) return;
868                
869                const scrollTop = insegnamentiList.scrollTop;
870                const isAtTop = scrollTop === 0 && e.deltaY < 0;
871                const isAtBottom = scrollTop + clientHeight >= scrollHeight - 1 && e.deltaY > 0;
872                
873                // Se non siamo ai limiti, gestisci lo scroll internamente
874                if (!isAtTop && !isAtBottom) {
875                    e.preventDefault();
876                    e.stopPropagation();
877                    insegnamentiList.scrollTop += e.deltaY;
878                }
879            }, { passive: false, capture: true });
880        }
881        
882        // Inizializza lo scroll delle liste insegnamenti
883        initInsegnamentiListScroll();
884        */
885        /**
886         * Custom Scrollbar
887         */
888        function initCustomScrollbar(rootElement, options = {}) {
889            if (!rootElement) return null;
890
891            const contentSelector = options.contentSelector || '.secondary-offcanvas__content';
892            const scrollbarSelector = options.scrollbarSelector || '.custom-scrollbar';
893            const content = options.content || rootElement.querySelector(contentSelector);
894            const scrollbar = options.scrollbar || rootElement.querySelector(scrollbarSelector);
895            const track = scrollbar ? scrollbar.querySelector('.custom-scrollbar__track') : null;
896            const thumb = track ? track.querySelector('.custom-scrollbar__thumb') : null;
897
898            if (!content || !scrollbar || !track || !thumb) return null;
899
900            let isDragging = false;
901            let startY = 0;
902            let startScrollTop = 0;
903
904            // Calcola e aggiorna l'altezza del thumb
905            function updateScrollbar() {
906                const scrollHeight = content.scrollHeight;
907                const clientHeight = content.clientHeight;
908                const trackHeight = track.clientHeight;
909
910                // Mostra scrollbar solo se c'è contenuto scrollabile
911                if (scrollHeight <= clientHeight) {
912                    scrollbar.classList.remove('active');
913                    return;
914                }
915
916                scrollbar.classList.add('active');
917
918                // Calcola l'altezza del thumb proporzionale al contenuto
919                const thumbHeight = Math.max((clientHeight / scrollHeight) * trackHeight, 30);
920                thumb.style.height = thumbHeight + 'px';
921
922                // Posiziona il thumb in base allo scroll corrente
923                updateThumbPosition();
924            }
925
926            // Aggiorna la posizione del thumb in base allo scroll
927            function updateThumbPosition() {
928                const scrollHeight = content.scrollHeight;
929                const clientHeight = content.clientHeight;
930                const scrollTop = content.scrollTop;
931                const trackHeight = track.clientHeight;
932                const thumbHeight = thumb.clientHeight;
933
934                const scrollPercentage = scrollTop / (scrollHeight - clientHeight);
935                const thumbTop = scrollPercentage * (trackHeight - thumbHeight);
936
937                thumb.style.transform = `translateY(${thumbTop}px)`;
938            }
939
940            // Eventi drag - SOLO MOUSE (come in docente-offcanvas.js)
941            // Su mobile, lo scroll funziona nativamente senza listener touch che interferiscono
942            // Su mobile, lo scroll funziona nativamente senza listener touch che interferiscono
943            thumb.addEventListener('mousedown', function(e) {
944                isDragging = true;
945                startY = e.clientY;
946                startScrollTop = content.scrollTop;
947                document.body.style.userSelect = 'none';
948            });
949
950            document.addEventListener('mousemove', function(e) {
951                if (!isDragging) return;
952                e.preventDefault();
953
954                const deltaY = e.clientY - startY;
955                const trackHeight = track.clientHeight;
956                const thumbHeight = thumb.clientHeight;
957                const scrollHeight = content.scrollHeight;
958                const clientHeight = content.clientHeight;
959
960                const maxThumbTop = trackHeight - thumbHeight;
961                const thumbTop = Math.max(0, Math.min(maxThumbTop, deltaY));
962                const scrollPercentage = thumbTop / maxThumbTop;
963
964                content.scrollTop = startScrollTop + (scrollPercentage * (scrollHeight - clientHeight) - startScrollTop);
965            });
966
967            document.addEventListener('mouseup', function() {
968                if (isDragging) {
969                    isDragging = false;
970                    document.body.style.userSelect = '';
971                }
972            });
973
974            // Click sulla track per saltare a quella posizione
975            track.addEventListener('click', function(e) {
976                if (e.target === thumb) return;
977
978                const trackRect = track.getBoundingClientRect();
979                const clickY = e.clientY - trackRect.top;
980                const trackHeight = track.clientHeight;
981                const thumbHeight = thumb.clientHeight;
982
983                const scrollPercentage = (clickY - thumbHeight / 2) / (trackHeight - thumbHeight);
984                const scrollHeight = content.scrollHeight;
985                const clientHeight = content.clientHeight;
986
987                content.scrollTop = scrollPercentage * (scrollHeight - clientHeight);
988            });
989
990            // Aggiorna la scrollbar quando il content viene scrollato
991            content.addEventListener('scroll', updateThumbPosition);
992
993            // Wheel event - identico a docente-offcanvas.js
994            content.addEventListener('wheel', function(e) {
995                const scrollHeight = content.scrollHeight;
996                const clientHeight = content.clientHeight;
997                
998                if (scrollHeight > clientHeight) {
999                    e.preventDefault();
1000                    content.scrollTop += e.deltaY;
1001                }
1002            }, { passive: false });
1003
1004            // Touch scroll per dispositivi mobili
1005            let touchStartY = 0;
1006            let touchStartScrollTop = 0;
1007
1008            content.addEventListener('touchstart', function(e) {
1009                touchStartY = e.touches[0].clientY;
1010                touchStartScrollTop = content.scrollTop;
1011            }, { passive: true });
1012
1013            content.addEventListener('touchmove', function(e) {
1014                const touchY = e.touches[0].clientY;
1015                const deltaY = touchStartY - touchY;
1016                content.scrollTop = touchStartScrollTop + deltaY;
1017                
1018                // Aggiorna la posizione della thumb
1019                updateThumbPosition();
1020            }, { passive: true });
1021
1022            // Inizializza e aggiorna al resize
1023            updateScrollbar();
1024            window.addEventListener('resize', updateScrollbar);
1025
1026            return {
1027                update: updateScrollbar
1028            };
1029        }
1030
1031    });
1032
1033})();
1034
1035/**
1036 * Offcanvas Sublevel Management
1037 * Gestisce i sottolivelli all'interno degli offcanvas
1038 */
1039(function() {
1040    'use strict';
1041    document.addEventListener('DOMContentLoaded', function() {
1042        // Seleziona tutti i trigger per i sottolivelli
1043        const sublevelTriggers = document.querySelectorAll('.offcanvas-sublevel-trigger');
1044
1045        // Seleziona tutti i bottoni "Indietro"
1046        const backButtons = document.querySelectorAll('.offcanvas-back-button');
1047
1048        if (sublevelTriggers.length === 0) {
1049            return;
1050        }
1051
1052        const layeredContents = new Set();
1053
1054        // Trova TUTTI i sublevel nel documento (potrebbero esserci duplicati desktop/mobile)
1055        // e inizializzali tutti
1056        sublevelTriggers.forEach(trigger => {
1057            const sublevelId = trigger.getAttribute('data-sublevel');
1058            if (!sublevelId) {
1059                return;
1060            }
1061
1062            // Cerca il sublevel nel contesto dell'offcanvas parent del trigger
1063            const parentOffcanvas = trigger.closest('.secondary-offcanvas');
1064            if (parentOffcanvas) {
1065                const sublevelElement = parentOffcanvas.querySelector('#' + sublevelId);
1066                if (sublevelElement) {
1067                    const contentContainer = sublevelElement.closest('.secondary-offcanvas__content');
1068                    if (contentContainer) {
1069                        layeredContents.add(contentContainer);
1070                    }
1071                }
1072            }
1073            
1074            // Fallback: cerca anche globalmente per retrocompatibilità
1075            const sublevelElement = document.getElementById(sublevelId);
1076            if (sublevelElement) {
1077                const contentContainer = sublevelElement.closest('.secondary-offcanvas__content');
1078                if (contentContainer) {
1079                    layeredContents.add(contentContainer);
1080                }
1081            }
1082        });
1083
1084        // Inizializza anche tutti i content container che contengono sublevel
1085        // (per gestire i casi di ID duplicati)
1086        document.querySelectorAll('.secondary-offcanvas__content').forEach(contentContainer => {
1087            if (contentContainer.querySelector('.offcanvas-sublevel')) {
1088                layeredContents.add(contentContainer);
1089            }
1090        });
1091
1092        layeredContents.forEach(contentContainer => {
1093            const mainContent = contentContainer.querySelector('.offcanvas-main-content');
1094            const sublevels = contentContainer.querySelectorAll('.offcanvas-sublevel');
1095
1096            gsap.set(contentContainer, {
1097                position: 'relative'
1098            });
1099
1100            if (mainContent) {
1101                gsap.set(mainContent, {
1102                    position: 'relative',
1103                    zIndex: 1,
1104                    x: '0%',
1105                    autoAlpha: 1,
1106                    pointerEvents: 'auto'
1107                });
1108            }
1109
1110            sublevels.forEach(sublevel => {
1111                gsap.set(sublevel, {
1112                    position: 'absolute',
1113                    top: 0,
1114                    left: 0,
1115                    width: '100%',
1116                    height: '100%',
1117                    display: 'flex',
1118                    flexDirection: 'column',
1119                    x: '18%',
1120                    autoAlpha: 0,
1121                    pointerEvents: 'none',
1122                    zIndex: 2
1123                });
1124            });
1125        });
1126
1127        function showSublevel(sublevelId, newLabel, triggerElement) {
1128            //console.log('=== showSublevel chiamato ===');
1129            //console.log('sublevelId:', sublevelId);
1130            
1131            // Cerca il sublevel nel contesto dell'offcanvas attualmente aperto
1132            // invece di usare getElementById che trova sempre il primo nel DOM
1133            let sublevel = null;
1134            
1135            if (triggerElement) {
1136                // Trova l'offcanvas parent del trigger
1137                const parentOffcanvas = triggerElement.closest('.secondary-offcanvas');
1138                if (parentOffcanvas) {
1139                    // Cerca il sublevel SOLO dentro questo offcanvas
1140                    sublevel = parentOffcanvas.querySelector('#' + sublevelId);
1141                    //console.log('Sublevel trovato nel contesto del trigger:', sublevel);
1142                }
1143            }
1144            
1145            // Fallback: se non trovato nel contesto, usa getElementById
1146            if (!sublevel) {
1147                sublevel = document.getElementById(sublevelId);
1148                //console.log('Sublevel trovato con getElementById (fallback):', sublevel);
1149            }
1150            
1151            if (!sublevel) {
1152                console.error('Sublevel non trovato!');
1153                return;
1154            }
1155
1156            const parentOffcanvas = sublevel.closest('.secondary-offcanvas');
1157            const mainContent = parentOffcanvas ? parentOffcanvas.querySelector('.offcanvas-main-content') : null;
1158            const cutoutLabel = parentOffcanvas ? parentOffcanvas.querySelector('.secondary-offcanvas__cutout-label') : null;
1159            const headerBackButton = parentOffcanvas ? parentOffcanvas.querySelector('.secondary-offcanvas__header .offcanvas-back-button') : null;
1160            const contentContainer = sublevel.closest('.secondary-offcanvas__content');
1161
1162            //console.log('parentOffcanvas:', parentOffcanvas);
1163            //console.log('mainContent:', mainContent);
1164            //console.log('contentContainer:', contentContainer);
1165
1166            if (!parentOffcanvas || !mainContent) {
1167                console.error('parentOffcanvas o mainContent non trovato!');
1168                return;
1169            }
1170            
1171            // Rimuovi display:none se presente
1172            sublevel.style.display = 'flex';
1173
1174            parentOffcanvas.dataset.activeSublevel = sublevelId;
1175            
1176            // Su tablet/mobile, il contenitore ha overflow:auto che clippa il sublevel posizionato assolutamente
1177            // Impostiamo overflow:visible quando il sublevel è attivo
1178            if (contentContainer) {
1179                contentContainer.style.overflow = 'visible';
1180            }
1181            if (headerBackButton) {
1182                headerBackButton.dataset.target = sublevelId;
1183            }
1184
1185            if (cutoutLabel && !cutoutLabel.dataset.originalLabel) {
1186                cutoutLabel.dataset.originalLabel = cutoutLabel.textContent;
1187            }
1188
1189            const tl = gsap.timeline();
1190
1191            tl.to(mainContent, {
1192                x: '-22%',
1193                autoAlpha: 0,
1194                duration: 0.45,
1195                ease: 'power3.inOut',
1196                onStart: () => {
1197                    gsap.set(mainContent, { pointerEvents: 'none' });
1198                }
1199            });
1200
1201            if (cutoutLabel && newLabel) {
1202                tl.to(cutoutLabel, {
1203                    opacity: 0,
1204                    duration: 0.2,
1205                    onComplete: () => {
1206                        cutoutLabel.textContent = newLabel;
1207                    }
1208                }, 0);
1209                tl.to(cutoutLabel, {
1210                    opacity: 1,
1211                    duration: 0.2
1212                }, 0.3);
1213            }
1214
1215            tl.to(sublevel, {
1216                x: '0%',
1217                autoAlpha: 1,
1218                duration: 0.55,
1219                ease: 'back.out(1.2)',
1220                onStart: () => {
1221                    gsap.set(sublevel, { pointerEvents: 'auto' });
1222                }
1223            }, '-=0.25');
1224
1225            if (headerBackButton) {
1226                tl.to(headerBackButton, {
1227                    autoAlpha: 1,
1228                    duration: 0.3,
1229                    ease: 'power2.out',
1230                    onStart: () => {
1231                        gsap.set(headerBackButton, { pointerEvents: 'auto' });
1232                    }
1233                }, '-=0.2');
1234            }
1235        }
1236
1237        function hideSublevel(backButton) {
1238            const parentOffcanvas = backButton.closest('.secondary-offcanvas');
1239            const headerBackButton = parentOffcanvas ? parentOffcanvas.querySelector('.secondary-offcanvas__header .offcanvas-back-button') : null;
1240
1241            let sublevel = backButton.closest('.offcanvas-sublevel');
1242            if (!sublevel && parentOffcanvas) {
1243                const targetId = backButton.dataset.target || parentOffcanvas.dataset.activeSublevel;
1244                if (targetId) {
1245                    sublevel = parentOffcanvas.querySelector(`#${targetId}`);
1246                }
1247            }
1248
1249            if (!parentOffcanvas || !sublevel) return;
1250
1251            const mainContent = parentOffcanvas.querySelector('.offcanvas-main-content');
1252            const cutoutLabel = parentOffcanvas.querySelector('.secondary-offcanvas__cutout-label');
1253            const contentContainer = sublevel.closest('.secondary-offcanvas__content');
1254
1255            if (!mainContent) return;
1256
1257            const tl = gsap.timeline({
1258                onComplete: () => {
1259                    // Ripristina l'overflow del contenitore quando il sublevel è nascosto
1260                    if (contentContainer) {
1261                        contentContainer.style.overflow = '';
1262                    }
1263                }
1264            });
1265
1266            if (headerBackButton) {
1267                tl.to(headerBackButton, {
1268                    autoAlpha: 0,
1269                    duration: 0.2,
1270                    ease: 'power2.in',
1271                    onComplete: () => {
1272                        gsap.set(headerBackButton, { pointerEvents: 'none' });
1273                        delete headerBackButton.dataset.target;
1274                    }
1275                }, 0);
1276            }
1277
1278            tl.to(sublevel, {
1279                x: '18%',
1280                autoAlpha: 0,
1281                duration: 0.4,
1282                ease: 'back.in(1.05)',
1283                onComplete: () => {
1284                    gsap.set(sublevel, {
1285                        pointerEvents: 'none',
1286                        x: '18%'
1287                    });
1288                }
1289            });
1290
1291            if (cutoutLabel && cutoutLabel.dataset.originalLabel) {
1292                tl.to(cutoutLabel, {
1293                    opacity: 0,
1294                    duration: 0.2,
1295                    onComplete: () => {
1296                        cutoutLabel.textContent = cutoutLabel.dataset.originalLabel;
1297                    }
1298                }, 0);
1299                tl.to(cutoutLabel, {
1300                    opacity: 1,
1301                    duration: 0.2
1302                }, 0.3);
1303            }
1304
1305            tl.to(mainContent, {
1306                x: '0%',
1307                autoAlpha: 1,
1308                duration: 0.55,
1309                ease: 'back.out(1.2)',
1310                onStart: () => {
1311                    gsap.set(mainContent, {
1312                        pointerEvents: 'auto'
1313                    });
1314                }
1315            }, '-=0.25');
1316
1317            if (cutoutLabel && cutoutLabel.dataset.originalLabel === cutoutLabel.textContent) {
1318                delete cutoutLabel.dataset.originalLabel;
1319            }
1320
1321            delete parentOffcanvas.dataset.activeSublevel;
1322        }
1323
1324        sublevelTriggers.forEach(trigger => {
1325            trigger.addEventListener('click', function() {
1326                const sublevelId = this.getAttribute('data-sublevel');
1327                const newLabel = this.getAttribute('data-label');
1328                if (sublevelId) {
1329                    // Passa 'this' come triggerElement per cercare nel contesto corretto
1330                    showSublevel(sublevelId, newLabel, this);
1331                }
1332            });
1333        });
1334
1335        backButtons.forEach(button => {
1336            button.addEventListener('click', function() {
1337                hideSublevel(this);
1338            });
1339        });
1340    });
1341
1342})();
1343

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.