PageSourceSearch

https://www.atelierdesarts.com/js/modal-con-zoom.js?v=20260212-1

js atelierdesarts.com collected 2026-10-02 13:33:11 UTC 14,634 bytes, 498 lines download raw bytes

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.