PageSourceSearch

https://elliotjaystocks.com/javascripts/book-gallery.js

js elliotjaystocks.com collected 2026-10-02 02:27:36 UTC 5,972 bytes, 175 lines download raw bytes

1(function () {
2  'use strict';
3
4  var lightbox = null;
5  var currentGallery = [];
6  var currentIndex = 0;
7
8  function openLightbox(gallery, index) {
9    currentGallery = gallery;
10    currentIndex = index;
11
12    if (!lightbox) {
13      lightbox = document.createElement('div');
14      lightbox.className = 'book-lightbox';
15      lightbox.setAttribute('role', 'dialog');
16      lightbox.setAttribute('aria-modal', 'true');
17      lightbox.setAttribute('aria-label', 'Image gallery');
18      lightbox.innerHTML =
19        '<button class="book-lightbox__close" aria-label="Close gallery">&times;</button>' +
20        '<button class="book-lightbox__prev" aria-label="Previous image">&#8249;</button>' +
21        '<button class="book-lightbox__next" aria-label="Next image">&#8250;</button>' +
22        '<div class="book-lightbox__img-wrap">' +
23          '<img class="book-lightbox__img" src="" alt="" />' +
24        '</div>';
25      document.body.appendChild(lightbox);
26
27      lightbox.querySelector('.book-lightbox__close').addEventListener('click', closeLightbox);
28      lightbox.querySelector('.book-lightbox__prev').addEventListener('click', showPrev);
29      lightbox.querySelector('.book-lightbox__next').addEventListener('click', showNext);
30      lightbox.addEventListener('click', function (e) {
31        if (e.target === lightbox || e.target.classList.contains('book-lightbox__img-wrap')) {
32          closeLightbox();
33        }
34      });
35    }
36
37    updateImage();
38    lightbox.classList.add('is-open');
39    document.body.style.overflow = 'hidden';
40    lightbox.querySelector('.book-lightbox__close').focus();
41  }
42
43  function closeLightbox() {
44    if (!lightbox) return;
45    lightbox.classList.remove('is-open');
46    document.body.style.overflow = '';
47  }
48
49  function showPrev() {
50    currentIndex = (currentIndex - 1 + currentGallery.length) % currentGallery.length;
51    updateImage();
52  }
53
54  function showNext() {
55    currentIndex = (currentIndex + 1) % currentGallery.length;
56    updateImage();
57  }
58
59  function updateImage() {
60    var img = lightbox.querySelector('.book-lightbox__img');
61    var item = currentGallery[currentIndex];
62    img.src = item.src;
63    img.alt = item.alt;
64    lightbox.querySelector('.book-lightbox__prev').style.visibility = currentGallery.length > 1 ? 'visible' : 'hidden';
65    lightbox.querySelector('.book-lightbox__next').style.visibility = currentGallery.length > 1 ? 'visible' : 'hidden';
66  }
67
68  document.addEventListener('keydown', function (e) {
69    if (!lightbox || !lightbox.classList.contains('is-open')) return;
70    if (e.key === 'Escape') closeLightbox();
71    if (e.key === 'ArrowLeft') showPrev();
72    if (e.key === 'ArrowRight') showNext();
73  });
74
75  document.addEventListener('DOMContentLoaded', function () {
76    var galleries = document.querySelectorAll('.book-gallery');
77    galleries.forEach(function (gallery) {
78      var items = gallery.querySelectorAll('.book-gallery__item');
79      items.forEach(function (item, i) {
80        item.addEventListener('click', function (e) {
81          e.preventDefault();
82          var images = [];
83          items.forEach(function (el) {
84            var img = el.querySelector('img');
85            images.push({ src: img.src, alt: img.alt });
86          });
87          openLightbox(images, i);
88        });
89      });
90    });
91  });
92})();
93
94/* ----------
95   Buy-direct modal
96   Progressive enhancement of the pure-CSS :target overlay rendered by
97   the buy-modal snippet. With JS on we take over: prevent the hash
98   change, manage focus, trap Tab, close on Escape / backdrop click,
99   and restore focus to the trigger. With JS off, the CSS :target
100   fallback keeps the shipping links fully usable.
101---------- */
102(function () {
103  'use strict';
104
105  var openModal = null;
106  var lastTrigger = null;
107
108  function openBuyModal(modal, trigger) {
109    openModal = modal;
110    lastTrigger = trigger;
111    modal.classList.add('is-open');
112    document.body.style.overflow = 'hidden';
113    var closeBtn = modal.querySelector('.buy-modal__close');
114    if (closeBtn) closeBtn.focus();
115  }
116
117  function closeBuyModal() {
118    if (!openModal) return;
119    openModal.classList.remove('is-open');
120    document.body.style.overflow = '';
121    if (lastTrigger) lastTrigger.focus();
122    openModal = null;
123    lastTrigger = null;
124  }
125
126  document.addEventListener('DOMContentLoaded', function () {
127    var triggers = document.querySelectorAll('[data-buy-trigger]');
128    triggers.forEach(function (trigger) {
129      trigger.addEventListener('click', function (e) {
130        var id = (trigger.getAttribute('href') || '').replace('#', '');
131        var modal = document.getElementById(id);
132        if (!modal) return; // no modal found: let the link behave normally
133        e.preventDefault();
134        openBuyModal(modal, trigger);
135      });
136    });
137
138    var modals = document.querySelectorAll('.buy-modal');
139    modals.forEach(function (modal) {
140      // Click on the backdrop (outside the inner panel) closes the modal
141      modal.addEventListener('click', function (e) {
142        if (e.target === modal) closeBuyModal();
143      });
144      var closeBtn = modal.querySelector('.buy-modal__close');
145      if (closeBtn) {
146        closeBtn.addEventListener('click', function (e) {
147          e.preventDefault();
148          closeBuyModal();
149        });
150      }
151    });
152  });
153
154  document.addEventListener('keydown', function (e) {
155    if (!openModal) return;
156    if (e.key === 'Escape') {
157      closeBuyModal();
158      return;
159    }
160    if (e.key === 'Tab') {
161      // Simple focus trap within the open modal
162      var focusables = openModal.querySelectorAll('a[href], button');
163      if (!focusables.length) return;
164      var first = focusables[0];
165      var last = focusables[focusables.length - 1];
166      if (e.shiftKey && document.activeElement === first) {
167        e.preventDefault();
168        last.focus();
169      } else if (!e.shiftKey && document.activeElement === last) {
170        e.preventDefault();
171        first.focus();
172      }
173    }
174  });
175})();

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.