1(function(){ 2 'use strict'; 3 4 // ===== Config ===== 5 var DEFAULT_OPEN_MODE = 'fit'; 6 7 // ===== Stato ===== 8 var modal, stage, img, closeBtn, captionEl, captionWrap, toolbar; 9 var prevBtn, nextBtn; 10 11 var scale=1, fitScale=1, minScale=0.1, maxScale=8, tx=0, ty=0; 12 var dragging=false, sx=0, sy=0, stx=0, sty=0, bound=false; 13 14 var galleryEls=[], galleryIdx=-1, lastOpenMode=DEFAULT_OPEN_MODE, currentEl=null; 15 16 // ===== Modal ===== 17 function ensureModal(){ 18 if (document.getElementById('operaModal')){ 19 modal = document.getElementById('operaModal'); 20 if (!bound) { refs(); bind(); } 21 return modal; 22 } 23 24 modal = document.createElement('div'); 25 modal.id='operaModal'; 26 modal.className='w3-modal'; 27 modal.style.zIndex='99999'; 28 modal.setAttribute('aria-hidden','true'); 29 modal.addEventListener('click', function(e){ if(e.target===modal) closeOpera(); }); 30 31 // HTML MINIMALE + FRECCE 32 modal.innerHTML = 33 '<div class="w3-modal-content w3-animate-opacity" style="background:transparent;box-shadow:none;position:relative;overflow:hidden;">' 34 + ' <div id="operaStage" style="position:fixed;inset:0;width:100vw;height:100vh;overflow:hidden;">' 35 + ' <button type="button" id="operaClose" aria-label="Chiudi">Ã</button>' 36 + ' <button type="button" id="operaPrev" aria-label="Precedente">â¹</button>' 37 + ' <button type="button" id="operaNext" aria-label="Successiva">âº</button>' 38 + ' <img id="operaImg" alt="" style="position:absolute;left:0;top:0;max-width:none;max-height:none;width:auto;height:auto;will-change:transform;transform-origin:0 0;user-select:none;-webkit-user-drag:none;pointer-events:auto;">' 39 + ' </div>' 40+ ' <div id="operaCaptionWrap">' 41+ ' <div id="operaCaption"></div>' 42+ ' <a id="operaArtistLink" href="#" target="_blank" style="display:none;">Pagina artista</a>' 43+ ' </div>' 44 + ' <div id="operaToolbar">' 45 + ' <button type="button" id="operaZoomOut" title="Zoom -">â</button>' 46 + ' <div class="hint">Rotellina = zoom</div>' 47 + ' <button type="button" id="operaZoomIn" title="Zoom +">+</button>' 48 + ' </div>' 49 + '</div>'; 50 51 document.body.appendChild(modal); 52 53 refs(); 54 injectStyles(); 55 bind(); 56 57 return modal; 58 } 59 60 function refs(){ 61 modal = document.getElementById('operaModal'); 62 stage = document.getElementById('operaStage'); 63 img = document.getElementById('operaImg'); 64 closeBtn = document.getElementById('operaClose'); 65 prevBtn = document.getElementById('operaPrev'); 66 nextBtn = document.getElementById('operaNext'); 67 captionWrap = document.getElementById('operaCaptionWrap'); 68 captionEl = document.getElementById('operaCaption'); 69 toolbar = document.getElementById('operaToolbar'); 70 } 71 72 function injectStyles(){ 73 if (document.getElementById('operaModalMiniCSS')) return; 74 var st = document.createElement('style'); 75 st.id = 'operaModalMiniCSS'; 76 st.textContent = ` 77#operaClose{ 78 position:fixed; top:14px; right:14px; 79 width:42px; height:42px; border-radius:999px; 80 border:1px solid rgba(255,255,255,.25); 81 background:rgba(0,0,0,.45); 82 color:#fff; font-size:22px; line-height:40px; 83 text-align:center; cursor:pointer; 84 z-index:1000000; 85} 86 87#operaToolbar{ 88 position:fixed; left:50%; bottom:18px; 89 transform:translateX(-50%); 90 display:flex; align-items:center; gap:14px; 91 background:rgba(0,0,0,.55); 92 border:1px solid rgba(255,255,255,.18); 93 padding:10px 14px; border-radius:999px; 94 z-index:1000000; 95} 96#operaToolbar button{ 97 width:44px; height:44px; border-radius:999px; 98 border:1px solid rgba(255,255,255,.25); 99 background:rgba(255,255,255,.08); 100 color:#fff; font-size:22px; cursor:pointer; 101} 102#operaToolbar .hint{ 103 color:rgba(255,255,255,.75); 104 font-size:12px; white-space:nowrap; 105} 106 107#operaCaptionWrap{ 108 position:fixed; 109 top:12px; 110 left:50%; 111 transform:translateX(-50%); 112 max-width:calc(100vw - 140px); 113 text-align:center !important; 114 padding:6px 10px; 115 z-index:1000000; 116 pointer-events:none; 117} 118#operaCaption{ 119 display:inline-block; 120 max-width:min(1100px, 92vw); 121 margin:0 auto !important; 122 padding:6px 12px; 123 border-radius:999px; 124 background:rgba(0,0,0,.35); 125 border:1px solid rgba(255,255,255,.18); 126 color:#fff; 127 text-align:center !important; 128 font-size:14px; 129} 130 131#operaArtistLink{ 132 display:inline-block; 133 margin-top:6px; 134 padding:5px 12px; 135 border-radius:999px; 136 background:rgba(0,0,0,.55); 137 border:1px solid rgba(255,255,255,.35); 138 color:#fff; 139 text-decoration:none; 140 font-size:12px; 141 pointer-events:auto; 142} 143 144#operaArtistLink:hover{ 145 background:rgba(255,255,255,.18); 146} 147 148/* Frecce */ 149#operaPrev,#operaNext{ 150 position:fixed; 151 top:50%; 152 transform:translateY(-50%); 153 width:54px; height:54px; 154 border-radius:999px; 155 border:1px solid rgba(255,255,255,.22); 156 background:rgba(0,0,0,.35); 157 color:#fff; 158 font-size:36px; 159 line-height:48px; 160 text-align:center; 161 cursor:pointer; 162 z-index:1000000; 163}
164#operaPrev{ left:14px; } 165#operaNext{ right:14px; } 166 167#operaStage{ cursor:default; } 168#operaImg{ 169 transform: translate(0px,0px) scale(1); 170 transition: transform 80ms linear; 171 cursor: grab; 172} 173#operaImg:active{ cursor: grabbing; } 174 175@media (max-width:600px){ 176 #operaToolbar{ bottom:12px; padding:8px 12px; gap:12px; } 177 #operaToolbar button{ width:42px; height:42px; } 178 #operaPrev,#operaNext{ width:48px; height:48px; font-size:32px; line-height:44px; } 179} 180`; 181 document.head.appendChild(st); 182 } 183 184 // ===== Geometria ===== 185 function stageSize(){ return { w: stage.clientWidth, h: stage.clientHeight }; } 186 187 function computeFitScale(){ 188 var s = stageSize(); 189 fitScale = Math.min( 190 s.w / img.naturalWidth, 191 s.h / img.naturalHeight 192) || 1; 193 } 194 195 function apply(){ 196 img.style.transform = 'translate(' + tx + 'px,' + ty + 'px) scale(' + scale + ')'; 197 } 198 199 function centerAtCurrentScale(){ 200 var s=stageSize(), iw=img.naturalWidth*scale, ih=img.naturalHeight*scale; 201 tx = Math.round((s.w - iw)/2); 202 ty = Math.round((s.h - ih)/2); 203 apply(); 204 } 205 206 function clamp(v,a,b){ return Math.max(a, Math.min(b, v)); } 207 208 function zoomTo(newScale, px, py){ 209 newScale = clamp(newScale, minScale, maxScale); 210 var prev = scale; 211 if (Math.abs(newScale-prev) < 0.00001) return; 212 213 var dx = px - tx; 214 var dy = py - ty; 215 var k = newScale/prev; 216 tx = px - dx*k; 217 ty = py - dy*k; 218 219 scale = newScale; 220 apply(); 221 } 222 223 function zoomIn(){ 224 var r=stage.getBoundingClientRect(); 225 zoomTo(scale*1.15, r.width/2, r.height/2); 226 } 227 function zoomOut(){ 228 var r=stage.getBoundingClientRect(); 229 zoomTo(scale/1.15, r.width/2, r.height/2); 230 } 231 function zoomFit(){ 232 computeFitScale(); 233 scale = fitScale; 234 centerAtCurrentScale(); 235 } 236 237 // ===== Gallery ===== 238 function getSrcFromEl(el){ 239 if (!el) return ''; 240 var tag = (el.tagName || '').toUpperCase(); 241 if (tag === 'AREA' || tag === 'A') { 242 return el.getAttribute('data-opera-src') || el.getAttribute('href') || ''; 243 } 244 return el.getAttribute('data-full') || 245 el.getAttribute('data-src') || 246 el.currentSrc || 247 el.src || 248 ''; 249 } 250 251 252function buildGalleryFrom(el){ 253 // â caso MAP <area>: galleria = tutte le <area href> della stessa mappa 254 if (el && (el.tagName || '').toUpperCase() === 'AREA'){ 255 var map = el.closest ? el.closest('map') : null; 256 var listA = map ? map.querySelectorAll('area[href], area[data-opera-src]') : null; 257 galleryEls = Array.prototype.slice.call(listA || [el]); 258 galleryIdx = galleryEls.indexOf(el); 259 if (galleryIdx < 0) galleryIdx = 0; 260 return; 261 } 262 263 // 1) prova a trovare il contenitore âgalleriaâ più vicino 264 var root = 265 (el.closest && (el.closest('.gallery') || el.closest('.gallery-grid') || el.closest('.search-gallery'))) 266 || null; 267 268 // 2) se non lo trova, usa fallback: tutte le immagini "cliccabili" 269 var list; 270 if (root) { 271 list = root.querySelectorAll('img'); 272 } else { 273 // list = document.querySelectorAll('img.zoomable,[data-zoom],[data-full],[data-opera],.search-gallery img,.gallery img,.gallery-grid img'); 274 275 list = document.querySelectorAll( 276 'img.zoomable,' + 277 'img[onclick*="onClick"],' + 278 '[data-zoom],[data-full],[data-opera],' + 279 '.search-gallery img,.gallery img,.gallery-grid img' 280); 281 282 } 283 284 galleryEls = Array.prototype.slice.call(list || []); 285 galleryIdx = galleryEls.indexOf(el); 286 287 // 3) se non lo trova ancora, prova a matchare per src 288 if (galleryIdx < 0) { 289 var src = getSrcFromEl(el); 290 for (var i=0;i<galleryEls.length;i++){ 291 if (getSrcFromEl(galleryEls[i]) === src) { galleryIdx = i; break; } 292 } 293 } 294 295 if (galleryIdx < 0) galleryIdx = 0; 296} 297 298 299 function updateCaption(){ 300 if (!captionEl) return; 301 302 var txt = ''; 303 if (currentEl){ 304 txt = ( 305 currentEl.getAttribute('data-opera') || 306 currentEl.getAttribute('data-caption') || 307 currentEl.getAttribute('alt') || 308 currentEl.getAttribute('title') || 309 '' 310 ).trim(); 311 } 312 313 // contatore (se galleria) 314 if (galleryEls && galleryEls.length > 1 && galleryIdx >= 0){ 315 var count = (galleryIdx + 1) + '/' + galleryEls.length; 316 captionEl.textContent = txt ? (txt + ' · ' + count) : count; 317 } else { 318 captionEl.textContent = txt; 319 } 320
321 // titolo sempre centrato (utile soprattutto per le <area>) 322 captionEl.style.textAlign = 'center'; 323 324var artistLink = document.getElementById('operaArtistLink'); 325 326if (artistLink){ 327 var linkUrl = currentEl 328 ? (currentEl.getAttribute('data-link-url') || 329 currentEl.getAttribute('data-artista-url') || '') 330 : ''; 331 332 var linkLabel = currentEl 333 ? (currentEl.getAttribute('data-link-label') || 334 (currentEl.getAttribute('data-artista-url') ? 'Pagina artista' : '')) 335 : ''; 336 337 if (linkUrl){ 338 artistLink.href = linkUrl; 339 artistLink.textContent = linkLabel || 'Apri pagina'; 340 artistLink.style.display = 'inline-block'; 341 } else { 342 artistLink.removeAttribute('href'); 343 artistLink.textContent = ''; 344 artistLink.style.display = 'none'; 345 } 346} 347 348 } 349 350 function updateArrows(){ 351 var show = (galleryEls && galleryEls.length > 1); 352 if (prevBtn) prevBtn.style.display = show ? 'block' : 'none'; 353 if (nextBtn) nextBtn.style.display = show ? 'block' : 'none'; 354 } 355 356 function showAt(idx){ 357 if (!galleryEls || !galleryEls.length) return; 358 var len = galleryEls.length; 359 galleryIdx = (idx + len) % len; 360 361 var el = galleryEls[galleryIdx]; 362 currentEl = el; 363 364 var src = getSrcFromEl(el); 365 img.onload = function(){ 366 zoomFit(); 367 updateCaption(); 368 updateArrows(); 369 }; 370 img.src = src; 371 } 372 373 function prevImage(){ if (galleryEls.length>1) showAt(galleryIdx-1); } 374 function nextImage(){ if (galleryEls.length>1) showAt(galleryIdx+1); } 375 376 // ===== Bind ===== 377 function bind(){ 378 if (bound) return; 379 bound=true; 380 381 closeBtn.addEventListener('click', closeOpera); 382 prevBtn.addEventListener('click', function(e){ e.stopPropagation(); prevImage(); }); 383 nextBtn.addEventListener('click', function(e){ e.stopPropagation(); nextImage(); }); 384 385 document.getElementById('operaZoomIn').addEventListener('click', function(e){ e.stopPropagation(); zoomIn(); }); 386 document.getElementById('operaZoomOut').addEventListener('click', function(e){ e.stopPropagation(); zoomOut(); }); 387 388 // Drag pan 389 stage.addEventListener('mousedown', function(e){ 390 // non iniziare drag se clicchi bottoni 391 if (e.target && (e.target.id==='operaClose' || e.target.id==='operaPrev' || e.target.id==='operaNext' || e.target.closest('#operaToolbar'))) return; 392 dragging=true; sx=e.clientX; sy=e.clientY; stx=tx; sty=ty; 393 e.preventDefault(); 394 }); 395 window.addEventListener('mousemove', function(e){ 396 if(!dragging) return; 397 tx=stx+(e.clientX-sx); 398 ty=sty+(e.clientY-sy); 399 apply(); 400 }); 401 window.addEventListener('mouseup', function(){ dragging=false; }); 402 403 // Rotellina zoom 404 stage.addEventListener('wheel', function(e){ 405 if (modal.style.display !== 'block') return; 406 e.preventDefault(); 407 var r=stage.getBoundingClientRect(); 408 var px=e.clientX - r.left; 409 var py=e.clientY - r.top; 410 zoomTo(scale * ((e.deltaY>0)? 1/1.1 : 1.1), px, py); 411 }, { passive:false }); 412 413 // Tastiera 414 document.addEventListener('keydown', function(e){ 415 if (!modal || modal.style.display!=='block') return; 416 if (e.key==='Escape') closeOpera(); 417 if (e.key==='ArrowLeft') prevImage(); 418 if (e.key==='ArrowRight') nextImage(); 419 if (e.key==='+' || e.key==='=') zoomIn(); 420 if (e.key==='-') zoomOut(); 421 }); 422 } 423 424 // ===== API ===== 425 window.openOpera = function(src, opts){ 426 ensureModal(); 427 opts = opts || {}; 428 lastOpenMode = (opts.mode || DEFAULT_OPEN_MODE); 429 430 scale=1; tx=0; ty=0; 431 img.style.transform='translate(0,0) scale(1)'; 432 433 if (opts.el){ 434 currentEl = opts.el; 435 buildGalleryFrom(opts.el); 436 updateCaption(); 437 updateArrows(); 438 // mostra l'immagine corretta dalla galleria 439 showAt(galleryIdx); 440 } else { 441 // singola immagine âfuori galleriaâ 442 galleryEls = []; 443 galleryIdx = -1; 444 currentEl = null; 445 if (src){ 446 img.onload = function(){ zoomFit(); updateCaption(); updateArrows(); }; 447 img.src = src; 448 } 449 updateArrows(); 450 } 451 452 modal.style.display='block'; 453 document.body.style.overflow='hidden'; 454 }; 455 456 window.closeOpera = function(){ closeOpera(); }; 457 458 function closeOpera(){ 459 if (!modal) return; 460 modal.style.display='none'; 461 img.removeAttribute('src'); 462 document.body.style.overflow=''; 463 } 464 465 // Auto-hook (se clicchi img.zoomable / data-full / data-zoom) 466 (function autoHook(){ 467 /* function pick(el){ 468 if (!el) return null; 469 if (el.matches && el.matches('img.zoomable,[data-zoom],[data-full],[data-opera]')) return el; 470 if (el.closest) return el.closest('img.zoomable,[data-zoom],[data-full],[data-opera]'); 471 return null; 472 } */ 473 474 function pick(el){ 475 if (!el) return null; 476 477 var selector = 478 'img.zoomable,' + 479 'img[onclick*="onClick"],' + 480 '[data-zoom],[data-full],[data-opera]'; 481 482 if (el.matches && el.matches(selector)) return el; 483 if (el.closest) return el.closest(selector); 484 485 return null; 486} 487 488 document.addEventListener('click', function(e){ 489 var t = pick(e.target); 490 if (!t) return; 491 e.preventDefault(); 492 e.stopPropagation(); 493 var url = getSrcFromEl(t); 494 if (url) openOpera(url, { el:t }); 495 }, true); 496 })(); 497 498})();
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.