1(function () { 2 function rafThrottle(fn) { 3 let ticking = false; 4 return function () { 5 if (ticking) return; 6 ticking = true; 7 requestAnimationFrame(() => { 8 ticking = false; 9 fn(); 10 }); 11 }; 12 } 13 14 function clamp(n, min, max) { 15 return Math.max(min, Math.min(max, n)); 16 } 17 18 function initSlider(root) { 19 const track = root.querySelector(".lnv-pubbl-track"); 20 const dotsWrap = root.querySelector(".lnv-pubbl-dots"); 21 if (!track || !dotsWrap) return; 22 23 const cards = Array.from(root.querySelectorAll(".lnv-pubbl-card")); 24 const count = cards.length; 25 if (!count) return; 26 27 // â 4 breakpoint config (da dataset) 28 const slidesL = parseInt(root.dataset.slidesL || "3", 10); // >=1200 29 const slidesD = parseInt(root.dataset.slidesD || "2", 10); // 1024-1199 30 const slidesT = parseInt(root.dataset.slidesT || "1", 10); // 768-1023 31 const slidesM = parseInt(root.dataset.slidesM || "1", 10); // <768 32 33 const peekL = parseFloat(root.dataset.peekL || root.dataset.peekD || "0.25"); 34 const peekD = parseFloat(root.dataset.peekD || "0.20"); 35 const peekT = parseFloat(root.dataset.peekT || "0.15"); 36 const peekM = parseFloat(root.dataset.peekM || "0.15"); 37 38 root.style.setProperty("--count", String(count)); 39 40 let currentSlides = 2; 41 let pageStarts = []; 42 let dots = []; 43 let activePage = 0; 44 45 // Lock per evitare rimbalzi durante smooth scroll 46 let isProgrammatic = false; 47 let programTarget = 0; 48 49 // IntersectionObserver per mobile swipe (più affidabile) 50 let io = null; 51 let ioPageCards = []; 52 53 function getDeviceConfig() { 54 // â breakpoints: 55 // large >=1200 56 // desktop 1024-1199 57 // tablet 768-1023 58 // mobile <768 59 if (window.matchMedia("(min-width: 1200px)").matches) return { slides: slidesL, peek: peekL }; 60 if (window.matchMedia("(min-width: 1024px)").matches) return { slides: slidesD, peek: peekD }; 61 if (window.matchMedia("(min-width: 768px)").matches) return { slides: slidesT, peek: peekT }; 62 return { slides: slidesM, peek: peekM }; 63 } 64 65 function setActive(pageIndex) { 66 if (!dots.length) return; 67 68 const idx = clamp(pageIndex, 0, dots.length - 1); 69 activePage = idx; 70 71 dots.forEach((d, i) => { 72 const on = i === idx; 73 d.setAttribute("aria-current", on ? "true" : "false"); 74 d.classList.toggle("is-active", on); 75 }); 76 } 77 78 function applyLayout() { 79 const { slides, peek } = getDeviceConfig(); 80 currentSlides = Math.max(1, slides); 81 82 if (count <= currentSlides) { 83 root.classList.add("is-fit"); 84 root.style.setProperty("--perview", String(count)); 85 dotsWrap.style.display = "none"; 86 } else { 87 root.classList.remove("is-fit"); 88 root.style.setProperty("--perview", String(currentSlides + Math.max(0, peek))); 89 dotsWrap.style.display = ""; 90 } 91 } 92 93 function computePages() { 94 pageStarts = []; 95 if (count <= currentSlides) return; 96 97 const pages = Math.ceil(count / currentSlides); 98 for (let p = 0; p < pages; p++) { 99 const idx = p * currentSlides; 100 const card = cards[Math.min(idx, count - 1)]; 101 pageStarts.push(card.offsetLeft); // relativo al track 102 } 103 } 104 105 function buildDots() { 106 dotsWrap.innerHTML = ""; 107 dots = []; 108 109 if (count <= currentSlides) return; 110 111 for (let p = 0; p < pageStarts.length; p++) { 112 const b = document.createElement("button"); 113 b.type = "button"; 114 b.className = "lnv-pubbl-dot"; 115 b.setAttribute("aria-label", `Vai alla pagina ${p + 1}`); 116 b.addEventListener("click", () => goToPage(p, "smooth")); 117 dotsWrap.appendChild(b); 118 dots.push(b); 119 } 120 } 121 122 function goToPage(p, behavior) { 123 if (!pageStarts.length) return; 124 125 const idx = clamp(p, 0, pageStarts.length - 1); 126 127 isProgrammatic = true; 128 programTarget = idx;
129 setActive(idx); // attivo subito quello cliccato 130 131 track.scrollTo({ 132 left: pageStarts[idx], 133 behavior: behavior || "smooth", 134 }); 135 } 136 137 // fallback: calcola pagina attiva dal scrollLeft 138 function getActivePageFromScroll() { 139 if (!pageStarts.length) return 0; 140 141 const left = track.scrollLeft; 142 let best = 0; 143 let bestDist = Infinity; 144 145 for (let i = 0; i < pageStarts.length; i++) { 146 const dist = Math.abs(pageStarts[i] - left); 147 if (dist < bestDist) { 148 bestDist = dist; 149 best = i; 150 } 151 } 152 return best; 153 } 154 155 // â IntersectionObserver (soluzione robusta su mobile) 156 function setupObserver() { 157 if (!("IntersectionObserver" in window)) return null; 158 159 // osserva la prima card di ogni pagina: 0, currentSlides, 2*currentSlides... 160 ioPageCards = []; 161 const pages = Math.ceil(count / currentSlides); 162 for (let p = 0; p < pages; p++) { 163 ioPageCards.push(cards[Math.min(p * currentSlides, count - 1)]); 164 } 165 166 const observer = new IntersectionObserver( 167 (entries) => { 168 // se sto scrollando programmaticamente, non farti ârubareâ lâattivo 169 if (isProgrammatic) return; 170 171 let bestIdx = activePage; 172 let bestRatio = 0; 173 174 for (const e of entries) { 175 if (!e.isIntersecting) continue; 176 const idx = ioPageCards.indexOf(e.target); 177 if (idx >= 0 && e.intersectionRatio > bestRatio) { 178 bestRatio = e.intersectionRatio; 179 bestIdx = idx; 180 } 181 } 182
183 setActive(bestIdx); 184 }, 185 { 186 root: track, 187 // quando circa metà della pagina è visibile, aggiorna il dot 188 threshold: [0.5, 0.6, 0.7], 189 } 190 ); 191 192 ioPageCards.forEach((el) => el && observer.observe(el)); 193 return observer; 194 } 195 196 const onScroll = rafThrottle(() => { 197 if (!pageStarts.length) return; 198 199 if (isProgrammatic) { 200 const targetLeft = pageStarts[programTarget]; 201 const delta = Math.abs(track.scrollLeft - targetLeft); 202 203 // tolleranza piccola (alcuni browser non arrivano mai a 0 perfetto) 204 if (delta <= 3) { 205 isProgrammatic = false; 206 setActive(programTarget); 207 } else { 208 setActive(programTarget); 209 } 210 return; 211 } 212 213 // se IO non c'è, aggiorna da scrollLeft 214 if (!io) { 215 setActive(getActivePageFromScroll()); 216 } 217 }); 218 219 if (!root.__lnvPubblInited) { 220 root.__lnvPubblInited = true; 221 222 track.addEventListener("scroll", onScroll, { passive: true }); 223 224 // â su mobile: se lâutente tocca/trascina, sblocca il lock programmatico 225 track.addEventListener( 226 "touchstart", 227 () => { 228 isProgrammatic = false; 229 }, 230 { passive: true } 231 ); 232 track.addEventListener( 233 "pointerdown", 234 () => { 235 isProgrammatic = false; 236 }, 237 { passive: true } 238 ); 239 240 track.addEventListener("keydown", (e) => { 241 if (!pageStarts.length) return; 242 if (e.key === "ArrowRight") { 243 e.preventDefault(); 244 goToPage(activePage + 1, "smooth"); 245 } else if (e.key === "ArrowLeft") { 246 e.preventDefault(); 247 goToPage(activePage - 1, "smooth"); 248 } 249 }); 250 } 251 252 function rebuild(keepPage) { 253 const prev = activePage; 254 255 applyLayout(); 256 computePages(); 257 buildDots(); 258 259 // ricrea observer dopo ogni cambio layout 260 if (io) { 261 io.disconnect(); 262 io = null; 263 } 264 io = setupObserver(); 265 266 const target = keepPage 267 ? clamp(prev, 0, pageStarts.length ? pageStarts.length - 1 : 0) 268 : 0; 269 270 isProgrammatic = false; 271 programTarget = target; 272 273 setActive(target); 274 if (pageStarts.length) { 275 track.scrollTo({ left: pageStarts[target], behavior: "auto" }); 276 } 277 278 // per sicurezza: dopo un attimo ricalcola ancora (font/immagini) 279 window.clearTimeout(root.__lnvRebuildT); 280 root.__lnvRebuildT = window.setTimeout(() => { 281 computePages(); 282 // se IO non esiste, riallinea il pallino 283 if (!io) setActive(getActivePageFromScroll()); 284 }, 180); 285 } 286 287 rebuild(false); 288 window.setTimeout(() => rebuild(true), 150); 289 290 let rT; 291 window.addEventListener( 292 "resize", 293 () => { 294 clearTimeout(rT); 295 rT = setTimeout(() => rebuild(true), 120); 296 }, 297 { passive: true } 298 ); 299 } 300 301 function boot() { 302 document.querySelectorAll(".lnv-pubbl-slider[data-lnv-pubbl-slider]").forEach(initSlider); 303 } 304 305 if (document.readyState === "loading") { 306 document.addEventListener("DOMContentLoaded", boot); 307 } else { 308 boot(); 309 } 310})();
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.