1/** 2 * Dipartimenti Block Mobile - Scroll Animation & Interaction 3 * SEQUENZA: 4 * 1. Testo "Dipartimenti" sale dal basso 5 * 2. Testo si blocca in alto (pin) 6 * 3. Durante il pin, le card appaiono dal basso 7 * 4. Click su card: naviga direttamente al link 8 */ 9(function() { 10 'use strict'; 11 12 document.addEventListener('DOMContentLoaded', () => { 13 const dipartimentiBlockMobile = document.querySelector('.dipartimenti-block-mobile'); 14 15 if (!dipartimentiBlockMobile) return; 16 17 // Verifica che l'elemento sia visibile (non nascosto da display:none) 18 const isVisible = window.getComputedStyle(dipartimentiBlockMobile).display !== 'none'; 19 if (!isVisible) { 20 // Se non è visibile, aspetta che diventi visibile (per template responsive dinamici) 21 const checkVisibility = () => { 22 const checkVisible = window.getComputedStyle(dipartimentiBlockMobile).display !== 'none'; 23 if (checkVisible) { 24 initDipartimentiMobileAnimation(); 25 } else { 26 setTimeout(checkVisibility, 100); 27 } 28 }; 29 checkVisibility(); 30 return; 31 } 32 33 initDipartimentiMobileAnimation(); 34 }); 35 36 function initDipartimentiMobileAnimation() { 37 const dipartimentiBlockMobile = document.querySelector('.dipartimenti-block-mobile'); 38 if (!dipartimentiBlockMobile) return; 39 40 // Verifica nuovamente la visibilità prima di inizializzare 41 if (window.getComputedStyle(dipartimentiBlockMobile).display === 'none') { 42 return; 43 } 44 45 const titleWrapper = dipartimentiBlockMobile.querySelector('.dipartimenti-block-mobile__title-wrapper'); 46 const cardsWrapper = dipartimentiBlockMobile.querySelector('.dipartimenti-block-mobile__cards'); 47 const cards = dipartimentiBlockMobile.querySelectorAll('.dipartimento-card-mobile'); 48 49 if (!titleWrapper || !cardsWrapper || cards.length === 0) return; 50 51 // Registra ScrollTrigger plugin 52 gsap.registerPlugin(ScrollTrigger); 53 54 // === SETUP: Calcola le posizioni === 55 const viewportHeight = window.innerHeight; 56 const cardHeight = 305; // Altezza fissa di ogni card 57 const gapBetweenCards = 10; // Gap tra le card 58 const totalCardSpace = cardHeight + gapBetweenCards; // 315px per card 59 const numCards = cards.length; 60 61 // Calcolo altezza totale delle card impilate 62 // 4 card = (305 * 4) + (10 * 3) = 1220 + 30 = 1250px 63 const totalCardsHeight = (cardHeight * numCards) + (gapBetweenCards * (numCards - 1)); 64 65 // Verifica se siamo su mobile 66 const isMobile = window.innerWidth <= 768; 67 const container = dipartimentiBlockMobile.querySelector('.dipartimenti-block-mobile__container'); 68 69 // === STATO INIZIALE === 70 // Testo: posizionato direttamente nel contenitore (nessuna animazione di ingresso) 71 gsap.set(titleWrapper, { 72 y: 20, 73 opacity: 1 74 }); 75 76 // Il wrapper delle card parte sotto il container 77 // Le card dentro sono in layout normale (relative) quindi mantengono i gap automaticamente 78 gsap.set(cardsWrapper, { 79 y: viewportHeight, 80 opacity: 1 81 }); 82 83 if (isMobile) { 84 // === ANIMAZIONE MOBILE: Sezione si blocca (pin) e card salgono con velocità pari allo scroll === 85 const cardsWrapperTargetY = 20; 86 const cardsWrapperTravel = viewportHeight - cardsWrapperTargetY; 87 88 // Calcola la distanza di scroll necessaria per far salire tutte le card 89 const scrollDistance = viewportHeight * 1.5; // Distanza di scroll per completare l'animazione 90 91 // Timeline principale con pin per bloccare la sezione e tenere fissa la scritta "Dipartimenti" 92 const mainTimeline = gsap.timeline({ 93 scrollTrigger: { 94 trigger: dipartimentiBlockMobile, 95 start: 'top top', // Inizia quando il top del blocco arriva al top del viewport 96 end: `+=${scrollDistance}px`, // Pin dura per tutta la distanza di scroll necessaria 97 pin: true, // Blocca la sezione per tenere fissa la scritta 98 scrub: true, // Segue immediatamente lo scroll senza lag (true = 0 lag) 99 anticipatePin: 1, 100 // markers: true, // Debug 101 onLeave: () => { 102 // Quando il pin finisce, permetti overflow normale 103 if (container) container.classList.add('animation-complete'); 104 }, 105 onEnterBack: () => { 106 // Quando torni indietro, riattiva overflow hidden 107 if (container) container.classList.remove('animation-complete'); 108 } 109 } 110 }); 111
112 // Animazione delle card che sale in modo continuo e reattivo 113 // Le card iniziano a salire immediatamente quando inizia il pin 114 mainTimeline.to(cardsWrapper, { 115 y: `-=${cardsWrapperTravel}`, 116 ease: 'none', // Nessun easing per seguire perfettamente lo scroll 117 duration: 1, // Durata della timeline (1 = 100% della durata del pin) 118 }, 0); // Inizia immediatamente (time 0) quando inizia il pin 119 } else { 120 // === ANIMAZIONE DESKTOP: Mantiene il comportamento originale con pin === 121 const pinDuration = `+=${(numCards * 30) + 30}%`; 122 123 const mainTimeline = gsap.timeline({ 124 scrollTrigger: { 125 trigger: dipartimentiBlockMobile, 126 start: 'top top', 127 end: pinDuration, 128 pin: true, 129 scrub: 1, 130 anticipatePin: 1, 131 // markers: true, // Debug 132 onLeave: () => { 133 if (container) container.classList.add('animation-complete'); 134 }, 135 onEnterBack: () => { 136 if (container) container.classList.remove('animation-complete'); 137 } 138 } 139 }); 140 141 const cardsWrapperTargetY = 20; 142 const cardsWrapperTravel = viewportHeight - cardsWrapperTargetY; 143 const animationDuration = 0.1; 144 145 mainTimeline.to(cardsWrapper, 146 { 147 y: `-=${cardsWrapperTravel}`, 148 duration: animationDuration, 149 ease: 'none', 150 }, 151 0 152 ); 153 } 154 155 // Dopo il pin (onLeave callback), overflow diventa visible 156 // Le card rimanenti possono essere viste scrollando normalmente la pagina 157 158 // === INTERAZIONE: Click naviga direttamente al link === 159 cards.forEach((card) => { 160 const href = card.dataset.href; 161 if (href) { 162 card.addEventListener('click', () => { 163 window.location.href = href; 164 }); 165 } 166 }); 167 168 //console.log('Dipartimenti block mobile initialized:', cards.length, 'cards'); 169 } 170 171})(); 172
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.