website-htmx-rustelo-code/crates/pages_htmx/templates/partials/image-zoom.j2
2026-07-10 03:44:13 +01:00

113 lines
3.9 KiB
Django/Jinja
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{# 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>