Interactive preview, sandboxed: nothing you type leaves this page.
import { useState } from "react";type Work = { title: string; materials: string; year: string; note: string; alt: string; number: string };const works: Work[] = [ { title: "A room held open", materials: "Oil on linen", year: "2024", note: "The canvas is the same black at every hour; the room changes instead.", alt: "A bare white gallery hall at dusk, with one large dark canvas on its far wall.", number: "01" }, { title: "Weight of weather", materials: "Cast bronze", year: "2023", note: "A surface keeps the quiet record of every season it has outlasted.", alt: "Close crop of a weathered bronze sculpture, its patina and worn contours visible.", number: "02" }, { title: "After the fold", materials: "Paper, glass, light", year: "2025", note: "Each crease holds the memory of a hand that is no longer here.", alt: "A lit glass vitrine displaying a carefully stacked group of folded paper sheets.", number: "03" },];function Caption({ work, compact = false }: { work: Work; compact?: boolean }) { return <div className={`flex min-w-0 flex-col ${compact ? "gap-2" : "gap-3"}`}><span className="font-mono text-[10px] tracking-[0.18em] text-neutral-600 dark:text-neutral-400">{work.number} / 03</span><div><h2 className={`${compact ? "text-[15px] leading-[1.2]" : "text-xl leading-tight"} font-normal tracking-[-0.025em] text-neutral-900 dark:text-neutral-100`}>{work.title}</h2><p className="mt-2 text-[11px] leading-[1.5] text-neutral-700 dark:text-neutral-300">{work.materials}<span className="px-1.5">·</span>{work.year}</p></div><p className={`${compact ? "text-[11px] leading-[1.45]" : "text-[13px] leading-[1.55]"} text-neutral-700 dark:text-neutral-300`}>{work.note}</p></div>;}export default function Block() { const [active, setActive] = useState<Work | null>(null); return <section className="bg-[#f6f5f1] px-5 py-12 text-neutral-900 dark:bg-neutral-950 dark:text-neutral-100 sm:px-8 sm:py-16 lg:px-12 lg:py-20" aria-labelledby="sala-heading"> <div className="mx-auto max-w-[1320px]"> <header className="mb-9 flex items-end justify-between border-b border-neutral-300 pb-5 dark:border-neutral-700 sm:mb-12 sm:pb-6"><div><p className="mb-3 font-mono text-[10px] uppercase tracking-[0.2em] text-neutral-600 dark:text-neutral-400">Sala Norte <span className="mx-1.5">/</span> Current exhibition</p><h1 id="sala-heading" className="text-[32px] font-normal leading-none tracking-[-0.045em] sm:text-[42px]">The changing room</h1></div><p className="hidden text-right font-mono text-[10px] leading-relaxed tracking-[0.12em] text-neutral-600 dark:text-neutral-400 sm:block">MADRID<br />18.04 — 29.06.25</p></header> <div className="grid grid-cols-1 gap-x-8 gap-y-9 md:grid-cols-12 md:gap-x-6 lg:gap-x-10 lg:gap-y-10"> <article className="grid min-w-0 grid-cols-[minmax(0,1.55fr)_minmax(0,1fr)] items-start gap-4 md:col-span-7 md:grid-cols-[minmax(0,1fr)_minmax(130px,0.43fr)] md:gap-5 lg:col-span-8 lg:grid-cols-[minmax(0,1fr)_minmax(165px,0.38fr)] lg:gap-7"> <button type="button" onClick={() => setActive(works[0])} aria-label={`OPEN WORK: View ${works[0].title} full screen`} className="group relative block aspect-[1.45] w-full overflow-hidden bg-neutral-300 text-left focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-neutral-900 dark:bg-neutral-800 dark:focus-visible:outline-neutral-100"><img src="https://res.cloudinary.com/ddjovluur/image/upload/genblocks/images/e797f5bfe82a5c5b" alt={works[0].alt} className="h-full w-full object-cover transition-transform duration-500 ease-out group-hover:scale-[1.015] motion-reduce:transition-none"/></button> <div className="pt-2 md:pt-1"><Caption work={works[0]}/></div> </article> <div className="grid min-w-0 grid-cols-1 gap-8 sm:grid-cols-2 md:col-span-5 md:grid-cols-1 md:gap-7 lg:col-span-4 lg:gap-8"> {works.slice(1).map((work, index) => <article key={work.number} className={`grid min-w-0 grid-cols-[minmax(0,1.2fr)_minmax(0,1fr)] items-start gap-4 ${index === 1 ? "sm:mt-8 md:mt-0" : ""} md:grid-cols-[minmax(0,1.15fr)_minmax(0,1fr)] md:gap-4 lg:gap-5`}> <button type="button" onClick={() => setActive(work)} aria-label={`View ${work.title} full screen`} className="group relative block aspect-[0.83] w-full overflow-hidden bg-neutral-300 text-left focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-neutral-900 dark:bg-neutral-800 dark:focus-visible:outline-neutral-100">{index === 0 ? <img src="https://res.cloudinary.com/ddjovluur/image/upload/genblocks/images/1fc661474329f60c" alt={work.alt} className="h-full w-full object-cover transition-transform duration-500 ease-out group-hover:scale-[1.02] motion-reduce:transition-none"/> : <img src="https://res.cloudinary.com/ddjovluur/image/upload/genblocks/images/1a47ec0edb71832d" alt={work.alt} className="h-full w-full object-cover transition-transform duration-500 ease-out group-hover:scale-[1.02] motion-reduce:transition-none"/>}</button> <div className="pt-1"><Caption work={work} compact/></div> </article>)} </div> </div> <footer className="mt-10 flex items-center justify-between border-t border-neutral-300 pt-4 font-mono text-[9px] uppercase tracking-[0.16em] text-neutral-600 dark:border-neutral-700 dark:text-neutral-400 sm:mt-14"><span>Three works on view</span><span>Admission free <span className="mx-1.5">·</span> Tue—Sun, 12—19</span></footer> </div> {active && <div className="fixed inset-0 z-50 flex items-center justify-center bg-[#111210] p-4 text-white sm:p-8" role="dialog" aria-modal="true" aria-label={`${active.title} — full view`} onKeyDown={(event) => { if (event.key === "Escape") setActive(null); }}> <button type="button" onClick={() => setActive(null)} autoFocus aria-label="Close full view" className="absolute right-4 top-4 z-10 flex min-h-11 min-w-11 items-center justify-center border border-white/40 text-sm transition-colors hover:bg-white hover:text-neutral-900 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-white sm:right-7 sm:top-7">ESC</button> <div className="grid max-h-full w-full max-w-[1400px] grid-cols-1 items-center gap-5 overflow-y-auto sm:grid-cols-[minmax(0,1fr)_240px] sm:gap-10"><div className="flex min-h-[45vh] items-center justify-center sm:min-h-[70vh]">{active.number === "01" ? <img src="https://res.cloudinary.com/ddjovluur/image/upload/genblocks/images/e797f5bfe82a5c5b" alt={active.alt} className="max-h-[76vh] w-full object-contain"/> : active.number === "02" ? <img src="https://res.cloudinary.com/ddjovluur/image/upload/genblocks/images/1fc661474329f60c" alt={active.alt} className="max-h-[76vh] w-full object-contain"/> : <img src="https://res.cloudinary.com/ddjovluur/image/upload/genblocks/images/1a47ec0edb71832d" alt={active.alt} className="max-h-[76vh] w-full object-contain"/>}</div><div className="max-w-[360px] pb-3 sm:pb-0"><span className="mb-4 block font-mono text-[10px] tracking-[0.18em] text-neutral-400">SALA NORTE <span className="mx-1">/</span> {active.number}</span><h2 className="text-2xl font-normal tracking-[-0.03em]">{active.title}</h2><p className="mt-2 text-sm text-neutral-300">{active.materials} <span className="px-1">·</span> {active.year}</p><p className="mt-5 text-sm leading-relaxed text-neutral-200">{active.note}</p></div></div> </div>} </section>;}