1/** 2 * Three Card Carousel - Swiper Centered Mode 3 * Carousel orizzontale con 3 card visibili e centro attivo 4 */ 5 6(function() { 7 'use strict'; 8 9 document.addEventListener('DOMContentLoaded', function() { 10 // Inizializza il carousel solo se esiste 11 const carouselElement = document.querySelector('.three-card-carousel-swiper'); 12 13 if (!carouselElement) return; 14 15 const slides = carouselElement.querySelectorAll('.swiper-slide'); 16 const slidesCount = slides.length; 17 const isDesktop = window.innerWidth >= 1024; 18 const desktopStatic = isDesktop && slidesCount <= 3; 19 const desktopStaticTwo = isDesktop && slidesCount === 2; 20 const desktopStaticThree = isDesktop && slidesCount === 3; 21 22 // Inizializza Swiper con centered mode 23 const threeCardSwiper = new Swiper('.three-card-carousel-swiper', { 24 // Modalità centrata - la slide attiva è sempre al centro 25 centeredSlides: true, 26 27 // Loop infinito (solo se non siamo in modalità statica desktop) 28 loop: !desktopStatic, 29 30 // Numero di slide visibili 31 slidesPerView: 1, 32 spaceBetween: 20, // Margine tra slide su mobile 33 34 // Centra le slide nel container 35 centerInsufficientSlides: true, 36 37 // Velocità di transizione per effetto bounce (più lenta) 38 speed: 1200, 39 40 // Effetto di transizione 41 effect: 'slide', 42 43 // Easing per effetto bounce (cubic-bezier simula un overshoot/bounce) 44 // Questo crea un effetto dove le slide "rimbalzano" leggermente oltre la posizione finale 45 cssMode: false, 46
47 // Breakpoints responsive 48 breakpoints: { 49 // Mobile (<768px): 1 card (default sopra) 50 51 // Tablet (>= 768px): 2 card 52 768: { 53 slidesPerView: 2, 54 spaceBetween: 20, 55 }, 56 // Desktop (>= 1024px): manteniamo sempre layout a 3 card, 57 // anche se i corsi sono <= 3, così le card hanno sempre la stessa dimensione. 58 1024: { 59 slidesPerView: 3, 60 spaceBetween: -60, 61 centeredSlides: true, 62 }, 63 }, 64 65 // Navigazione con frecce: 66 // - attiva normalmente 67 // - ma se siamo in modalità statica desktop, verrà disabilitata via CSS/JS 68 navigation: desktopStatic ? {} : { 69 nextEl: '.three-card-carousel .carousel-nav-next', 70 prevEl: '.three-card-carousel .carousel-nav-prev', 71 }, 72 73 // Paginazione con dots 74 pagination: { 75 el: '.three-card-carousel .carousel-dots-container', 76 clickable: true, 77 bulletClass: 'carousel-dot', 78 bulletActiveClass: 'active', 79 renderBullet: function (index, className) { 80 return '<button class="' + className + '" data-index="' + index + '" aria-label="Go to slide ' + (index + 1) + '"></button>'; 81 }, 82 }, 83 84 // Grab cursor 85 grabCursor: !desktopStatic, 86 87 // Keyboard control (disabilitato in modalità statica desktop) 88 keyboard: { 89 enabled: !desktopStatic, 90 onlyInViewport: true, 91 }, 92 // Abilita progress per effetto di distacco (solo elementi interni) 93 watchSlidesProgress: true, 94 95 // Autoplay opzionale (commentato per default) 96 /* 97 autoplay: { 98 delay: 5000, 99 disableOnInteraction: false, 100 pauseOnMouseEnter: true, 101 }, 102 */ 103 104 // Callback quando la slide cambia 105 on: { 106 // Dopo l'inizializzazione 107 init: function() { 108 updateSlideStates(this); 109 110 // Se siamo in modalità statica desktop, disabilita interazioni e UI di navigazione 111 if (desktopStatic) { 112 const root = carouselElement.closest('.three-card-carousel'); 113 if (root) { 114 root.classList.add('three-card-carousel--static-desktop'); 115 if (desktopStaticTwo) { 116 root.classList.add('three-card-carousel--static-desktop-two'); 117 } 118 } 119 // Disabilita drag 120 this.allowTouchMove = false; 121 122 // Se abbiamo esattamente 3 slide, forziamo la card centrale come attiva 123 if (desktopStaticThree && slidesCount === 3) { 124 this.slideTo(1, 0); // indice centrale 125 } 126 } 127 }, 128 129 // Durante lo slide aggiorna lo "stacco" sulle slide che escono 130 setTranslate: function() { 131 applyDetachInner(this); 132 }, 133 134 // Dopo ogni cambio slide 135 slideChangeTransitionEnd: function() { 136 updateSlideStates(this); 137 }, 138 transitionStart: function(){ 139 const wrapper = this.wrapperEl; 140 if (wrapper) { 141 // Overshoot per bounce (più rapido) 142 wrapper.style.transitionTimingFunction = 'cubic-bezier(0.34, 1.56, 0.64, 1)'; 143 } 144 }, 145 transitionEnd: function(){ 146 const wrapper = this.wrapperEl; 147 if (wrapper) { 148 wrapper.style.transitionTimingFunction = ''; 149 } 150 }, 151 slideChange: function(){
152 // assicura immediata riassegnazione durante cambio indice 153 applyDetachInner(this); 154 }, 155 afterInit: function(){ 156 // imposta correttamente al primo load 157 applyDetachInner(this); 158 } 159 }, 160 }); 161 162 // Funzione per aggiornare gli stati delle slide (opacità e scala) 163 function updateSlideStates(swiper) { 164 const slides = swiper.slides; 165 166 slides.forEach((slide, index) => { 167 // Rimuovi tutte le classi di stato 168 slide.classList.remove('slide-active', 'slide-prev', 'slide-next'); 169 170 // La slide attiva è quella centrale 171 if (slide.classList.contains('swiper-slide-active')) { 172 slide.classList.add('slide-active'); 173 } 174 // Slide precedente 175 else if (slide.classList.contains('swiper-slide-prev')) { 176 slide.classList.add('slide-prev'); 177 } 178 // Slide successiva 179 else if (slide.classList.contains('swiper-slide-next')) { 180 slide.classList.add('slide-next'); 181 } 182 }); 183 } 184 185 // Effetto di distacco SOLO sugli elementi interni (non sulla slide intera) 186 function applyDetachInner(swiper) { 187 const slides = swiper.slides; 188 const isMobile = window.innerWidth < 1024; 189 190 slides.forEach((slide) => { 191 const p = slide.progress || 0; // -2 .. 2 192 const ap = Math.min(2, Math.abs(p)); 193 194 const content = slide.querySelector('.card-item-inner'); 195 const bg = slide.querySelector('.card_bg_rotate'); 196 197 if (isMobile) { 198 // MOBILE: Solo rotate ridotto (1deg), no translateY, no scale 199 if (bg) { 200 const angle = ap < 0.5 ? -1 : 1; // 1deg invece di 4deg 201 bg.style.transform = `rotate(${angle}deg)`; 202 } 203 if (content) { 204 content.style.transform = 'none'; // Nessun transform 205 } 206 } else { 207 // DESKTOP: Comportamento originale 208 // Offset progressivo: 0 -> 0px (centro), 1 -> 12px (prev/next), 2 -> 20px (fuori) 209 const offset = ap <= 1 ? (ap * 12) : (12 + (8 * (ap - 1))); 210 211 if (content) { 212 content.style.transform = `translateY(${offset}px)`; 213 } 214 if (bg) { 215 // Vicino al centro: -4deg; man mano che si allontana: 4deg 216 const angle = ap < 0.5 ? -4 : 4; 217 bg.style.transform = `rotate(${angle}deg) translateY(${Math.round(offset * 1.2)}px)`; 218 } 219 } 220 }); 221 } 222 223 // Esponi l'istanza Swiper globalmente se necessario 224 window.threeCardSwiper = threeCardSwiper; 225 }); 226 227})(); 228
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.