1(function () { 2 const MAX_DOTS = 5; 3 4 const reduceMotion = 5 window.matchMedia && 6 window.matchMedia("(prefers-reduced-motion: reduce)").matches; 7 8 const BEHAVIOR = reduceMotion ? "auto" : "smooth"; 9 10 function clamp(n, min, max) { 11 return Math.max(min, Math.min(max, n)); 12 } 13 14 function isDesktopMode() { 15 return ( 16 window.matchMedia && 17 window.matchMedia("(min-width: 992px)").matches && 18 window.matchMedia("(pointer: fine)").matches && 19 window.matchMedia("(hover: hover)").matches 20 ); 21 } 22 23 // ========================= 24 // PhotoSwipe SOLO manifesto 25 // ========================= 26function initManifestoPhotoSwipe() { 27 if (!window.PhotoSwipeLightbox || !window.PhotoSwipe) return; 28 29 const sizeCache = new Map(); 30 31 function getImageSize(url) { 32 if (sizeCache.has(url)) return Promise.resolve(sizeCache.get(url)); 33 34 return new Promise((resolve, reject) => { 35 const img = new Image(); 36 img.onload = () => { 37 const w = img.naturalWidth || 0; 38 const h = img.naturalHeight || 0; 39 if (w && h) { 40 const v = { w, h }; 41 sizeCache.set(url, v); 42 resolve(v); 43 } else { 44 reject(new Error("No natural size")); 45 } 46 }; 47 img.onerror = () => reject(new Error("Image load error")); 48 img.src = url; 49 }); 50 } 51 52 // Lightbox âmanualeâ: la apriamo noi con loadAndOpen() 53 const lb = new PhotoSwipeLightbox({ 54 pswpModule: PhotoSwipe, 55 }); 56 lb.init(); 57 58 // Intercetta click prima che altri handler possano intervenire 59 document.addEventListener( 60 "click", 61 async function (e) { 62 const a = e.target.closest("a.lnv-pswp-manifesto"); 63 if (!a) return; 64 65 const href = a.getAttribute("href") || ""; 66 if (!href) return; 67 68 const clean = href.split("?")[0].toLowerCase(); 69 70 // PDF: apri in nuova scheda 71 if (clean.endsWith(".pdf")) { 72 e.preventDefault(); 73 window.open(a.href, "_blank", "noopener"); 74 return; 75 } 76 77 // Immagine: apri in PhotoSwipe con dimensioni REALI 78 e.preventDefault(); 79 e.stopPropagation(); 80 81 try { 82 const { w, h } = await getImageSize(href); 83 84 lb.loadAndOpen(0, [ 85 { 86 src: href, 87 width: w, 88 height: h, 89 // compat extra 90 w: w, 91 h: h, 92 }, 93 ]); 94 } catch (err) { 95 // fallback sicuro: apri lâimmagine in nuova scheda 96 window.open(a.href, "_blank", "noopener"); 97 } 98 }, 99 true 100 ); 101} 102 103 104 // ========================= 105 // Carousel helpers 106 // ========================= 107 function findClosestIndexByRects(track, slides) { 108 const trackRect = track.getBoundingClientRect(); 109 let bestIdx = 0; 110 let bestDist = Infinity; 111 112 for (let i = 0; i < slides.length; i++) { 113 const r = slides[i].getBoundingClientRect(); 114 const dist = Math.abs(r.left - trackRect.left); 115 if (dist < bestDist) { 116 bestDist = dist; 117 bestIdx = i; 118 } 119 } 120 return bestIdx; 121 } 122 123 function getTargetLeftByRects(track, slide) { 124 const trackRect = track.getBoundingClientRect(); 125 const slideRect = slide.getBoundingClientRect(); 126 127 const delta = slideRect.left - trackRect.left; 128 const targetLeft = track.scrollLeft + delta; 129 const maxLeft = track.scrollWidth - track.clientWidth; 130 131 return clamp(targetLeft, 0, maxLeft); 132 } 133 134 // ========================= 135 // Dots UI 136 // ========================= 137 function ensureDotsUI(root, label) { 138 let dots = root.querySelector(".lnv-carousel-dots"); 139 if (!dots) { 140 dots = document.createElement("div"); 141 dots.className = "lnv-carousel-dots"; 142 root.appendChild(dots); 143 } 144 145 if (reduceMotion) dots.classList.add("is-reduced-motion"); 146 147 dots.setAttribute("role", "tablist"); 148 dots.setAttribute("aria-label", label || "Paginazione"); 149 dots.innerHTML = ""; 150 151 const dotsTrack = document.createElement("div"); 152 dotsTrack.className = "lnv-carousel-dots-track"; 153 dots.appendChild(dotsTrack); 154
155 let counter = root.querySelector(".lnv-carousel-counter"); 156 if (!counter) { 157 counter = document.createElement("div"); 158 counter.className = "lnv-carousel-counter"; 159 counter.setAttribute("aria-hidden", "true"); 160 root.appendChild(counter); 161 } 162 163 return { dots, dotsTrack, counter, buttons: [], dotStep: 0 }; 164 } 165 166 function computeDotStep(ui) { 167 const csDots = getComputedStyle(ui.dots); 168 const dot = parseFloat(csDots.getPropertyValue("--dot")) || 0; 169 const gap = parseFloat(csDots.getPropertyValue("--gap")) || 0; 170 if (dot > 0) return dot + gap; 171 172 const first = ui.buttons[0]; 173 if (!first) return 0; 174 175 const w = first.getBoundingClientRect().width; 176 const csTrack = getComputedStyle(ui.dotsTrack); 177 const g = parseFloat(csTrack.columnGap || csTrack.gap || "0") || 0; 178 179 return w + g; 180 } 181 182 function buildAllDots(ui, total, onGo) { 183 ui.dotsTrack.innerHTML = ""; 184 ui.buttons = []; 185 186 for (let i = 0; i < total; i++) { 187 const btn = document.createElement("button"); 188 btn.type = "button"; 189 btn.className = "lnv-carousel-dot"; 190 btn.setAttribute("aria-label", `Vai all'elemento ${i + 1} di ${total}`); 191 btn.setAttribute("aria-current", "false"); 192 btn.addEventListener("click", () => onGo(i)); 193 ui.dotsTrack.appendChild(btn); 194 ui.buttons.push(btn); 195 } 196 197 ui.dots.style.setProperty("--max-dots", String(Math.min(MAX_DOTS, total))); 198 199 requestAnimationFrame(() => { 200 ui.dotStep = computeDotStep(ui); 201 }); 202 } 203 204 function updateDots(ui, active, total) { 205 for (let i = 0; i < ui.buttons.length; i++) { 206 const isActive = i === active; 207 ui.buttons[i].classList.toggle("is-active", isActive); 208 ui.buttons[i].setAttribute("aria-current", isActive ? "true" : "false"); 209 } 210 211 ui.counter.textContent = `${active + 1} / ${total}`; 212 213 if (total <= MAX_DOTS) { 214 ui.dotsTrack.style.transform = "translateX(0px)"; 215 return; 216 } 217 218 if (!ui.dotStep) return; 219 220 const start = clamp(active - 2, 0, total - MAX_DOTS); 221 ui.dotsTrack.style.transform = `translateX(${-(start * ui.dotStep)}px)`; 222 } 223 224 // ========================= 225 // Init carousel (per root) 226 // ========================= 227 function initCarousel(root) { 228 if (root.dataset.lnvInit === "1") return; 229 root.dataset.lnvInit = "1"; 230 231 const track = root.querySelector(".lnv-carousel-track"); 232 const slides = Array.from(root.querySelectorAll(".lnv-carousel-slide")); 233 if (!track || slides.length < 2) return; 234 235 const label = 236 track.getAttribute("aria-label") || 237 root.getAttribute("data-carousel") || 238 "Paginazione"; 239 240 const ui = ensureDotsUI(root, "Paginazione " + label); 241 242 let currentIndex = 0; 243 let lockTargetLeft = null; 244 let isLocked = false; 245 246 function setActive(i) { 247 const next = clamp(i, 0, slides.length - 1); 248 if (next !== currentIndex) currentIndex = next; 249 updateDots(ui, currentIndex, slides.length); 250 } 251 252 function goTo(idx) { 253 const next = clamp(idx, 0, slides.length - 1); 254 if (next === currentIndex) return; 255 256 const targetLeft = getTargetLeftByRects(track, slides[next]); 257 lockTargetLeft = targetLeft; 258 isLocked = true; 259 260 setActive(next); 261 track.scrollTo({ left: targetLeft, behavior: BEHAVIOR }); 262 } 263 264 buildAllDots(ui, slides.length, (goIdx) => goTo(goIdx)); 265 266 // Desktop drag prev/next 267 if (isDesktopMode()) { 268 (function enableDragPrevNext() { 269 const THRESHOLD = 45; 270 let pointerId = null; 271 let startX = 0; 272 let fired = false; 273 let suppressClickUntil = 0; 274 275 function isInteractiveTarget(el) { 276 return !!el.closest("a, button, input, textarea, select, label"); 277 } 278 279 function onPointerDown(e) { 280 if (e.pointerType === "mouse" && e.button !== 0) return; 281 if (isInteractiveTarget(e.target)) return; 282 283 pointerId = e.pointerId; 284 startX = e.clientX; 285 fired = false; 286
287 isLocked = false; 288 lockTargetLeft = null; 289 290 try { 291 track.setPointerCapture(pointerId); 292 } catch (err) {} 293 } 294 295 function onPointerMove(e) { 296 if (pointerId === null || e.pointerId !== pointerId) return; 297 if (fired) return; 298 299 const dx = e.clientX - startX; 300 301 if (Math.abs(dx) >= THRESHOLD) { 302 fired = true; 303 if (dx < 0) goTo(currentIndex + 1); 304 else goTo(currentIndex - 1); 305 306 e.preventDefault(); 307 suppressClickUntil = Date.now() + 350; 308 } 309 } 310 311 function onPointerUp(e) { 312 if (pointerId === null || e.pointerId !== pointerId) return; 313 pointerId = null; 314 } 315 316 track.addEventListener( 317 "click", 318 (e) => { 319 if (Date.now() < suppressClickUntil) { 320 e.preventDefault(); 321 e.stopPropagation(); 322 } 323 }, 324 true 325 ); 326 327 track.addEventListener("pointerdown", onPointerDown, { passive: true }); 328 track.addEventListener("pointermove", onPointerMove, { passive: false }); 329 track.addEventListener("pointerup", onPointerUp, { passive: true }); 330 track.addEventListener("pointercancel", onPointerUp, { passive: true }); 331 })(); 332 } 333 334 // scroll/swipe -> update index 335 let raf = 0; 336 let settleT = 0; 337 338 track.addEventListener( 339 "scroll", 340 () => { 341 if (raf) return; 342 343 raf = requestAnimationFrame(() => { 344 raf = 0; 345 346 if (isLocked && lockTargetLeft !== null) { 347 if (Math.abs(track.scrollLeft - lockTargetLeft) <= 2) { 348 isLocked = false; 349 lockTargetLeft = null; 350 } else { 351 return; 352 } 353 } 354 355 const idx = findClosestIndexByRects(track, slides);
356 setActive(idx); 357 358 clearTimeout(settleT); 359 settleT = setTimeout(() => { 360 setActive(findClosestIndexByRects(track, slides)); 361 }, 80); 362 }); 363 }, 364 { passive: true } 365 ); 366 367 // resize 368 let resizeT = 0; 369 window.addEventListener( 370 "resize", 371 () => { 372 clearTimeout(resizeT); 373 resizeT = setTimeout(() => { 374 ui.dotStep = computeDotStep(ui); 375 setActive(findClosestIndexByRects(track, slides)); 376 }, 120); 377 }, 378 { passive: true } 379 ); 380 381 setActive(0); 382 } 383 384 document.addEventListener("DOMContentLoaded", function () { 385 initManifestoPhotoSwipe(); 386 document.querySelectorAll(".lnv-carousel").forEach(initCarousel); 387 }); 388})();
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.