"use client"; import Image from "next/image"; import { useCallback, useEffect, useRef, useState } from "react"; import { AnimatePresence, motion } from "framer-motion"; import { Stagger, StaggerItem } from "../ui/Stagger"; import { EASE_OUT_EXPO } from "@/lib/motion"; import type { Screenshot } from "@/lib/games"; type Props = { screenshots: Screenshot[]; /** Marks the captures as work in progress. */ wip?: boolean; }; /** * Screenshot grid with a lightbox. * * Captions live under the thumbnail and on a solid bar inside the lightbox — * never floating over the image, which is far too bright to carry text. */ /** * Which way the next capture slides in. Stored rather than derived: after the * index has changed there is no way to tell whether it went up or wrapped * around from the last shot to the first. */ const slide = { enter: (direction: number) => ({ opacity: 0, x: direction * 48 }), center: { opacity: 1, x: 0 }, exit: (direction: number) => ({ opacity: 0, x: direction * -48 }), }; export function ScreenshotGallery({ screenshots, wip = true }: Props) { const [openIndex, setOpenIndex] = useState(null); const [direction, setDirection] = useState(1); const triggersRef = useRef<(HTMLButtonElement | null)[]>([]); const dialogRef = useRef(null); const close = useCallback(() => { setOpenIndex((current) => { if (current !== null) triggersRef.current[current]?.focus(); return null; }); }, []); const step = useCallback( (delta: number) => { setDirection(delta); setOpenIndex((current) => current === null ? current : (current + delta + screenshots.length) % screenshots.length, ); }, [screenshots.length], ); // Keyboard controls + scroll lock while the lightbox is open. useEffect(() => { if (openIndex === null) return; const onKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape") close(); else if (event.key === "ArrowRight") step(1); else if (event.key === "ArrowLeft") step(-1); }; document.addEventListener("keydown", onKeyDown); const previousOverflow = document.body.style.overflow; document.body.style.overflow = "hidden"; dialogRef.current?.focus(); return () => { document.removeEventListener("keydown", onKeyDown); document.body.style.overflow = previousOverflow; }; }, [openIndex, close, step]); if (screenshots.length === 0) return null; const active = openIndex === null ? null : screenshots[openIndex]; return ( <> {screenshots.map((shot, index) => ( ))} {active && ( event.stopPropagation()} >
{/* Each capture slides in from the side you asked for. Without it, stepping through a gallery of similar screenshots gives no sense of having moved at all. */} {active.caption}

{active.caption}

{(openIndex ?? 0) + 1} / {screenshots.length}

{screenshots.length > 1 && (
event.stopPropagation()} >
)}
)}
); }