1/** 2 * Exchange Program Mobile - Scroll Animation 3 * Titolo centrato, card che salgono e si sovrappongono 4 */ 5(function() { 6 'use strict'; 7 8 document.addEventListener('DOMContentLoaded', () => { 9 const exchangeBlock = document.querySelector('.exchange-program-mobile'); 10 11 if (!exchangeBlock) return; 12 13 // Verifica che l'elemento sia visibile (non nascosto da display:none) 14 const isVisible = window.getComputedStyle(exchangeBlock).display !== 'none'; 15 if (!isVisible) { 16 // Se non è visibile, aspetta che diventi visibile (per template responsive dinamici) 17 const checkVisibility = () => { 18 const checkVisible = window.getComputedStyle(exchangeBlock).display !== 'none'; 19 if (checkVisible) { 20 initExchangeProgramMobileAnimation(); 21 } else { 22 setTimeout(checkVisibility, 100); 23 } 24 }; 25 checkVisibility(); 26 return; 27 } 28 29 initExchangeProgramMobileAnimation(); 30 }); 31 32 function initExchangeProgramMobileAnimation() { 33 const exchangeBlock = document.querySelector('.exchange-program-mobile'); 34 if (!exchangeBlock) return; 35 36 // Verifica nuovamente la visibilità prima di inizializzare 37 if (window.getComputedStyle(exchangeBlock).display === 'none') { 38 return; 39 } 40 41 const titleWrapper = exchangeBlock.querySelector('.exchange-program-mobile__title-wrapper'); 42 const cards = exchangeBlock.querySelectorAll('.exchange-card-mobile'); 43 const footer = exchangeBlock.querySelector('.exchange-program-mobile__footer'); 44 45 if (!titleWrapper || cards.length === 0) return; 46 47 // Registra ScrollTrigger plugin 48 gsap.registerPlugin(ScrollTrigger); 49 50 const viewportHeight = window.innerHeight; 51 const numCards = cards.length; 52 53 // === STATO INIZIALE === 54 // Titolo: posizionato direttamente nel contenitore (nessuna animazione di ingresso) 55 gsap.set(titleWrapper, { 56 y: 0, 57 opacity: 1 // NO FADE 58 }); 59 60 // Tutte le card partono dal basso, sovrapposte 61 gsap.set(cards, { 62 y: viewportHeight, 63 opacity: 1 64 }); 65 66 // Footer parte dal basso 67 if (footer) { 68 gsap.set(footer, { 69 y: 50, 70 opacity: 1 // NO FADE 71 }); 72 } 73 74 // === ANIMAZIONE CON PIN === 75 // Durata ridotta per evitare scroll eccessivo 76 // Calcoliamo in base al numero di card ma con durata più compatta 77 const pinDuration = `+=${(numCards * 50) + 50}%`; // Ridotto da 100% a 50% per card 78 79 const mainTimeline = gsap.timeline({ 80 scrollTrigger: { 81 trigger: exchangeBlock, 82 start: 'top top', 83 end: pinDuration, 84 pin: true, 85 scrub: 1, 86 anticipatePin: 1, 87 // markers: true, // Debug 88 } 89 }); 90 91 // FASE 1: Titolo rimane fisso (non animiamo, il CSS lo centra) 92 // Non aggiungiamo animazione per mantenere il centraggio verticale del CSS 93 94 // FASE 2: Pausa minima (0-2% timeline) - Ridotta per iniziare subito 95 mainTimeline.to({}, { duration: 0.02 }); 96 97 // FASI 2-N: Ogni card sale singolarmente al centro (2-85% timeline) - Inizia subito 98 cards.forEach((card, index) => { 99 const startTime = 0.02 + (index * 0.08); // Ridotto spazio tra card (da 0.12 a 0.08) 100 101 // Card sale dal basso al centro (y: 0, sovrapposta) 102 mainTimeline.to(card, { 103 y: 0, 104 duration: 0.08, // Ridotta durata (da 0.12 a 0.08) per animazione più veloce 105 ease: 'none', 106 onStart: () => { 107 // Z-index crescente per sovrapposizione corretta 108 card.style.zIndex = 10 + index; 109 } 110 }, startTime); 111 }); 112 113 // FASE FINALE: Footer sale subito dopo l'ultima card 114 if (footer) { 115 const footerStartTime = 0.02 + (numCards * 0.08) + 0.02; // Ridotto spazio prima del footer 116 117 mainTimeline.to(footer, { 118 y: 0, 119 duration: 0.15, // Ridotta durata footer (da 0.2 a 0.15) 120 ease: 'none', 121 }, footerStartTime); 122 } 123 124 //console.log('Exchange Program Mobile initialized:', numCards, 'cards'); 125 } 126 127})(); 128
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.