PageSourceSearch

https://www.accademiamoda.it/wp-content/themes/iuad-theme/src/js/c…xchange-program-offcanvas.js?ver=1779095505

js accademiamoda.it collected 2026-10-04 13:23:51 UTC 32,727 bytes, 753 lines download raw bytes

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.