// folio/Lightbox.jsx — full-screen viewer. // Close by: clicking the image · clicking dark backdrop · ESC · swipe down. // Navigate by: ← → arrows · keyboard · swipe left/right. function FolioLightbox({ images, open, index, onClose, onChange }) { const trackRef = React.useRef(null); const startX = React.useRef(0); const startY = React.useRef(0); const dragging = React.useRef(false); // Tracks whether a swipe gesture just fired so the subsequent click event // (browsers emit click after mouseup / touchend) doesn't also close/navigate. const swipeHandled = React.useRef(false); // Keyboard navigation React.useEffect(() => { if (!open) return; const onKey = (e) => { if (e.key === "Escape") onClose(); if (e.key === "ArrowRight") onChange(Math.min(index + 1, images.length - 1)); if (e.key === "ArrowLeft") onChange(Math.max(index - 1, 0)); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [open, index, images.length, onChange, onClose]); // Body scroll lock React.useEffect(() => { if (open) { const prev = document.body.style.overflow; document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = prev; }; } }, [open]); // Touch / mouse swipe const onDown = (e) => { const p = e.touches ? e.touches[0] : e; startX.current = p.clientX; startY.current = p.clientY; dragging.current = true; }; const onUp = (e) => { if (!dragging.current) return; dragging.current = false; const p = e.changedTouches ? e.changedTouches[0] : e; const dx = p.clientX - startX.current; const dy = p.clientY - startY.current; if (Math.abs(dx) > 60 && Math.abs(dx) > Math.abs(dy)) { swipeHandled.current = true; if (dx < 0) onChange(Math.min(index + 1, images.length - 1)); else onChange(Math.max(index - 1, 0)); } else if (Math.abs(dy) > 100 && Math.abs(dy) > Math.abs(dx)) { swipeHandled.current = true; onClose(); } }; // Clicking the dark backdrop (not an arrow / top / bottom bar) closes. const onStageClick = (e) => { if (swipeHandled.current) { swipeHandled.current = false; return; } if (e.target.closest(".lb-arrow, .lb-btn, .lb-bottom, .lb-top")) return; onClose(); }; // Clicking the image (any device) closes the lightbox. // Use arrows or swipe left/right to navigate between photos. const onFrameClick = (e) => { e.stopPropagation(); if (swipeHandled.current) { swipeHandled.current = false; return; } onClose(); }; const stopProp = (e) => e.stopPropagation(); if (!images || images.length === 0) return null; const img = images[index] || images[0]; const pct = ((index + 1) / images.length) * 100; return ReactDOM.createPortal(