1/** 2 * Exchange Program Offcanvas 3 */ 4 5(function() { 6 'use strict'; 7 8 // Variabili globali per l'offcanvas 9 let offcanvas = null; 10 let overlay = null; 11 let animation = null; 12 let panel = null; 13 let closeBtn = null; 14 let cutoutLabel = null; 15 let loadingSpinner = null; 16 let nomeElement = null; 17 let dipartimentiElement = null; 18 let citazioneWrapper = null; 19 let citazioneElement = null; 20 let biografiaWrapper = null; 21 let biografiaElement = null; 22 let imageWrapper = null; 23 let imageElement = null; 24 let linkWrapper = null; 25 let linkElement = null; 26 let customScrollbar = null; 27 28 /** 29 * Funzione per ottenere l'offcanvas corretto in base alla viewport 30 * Breakpoint: 1024px (come negli altri componenti) 31 */ 32 function getOffcanvas() { 33 const isMobile = window.innerWidth <= 1024; 34 35 if (isMobile) { 36 let offcanvas = document.getElementById('offcanvas-exchange-program-mobile'); 37 if (offcanvas) { 38 return offcanvas; 39 } 40 offcanvas = document.getElementById('offcanvas-exchange-program-desktop'); 41 if (offcanvas) { 42 return offcanvas; 43 } 44 } else { 45 let offcanvas = document.getElementById('offcanvas-exchange-program-desktop'); 46 if (offcanvas) { 47 return offcanvas; 48 } 49 offcanvas = document.getElementById('offcanvas-exchange-program-mobile'); 50 if (offcanvas) { 51 return offcanvas; 52 } 53 } 54 55 // Fallback finale: legacy ID 56 return document.getElementById('offcanvas-exchange-program'); 57 } 58 59 // Alias per compatibilità 60 function findVisibleOffcanvas() { 61 return getOffcanvas(); 62 } 63 64 document.addEventListener('DOMContentLoaded', function() { 65 if (typeof gsap === 'undefined') { 66 console.error('GSAP non è caricato!'); 67 return; 68 } 69 70 //console.log('=== Exchange Program Offcanvas Script Loaded ==='); 71 72 // Debug: verifica quali offcanvas sono presenti nel DOM 73 const mobileOffcanvas = document.getElementById('offcanvas-exchange-program-mobile'); 74 const desktopOffcanvas = document.getElementById('offcanvas-exchange-program-desktop'); 75 const legacyOffcanvas = document.getElementById('offcanvas-exchange-program'); 76 77 //console.log('Offcanvas presenti nel DOM:', { 78 //mobile: !!mobileOffcanvas, 79 //desktop: !!desktopOffcanvas, 80 //legacy: !!legacyOffcanvas 81 //}); 82 83 // Verifica che l'offcanvas esista nel DOM (usa la funzione helper per trovare quello visibile) 84 const offcanvasEl = getOffcanvas(); 85 86 //console.log('Offcanvas element selezionato:', offcanvasEl ? 'trovato (' + offcanvasEl.id + ')' : 'NON trovato'); 87 if (!offcanvasEl) { 88 console.warn('Exchange Program Offcanvas: offcanvas non trovato, riprovo dopo 100ms...'); 89 setTimeout(function() { 90 const retryOffcanvas = getOffcanvas(); 91 if (retryOffcanvas) { 92 //console.log('Offcanvas trovato dopo delay:', retryOffcanvas.id); 93 initExchangeProgramOffcanvas(); 94 } else { 95 console.error('Exchange Program Offcanvas: offcanvas ancora non trovato dopo delay'); 96 } 97 }, 100); 98 return; 99 } 100 101 initExchangeProgramOffcanvas(); 102 }); 103 104 // Handler per bloccare il touch scroll fuori dall'offcanvas 105 let preventTouchMoveHandler = null; 106 107 /** 108 * Blocca lo scroll della pagina senza causare salti visivi 109 */ 110 function disablePageScroll() { 111 // Se c'è Lenis, fermalo 112 if (window.lenis) { 113 window.lenis.stop(); 114 } 115 116 // Blocca lo scroll su html e body 117 document.documentElement.style.overflow = 'hidden'; 118 document.body.style.overflow = 'hidden'; 119 120 // Su mobile, blocca il touchmove sul document ma permetti lo scroll nel content dell'offcanvas 121 preventTouchMoveHandler = function(e) { 122 // Permetti lo scroll se l'evento proviene dal contenuto dell'offcanvas 123 const offcanvasContent = e.target.closest('.secondary-offcanvas__content'); 124 if (offcanvasContent) { 125 // Permetti lo scroll nel contenuto dell'offcanvas 126 return; 127 } 128 // Blocca lo scroll fuori dall'offcanvas 129 e.preventDefault(); 130 }; 131 132 document.addEventListener('touchmove', preventTouchMoveHandler, { passive: false }); 133 } 134 135 /** 136 * Riabilita lo scroll della pagina 137 */ 138 function enablePageScroll() { 139 // Rimuovi il blocco dello scroll 140 document.documentElement.style.removeProperty('overflow'); 141 document.body.style.removeProperty('overflow'); 142 143 // Rimuovi il listener touchmove 144 if (preventTouchMoveHandler) { 145 document.removeEventListener('touchmove', preventTouchMoveHandler); 146 preventTouchMoveHandler = null; 147 } 148 149 // Se c'è Lenis, riavvialo 150 if (window.lenis) { 151 window.lenis.start(); 152 } 153 } 154 155 // Funzioni helper (devono essere accessibili globalmente) 156 function clearOffcanvasContent() { 157 if (!nomeElement) return; 158 nomeElement.textContent = ''; 159 if (dipartimentiElement) { 160 dipartimentiElement.textContent = ''; 161 } 162 if (cutoutLabel) { 163 cutoutLabel.textContent = 'EXCHANGE PROGRAM'; 164 } 165 if (citazioneElement) { 166 citazioneElement.innerHTML = ''; 167 } 168 if (biografiaElement) { 169 biografiaElement.innerHTML = ''; 170 } 171 if (citazioneWrapper) { 172 citazioneWrapper.style.display = 'none'; 173 } 174 if (biografiaWrapper) { 175 biografiaWrapper.style.display = 'none'; 176 } 177 if (imageWrapper) { 178 imageWrapper.style.display = 'none'; 179 } 180 if (imageElement) { 181 imageElement.setAttribute('href', ''); 182 imageElement.removeAttribute('aria-label'); 183 imageElement.removeAttribute('alt'); 184 } 185 if (linkWrapper) { 186 linkWrapper.style.display = 'none'; 187 } 188 if (linkElement) { 189 linkElement.setAttribute('href', '#'); 190 } 191 } 192 193 function populateOffcanvasContent(data) { 194 if (!nomeElement) return; 195 nomeElement.textContent = data.nome_completo; 196 197 if (cutoutLabel && data.location && data.location.trim() !== '') { 198 cutoutLabel.textContent = data.location; 199 } 200 201 if (dipartimentiElement && data.dipartimenti && data.dipartimenti.trim() !== '') { 202 dipartimentiElement.textContent = data.dipartimenti; 203 } 204 205 if (data.citazione && citazioneElement) { 206 citazioneElement.innerHTML = data.citazione; 207 if (citazioneWrapper) citazioneWrapper.style.display = 'block'; 208 } else if (citazioneWrapper) { 209 citazioneWrapper.style.display = 'none'; 210 } 211 212 if (data.biografia && biografiaElement) { 213 biografiaElement.innerHTML = data.biografia; 214 if (biografiaWrapper) biografiaWrapper.style.display = 'block'; 215 } else if (biografiaWrapper) { 216 biografiaWrapper.style.display = 'none'; 217 } 218 219 if (data.immagine && imageElement && imageWrapper) { 220 // Imposta l'URL dell'immagine 221 imageElement.setAttribute('href', data.immagine); 222 223 if (data.immagine_alt) { 224 imageElement.setAttribute('aria-label', data.immagine_alt); 225 imageElement.setAttribute('alt', data.immagine_alt); 226 } else { 227 imageElement.removeAttribute('aria-label'); 228 imageElement.removeAttribute('alt'); 229 } 230 231 // Mostra il wrapper 232 // NOTA: L'attributo mask è già presente nel markup HTML, non lo modifichiamo 233 // (come nel docente-offcanvas.js) per evitare problemi di rendering 234 imageWrapper.style.display = 'block'; 235 } else if (imageWrapper) { 236 imageWrapper.style.display = 'none'; 237 //console.log('Exchange Program Offcanvas: Immagine non trovata o non disponibile', { 238 //hasImage: !!data.immagine, 239 //hasImageElement: !!imageElement, 240 //hasImageWrapper: !!imageWrapper, 241 //imageWrapperClasses: imageWrapper ? imageWrapper.className : null 242 //}); 243 } 244 245 const canShowLink = data && data.show_link && data.permalink; 246 if (canShowLink && linkWrapper && linkElement) { 247 linkElement.setAttribute('href', data.permalink); 248 linkWrapper.style.display = 'block'; 249 } else if (linkWrapper) { 250 linkWrapper.style.display = 'none'; 251 } 252 } 253 254 function loadExchangeProgramData(programId) { 255 if (!offcanvas) return; 256 257 if (loadingSpinner) { 258 loadingSpinner.style.display = 'block'; 259 } 260 261 const formData = new FormData(); 262 formData.append('action', 'get_exchange_program_data'); 263 formData.append('program_id', programId); 264 265 if (typeof exchangeOffcanvas !== 'undefined') { 266 formData.append('nonce', exchangeOffcanvas.nonce); 267 } else { 268 console.error('exchangeOffcanvas non è definito'); 269 if (loadingSpinner) loadingSpinner.style.display = 'none'; 270 return; 271 } 272 273 fetch(exchangeOffcanvas.ajaxurl, { 274 method: 'POST', 275 credentials: 'same-origin', 276 body: formData 277 }) 278 .then(response => response.json()) 279 .then(data => { 280 if (loadingSpinner) { 281 loadingSpinner.style.display = 'none'; 282 } 283 284 if (data.success) { 285 populateOffcanvasContent(data.data); 286 287 setTimeout(() => { 288 if (!customScrollbar) { 289 customScrollbar = initCustomScrollbar(offcanvas); 290 } else {
291 customScrollbar.update(); 292 } 293 }, 100); 294 } else { 295 console.error('Errore nel caricamento dei dati Exchange Program'); 296 } 297 }) 298 .catch(error => { 299 console.error('Errore AJAX:', error); 300 if (loadingSpinner) { 301 loadingSpinner.style.display = 'none'; 302 } 303 }); 304 } 305 306 function openOffcanvas(programId) { 307 //console.log('=== openOffcanvas chiamato ==='); 308 //console.log('Exchange Program ID:', programId); 309 310 // Aggiorna l'offcanvas prima di aprire (nel caso sia cambiato) 311 const currentOffcanvas = getOffcanvas(); 312 //console.log('Current offcanvas:', currentOffcanvas ? currentOffcanvas.id : 'NON TROVATO'); 313 //console.log('Previous offcanvas:', offcanvas ? offcanvas.id : 'NON TROVATO'); 314 315 if (!currentOffcanvas) { 316 console.error('Exchange Program Offcanvas: Nessun offcanvas trovato'); 317 return; 318 } 319 320 // Se l'offcanvas è cambiato, reinizializza 321 if (currentOffcanvas !== offcanvas) { 322 //console.log('Exchange Program Offcanvas: Offcanvas cambiato, aggiornamento...'); 323 offcanvas = currentOffcanvas; 324 // Reinizializza tutti gli elementi 325 overlay = offcanvas.querySelector('.secondary-offcanvas__overlay'); 326 animation = offcanvas.querySelector('.secondary-offcanvas__animation'); 327 panel = offcanvas.querySelector('.secondary-offcanvas__panel'); 328 closeBtn = offcanvas.querySelector('.secondary-offcanvas__close'); 329 cutoutLabel = offcanvas.querySelector('.secondary-offcanvas__cutout-label'); 330 loadingSpinner = offcanvas.querySelector('.docente-offcanvas-loading'); 331 nomeElement = offcanvas.querySelector('.docente-nome'); 332 dipartimentiElement = offcanvas.querySelector('.docente-dipartimenti'); 333 citazioneWrapper = offcanvas.querySelector('.docente-citazione-wrapper'); 334 citazioneElement = offcanvas.querySelector('.docente-citazione'); 335 biografiaWrapper = offcanvas.querySelector('.docente-biografia-wrapper'); 336 biografiaElement = offcanvas.querySelector('.docente-biografia'); 337 // Cerca l'immagine wrapper - su mobile ha anche la classe .docente-image-mobile ed è dentro .docente-offcanvas-content-wrapper 338 // Prima cerca dentro il content wrapper (mobile), poi direttamente nell'offcanvas (desktop) 339 const contentWrapper = offcanvas.querySelector('.docente-offcanvas-content-wrapper'); 340 imageWrapper = (contentWrapper && contentWrapper.querySelector('.docente-image-wrapper.docente-image-mobile')) || 341 offcanvas.querySelector('.docente-image-wrapper.docente-image-mobile') || 342 offcanvas.querySelector('.docente-image-wrapper'); 343 // Cerca l'elemento image all'interno del wrapper trovato 344 imageElement = imageWrapper ? imageWrapper.querySelector('.docente-image-element') : 345 offcanvas.querySelector('.docente-image-element'); 346 linkWrapper = offcanvas.querySelector('.docente-links-wrapper'); 347 linkElement = offcanvas.querySelector('.docente-link-post'); 348 349 // Reattacca i listener se necessario 350 if (closeBtn && !closeBtn.dataset.listenerAttached) { 351 closeBtn.addEventListener('click', function(e) { 352 e.preventDefault(); 353 e.stopPropagation(); 354 closeOffcanvas(e); 355 }); 356 closeBtn.dataset.listenerAttached = 'true'; 357 } 358 359 if (overlay && !overlay.dataset.listenerAttached) { 360 overlay.addEventListener('click', function(e) { 361 e.preventDefault(); 362 e.stopPropagation(); 363 closeOffcanvas(e); 364 }); 365 overlay.dataset.listenerAttached = 'true'; 366 } 367 } else if (!offcanvas) { 368 // Se non c'è ancora un offcanvas, prova a trovarlo 369 offcanvas = currentOffcanvas; 370 if (offcanvas) { 371 // Reinizializza tutti gli elementi
372 overlay = offcanvas.querySelector('.secondary-offcanvas__overlay'); 373 animation = offcanvas.querySelector('.secondary-offcanvas__animation'); 374 panel = offcanvas.querySelector('.secondary-offcanvas__panel'); 375 closeBtn = offcanvas.querySelector('.secondary-offcanvas__close'); 376 cutoutLabel = offcanvas.querySelector('.secondary-offcanvas__cutout-label'); 377 loadingSpinner = offcanvas.querySelector('.docente-offcanvas-loading'); 378 nomeElement = offcanvas.querySelector('.docente-nome'); 379 dipartimentiElement = offcanvas.querySelector('.docente-dipartimenti'); 380 citazioneWrapper = offcanvas.querySelector('.docente-citazione-wrapper'); 381 citazioneElement = offcanvas.querySelector('.docente-citazione'); 382 biografiaWrapper = offcanvas.querySelector('.docente-biografia-wrapper'); 383 biografiaElement = offcanvas.querySelector('.docente-biografia'); 384 const contentWrapper = offcanvas.querySelector('.docente-offcanvas-content-wrapper'); 385 imageWrapper = (contentWrapper && contentWrapper.querySelector('.docente-image-wrapper.docente-image-mobile')) || 386 offcanvas.querySelector('.docente-image-wrapper.docente-image-mobile') || 387 offcanvas.querySelector('.docente-image-wrapper'); 388 imageElement = imageWrapper ? imageWrapper.querySelector('.docente-image-element') : 389 offcanvas.querySelector('.docente-image-element'); 390 linkWrapper = offcanvas.querySelector('.docente-links-wrapper'); 391 linkElement = offcanvas.querySelector('.docente-link-post'); 392 } 393 } 394 395 if (!offcanvas) { 396 console.error('Offcanvas non disponibile'); 397 return; 398 } 399 400 if (!overlay || !animation || !panel) { 401 console.error('Elementi offcanvas non disponibili:', { 402 overlay: !!overlay, 403 animation: !!animation, 404 panel: !!panel 405 }); 406 // Prova a reinizializzare 407 overlay = offcanvas.querySelector('.secondary-offcanvas__overlay'); 408 animation = offcanvas.querySelector('.secondary-offcanvas__animation'); 409 panel = offcanvas.querySelector('.secondary-offcanvas__panel'); 410 if (!overlay || !animation || !panel) { 411 console.error('Ancora non disponibili dopo reinizializzazione'); 412 return; 413 } 414 } 415 416 //console.log('Tutti gli elementi disponibili, procedo con apertura'); 417 418 // REINIZIALIZZA SEMPRE gli elementi quando si apre l'offcanvas 419 // Questo assicura che gli elementi puntino all'offcanvas corretto (mobile o desktop) 420 overlay = offcanvas.querySelector('.secondary-offcanvas__overlay'); 421 animation = offcanvas.querySelector('.secondary-offcanvas__animation'); 422 panel = offcanvas.querySelector('.secondary-offcanvas__panel'); 423 closeBtn = offcanvas.querySelector('.secondary-offcanvas__close'); 424 cutoutLabel = offcanvas.querySelector('.secondary-offcanvas__cutout-label'); 425 loadingSpinner = offcanvas.querySelector('.docente-offcanvas-loading'); 426 nomeElement = offcanvas.querySelector('.docente-nome'); 427 dipartimentiElement = offcanvas.querySelector('.docente-dipartimenti'); 428 citazioneWrapper = offcanvas.querySelector('.docente-citazione-wrapper'); 429 citazioneElement = offcanvas.querySelector('.docente-citazione'); 430 biografiaWrapper = offcanvas.querySelector('.docente-biografia-wrapper'); 431 biografiaElement = offcanvas.querySelector('.docente-biografia'); 432 // Cerca l'immagine wrapper - su mobile ha anche la classe .docente-image-mobile ed è dentro .docente-offcanvas-content-wrapper 433 // Prima cerca dentro il content wrapper (mobile), poi direttamente nell'offcanvas (desktop) 434 const contentWrapper = offcanvas.querySelector('.docente-offcanvas-content-wrapper'); 435 imageWrapper = (contentWrapper && contentWrapper.querySelector('.docente-image-wrapper.docente-image-mobile')) || 436 offcanvas.querySelector('.docente-image-wrapper.docente-image-mobile') || 437 offcanvas.querySelector('.docente-image-wrapper'); 438 // Cerca l'elemento image all'interno del wrapper trovato 439 imageElement = imageWrapper ? imageWrapper.querySelector('.docente-image-element') : 440 offcanvas.querySelector('.docente-image-element'); 441 linkWrapper = offcanvas.querySelector('.docente-links-wrapper'); 442 linkElement = offcanvas.querySelector('.docente-link-post'); 443 444 //console.log('Exchange Program Offcanvas: Elementi reinizializzati all\'apertura', { 445 //imageWrapper: imageWrapper, 446 //imageElement: imageElement, 447 //isMobile: imageWrapper && imageWrapper.classList.contains('docente-image-mobile'), 448 //mask: imageElement ? imageElement.getAttribute('mask') : null, 449 //contentWrapper: !!contentWrapper 450 //}); 451 452 clearOffcanvasContent(); 453 loadExchangeProgramData(programId); 454 455 gsap.set(offcanvas, { display: 'block' }); 456 457 const tl = gsap.timeline(); 458 459 tl.fromTo(overlay, { opacity: 0 }, { opacity: 1, duration: 0.3, ease: 'power2.out' }, 0); 460 tl.fromTo(animation, { x: '100%' }, { x: '0%', duration: 0.6, ease: 'back.out(0.9)' }, 0.1); 461 tl.fromTo(panel, { opacity: 0 }, { opacity: 1, duration: 0.3, ease: 'power2.out' }, 0.3); 462 463 // Blocca lo scroll della pagina 464 disablePageScroll(); 465 } 466 467 function closeOffcanvas(e) { 468 if (!offcanvas || !overlay || !animation || !panel) return; 469 470 // Previeni il comportamento di default e la propagazione dell'evento 471 // per evitare che il browser scrolli automaticamente 472 if (e) { 473 e.preventDefault(); 474 e.stopPropagation(); 475 } 476 477 // Salva la posizione corrente dello scroll PRIMA di chiudere 478 const currentScrollPosition = window.lenis ? window.lenis.scroll : (window.pageYOffset || document.documentElement.scrollTop); 479 480 // Funzione helper per ripristinare la posizione 481 const restoreScrollPosition = () => { 482 if (window.lenis) { 483 window.lenis.scrollTo(currentScrollPosition, { 484 immediate: true, 485 force: true 486 }); 487 } else { 488 window.scrollTo(0, currentScrollPosition); 489 document.documentElement.scrollTop = currentScrollPosition; 490 document.body.scrollTop = currentScrollPosition; 491 } 492 }; 493 494 const tl = gsap.timeline({ 495 onComplete: () => { 496 gsap.set(offcanvas, { display: 'none' }); 497 clearOffcanvasContent(); 498 499 // Riabilita lo scroll della pagina 500 enablePageScroll(); 501 } 502 }); 503 504 tl.to(panel, { opacity: 0, duration: 0.2, ease: 'power2.in' }, 0); 505 tl.to(animation, { x: '100%', duration: 0.4, ease: 'power3.in' }, 0.1); 506 tl.to(overlay, { opacity: 0, duration: 0.3, ease: 'power2.in' }, 0.2); 507 } 508 509 function attachCardListeners() { 510 // Assicurati che l'offcanvas sia inizializzato 511 if (!offcanvas) { 512 const offcanvasEl = getOffcanvas(); 513 if (offcanvasEl && typeof initExchangeProgramOffcanvas === 'function') { 514 initExchangeProgramOffcanvas(); 515 } else { 516 // Se l'offcanvas non è ancora disponibile, aspetta 517 setTimeout(attachCardListeners, 100); 518 return; 519 } 520 } 521 522 const cards = document.querySelectorAll('.exchange-program-card-trigger'); 523 //console.log('Exchange Program Offcanvas: Trovate', cards.length, 'card trigger'); 524 525 cards.forEach(card => { 526 // Evita di attaccare listener multipli alla stessa card 527 if (card.dataset.offcanvasBound === 'true') { 528 return; 529 } 530 531 const programId = card.getAttribute('data-exchange-id'); 532 //console.log('Exchange Program Offcanvas: Attacco listener a card con ID', programId); 533 534 card.addEventListener('click', function(e) { 535 e.preventDefault(); 536 e.stopPropagation(); 537 e.stopImmediatePropagation(); 538 539 const programId = this.getAttribute('data-exchange-id'); 540 //console.log('Exchange Program Offcanvas: Click su card con ID', programId); 541 if (programId) { 542 openOffcanvas(programId); 543 } else { 544 console.error('Exchange Program Offcanvas: Nessun ID trovato sulla card'); 545 } 546 }); 547 548 card.dataset.offcanvasBound = 'true'; 549 }); 550 551 //console.log('Exchange Program Offcanvas: Listener attaccati a', cards.length, 'card'); 552 } 553 554 // Esponi la funzione globalmente per poterla chiamare quando il template diventa visibile 555 window.attachExchangeProgramCardListeners = attachCardListeners; 556 557 // Dichiarazione della funzione (viene hoisted) 558 function initExchangeProgramOffcanvas() { 559 offcanvas = getOffcanvas(); 560 561 if (!offcanvas) { 562 //console.log('Exchange Program Offcanvas: Nessun offcanvas trovato'); 563 return; 564 } 565 566 //console.log('Exchange Program Offcanvas: Offcanvas trovato e inizializzato -', offcanvas.id
566); 567 568 // Inizializza tutti gli elementi dell'offcanvas 569 overlay = offcanvas.querySelector('.secondary-offcanvas__overlay'); 570 animation = offcanvas.querySelector('.secondary-offcanvas__animation'); 571 panel = offcanvas.querySelector('.secondary-offcanvas__panel'); 572 closeBtn = offcanvas.querySelector('.secondary-offcanvas__close'); 573 cutoutLabel = offcanvas.querySelector('.secondary-offcanvas__cutout-label'); 574 575 loadingSpinner = offcanvas.querySelector('.docente-offcanvas-loading'); 576 nomeElement = offcanvas.querySelector('.docente-nome'); 577 dipartimentiElement = offcanvas.querySelector('.docente-dipartimenti'); 578 citazioneWrapper = offcanvas.querySelector('.docente-citazione-wrapper'); 579 citazioneElement = offcanvas.querySelector('.docente-citazione'); 580 biografiaWrapper = offcanvas.querySelector('.docente-biografia-wrapper'); 581 biografiaElement = offcanvas.querySelector('.docente-biografia'); 582 // Cerca l'immagine wrapper - su mobile ha anche la classe .docente-image-mobile ed è dentro .docente-offcanvas-content-wrapper 583 // Prima cerca dentro il content wrapper (mobile), poi direttamente nell'offcanvas (desktop) 584 const contentWrapper = offcanvas.querySelector('.docente-offcanvas-content-wrapper'); 585 imageWrapper = (contentWrapper && contentWrapper.querySelector('.docente-image-wrapper.docente-image-mobile')) || 586 offcanvas.querySelector('.docente-image-wrapper.docente-image-mobile') || 587 offcanvas.querySelector('.docente-image-wrapper'); 588 // Cerca l'elemento image all'interno del wrapper trovato 589 imageElement = imageWrapper ? imageWrapper.querySelector('.docente-image-element') : 590 offcanvas.querySelector('.docente-image-element'); 591 592 //console.log('Exchange Program Offcanvas: Elementi immagine trovati', { 593 //imageWrapper: imageWrapper, 594 //imageElement: imageElement, 595 //isMobile: imageWrapper && imageWrapper.classList.contains('docente-image-mobile'), 596 //mask: imageElement ? imageElement.getAttribute('mask') : null, 597 //contentWrapper: !!contentWrapper 598 //}); 599 linkWrapper = offcanvas.querySelector('.docente-links-wrapper'); 600 linkElement = offcanvas.querySelector('.docente-link-post'); 601 602 customScrollbar = null; 603 604 // Attacca i listener alle card se sono già disponibili 605 attachCardListeners(); 606 607 // Setup event listeners per offcanvas (solo una volta) 608 if (closeBtn && !closeBtn.dataset.listenerAttached) { 609 closeBtn.addEventListener('click', function(e) { 610 e.preventDefault(); 611 e.stopPropagation(); 612 closeOffcanvas(e); 613 }); 614 closeBtn.dataset.listenerAttached = 'true'; 615 } 616 617 if (overlay && !overlay.dataset.listenerAttached) { 618 overlay.addEventListener('click', function(e) { 619 e.preventDefault(); 620 e.stopPropagation(); 621 closeOffcanvas(e); 622 }); 623 overlay.dataset.listenerAttached = 'true'; 624 } 625 626 // Listener per ESC key (solo una volta) 627 if (!window.exchangeOffcanvasEscListenerAttached) { 628 document.addEventListener('keydown', function(e) { 629 if (e.key === 'Escape' && offcanvas && offcanvas.style.display === 'block') { 630 closeOffcanvas(); 631 } 632 }); 633 window.exchangeOffcanvasEscListenerAttached = true; 634 } 635 } 636 637 function initCustomScrollbar(offcanvasElement) { 638 const content = offcanvasElement.querySelector('.secondary-offcanvas__content'); 639 const scrollbar = offcanvasElement.querySelector('.custom-scrollbar'); 640 const track = offcanvasElement.querySelector('.custom-scrollbar__track'); 641 const thumb = offcanvasElement.querySelector('.custom-scrollbar__thumb'); 642 643 if (!content || !scrollbar || !track || !thumb) return null; 644 645 let isDragging = false;
646 let startY = 0; 647 let startScrollTop = 0; 648 649 function updateScrollbar() { 650 const scrollHeight = content.scrollHeight; 651 const clientHeight = content.clientHeight; 652 const trackHeight = track.clientHeight; 653 654 if (scrollHeight <= clientHeight) { 655 scrollbar.classList.remove('active'); 656 return; 657 } 658 659 scrollbar.classList.add('active'); 660 661 const thumbHeight = Math.max((clientHeight / scrollHeight) * trackHeight, 30); 662 thumb.style.height = thumbHeight + 'px'; 663 664 updateThumbPosition(); 665 } 666 667 function updateThumbPosition() { 668 const scrollHeight = content.scrollHeight; 669 const clientHeight = content.clientHeight; 670 const scrollTop = content.scrollTop; 671 const trackHeight = track.clientHeight; 672 const thumbHeight = thumb.clientHeight; 673 674 const scrollPercentage = scrollTop / (scrollHeight - clientHeight); 675 const thumbTop = scrollPercentage * (trackHeight - thumbHeight); 676 677 thumb.style.transform = `translateY(${thumbTop}px)`; 678 } 679 680 thumb.addEventListener('mousedown', function(e) { 681 isDragging = true; 682 startY = e.clientY; 683 startScrollTop = content.scrollTop; 684 document.body.style.userSelect = 'none'; 685 }); 686 687 document.addEventListener('mousemove', function(e) { 688 if (!isDragging) return; 689 e.preventDefault(); 690 691 const deltaY = e.clientY - startY; 692 const scrollHeight = content.scrollHeight; 693 const clientHeight = content.clientHeight; 694 const trackHeight = track.clientHeight; 695 const thumbHeight = thumb.clientHeight; 696 697 const scrollDelta = (deltaY / (trackHeight - thumbHeight)) * (scrollHeight - clientHeight); 698 content.scrollTop = startScrollTop + scrollDelta; 699 }); 700 701 document.addEventListener('mouseup', function() { 702 isDragging = false; 703 document.body.style.removeProperty('user-select'); 704 }); 705 706 content.addEventListener('scroll', function() { 707 updateThumbPosition(); 708 }); 709 710 content.addEventListener('wheel', function(e) { 711 const scrollHeight = content.scrollHeight; 712 const clientHeight = content.clientHeight; 713 714 if (scrollHeight > clientHeight) { 715 e.preventDefault(); 716 content.scrollTop += e.deltaY; 717 } 718 }, { passive: false }); 719 720 // Touch scroll per dispositivi mobili 721 let touchStartY = 0; 722 let touchStartScrollTop = 0; 723 724 content.addEventListener('touchstart', function(e) { 725 touchStartY = e.touches[0].clientY; 726 touchStartScrollTop = content.scrollTop; 727 }, { passive: true }); 728 729 content.addEventListener('touchmove', function(e) { 730 const touchY = e.touches[0].clientY; 731 const deltaY = touchStartY - touchY; 732 content.scrollTop = touchStartScrollTop + deltaY; 733 734 // Aggiorna la posizione della thumb 735 updateThumbPosition(); 736 }, { passive: true }); 737 738 window.addEventListener('resize', function() { 739 updateScrollbar(); 740 }); 741 742 updateScrollbar(); 743 744 return { 745 update: updateScrollbar 746 }; 747 } 748 749 // Esponi la funzione globalmente per poterla chiamare quando il template diventa visibile 750 window.initExchangeProgramOffcanvas = initExchangeProgramOffcanvas; 751 752})(); 753
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.