1// Lightbox for the About page "Our work in action" gallery. 2// Clicking a card opens the full photo; arrows, swipe and Esc work too. 3(function () { 4 var box, img, cap, list = [], idx = 0, lastFocus = null, startX = 0; 5 6 function cards() { 7 var seen = {}, out = []; 8 document.querySelectorAll('[data-ab-card][data-full]').forEach(function (c) { 9 var u = c.getAttribute('data-full'); 10 if (seen[u]) return; 11 seen[u] = 1; 12 out.push({ src: u, title: c.getAttribute('data-title') || '', tag: c.getAttribute('data-tag') || '' }); 13 }); 14 return out; 15 } 16 17 function build() { 18 box = document.createElement('div'); 19 box.className = 'ab-lb'; 20 box.setAttribute('role', 'dialog'); 21 box.setAttribute('aria-modal', 'true'); 22 box.setAttribute('aria-label', 'Photo viewer'); 23 box.innerHTML = 24 '<button type="button" class="ab-lb-x" aria-label="Close">×</button>' + 25 '<button type="button" class="ab-lb-nav ab-lb-prev" aria-label="Previous photo">‹</button>' + 26 '<figure class="ab-lb-fig"><img alt=""><figcaption><span class="ab-lb-tag"></span><span class="ab-lb-title"></span><span class="ab-lb-count"></span></figcaption></figure>' + 27 '<button type="button" class="ab-lb-nav ab-lb-next" aria-label="Next photo">›</button>'; 28 document.body.appendChild(box); 29 img = box.querySelector('img'); 30 cap = box.querySelector('figcaption'); 31 box.addEventListener('click', function (e) { 32 if (e.target === box || e.target.closest('.ab-lb-x')) close(); 33 else if (e.target.closest('.ab-lb-prev')) show(idx - 1); 34 else if (e.target.closest('.ab-lb-next')) show(idx + 1); 35 }); 36 box.addEventListener('touchstart', function (e) { startX = e.touches[0].clientX; }, { passive: true }); 37 box.addEventListener('touchend', function (e) { 38 var dx = e.changedTouches[0].clientX - startX; 39 if (Math.abs(dx) > 50) show(idx + (dx < 0 ? 1 : -1)); 40 }); 41 } 42 43 function show(i) { 44 idx = (i + list.length) % list.length; 45 var it = list[idx]; 46 box.classList.add('ab-lb-loading'); 47 img.onload = function () { box.classList.remove('ab-lb-loading'); }; 48 img.src = it.src; 49 img.alt = it.title + ' at BigiByte'; 50 cap.querySelector('.ab-lb-tag').textContent = it.tag; 51 cap.querySelector('.ab-lb-title').textContent = it.title; 52 cap.querySelector('.ab-lb-count').textContent = (idx + 1) + ' / ' + list.length; 53 } 54 55 function open(src) { 56 list = cards(); 57 if (!box) build(); 58 var i = list.findIndex(function (x) { return x.src === src; }); 59 lastFocus = document.activeElement; 60 show(i < 0 ? 0 : i); 61 box.classList.add('ab-lb-open'); 62 document.documentElement.style.overflow = 'hidden'; 63 if (window.__lenis) window.__lenis.stop(); 64 box.querySelector('.ab-lb-x').focus(); 65 } 66 67 function close() { 68 box.classList.remove('ab-lb-open'); 69 document.documentElement.style.overflow = ''; 70 if (window.__lenis) window.__lenis.start(); 71 if (lastFocus && lastFocus.focus) lastFocus.focus(); 72 } 73 74 document.addEventListener('click', function (e) { 75 var c = e.target.closest && e.target.closest('[data-ab-card][data-full]'); 76 if (c) { e.preventDefault(); open(c.getAttribute('data-full')); } 77 }); 78 document.addEventListener('keydown', function (e) { 79 if (box && box.classList.contains('ab-lb-open')) { 80 if (e.key === 'Escape') close(); 81 else if (e.key === 'ArrowRight') show(idx + 1); 82 else if (e.key === 'ArrowLeft') show(idx - 1); 83 return; 84 } 85 var c = e.target.closest && e.target.closest('[data-ab-card][data-full]'); 86 if (c && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); open(c.getAttribute('data-full')); } 87 }); 88})();
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.