1/* ===================================================================== 2 LIGHTBOX (.lrf) â sostituisce swipebox nelle pagine rifatte. 3 Si aggancia ai link con data-lb; quelli con lo stesso data-lb-group 4 formano una galleria navigabile. 5 6 Foto: <a data-lb data-lb-group="galleria" href="grande.jpg"> 7 Video: <a data-lb data-lb-group="video" href="https://youtu.be/ID"> 8 (YouTube e Vimeo vengono riconosciuti e aperti a tutto schermo) 9 ===================================================================== */ 10(function () { 11 var links = Array.prototype.slice.call(document.querySelectorAll('[data-lb]')); 12 if (!links.length) { return; } 13 14 var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; 15 var box = null; 16 var stage = null; 17 var counter = null; 18 var caption = null; 19 var prevBtn = null; 20 var nextBtn = null; 21 var fsBtn = null; 22 var closeBtn = null; 23 var items = []; 24 var index = 0; 25 var lastFocus = null; 26 var hideTimer = null; 27 var audioBtn = null; 28 var audioOn = false; 29 var closedByKey = false; 30 31 // Chrome e Firefox lasciano partire il video con l'audio, visto che 32 // l'apertura nasce da un clic. Safari no: là parte muto e l'audio si 33 // accende col bottone. 34 var isSafari = /^((?!chrome|android|crios|fxios).)*safari/i.test(navigator.userAgent); 35 36 var ICON = { 37 close: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>', 38 prev: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 5l-7 7 7 7" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>', 39 next: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 5l7 7-7 7" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>', 40 full: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>', 41 sound: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 9.5h3.5L12 6v12L7.5 14.5H4z M16 9.5a4 4 0 0 1 0 5M18.5 7a7 7 0 0 1 0 10" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>', 42 muted: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 9.5h3.5L12 6v12L7.5 14.5H4z M16 9.5l5 5M21 9.5l-5 5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>', 43 exit: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 4v5H4M15 4v5h5M9 20v-5H4M15 20v-5h5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>' 44 }; 45 46 47 /* -------- sorgente: foto o video -------- */ 48 function parse(link) { 49 var url = link.getAttribute('href') || ''; 50 var yt = url.match(/(?:youtube\.com\/(?:watch\?(?:.*&)?v=|embed\/|shorts\/)|youtu\.be\/)([\w-]{11})/); 51 var vimeo = url.match(/vimeo\.com\/(?:video\/)?(\d+)/); 52 53 return { 54 url: url, 55 kind: (yt || vimeo) ? 'video' : 'image', 56 embed: yt 57 ? 'https://www.youtube-nocookie.com/embed/' + yt[1] + '?autoplay=1&mute=' + (isSafari ? 1 : 0) + '&enablejsapi=1&rel=0&modestbranding=1&playsinline=1' 58 : (vimeo ? 'https://player.vimeo.com/video/' + vimeo[1] + '?autoplay=1&muted=' + (isSafari ? 1 : 0) : ''), 59 title: link.getAttribute('data-lb-title') 60 || (link.querySelector('.lrf-video__title, .lrf-videos__title') || {}).textContent 61 || '' 62 }; 63 } 64 65 66 /* -------- costruzione -------- */ 67 function build() { 68 box = document.createElement('div'); 69 box.className = 'lrf-lb'; 70 box.setAttribute('role', 'dialog'); 71 box.setAttribute('aria-modal', 'true'); 72 box.innerHTML = 73 '<div class="lrf-lb__bg" data-lb-close></div>' + 74 '<div class="lrf-lb__hud">' + 75 '<p class="lrf-lb__caption"></p>' + 76 '<div class="lrf-lb__tools">' + 77 '<span class="lrf-lb__counter"></span>' + 78 '<button type="button" class="lrf-lb__btn lrf-lb__audio" data-lb-audio aria-label="Attiva audio">' + ICON.muted + '</button>' + 79 '<button type="button" class="lrf-lb__btn" data-lb-full aria-label="Schermo intero">' + ICON.full + '</button>' + 80 '<button type="button" class="lrf-lb__btn" data-lb-close aria-label="Chiudi">' + ICON.close + '</button>' + 81 '</div>' + 82 '</div>' + 83 '<button type="button" class="lrf-lb__nav is-prev" data-lb-prev aria-label="Precedente">' + ICON.prev + '</button>' + 84 '<button type="button" class="lrf-lb__nav is-next" data-lb-next aria-label="Successivo">' + ICON.next + '</button>' + 85 '<div class="lrf-lb__stage"></div>'; 86 87 document.body.appendChild(box); 88 89 stage = box.querySelector('.lrf-lb__stage'); 90 counter = box.querySelector('.lrf-lb__counter'); 91 caption = box.querySelector('.lrf-lb__caption'); 92 prevBtn = box.querySelector('[data-lb-prev]'); 93 nextBtn = box.querySelector('[data-lb-next]'); 94 fsBtn = box.querySelector('[data-lb-full]'); 95 audioBtn = box.querySelector('[data-lb-audio]'); 96 closeBtn = box.querySelector('[data-lb-close].lrf-lb__btn'); 97 98 box.querySelectorAll('[data-lb-close]').forEach(function (el) { 99 el.addEventListener('click', close); 100 }); 101 prevBtn.addEventListener('click', function () { go(index - 1); }); 102 nextBtn.addEventListener('click', function () { go(index + 1); }); 103 fsBtn.addEventListener('click', toggleFullscreen); 104 audioBtn.addEventListener('click', function () { setAudio(!audioOn); }); 105 106 // muovendo il dito o il mouse i comandi riappaiono 107 box.addEventListener('pointermove', wake); 108 box.addEventListener('pointerdown', wake); 109 110 // trascinamento orizzontale per cambiare foto 111 var startX = null; 112 box.addEventListener('pointerdown', function (e) { 113 if (e.pointerType === 'mouse') { return; } 114 startX = e.clientX; 115 }); 116 box.addEventListener('pointerup', function (e) { 117 if (startX === null) { return; } 118 var dx = e.clientX - startX; 119 startX = null; 120 if (Math.abs(dx) > 60) { go(index + (dx < 0 ? 1 : -1)); } 121 }); 122 123 document.addEventListener('fullscreenchange', function () { 124 var on = !!document.fullscreenElement; 125 box.classList.toggle('is-fullscreen', on); 126 fsBtn.innerHTML = on ? ICON.exit : ICON.full; 127 fsBtn.setAttribute('aria-label', on ? 'Esci da schermo intero' : 'Schermo intero'); 128 }); 129 } 130 131 132 /* -------- audio del video (comandi YouTube via postMessage) -------- */ 133 function command(func) { 134 var frame = stage.querySelector('iframe'); 135 if (!frame || !frame.contentWindow) { return; } 136 frame.contentWindow.postMessage(JSON.stringify({ event: 'command', func: func, args: [] }), '*'); 137 } 138 139 function setAudio(on) { 140 audioOn = on; 141 command(on ? 'unMute' : 'mute'); 142 audioBtn.innerHTML = on ? ICON.sound : ICON.muted; 143 audioBtn.setAttribute('aria-label', on ? 'Disattiva audio' : 'Attiva audio'); 144 } 145 146 147 /* -------- comandi che si nascondono da soli -------- */ 148 function wake() { 149 box.classList.remove('is-idle');
150 clearTimeout(hideTimer); 151 hideTimer = setTimeout(function () { 152 if (items[index] && items[index].kind === 'video') { box.classList.add('is-idle'); } 153 }, 2600); 154 } 155 156 157 /* -------- apertura, navigazione, chiusura -------- */ 158 function render() { 159 var item = items[index]; 160 stage.innerHTML = ''; 161 162 if (item.kind === 'video') { 163 var frame = document.createElement('div'); 164 frame.className = 'lrf-lb__video'; 165 frame.innerHTML = '<iframe src="' + item.embed + '" title="' + (item.title || 'Video') + '" allow="autoplay; encrypted-media; picture-in-picture; fullscreen" allowfullscreen frameborder="0"></iframe>'; 166 stage.appendChild(frame); 167 168 // su Safari parte muto (lì l'audio bloccherebbe la partenza), 169 // altrove parte con l'audio già acceso 170 audioOn = !isSafari; 171 audioBtn.innerHTML = audioOn ? ICON.sound : ICON.muted; 172 audioBtn.setAttribute('aria-label', audioOn ? 'Disattiva audio' : 'Attiva audio'); 173 } else { 174 var img = document.createElement('img'); 175 img.className = 'lrf-lb__img'; 176 img.alt = item.title || ''; 177 img.src = item.url; 178 stage.appendChild(img); 179 } 180 181 box.classList.toggle('is-video', item.kind === 'video'); 182 caption.textContent = item.title || ''; 183 counter.textContent = items.length > 1 ? (index + 1) + ' / ' + items.length : ''; 184 prevBtn.hidden = nextBtn.hidden = items.length < 2; 185 186 // precarico la foto successiva 187 var next = items[(index + 1) % items.length]; 188 if (next && next.kind === 'image') { new Image().src = next.url; } 189 190 wake(); 191 } 192 193 function go(i) { 194 if (items.length < 2) { return; } 195 index = (i + items.length) % items.length; 196 render(); 197 } 198 199 function open(link) { 200 if (!box) { build(); } 201 202 var group = link.getAttribute('data-lb-group'); 203 var groupLinks = group 204 ? links.filter(function (l) { return l.getAttribute('data-lb-group') === group; }) 205 : [link]; 206 207 items = groupLinks.map(parse); 208 index = Math.max(0, groupLinks.indexOf(link)); 209 lastFocus = document.activeElement; 210 211 var gap = window.innerWidth - document.documentElement.clientWidth; 212 document.body.style.paddingRight = gap > 0 ? gap + 'px' : ''; 213 document.body.classList.add('lrf-lb-open'); 214 box.classList.add('is-open'); 215 if (reduceMotion) { box.classList.add('is-instant'); } 216 render(); 217 closeBtn.focus({ preventScroll: true }); 218 219 document.addEventListener('keydown', onKey); 220 } 221 222 function close() { 223 if (!box || !box.classList.contains('is-open')) { return; } 224 if (document.fullscreenElement) { document.exitFullscreen(); } 225 226 box.classList.remove('is-open'); 227 document.body.classList.remove('lrf-lb-open'); 228 document.body.style.paddingRight = ''; 229 document.removeEventListener('keydown', onKey); 230 clearTimeout(hideTimer); 231 232 // fermo subito il video 233 setTimeout(function () { stage.innerHTML = ''; }, reduceMotion ? 0 : 260); 234 235 // col mouse non rimetto il fuoco sul link (lascerebbe il contorno 236 // evidenziato); da tastiera invece serve per non perdere il segno. 237 if (closedByKey && lastFocus && lastFocus.focus) { lastFocus.focus({ preventScroll: true }); } 238 closedByKey = false; 239 } 240 241 function toggleFullscreen() { 242 if (document.fullscreenElement) { 243 document.exitFullscreen(); 244 } else if (box.requestFullscreen) { 245 box.requestFullscreen().catch(function () { /* niente: resta a schermo pieno nella pagina */ }); 246 } 247 } 248 249 function onKey(e) { 250 if (e.key === 'Escape') { closedByKey = true; close(); } 251 if (e.key === 'ArrowRight') { go(index + 1); } 252 if (e.key === 'ArrowLeft') { go(index - 1); } 253 } 254 255 256 links.forEach(function (link) { 257 link.addEventListener('click', function (e) { 258 if (e.metaKey || e.ctrlKey || e.shiftKey || e.button > 0) { return; } 259 e.preventDefault(); 260 open(link); 261 }); 262 }); 263})();
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.