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.