113 lines
3.9 KiB
Django/Jinja
113 lines
3.9 KiB
Django/Jinja
{# Global in-page lightbox. Click to zoom, full-screen modal overlay.
|
||
Handles two media kinds via event delegation:
|
||
• <picture> images → shown as <img> (browser-picked AVIF/WebP, currentSrc)
|
||
• .content-graph-mini-preview → its inline ego-network <svg>, cloned & enlarged
|
||
|
||
Boost-proof: htmx navigation can drop the overlay node, so styles + overlay are
|
||
recreated ON DEMAND (looked up by id, rebuilt if missing) rather than cached in
|
||
closure refs. Listeners bind to `document` exactly once (document survives htmx
|
||
swaps), guarded by a window flag so re-running after a swap never double-binds. #}
|
||
<script>
|
||
(function () {
|
||
var STYLE_ID = 'oz-style';
|
||
var OVERLAY_ID = 'oz-overlay';
|
||
|
||
function ensureStyle() {
|
||
if (document.getElementById(STYLE_ID)) return;
|
||
var s = document.createElement('style');
|
||
s.id = STYLE_ID;
|
||
s.textContent = ''
|
||
+ '.oz-overlay{position:fixed;inset:0;z-index:2147483000;display:none;'
|
||
+ 'align-items:center;justify-content:center;background:rgba(8,11,16,.92);'
|
||
+ 'padding:2rem;cursor:zoom-out;opacity:0;transition:opacity .15s ease}'
|
||
+ '.oz-overlay.oz-open{display:flex;opacity:1}'
|
||
+ '.oz-overlay img.oz-media{max-width:100%;max-height:100%;width:auto;height:auto;'
|
||
+ 'border-radius:.5rem;box-shadow:0 10px 40px rgba(0,0,0,.5);cursor:default}'
|
||
+ '.oz-overlay svg.oz-media{width:min(90vw,90vh);height:min(90vw,90vh);'
|
||
+ 'max-width:100%;max-height:100%;cursor:default}'
|
||
+ '.oz-close{position:absolute;top:1rem;right:1.25rem;font-size:2.25rem;'
|
||
+ 'line-height:1;color:#fff;background:none;border:0;cursor:pointer;opacity:.85}'
|
||
+ '.oz-close:hover{opacity:1}'
|
||
+ 'picture img{cursor:zoom-in}';
|
||
(document.head || document.documentElement).appendChild(s);
|
||
}
|
||
|
||
function ensureOverlay() {
|
||
var o = document.getElementById(OVERLAY_ID);
|
||
if (o) return o;
|
||
o = document.createElement('div');
|
||
o.id = OVERLAY_ID;
|
||
o.className = 'oz-overlay';
|
||
o.setAttribute('role', 'dialog');
|
||
o.setAttribute('aria-modal', 'true');
|
||
o.innerHTML = '<button class="oz-close" type="button" aria-label="Close">×</button>';
|
||
document.body.appendChild(o);
|
||
return o;
|
||
}
|
||
|
||
function setMedia(node) {
|
||
ensureStyle();
|
||
var o = ensureOverlay();
|
||
var old = o.querySelector('.oz-media');
|
||
if (old) old.remove();
|
||
node.classList.add('oz-media');
|
||
o.appendChild(node);
|
||
o.classList.add('oz-open');
|
||
document.body.style.overflow = 'hidden';
|
||
}
|
||
|
||
function openImg(src, alt) {
|
||
var im = new Image();
|
||
im.src = src;
|
||
im.alt = alt || '';
|
||
setMedia(im);
|
||
}
|
||
|
||
function openGraph(svgEl) {
|
||
var c = svgEl.cloneNode(true);
|
||
// Overlay is always dark — force the on-dark palette so labels/edges stay
|
||
// legible even when the page itself is in light mode.
|
||
c.style.setProperty('--cg-label', '#d1d5db');
|
||
c.style.setProperty('--cg-edge', '#4b5563');
|
||
c.style.setProperty('--cg-node', '#9ca3af');
|
||
setMedia(c);
|
||
}
|
||
|
||
function closeImg() {
|
||
var o = document.getElementById(OVERLAY_ID);
|
||
if (o) o.classList.remove('oz-open');
|
||
document.body.style.overflow = '';
|
||
}
|
||
|
||
if (!window.__ozBound) {
|
||
window.__ozBound = true;
|
||
document.addEventListener('click', function (e) {
|
||
var t = e.target;
|
||
if (!t || !t.closest) return;
|
||
|
||
var img = t.closest('picture img');
|
||
if (img) {
|
||
e.preventDefault();
|
||
openImg(img.currentSrc || img.src, img.alt);
|
||
return;
|
||
}
|
||
|
||
var mini = t.closest('.content-graph-mini-preview');
|
||
if (mini) {
|
||
var svg = mini.querySelector('svg');
|
||
if (svg) {
|
||
e.preventDefault();
|
||
openGraph(svg);
|
||
return;
|
||
}
|
||
}
|
||
|
||
var ov = t.closest('#' + OVERLAY_ID);
|
||
if (ov && (t.id === OVERLAY_ID || t.closest('.oz-close'))) closeImg();
|
||
});
|
||
document.addEventListener('keydown', function (e) {
|
||
if (e.key === 'Escape' || e.key === 'Esc') closeImg();
|
||
});
|
||
}
|
||
})();
|
||
</script>
|