File size: 5,262 Bytes
619344d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/* viewer.js
   =========
   Light-box / zoom / keyboard logic for the result grid.
   Expects:
     • the page to contain thumbnails inside elements with class="item"
       and data-idx attributes that match the order of
         window.GRID_DATA.sources  &  window.GRID_DATA.captions
     • the CSS rules from viewer.css (same selectors as before)
*/

const INITIAL_SCALE_WHEN_FITS = 1.5;

/* ----------------------------------------------------------
   1.  Data from Python-generated <script> snippet
---------------------------------------------------------- */
const {
    sources,
    captions
} = window.GRID_DATA;

/* ----------------------------------------------------------
   2.  Build / cache overlay DOM elements
---------------------------------------------------------- */
const overlay = document.createElement('div');
overlay.id = 'overlay';
overlay.innerHTML = `
  <div id="container"></div>
  <div id="caption"></div>
`;
document.body.appendChild(overlay);

const container = overlay.querySelector('#container');
const captionEl = overlay.querySelector('#caption');

/* ----------------------------------------------------------
   3.  Runtime state
---------------------------------------------------------- */
let idx = 0;
let isZoomed = false;
let canZoom = false; // false for videos
let media = null; // <img> | <video>
let baseWidth = 0; // natural width for wheel-zoom
let scale = 1;

/* ----------------------------------------------------------
   4.  Helpers
---------------------------------------------------------- */
const mediaTag = src =>
    src.toLowerCase().endsWith('.mp4') ?
    `<video src="${src}" controls autoplay></video>` :
    `<img src="${src}" alt="preview">`;

/* Center or pin content when zoomed */
function adjustAlignment() {
    if (!isZoomed) {
        container.style.alignSelf = '';
        overlay.classList.remove('scrollY');
        return;
    }
    const hOverflow = overlay.scrollWidth > overlay.clientWidth;
    const vOverflow = overlay.scrollHeight > overlay.clientHeight;

    container.style.alignSelf = hOverflow ? 'flex-start' : '';
    overlay.classList.toggle('scrollY', vOverflow);
}

/* ----------------------------------------------------------
   5.  Open / close / zoom
---------------------------------------------------------- */
function show(i) {
    idx = (i + sources.length) % sources.length;

    container.innerHTML = mediaTag(sources[idx]);
    captionEl.textContent = captions[idx];
    overlay.style.display = 'flex';
    document.body.style.overflow = 'hidden'; // lock background scroll

    media = container.firstElementChild;
    canZoom = media.tagName === 'IMG';
    isZoomed = false;
    scale = 1;

    media.style.width = '';
    media.classList.remove('zoomed');
    overlay.classList.remove('scrollY');
    container.style.alignSelf = '';

    media.onclick = canZoom ? toggleZoom : null;
    adjustAlignment();
}

function hide() {
    overlay.style.display = 'none'; // make it invisible
    document.body.style.overflow = '';
    isZoomed = false;
    /* container will be overwritten on next show() call */
}

function toggleZoom() {
    if (!canZoom) return; // ignore for videos

    isZoomed = !isZoomed;
    media.classList.toggle('zoomed', isZoomed);

    if (isZoomed) {
        baseWidth = media.naturalWidth;
        media.style.width = (baseWidth * INITIAL_SCALE_WHEN_FITS) + 'px';
        overlay.scrollTop = overlay.scrollLeft = 0;
    } else {
        media.style.width = '';
    }
    adjustAlignment();
}

/* ----------------------------------------------------------
   6.  Event wiring
---------------------------------------------------------- */
/* Thumbnail clicks – activate only when the media itself is clicked */
document.querySelector('.grid').addEventListener('click', e => {
    if (!e.target.matches('img, video')) return; // ignore textarea, padding, …
    const idx = +e.target.closest('.item').dataset.idx; // ascend to the card
    show(idx);
});

/* Keyboard */
document.addEventListener('keydown', e => {
    if (overlay.style.display !== 'flex') return;

    /* Esc — exit zoom first, then close overlay */
    if (e.key === 'Escape') {
        e.preventDefault(); // ← stop “Stop loading” in Chrome/Firefox
        if (isZoomed) toggleZoom();
        else hide();
        return;
    }

    /* Enter toggles zoom for images */
    if (e.key === 'Enter' && canZoom) {
        toggleZoom();
        return;
    }

    /* Arrow navigation disabled while zoomed */
    if (isZoomed) return;
    if (e.key === 'ArrowRight') show(idx + 1);
    else if (e.key === 'ArrowLeft') show(idx - 1);
});

/* Click backdrop to close */
overlay.addEventListener('click', e => {
    if (e.target === overlay) hide();
});

/* Ctrl + wheel zoom for images only */
overlay.addEventListener('wheel', e => {
    if (!canZoom || !e.ctrlKey) return;

    if (!isZoomed) toggleZoom(); // auto-enter zoom
    e.preventDefault();

    scale += (e.deltaY < 0 ? 0.1 : -0.1);
    scale = Math.max(0.2, Math.min(5, scale));
    media.style.width = (baseWidth * scale) + 'px';
    adjustAlignment();
}, {
    passive: false
});

/* Keep centering correct on resize / browser zoom */
window.addEventListener('resize', adjustAlignment);