PageSourceSearch

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

js accademiamoda.it collected 2026-10-04 13:23:56 UTC 13,945 bytes, 301 lines download raw bytes

1/**
2 * Exchange Partner Mobile - Scroll Animation & Load More
3 * Titolo si blocca a sinistra, items scorrono a destra come dipartimenti
4 */
5(function() {
6    'use strict';
7
8    let scrollTriggerInstance = null;
9
10    function calculateScrollDuration(numCards) {
11        // Calcola la durata dello scroll in base al numero di card
12        // Più card = scroll più lungo per farle passare tutte sopra il titolo fisso
13        const baseEnd = 200; // Durata base per le prime 3 card
14        const additionalPerCard = 40; // +40% per ogni card aggiuntiva (per garantire visibilità)
15        
16        if (numCards <= 3) {
17            return baseEnd;
18        }
19        
20        // Per ogni card oltre le prime 3, aggiungi scroll extra
21        // Es: 6 card = 200 + (3 * 40) = 320%
22        // Es: 12 card = 200 + (9 * 40) = 560%
23        return baseEnd + ((numCards - 3) * additionalPerCard);
24    }
25
26    function initAnimation() {
27        const partnerBlock = document.querySelector('.exchange-partner-mobile');
28        
29        if (!partnerBlock) return;
30
31        const container = partnerBlock.querySelector('.exchange-partner-mobile__container');
32        const titleWrapper = partnerBlock.querySelector('.exchange-partner-mobile__title-wrapper');
33        const itemsWrapper = partnerBlock.querySelector('.exchange-partner-mobile__items');
34        const loadMoreWrapper = partnerBlock.querySelector('.exchange-partner-mobile__load-more');
35        
36        if (!titleWrapper || !itemsWrapper) return;
37
38        // Registra ScrollTrigger plugin
39        gsap.registerPlugin(ScrollTrigger);
40
41        const viewportHeight = window.innerHeight;
42        
43        // Container resta sempre 100vh (fisso)
44        // Solo lo scroll duration cambia in base al numero di card
45        
46        // Pulisci animazione precedente se esiste
47        if (scrollTriggerInstance) {
48            scrollTriggerInstance.kill();
49        }
50        
51        // === STATO INIZIALE ===
52        // Titolo: posizionato direttamente nel contenitore (nessuna animazione di ingresso)
53        // Non applichiamo transform Y per mantenere il centraggio verticale del CSS
54        gsap.set(titleWrapper, { 
55            opacity: 1,
56            clearProps: "transform" // Rimuove eventuali transform precedenti, lascia che il CSS centri
57        });
58
59        // Items wrapper parte dal basso
60        gsap.set(itemsWrapper, { 
61            y: viewportHeight,
62            opacity: 1
63        });
64
65        // === ANIMAZIONE CON PIN (titolo centrato FISSO, items scrollano sopra) ===
66        // Calcola dinamicamente l'end in base al numero di card
67        const cards = itemsWrapper.querySelectorAll('.partner-card-mobile');
68        const dynamicEnd = calculateScrollDuration(cards.length);
69        
70        //console.log('Inizializzazione con', cards.length, 'partner, scroll duration:', dynamicEnd + '%');
71        
72        const mainTimeline = gsap.timeline({
73            scrollTrigger: {
74                trigger: partnerBlock,
75                start: 'top top',
76                end: `+=${dynamicEnd}%`, // End dinamico: più card = scroll più lungo
77                pin: true,
78                scrub: 1,
79                anticipatePin: 1,
80                invalidateOnRefresh: true,
81                // markers: true, // Debug
82                onLeave: () => {
83                    if (container) container.classList.add('animation-complete');
84                },
85                onEnterBack: () => {
86                    if (container) container.classList.remove('animation-complete');
87                }
88            }
89        });
90
91        // Salva per cleanup
92        scrollTriggerInstance = mainTimeline.scrollTrigger;
93
94        // FASE 1: Titolo rimane fisso e centrato (non animiamo, il CSS lo centra)
95        // Non aggiungiamo animazione per mantenere il centraggio verticale del CSS
96
97        // FASE 2: Pausa minima (0-5% timeline)
98        mainTimeline.to({}, { duration: 0.05 });
99
100        // FASE 3: Items salgono dal basso e vanno sopra il titolo (5-100% timeline) - Inizia subito
101        // Calcola dinamicamente quanto devono salire per mostrare tutte le card
102        const itemsTargetY = 20;
103        
104        // Rileva se siamo su tablet per usare l'altezza corretta delle card
105        const isTablet = window.innerWidth >= 1000 && window.innerWidth <= 1499;
106        const cardHeight = isTablet ? 250 : 200; // 250px per tablet, 200px per mobile
107        
108        // Calcola l'altezza totale del contenuto items
109        const gap = 10;
110        const loadMoreBtn = itemsWrapper.querySelector('.exchange-partner-mobile__load-more');
111        const buttonHeight = loadMoreBtn ? 70 : 0;
112        const safetyMargin = 100; // Margine di sicurezza
113        const totalItemsHeight = (cards.length * cardHeight) + ((cards.length - 1) * gap) + buttonHeight + safetyMargin;
114        
115        // Items devono salire per mostrare tutto
116        // L'ultima card + bottone devono essere visibili alla fine dello scroll
117        const itemsTravel = totalItemsHeight - viewportHeight + itemsTargetY;
118        
119        //console.log('Items travel:', itemsTravel, 'px per', cards.length, 'card (total height:', totalItemsHeight, 'px)');
120        
121        mainTimeline.to(itemsWrapper, {
122            y: -itemsTravel,
123            duration: 0.95,
124            ease: 'none',
125        }, 0.05);
126
127        //console.log('Exchange Partner Mobile animation initialized');
128    }
129
130    // === LOAD MORE FUNCTIONALITY ===
131    function handleLoadMore() {
132        const partnerBlock = document.querySelector('.exchange-partner-mobile');
133        const loadMoreBtn = partnerBlock.querySelector('.exchange-partner-load-more-btn');
134        const itemsWrapper = partnerBlock.querySelector('.exchange-partner-mobile__items');
135        const loadMoreWrapper = partnerBlock.querySelector('.exchange-partner-mobile__load-more');
136        
137        if (!loadMoreBtn) return;
138
139        loadMoreBtn.addEventListener('click', function() {
140            // Recupera i partner rimanenti dai data attributes
141            const remainingPartnersJson = partnerBlock.dataset.remainingPartners;
142            
143            if (!remainingPartnersJson) return;
144            
145            let remainingPartners = JSON.parse(remainingPartnersJson);
146            
147            if (remainingPartners.length === 0) return;
148            
149            // Prendi i prossimi 6
150            const partnersPerPage = parseInt(partnerBlock.dataset.partnersPerPage) || 6;
151            const nextBatch = remainingPartners.slice(0, partnersPerPage);
152            remainingPartners = remainingPartners.slice(partnersPerPage);
153            
154            // Disabilita bottone durante caricamento
155            loadMoreBtn.disabled = true;
156            loadMoreBtn.textContent = 'Caricamento...';
157            
158            // Aggiungi le nuove card prima del bottone
159            const currentIndex = itemsWrapper.querySelectorAll('.partner-card-mobile').length;
160            
161            nextBatch.forEach((partner, index) => {
162                const card = document.createElement('div');
163                card.className = 'partner-card-mobile';
164                card.dataset.index = currentIndex + index;
165                card.innerHTML = `
166                    <img src="${partner.thumbnail}" 
167                         alt="${partner.title}"
168                         class="partner-card-mobile__image">
169                `;
170                // Inserisci prima del wrapper del bottone
171                itemsWrapper.insertBefore(card, loadMoreWrapper);
172            });
173            
174            // Aggiorna i dati rimanenti
175            partnerBlock.dataset.remainingPartners = JSON.stringify(remainingPartners);
176            
177            // Se non ci sono più partner, nascondi il bottone
178            if (remainingPartners.length === 0) {
179                if (loadMoreWrapper) loadMoreWrapper.style.display = 'none';
180            } else {
181                loadMoreBtn.disabled = false;
182                loadMoreBtn.textContent = 'Visualizza Partner';
183            }
184            
185            // Aspetta che le card siano nel DOM
186            setTimeout(() => {
187                // Container resta 100vh (fisso)
188                // Aggiorna solo la durata dello scroll (end)
189                const totalCards = itemsWrapper.querySelectorAll('.partner-card-mobile').length;
190                const newScrollDuration = calculateScrollDuration(totalCards);
191                
192                //console.log('Caricati', nextBatch.length, 'partner. Totale:', totalCards, 'Nuova scroll duration:', newScrollDuration + '%');
193                
194                // Kill e ricrea ScrollTrigger con nuovo end
195                // Ma mantieni la posizione corrente degli elementi
196                if (scrollTriggerInstance) {
197                    const container = partnerBlock.querySelector('.exchange-partner-mobile__container');
198                    const titleWrapper = partnerBlock.querySelector('.exchange-partner-mobile__title-wrapper');
199                    const itemsWrapper = partnerBlock.querySelector('.exchange-partner-mobile__items');
200                    
201                    // Salva le posizioni correnti
202                    const titleCurrentY = gsap.getProperty(titleWrapper, 'y');
203                    const itemsCurrentY = gsap.getProperty(itemsWrapper, 'y');
204                    
205                    //console.log('Posizioni correnti - Title Y:', titleCurrentY, 'Items Y:', itemsCurrentY);
206                    
207                    // Kill il vecchio ScrollTrigger
208                    scrollTriggerInstance.kill();
209                    
210                    // Mantieni le posizioni correnti (non resettare)
211                    // Per il titolo, non applichiamo transform Y per mantenere il centraggio CSS
212                    gsap.set(titleWrapper, { clearProps: "transform" });
213                    gsap.set(itemsWrapper, { y: itemsCurrentY });
214                    
215                    // Crea nuovo ScrollTrigger con end aggiornato
216                    const viewportHeight = window.innerHeight;
217                    const itemsTargetY = 20;
218                    
219                    // Calcola dinamicamente l'altezza totale del contenuto
220                    const allCards = itemsWrapper.querySelectorAll('.partner-card-mobile');
221                    // Rileva se siamo su tablet per usare l'altezza corretta delle card
222                    const isTablet = window.innerWidth >= 1000 && window.innerWidth <= 1499;
223                    const cardHeight = isTablet ? 250 : 200; // 250px per tablet, 200px per mobile
224                    const gap = 10;
225                    const loadMoreBtn = itemsWrapper.querySelector('.exchange-partner-mobile__load-more');
226                    const buttonHeight = loadMoreBtn && loadMoreBtn.offsetParent !== null ? 70 : 0;
227                    const safetyMargin = 100; // Margine di sicurezza
228                    const totalItemsHeight = (allCards.length * cardHeight) + ((allCards.length - 1) * gap) + buttonHeight + safetyMargin;
229                    
230                    // Items devono salire per mostrare tutto
231                    const itemsTravel = totalItemsHeight - viewportHeight + itemsTargetY;
232                    
233                    //console.log('Ricreato - Items travel:', itemsTravel, 'px per', allCards.length, 'card (total height:', totalItemsHeight, 'px)');
234                    
235                    const mainTimeline = gsap.timeline({
236                        scrollTrigger: {
237                            trigger: partnerBlock,
238                            start: 'top top',
239                            end: `+=${newScrollDuration}%`,
240                            pin: true,
241                            scrub: 1,
242                            anticipatePin: 1,
243                            invalidateOnRefresh: true,
244                            onLeave: () => {
245                                if (container) container.classList.add('animation-complete');
246                            },
247                            onEnterBack: () => {
248                                if (container) container.classList.remove('animation-complete');
249                            }
250                        }
251                    });
252                    
253                    // Timeline: titolo rimane fisso e centrato (non animiamo)
254                    mainTimeline.to({}, { duration: 0.05 });
255                    
256                    mainTimeline.to(itemsWrapper, {
257                        y: -itemsTravel,
258                        duration: 0.95,
259                        ease: 'none',
260                    }, 0.05);
261                    
262                    // Salva nuovo instance
263                    scrollTriggerInstance = mainTimeline.scrollTrigger;
264                    
265                    //console.log('ScrollTrigger ricreato con end:', newScrollDuration + '%');
266                }
267            }, 50);
268            
269            //console.log('Rimanenti:', remainingPartners.length, 'partner');
270        });
271    }
272
273    // Inizializza
274    document.addEventListener('DOMContentLoaded', () => {
275        const partnerBlock = document.querySelector('.exchange-partner-mobile');
276        
277        if (!partnerBlock) return;
278        
279        // Verifica che l'elemento sia visibile (non nascosto da display:none)
280        const isVisible = window.getComputedStyle(partnerBlock).display !== 'none';
281        if (!isVisible) {
282            // Se non è visibile, aspetta che diventi visibile (per template responsive dinamici)
283            const checkVisibility = () => {
284                const checkVisible = window.getComputedStyle(partnerBlock).display !== 'none';
285                if (checkVisible) {
286                    initAnimation();
287                    handleLoadMore();
288                } else {
289                    setTimeout(checkVisibility, 100);
290                }
291            };
292            checkVisibility();
293            return;
294        }
295
296        initAnimation();
297        handleLoadMore();
298    });
299
300})();
301

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.