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 (più affidabile su mobile) 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 function getActivePageFromScroll() { 138 if (!pageStarts.length) return 0; 139 140 const left = track.scrollLeft; 141 let best = 0; 142 let bestDist = Infinity; 143 144 for (let i = 0; i < pageStarts.length; i++) { 145 const dist = Math.abs(pageStarts[i] - left); 146 if (dist < bestDist) { 147 bestDist = dist; 148 best = i; 149 } 150 } 151 return best; 152 } 153 154 // â IntersectionObserver setup: osserva la prima card di ogni "pagina" 155 function setupObserver() { 156 if (!("IntersectionObserver" in window)) return null; 157 if (count <= currentSlides) return null; 158 159 ioPageCards = []; 160 const pages = Math.ceil(count / currentSlides); 161 for (let p = 0; p < pages; p++) { 162 ioPageCards.push(cards[Math.min(p * currentSlides, count - 1)]); 163 } 164 165 const observer = new IntersectionObserver( 166 (entries) => { 167 if (isProgrammatic) return; 168 169 let bestIdx = activePage; 170 let bestRatio = 0; 171 172 for (const e of entries) { 173 if (!e.isIntersecting) continue; 174 const idx = ioPageCards.indexOf(e.target); 175 if (idx >= 0 && e.intersectionRatio > bestRatio) { 176 bestRatio = e.intersectionRatio; 177 bestIdx = idx; 178 } 179 } 180
181 setActive(bestIdx); 182 }, 183 { 184 root: track, 185 threshold: [0.5, 0.6, 0.7], 186 } 187 ); 188 189 ioPageCards.forEach((el) => el && observer.observe(el)); 190 return observer; 191 } 192 193 const onScroll = rafThrottle(() => { 194 if (!pageStarts.length) return; 195 196 if (isProgrammatic) { 197 const targetLeft = pageStarts[programTarget]; 198 const delta = Math.abs(track.scrollLeft - targetLeft); 199 200 if (delta <= 3) { 201 isProgrammatic = false; 202 setActive(programTarget); 203 } else { 204 setActive(programTarget); 205 } 206 return; 207 } 208 209 // fallback se IO non è disponibile 210 if (!io) { 211 setActive(getActivePageFromScroll()); 212 } 213 }); 214 215 if (!root.__lnvPubblInited) { 216 root.__lnvPubblInited = true; 217 218 track.addEventListener("scroll", onScroll, { passive: true }); 219 220 // â importantissimo su mobile: se lâutente tocca/trascina, sblocca il lock 221 track.addEventListener("touchstart", () => { isProgrammatic = false; }, { passive: true }); 222 track.addEventListener("pointerdown", () => { isProgrammatic = false; }, { passive: true }); 223 224 track.addEventListener("keydown", (e) => { 225 if (!pageStarts.length) return; 226 if (e.key === "ArrowRight") { 227 e.preventDefault(); 228 goToPage(activePage + 1, "smooth"); 229 } else if (e.key === "ArrowLeft") { 230 e.preventDefault(); 231 goToPage(activePage - 1, "smooth"); 232 } 233 }); 234 } 235 236 function rebuild(keepPage) { 237 const prev = activePage; 238 239 applyLayout(); 240 computePages(); 241 buildDots(); 242 243 // â ricrea observer (layout/breakpoint possono cambiare) 244 if (io) { 245 io.disconnect(); 246 io = null; 247 } 248 io = setupObserver(); 249 250 const target = keepPage 251 ? clamp(prev, 0, pageStarts.length ? pageStarts.length - 1 : 0) 252 : 0; 253 254 isProgrammatic = false; 255 programTarget = target; 256 257 setActive(target); 258 if (pageStarts.length) { 259 track.scrollTo({ left: pageStarts[target], behavior: "auto" }); 260 } 261 262 // â micro-rebuild post layout (font/immagini) 263 clearTimeout(root.__lnvPostLayoutT); 264 root.__lnvPostLayoutT = setTimeout(() => { 265 computePages(); 266 if (!io) setActive(getActivePageFromScroll()); 267 }, 180); 268 } 269 270 rebuild(false); 271 window.setTimeout(() => rebuild(true), 150); 272 273 let rT; 274 window.addEventListener( 275 "resize", 276 () => { 277 clearTimeout(rT); 278 rT = setTimeout(() => rebuild(true), 120); 279 }, 280 { passive: true } 281 ); 282 } 283 284 function boot() { 285 document.querySelectorAll(".lnv-pubbl-slider[data-lnv-pubbl-slider]").forEach(initSlider); 286 } 287 288 if (document.readyState === "loading") { 289 document.addEventListener("DOMContentLoaded", boot); 290 } else { 291 boot(); 292 } 293})();
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.