PageSourceSearch

https://www.athensbuilding.com/js/lightbox.js

js athensbuilding.com collected 2026-09-30 06:29:36 UTC 3,742 bytes, 118 lines download raw bytes

1/* Athens Building Company — lightbox.js
2   Vanilla, dependency-free image lightbox.
3   - Trigger: any [data-lightbox-index] inside [data-lightbox-gallery]
4   - Keys: Arrow Left / Right navigates, Esc closes
5   - Click X to close, click backdrop to close
6   - Focus trap while open, restores focus on close
7*/
8
9(function () {
10  'use strict';
11
12  const gallery = document.querySelector('[data-lightbox-gallery]');
13  const lightbox = document.getElementById('lightbox');
14  if (!gallery || !lightbox) return;
15
16  const items = Array.from(gallery.querySelectorAll('[data-lightbox-index]'));
17  const sources = items.map(el => el.getAttribute('data-src'));
18  if (sources.length === 0) return;
19
20  const imgEl = lightbox.querySelector('.lightbox__img');
21  const closeBtn = lightbox.querySelector('.lightbox__close');
22  const prevBtn = lightbox.querySelector('.lightbox__nav--prev');
23  const nextBtn = lightbox.querySelector('.lightbox__nav--next');
24  const counter = lightbox.querySelector('.lightbox__counter');
25
26  let currentIndex = 0;
27  let lastFocused = null;
28
29  function open(idx) {
30    currentIndex = idx;
31    lastFocused = document.activeElement;
32    lightbox.hidden = false;
33    document.body.style.overflow = 'hidden';
34    requestAnimationFrame(() => lightbox.classList.add('is-open'));
35    update();
36    // Send focus to close button so keyboard users start there
37    closeBtn.focus({ preventScroll: true });
38  }
39
40  function close() {
41    lightbox.classList.remove('is-open');
42    setTimeout(() => {
43      lightbox.hidden = true;
44      document.body.style.overflow = '';
45      if (lastFocused && lastFocused.focus) lastFocused.focus({ preventScroll: true });
46    }, 280);
47  }
48
49  function update() {
50    imgEl.src = sources[currentIndex];
51    imgEl.alt = items[currentIndex].querySelector('img')?.alt || '';
52    counter.textContent = `Photo ${currentIndex + 1} of ${sources.length}`;
53    // Disable nav at the ends? No — wrap around feels nicer in a portfolio.
54  }
55
56  function next() {
57    currentIndex = (currentIndex + 1) % sources.length;
58    update();
59  }
60
61  function prev() {
62    currentIndex = (currentIndex - 1 + sources.length) % sources.length;
63    update();
64  }
65
66  // Click triggers
67  items.forEach((item, idx) => {
68    item.addEventListener('click', () => open(idx));
69  });
70
71  closeBtn.addEventListener('click', close);
72  nextBtn.addEventListener('click', next);
73  prevBtn.addEventListener('click', prev);
74
75  // Click outside the image (on the backdrop) closes
76  lightbox.addEventListener('click', (e) => {
77    if (e.target === lightbox) close();
78  });
79
80  // Keyboard
81  document.addEventListener('keydown', (e) => {
82    if (lightbox.hidden) return;
83    if (e.key === 'Escape') {
84      e.preventDefault();
85      close();
86    } else if (e.key === 'ArrowRight') {
87      e.preventDefault();
88      next();
89    } else if (e.key === 'ArrowLeft') {
90      e.preventDefault();
91      prev();
92    } else if (e.key === 'Tab') {
93      // Simple focus trap — keep focus on the three controls
94      const focusables = [closeBtn, prevBtn, nextBtn];
95      const idx = focusables.indexOf(document.activeElement);
96      if (idx === -1) {
97        e.preventDefault();
98        closeBtn.focus();
99      } else {
100        e.preventDefault();
101        const dir = e.shiftKey ? -1 : 1;
102        const nextIdx = (idx + dir + focusables.length) % focusables.length;
103        focusables[nextIdx].focus();
104      }
105    }
106  });
107
108  // Preload neighbors for snappier nav
109  function preload(idx) {
110    if (idx < 0 || idx >= sources.length) return;
111    const im = new Image();
112    im.src = sources[idx];
113  }
114  // Preload all once the page is idle
115  if ('requestIdleCallback' in window) {
116    requestIdleCallback(() => sources.forEach((_, i) => preload(i)));
117  }
118})();

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.