/* ==========================================================================
   NOLAN ZACHOS — College Animation Applicant 2026
   Section grounds follow Web Mock.pdf, in order. Palette is Kelly's:
   #fafafa ground, #16140f ink + secondary.
   ========================================================================== */

:root {
  --paper:      #fafafa;
  --ink:        #16140f;
  --ink-80:     rgba(22, 20, 15, 0.80);
  --ink-55:     rgba(22, 20, 15, 0.62);   /* 0.55 put captions and labels at 4.0:1 — under AA */
  --ink-30:     rgba(22, 20, 15, 0.30);
  --ink-12:     rgba(22, 20, 15, 0.12);
  --on-dark:    #fafafa;
  --on-dark-60: rgba(250, 250, 250, 0.60);
  --on-dark-20: rgba(250, 250, 250, 0.20);

  /* Headline is meant to be Helvetica Now Condensed. It is a licensed Monotype
     family — not on Google Fonts and not on this machine — so it is named first
     and will take over the moment the webfont files are dropped in; until then
     Bricolage Grotesque, wound down the width axis, stands in. See the README. */
  --f-display: "Helvetica Now Condensed", "Helvetica Neue Condensed",
               "Bricolage Grotesque", "Arial Narrow", sans-serif;
  --f-body:    "Architects Daughter", "Bradley Hand", "Helvetica Neue", Arial, sans-serif;
  --f-hand:    "Caveat", "Bradley Hand", cursive;

  /* Two sizes carry the page. Every section headline is --t-head and every
     passage of running text is --t-copy; there is no third option and no
     per-section variant. --t-mega is the wordmark alone, and the three small
     sizes below are for labels, captions and list items, which are neither. */
  --t-head:  clamp(2rem, 4.6vw, 4rem);
  --t-copy:  clamp(1.05rem, 1.52vw, 1.52rem);   /* Architects Daughter runs small */

  --t-mega:  clamp(3.4rem, 11vw, 11rem);   /* "Nolan Zachos", nothing else */
  --t-body:  clamp(0.98rem, 1.05vw, 1.1rem);
  --t-small: clamp(0.82rem, 0.9vw, 0.92rem);
  --t-label: clamp(0.68rem, 0.76vw, 0.8rem);

  --gut:  clamp(1.25rem, 4vw, 4.5rem);
  --band: clamp(4rem, 8vw, 8.5rem);
  --maxw: 1500px;
}

*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  overflow-x: clip;               /* clip, not hidden — keeps position:fixed working */
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--f-body);
  font-size: var(--t-body);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

img, video { max-width: 100%; height: auto; display: block; }
/* the line above beats the UA's own [hidden] rule, so say it again */
img[hidden], video[hidden] { display: none; }
a { color: inherit; }
h1, h2, h3, h4, p, figure, ul, blockquote { margin: 0; }
ul { padding: 0; list-style: none; }

/* ---------- type primitives ---------- */
.display {
  font-family: var(--f-display);
  font-weight: 700;
  line-height: 0.88;
  letter-spacing: -0.025em;
  font-variation-settings: "wdth" 100, "opsz" 48;
  text-wrap: balance;
}
.display--mega { font-size: var(--t-mega); letter-spacing: -0.04em; }

/* ONE headline. Every section title on the page is this and nothing else:
   the display face at 800, wound to wdth 88, lowercase, on the wordmark's
   slant. Sections differ in where the title sits, never in how it is set. */
.sec-title {
  font-family: var(--f-display);
  font-weight: 800;
  font-size: var(--t-head);
  font-variation-settings: "wdth" 88;
  letter-spacing: -0.02em;
  line-height: 0.95;
  text-transform: lowercase;
  text-wrap: balance;
  transform: skewX(-8deg);
  transform-origin: left bottom;
}

/* ONE body. Every passage of running text is this — the about copy, the
   footer quote. The quote is the same size and leading, italic. */
.copy {
  font-size: var(--t-copy);
  line-height: 1.45;
}

.label {
  font-family: var(--f-body);
  font-size: var(--t-label);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  line-height: 1.6;
}
.hand { font-family: var(--f-hand); font-size: 1.45em; line-height: 1.2; }

.prose { max-width: 62ch; }
.prose p + p { margin-top: 0.9em; }
.lede { font-size: var(--t-copy); line-height: 1.45; max-width: 46ch; }

/* a caption is one factual line: what it is, what for, what tool, when */
.caption { font-size: var(--t-small); color: var(--ink-55); margin-top: 0.7rem; }
.on-dark .caption { color: var(--on-dark-60); }

/* ---------- bands ---------- */
.band { position: relative; padding: var(--band) var(--gut); isolation: isolate; scroll-margin-top: 4.5rem; }
.wrap { max-width: var(--maxw); margin-inline: auto; width: 100%; position: relative; z-index: 1; }

/* every textured ground is one rule, driven by data-ground */
.band[data-ground]::before {
  content: "";
  position: absolute; inset: 0; z-index: -2;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
/* Drawn, not photographed. The plate is a 1.48:1 landscape scan and the hero is
   375x1314 on a phone, so `cover` threw away 81% of its width and blew the
   squares up to ~39px — Kelly's mockup draws them at 20px. Drawn, the grid is
   the right size at every viewport and every band height, stays sharp at any
   pixel density, and weighs nothing. Cell, line weight and both tones measured
   off 169 ARTBOARDS_Nolan Zachos.svg and MOBILE_NZ.com.pdf, which agree.
   The photographic plate is still in assets/backgrounds if it's ever wanted. */
.band[data-ground="graph-paper"]::before {
  background-color: #f8f6fc;                       /* the scan's paper white */
  background-image:
    linear-gradient(to right,  #a4d1f0 1px, transparent 1px),
    linear-gradient(to bottom, #a4d1f0 1px, transparent 1px);
  background-size: 20px 20px;
  background-repeat: repeat;
  background-position: 0 0;                        /* keeps lines on whole pixels */
}
.band[data-ground="notebook"]::before {
  background-color: #f4f2ec;                       /* warm page, not screen white */
  background-image: radial-gradient(circle, rgba(22,20,15,0.30) 1.4px, transparent 1.7px);
  background-size: 24px 24px;
  background-position: center;
}
.band[data-ground="lockers"]::before         { background-image: url("assets/lockers/wall-mobile.webp"); }
.band[data-ground="perspective"]::before     { background-image: url("assets/backgrounds/perspective-room.webp"); background-size: 100% 100%; }
.band[data-ground="wrinkled"]::before        { background-image: url("assets/backgrounds/wrinkled-paper.webp"); }
.band[data-ground="linen"]::before            { background-image: url("assets/fashion/linen.webp"); }
.band[data-ground="flat"]::before            { background: var(--paper); }
.band[data-ground="ink"]::before             { background: var(--ink); }
.band[data-ground="crt"]::before             { background: #2f0807; }   /* the Animation wall */

.on-dark { color: var(--on-dark); }
.on-dark .label { color: var(--on-dark); }

/* a scrim so copy stays legible over a photographic ground */
.band[data-scrim]::after {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: var(--paper);
  opacity: var(--scrim, 0.55);
}
.band[data-scrim="ink"]::after { background: var(--ink); }

/* ---------- section heading ---------- */
.sec-head { margin-bottom: clamp(2rem, 4vw, 3.5rem); }
.sec-head .label { color: var(--ink-55); margin-bottom: 0.6rem; }
.on-dark .sec-head .label { color: var(--on-dark-60); }
.sec-head .lede { margin-top: 1.1rem; }

/* ---------- header ---------- */
.masthead {
  /* Fixed, not sticky, and parked off the top of the screen. The hero is a
     composition with a band of its own at the top, and a nav bar laid over it
     on load reads as chrome bolted onto the artwork — so the bar waits until
     the page moves (script.js adds .is-stuck past 64px) and lifts away again
     at the top. Fixed also means it takes no flow space while it is out of
     sight, so the hero plate starts at the very top of the window. */
  position: fixed; top: 0; left: 0; right: 0; z-index: 60;
  transform: translateY(-100%);
  transition: transform 0.32s cubic-bezier(.3, .7, .3, 1);
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem;
  padding: 0.7rem var(--gut);
  /* opaque enough to stay paper-coloured over the dark bands, or the ink-
     coloured nav text drops to dark-on-dark as it passes Animation */
  background: color-mix(in srgb, var(--paper) 96%, transparent);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--ink-12);
}
/* The masthead speaks the same language as the sections under it: the name is
   the hero wordmark at reading size — Bricolage at wdth 80 with its 7 degrees
   of skew — and the nav is set lowercase in the display face, the way Overview
   and About head themselves, instead of the tracked-out uppercase body labels
   the first build used. Only the name is skewed; eight skewed nav items read as
   a wobble rather than a signature. */
.masthead__name {
  font-family: var(--f-display); font-weight: 800;
  font-size: clamp(1rem, 1.2vw, 1.3rem);
  font-variation-settings: "wdth" 80;
  letter-spacing: -0.01em;
  transform: skewX(-7deg); transform-origin: left bottom;
  text-decoration: none; white-space: nowrap;
  padding-block: 0.65rem;          /* a 44px target, not 41 */
}
.masthead__nav { display: flex; gap: clamp(0.7rem, 1.5vw, 1.7rem); flex-wrap: wrap; justify-content: flex-end; }
.masthead__nav a {
  font-family: var(--f-display); font-weight: 700;
  font-size: clamp(0.88rem, 1vw, 1.05rem);
  font-variation-settings: "wdth" 88;
  letter-spacing: -0.005em; text-transform: lowercase;
  text-decoration: none; color: var(--ink-55);
  padding: 0.6rem 0; transition: color 0.18s ease;
}
.masthead__nav a:hover, .masthead__nav a:focus-visible { color: var(--ink); }
/* :focus-within as well as the class, or tabbing in from the top of the page
   lands on links nobody can see. */
.masthead.is-stuck, .masthead:focus-within { transform: none; }
@media (prefers-reduced-motion: reduce) { .masthead { transition: none; } }

.masthead__toggle { display: none; }

/* ==========================================================================
   S1 · GRAPH PAPER — hero + character design
   ========================================================================== */
.hero { min-height: auto; display: grid; align-content: start;
        padding-top: clamp(2rem, 5vw, 4rem);
        padding-inline: 0; }            /* the wall is drawn edge to edge */
.hero__name {
  margin: 0.1em var(--gut) 0.6em;
  /* The desktop mockup is the only place the wordmark is drawn: a bold oblique,
     a little narrower than Bricolage's default but nowhere near as condensed as
     the Fashion title. Measured off the plate, "Zachos" runs 3.474 wide for
     every 1 tall, which is Bricolage at wdth 80. Applied at both breakpoints so
     the same two words aren't set two different ways. */
  font-variation-settings: "wdth" 80;
  letter-spacing: -0.01em;
  transform: skewX(-7deg);
  transform-origin: left bottom;
}
.hero__name span { display: block; }

/* The character wall, measured off MOBILE_NZ.com.pdf: a 1125x3275 page with
   every character placed as a percentage of it, inside a box of that ratio, so
   the arrangement scales whole rather than reflowing. Same approach as Fashion
   and the Animation set. */
.wall {
  /* The mockup is a 375px phone screen edge to edge: the band drops its
     gutter for this and the headline carries its own, so every character
     lands where it was drawn. */
  position: relative;
  width: 100%;
  max-width: 560px;              /* phone mockup only — see README */
  margin-inline: auto;
  /* Not a mockup's page any more. The phone arrangement is packed rather than
     placed (see the hero layout note in README / qa), so the wall's height is
     whatever the packing came to — which is what keeps a hole from opening up
     at the bottom. */
  aspect-ratio: 1000 / 3225;
}
/* Direct children only. `.wall img` also matched the <img> INSIDE each .chip,
   which inherits --l/--t and so was absolutely positioned that percentage of
   the BUTTON's own box — every one of the seventeen clickable stickers was
   drawn down and to the right of where it was measured, by up to 70px, while
   the four plain images were correct. That was the wall's spacing. */
.wall > img, .wall > .chip {
  position: absolute;
  left: var(--l); top: var(--t);
  width: var(--w); height: auto;
  transform: rotate(var(--rot, 0deg));
  filter: drop-shadow(0 6px 14px rgba(22, 20, 15, 0.18));
  transition: transform 0.28s cubic-bezier(.2,.7,.3,1), filter 0.28s ease;
}
/* the ones with a character sheet behind them are real buttons */
.wall .chip { padding: 0; border: 0; background: none; cursor: pointer; display: block; }
.wall .chip img { position: static; width: 100%; height: auto; display: block; filter: none; }
.wall .chip:focus-visible { outline: 3px solid var(--ink); outline-offset: 4px; border-radius: 4px; }
/* A small lift on hover, plus a few degrees of turn — as if the sticker were
   being peeled up off the page. It adds to --rot rather than replacing it, so a
   sticker with its own angle keeps it instead of swinging loose. Alternate ones
   turn the other way, which stops a row of them all tilting in unison. */
.wall > * { --turn: 3.5deg; }
.wall > *:nth-child(even) { --turn: -3.5deg; }
/* ...but only where there is a real pointer. iOS Safari leaves :hover stuck on
   whatever you last tapped, so a tapped character stayed turned 3.5 degrees and
   scaled 1.06 — easily enough to sit on its neighbour — and stayed that way
   after the lightbox closed, until you happened to tap somewhere else. That was
   the overlap on the phone: the layout underneath never moved. A keyboard user
   still gets the lift, because :focus-visible is a deliberate act either way. */
.wall .chip:focus-visible {
  transform: rotate(calc(var(--rot, 0deg) + var(--turn))) scale(1.06);
  filter: drop-shadow(0 12px 26px rgba(22, 20, 15, 0.3));
  z-index: 5;
}
@media (hover: hover) and (pointer: fine) {
  .wall > img:hover, .wall .chip:hover {
    transform: rotate(calc(var(--rot, 0deg) + var(--turn))) scale(1.06);
    filter: drop-shadow(0 12px 26px rgba(22, 20, 15, 0.3));
    z-index: 5;
  }
}
@media (prefers-reduced-motion: reduce) {
  .wall > img, .wall > .chip { transition: none; }
  .wall > img:hover, .wall .chip:hover, .wall .chip:focus-visible { transform: rotate(var(--rot, 0deg)); }
}

/* Parked, not dead: the hero's eyebrow, lede and Focus/Training/Credits list
   were pulled out of the markup on request. The rules stay so putting the copy
   back is a markup-only change. */
.hero__inner { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); gap: clamp(1.5rem, 4vw, 4rem); align-items: center; }
.hero__meta { display: flex; gap: clamp(1rem, 2.5vw, 2.5rem); flex-wrap: wrap; margin-top: 1.6rem; }
.hero__meta div { max-width: 22ch; }
.hero__meta dt { font-size: var(--t-label); font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-55); }
.hero__meta dd { margin: 0.25rem 0 0; font-size: var(--t-small); }

/* character work under the hero */
.char-grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: clamp(0.85rem, 1.8vw, 1.75rem); align-items: end; }
.char-grid figure { margin: 0; }
.char-grid img { width: 100%; border-radius: 3px; }
.span-3 { grid-column: span 3; } .span-4 { grid-column: span 4; }
.span-5 { grid-column: span 5; } .span-6 { grid-column: span 6; }
.span-7 { grid-column: span 7; } .span-8 { grid-column: span 8; }
.span-12 { grid-column: span 12; }

/* ==========================================================================
   S4 · FASHION — the dress form

   One composition from NolanZachos_Fashion Section Mockup.pdf: a 360x785 page
   that scales whole from phone to desktop rather than reflowing, which is what
   Kelly asked for. Every piece is a percentage of the board.
   ========================================================================== */

.fashion { padding: 0; }
.fashion .wrap { max-width: none; }

.board {
  position: relative;
  overflow: hidden;                  /* the carousel runs off the corner and
                                        must stop at the section, not spill
                                        into the next one */
  width: 100%;
  max-width: 620px;                  /* one layout at every width; this stops it
                                        becoming a five-foot poster on a big screen */
  margin-inline: auto;
  aspect-ratio: 360 / 785;           /* the mockup's own page */
  container-type: inline-size;       /* the headline is sized from the board */
}

/* The dress form is the only thing anchored from the BOTTOM, as asked — its
   stand ends on the section's bottom edge rather than floating above it.
   The width is then solved, not guessed: at 85.6% the form's widest row (its
   shoulders, 24.96% down the artwork) lands at 34.5% of the page, which is
   where the mockup puts it. Anything wider pushes the shoulders up and the
   pinned work stops sitting on the torso. */
.board__form {
  position: absolute;
  left: 6.8%; bottom: 0; width: 85.6%; height: auto;
}

/* The carousel: a window fixed at the mockup's angle, with the strip drifting
   along its own long axis inside it. Two copies and a -50% travel make the
   loop seamless. */
.board__rail {
  position: absolute;
  left: var(--l); top: var(--t); width: var(--w);
  aspect-ratio: 1388 / 254;
  transform: rotate(var(--rot));
  overflow: hidden;
  /* NO pointer-events: none here. The rail began life as a decorative <div>
     and carried that line; it is a <button> now, and because pointer-events
     inherits, the one line silently killed the tap on the rail, the track and
     both images. Scripted .click() still fired, which is why it tested clean. */
  cursor: pointer;
}
.board__rail-track {
  display: flex; width: 200%; height: 100%;
  animation: rail-drift 48s linear infinite;
}
.board__rail-track img { width: 50%; height: 100%; object-fit: fill; }
@keyframes rail-drift { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) { .board__rail-track { animation: none; } }
/* phone only for now, by request */
/* Phone only. On desktop the strip runs off the corner of the board and gets
   clipped, so it is dropped — compound selector because `.board__open` below
   sets display: block and a plain `.board__rail` in a media query does not
   outrank it (media queries add no specificity). */
@media (min-width: 900px) { .board__rail.board__open { display: none; } }

/* the pinned groups that open a board are real buttons */
.board__open {
  padding: 0; border: 0; background: none; cursor: pointer; display: block;
  transition: transform 0.25s cubic-bezier(.2,.7,.3,1), filter 0.25s ease;
}
.board__open .board__art { width: 100%; height: auto; display: block; }
.board__open:focus-visible {
  transform: rotate(var(--rot, 0deg)) scale(1.03);
  filter: drop-shadow(0 10px 22px rgba(22, 20, 15, 0.22));
  z-index: 4;
}
@media (hover: hover) and (pointer: fine) {
  .board__open:hover {
    transform: rotate(var(--rot, 0deg)) scale(1.03);
    filter: drop-shadow(0 10px 22px rgba(22, 20, 15, 0.22));
    z-index: 4;
  }
}
.board__open:focus-visible { outline: 3px solid var(--ink); outline-offset: 4px; }

.board__item {
  position: absolute;
  left: var(--l); top: var(--t); width: var(--w); height: auto;
  transform: rotate(var(--rot, 0deg));
}

/* the clip sits in the Fit Check polaroid's own photo window, at its angle */
.board__film {
  position: absolute;
  left: var(--l); top: var(--t); width: var(--w); height: var(--h);
  transform: rotate(var(--rot, 0deg));
  object-fit: cover;
  display: block;
}

.board__head { position: absolute; left: 10.27%; top: 8.07%; width: 62%; }
.fashion__title {
  /* The same setting as every other section headline — wdth 88, -8 degrees,
     lowercase — but sized to the board rather than the viewport, because it is
     drawn inside a measured composition. 4.577cqw is 5.44 solved back down for
     the wider axis, so the ink still runs the plate's ~15% of the board. */
  font-family: var(--f-display);
  font-weight: 800;
  font-variation-settings: "wdth" 88;
  font-size: 4.577cqw;
  line-height: 0.95; letter-spacing: -0.02em; text-transform: lowercase;
  transform: skewX(-8deg); transform-origin: left bottom;
  margin: 0 0 0.18em;
}
.fashion__sub {
  font-family: var(--f-hand);
  font-size: 3.25cqw; line-height: 1.15;
  letter-spacing: 0.01em; color: var(--ink-80); margin: 0;
}

/* ==========================================================================
   S2 · SKETCHBOOK — full-page click-to-flip notebook

   Built in CSS 3D rather than from the page-turn video: the artwork is a
   child of the turning sheet, so it rotates in perspective with the paper.
   An overlaid flat image on a filmed page cannot do that.
   ========================================================================== */
.band--full {
  min-height: 100svh;
  display: grid;
  align-content: center;
}
.sketch-stage { display: grid; justify-items: center; gap: clamp(1rem, 2.5vw, 2rem); }
.sec-head--center { text-align: center; margin-bottom: 0; }

/* The spread is shaped by the artwork, not the other way round. Every one of
   the 55 pages is 1100x1719, so a spread of two is 2200x1719 — give the book
   any other ratio and `object-fit: contain` has to letterbox each page, which
   is where the empty paper down both sides of the art was coming from. At this
   ratio the art runs the full width of its page and only the margin below is
   left over.
   The third term in the width caps the book against the window: aspect-ratio
   turns width into height, so the height is limited by limiting the width. */
.book {
  position: relative;
  width: min(100%, 980px, calc(88svh * 2200 / 1719));
  aspect-ratio: 2200 / 1719;
  perspective: 2400px;
  perspective-origin: 50% 46%;
}

/* the paper itself — drawn, so the section ground shows no photograph and
   nothing sits on black */
.book::before {
  content: "";
  position: absolute; inset: 0;
  background: #f7f5ef;
  border-radius: 3px;
  box-shadow:
    0 1px 2px rgba(22,20,15,0.10),
    0 18px 50px rgba(22,20,15,0.16);
}

/* spiral binding, also drawn */
.book__spine {
  position: absolute; top: 2%; bottom: 2%; left: 50%;
  width: 26px; transform: translateX(-50%);
  z-index: 60; pointer-events: none;
  background:
    repeating-linear-gradient(
      to bottom,
      transparent 0 10px,
      rgba(22,20,15,0.36) 10px 12px,
      rgba(22,20,15,0.10) 12px 14px,
      transparent 14px 24px
    );
  filter: drop-shadow(0 1px 0 rgba(255,255,255,0.7));
}

.book__sheets { position: absolute; inset: 0; transform-style: preserve-3d; }

/* one sheet = one piece of paper, hinged on the spine */
/* A sheet is a 3D object with two backface-hidden faces, so the compositor
   gives each face its own backing store. At 55 pages that is 110 layers the
   size of the book — on a phone, hundreds of megabytes, and the tab dies as
   soon as you scroll the sketchbook into view. Two rules keep it small:

   1. no blanket `will-change`. Promoting 55 elements up front is exactly what
      the property warns against; only the leaf actually turning gets it.
   2. `.is-far` takes every sheet outside the current spread out of rendering
      altogether, so no layer exists for it. See render() in script.js. */
.sheet {
  position: absolute;
  top: 0; left: 50%; width: 50%; height: 100%;
  transform-origin: left center;
  transform-style: preserve-3d;
  transition: transform 0.85s cubic-bezier(.32, .06, .28, 1);
}
.sheet.is-turning { will-change: transform; }
.sheet.is-far { display: none; }
.sheet.is-turned { transform: rotateY(-180deg); }
.no-motion .sheet { transition: none; }

/* the restart: sheets snap home with no 28-leaf flip, under a short fade */
.book.is-restarting .sheet { transition: none; }
.book.is-restarting .book__sheets { animation: book-restart 0.45s ease; }
@keyframes book-restart {
  0%   { opacity: 1; }
  32%  { opacity: 0; }
  100% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .book.is-restarting .book__sheets { animation: none; }
}

.sheet__face {
  position: absolute; inset: 0;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  background: #f7f5ef;
  display: grid; place-items: center;
  /* proportional, so the margin is the same fraction of the page at every size
     — a fixed clamp ate 2rem of a 490px page on a laptop and almost nothing of
     a phone's. The gutter side also has to clear the spiral, which is drawn at
     a fixed 26px, hence the floor. */
  padding: 2.8%;
  overflow: hidden;
}
.sheet__face--front { padding-left: max(2.8%, 16px); }
.sheet__face--back  { padding-right: max(2.8%, 16px); }

/* Lighting. A page that swings away from the reader loses light; the one
   swinging toward them catches it. Both overlays cross-fade on the same
   curve as the rotation, which is what stops the turn reading as a flat
   rectangle spinning. */
.sheet__face::before {
  content: ""; position: absolute; inset: 0; z-index: 2;
  pointer-events: none; opacity: 0;
  transition: opacity 0.85s cubic-bezier(.32, .06, .28, 1);
}
.sheet__face--front::before { background: linear-gradient(to left,  rgba(22,20,15,0.30), rgba(22,20,15,0.04) 55%); }
.sheet__face--back::before  { background: linear-gradient(to right, rgba(22,20,15,0.30), rgba(22,20,15,0.04) 55%); }
.sheet.is-turned .sheet__face--front::before { opacity: 1; }
.sheet:not(.is-turned) .sheet__face--back::before { opacity: 1; }

/* the leaf lifts off the stack as it goes over */
.sheet { box-shadow: 0 0 0 rgba(22,20,15,0); }
.sheet.is-turning { box-shadow: 0 12px 34px rgba(22,20,15,0.22); }
/* No mix-blend-mode here: inside a preserve-3d / backface-visibility subtree
   Chrome culls a blended child and the page renders empty. Page art should
   ship with a transparent background instead, which is better anyway. */
.sheet__face img { width: 100%; height: 100%; object-fit: contain; }
.sheet__face--front { transform: rotateY(0deg); border-left: 1px solid rgba(22,20,15,0.07); }
.sheet__face--back  { transform: rotateY(180deg); border-right: 1px solid rgba(22,20,15,0.07); }

/* a touch of gutter shading so the spread reads as a bound book */
.sheet__face--front { background-image: linear-gradient(to right, rgba(22,20,15,0.07), transparent 14%); }
.sheet__face--back  { background-image: linear-gradient(to left,  rgba(22,20,15,0.07), transparent 14%); }
.sheet__face--blank::after {
  content: ""; position: absolute; inset: 0;
  background-image: radial-gradient(circle, rgba(22,20,15,0.16) 1.2px, transparent 1.5px);
  background-size: 22px 22px;
}

/* click targets sit over each half; the spine strip between them is dead */
.book__hit {
  position: absolute; top: 0; bottom: 0; width: 48%;
  border: 0; background: transparent; cursor: pointer;
  z-index: 70; padding: 0;
}
.book__hit--prev { left: 0; }
.book__hit--next { right: 0; }
.book__hit:focus-visible { outline: 2px solid var(--ink); outline-offset: -4px; }
.book.is-first .book__hit--prev,
.book.is-last  .book__hit--next { cursor: default; }

/* the corner you can actually grab lifts slightly on hover */
.book__hit--next:hover ~ .book__sheets .sheet:not(.is-turned):last-of-type,
.book__hit--next:focus-visible ~ .book__sheets .sheet:not(.is-turned):last-of-type { }
.book:not(.is-last) .book__hit--next { cursor: e-resize; }
.book:not(.is-first) .book__hit--prev { cursor: w-resize; }

.book__nav { display: flex; align-items: center; gap: 1rem; }
.book__btn {
  font-family: var(--f-body); font-size: var(--t-label); font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase;
  background: none; border: 1px solid var(--ink-30); border-radius: 999px;
  color: var(--ink); padding: 0.55rem 1.1rem; cursor: pointer;
  transition: border-color 0.2s ease, opacity 0.2s ease;
}
.book__btn:hover { border-color: var(--ink); }
.book__btn[disabled] { opacity: 0.3; cursor: default; }

.book__counter {
  font-family: var(--f-body); font-variant-numeric: tabular-nums;
  font-size: var(--t-small); color: var(--ink-55); letter-spacing: 0.08em;
}
.book__hint { margin: 0; }

/* ==========================================================================
   BLANK SECTION SLOTS — awaiting direction
   ========================================================================== */
.slot {
  border: 1.5px dashed currentColor; border-radius: 4px;
  padding: clamp(1.75rem, 4vw, 3.5rem);
  min-height: clamp(160px, 22vw, 280px);
  display: grid; align-content: center; justify-items: start;
  opacity: 0.55;
}
.slot__tag {
  display: inline-block; font-size: var(--t-label); font-weight: 700;
  letter-spacing: 0.16em; text-transform: uppercase;
  border: 1px solid currentColor; border-radius: 999px;
  padding: 0.3em 0.9em; margin-bottom: 0.9rem;
}
.slot p { font-size: var(--t-small); max-width: 60ch; }
.slot code { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 0.92em; }

/* ==========================================================================
   S7 · ABOUT — the person behind the drawings

   DESKTOP_NZ.com (2).pdf, 1366x1328. Everything sits in a column from 20.70%
   to 81.62% of the page; inside it the headline is flush right, the collage is
   inset a little further, and the three lists run 31.59%-68.34% with a hairline
   between them. Set as text, not as the mockup's picture of text.
   ========================================================================== */
/* Everything is a percentage of the PAGE, as measured, so the band drops its
   own gutters and each row carries its own width with auto margins. */
.about { padding: clamp(3rem, 6.7vw, 6rem) 0 clamp(3rem, 8.1vw, 7rem); }
.about .wrap { max-width: 1500px; }   /* the mockup page, capped */
.about__inner { width: 100%; }
/* position only — the setting is .sec-title */
.about__title {
  width: 60.92%; margin: 0 auto;
  text-align: right;
  transform-origin: right bottom;
}
.about__copy {
  width: 60.92%; margin: clamp(0.9rem, 2vw, 1.8rem) auto 0;
  text-align: left;
}
.about__photos { width: 62.40%; margin: clamp(1.4rem, 3.15vw, 2.8rem) auto 0; }
.about__lists {
  display: grid; grid-template-columns: repeat(3, 1fr);
  width: 36.75%; margin: clamp(1.4rem, 2.8vw, 2.6rem) auto 0;
  column-gap: clamp(0.5rem, 1.3vw, 1.4rem);
}
.about__list + .about__list { border-left: 1px solid var(--ink-30); padding-left: clamp(0.5rem, 1.3vw, 1.4rem); }
.about__list--start  { text-align: left; }
.about__list--centre { text-align: center; }
.about__list--end    { text-align: right; }
.about__listhead {
  font-family: var(--f-body); font-weight: 400;
  font-size: var(--t-small);
  margin-bottom: 1.6em;
}
.about__list ul {
  font-size: var(--t-small); letter-spacing: 0.05em;
  text-transform: uppercase; line-height: 1.9; color: var(--ink-80);
}
.about__tools { width: 36.54%; margin: clamp(1.1rem, 2.1vw, 2rem) auto 0; }

/* No phone mockup for this one. Below 900px the column runs to the gutters,
   the collage goes full width, and the three lists stay three lists — they are
   short enough, and stacking them would lose the comparison they make. */
@media (max-width: 899px) {
  .about { padding-inline: var(--gut); }
  .about__title, .about__copy, .about__photos, .about__lists { width: 100%; }
  .about__tools { width: 86%; }
  .about__copy  { text-align: left; }
  .about__lists { column-gap: 0.5rem; }
  .about__list + .about__list { padding-left: 0.5rem; }
  .about__list ul { letter-spacing: 0.02em; }
}


/* ==========================================================================
   FOOTER — the quote and the seven links
   ========================================================================== */
.colophon { background: var(--ink); color: var(--on-dark); padding: clamp(2rem, 3.7vw, 3.4rem) var(--gut) clamp(2rem, 3.95vw, 3.4rem); text-align: center; }
.colophon__quote { margin: 0 auto clamp(1.6rem, 5.1vw, 4.2rem); max-width: min(49.3%, 700px); }
.colophon__quote p { font-style: italic; color: var(--on-dark); }
/* the mockup sets the quote inside heavy curly marks; quotes: keeps them out
   of the text so a screen reader doesn't spell them */
.colophon__quote p::before { content: "\201C"; margin-right: 0.12em; }
.colophon__quote p::after  { content: "\201D"; margin-left: 0.12em; }
.colophon__quote cite {
  display: block; margin-top: 0.7em;
  font-style: normal; font-size: var(--t-small); color: var(--on-dark-60);
}
.colophon__quote cite::before { content: "\2014\00a0"; }
.socials { display: flex; justify-content: center; gap: clamp(0.5rem, 1.7vw, 1.6rem); flex-wrap: wrap; }
.socials a { display: grid; place-items: center; padding: 0.7rem; opacity: 0.85; transition: opacity 0.18s ease, transform 0.18s ease; }
.socials a:hover, .socials a:focus-visible { opacity: 1; transform: translateY(-2px); }
.socials img, .socials svg {
  width: clamp(24px, 2.8vw, 38px);
  height: clamp(24px, 2.8vw, 38px);   /* reserve the box before load, or the
                                         tap target is 22px until it arrives */
  object-fit: contain;
}
.socials svg { fill: var(--on-dark); }

/* ---------- work-in-progress markers ---------- */
.wip {
  border: 1.5px dashed currentColor; border-radius: 4px;
  padding: clamp(1rem, 2vw, 1.5rem); opacity: 0.8;
}
.wip__tag {
  display: inline-block; font-size: var(--t-label); font-weight: 700;
  letter-spacing: 0.16em; text-transform: uppercase;
  border: 1px solid currentColor; border-radius: 999px;
  padding: 0.3em 0.9em; margin-bottom: 0.8rem;
}
.wip p { font-size: var(--t-small); max-width: 62ch; }

/* ---------- reveal ---------- */
/* `translate`, not `transform`. The section headlines carry an 8-degree skew
   in `transform`, and a reveal written as translateY() overwrote it — the two
   titles that happened to carry .reveal were being set upright while the ones
   without it slanted. The individual property composes instead of replacing. */
.reveal { opacity: 0; translate: 0 16px; transition: opacity 0.6s ease, translate 0.6s ease; }
.reveal.is-in { opacity: 1; translate: none; }

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */
@media (max-width: 1024px) {
  .room__wall { width: min(62%, 520px); }
  .span-3, .span-4 { grid-column: span 6; }
  .span-5, .span-7, .span-8 { grid-column: span 12; }
}

@media (max-width: 980px) {
  :root { --band: 3.25rem; --gut: 1.25rem; --t-mega: clamp(2.8rem, 15vw, 5rem); }

  .masthead__nav {
    position: absolute; top: 100%; left: 0; right: 0;
    flex-direction: column; align-items: flex-start; gap: 0;
    background: var(--paper); border-bottom: 1px solid var(--ink-12);
    padding: 0.4rem var(--gut) 1.1rem;
    max-height: calc(100svh - 3.5rem); overflow-y: auto;
    display: none;
  }
  .masthead.is-open .masthead__nav { display: flex; }
  .masthead__nav a { width: 100%; padding: 0.7rem 0; font-size: 1.15rem; }
  .masthead__toggle {
    display: block; background: none; border: 0; cursor: pointer; color: inherit;
    font-family: var(--f-display); font-size: 1rem; font-weight: 700;
    font-variation-settings: "wdth" 88;
    letter-spacing: -0.005em; text-transform: lowercase; padding: 0.85rem 0.4rem;
  }

  .span-6, .span-3, .span-4 { grid-column: span 6; }
  .anim-pair { grid-template-columns: minmax(0, 1fr); }
  .sequence__item { grid-template-columns: minmax(0, 1fr); gap: 0.5rem; }
  .room__wall { width: 82%; margin-block: 4rem; }
  .book {                              /* one page at a time, so one page's ratio */
    width: min(100%, calc(88svh * 1100 / 1719));
    aspect-ratio: 1100 / 1719;
    overflow: visible;
  }
  .book__spine { left: 0; transform: none; width: 13px; }
  .sheet { left: 0; width: 100%; }
  .sheet__face { padding: 3%; }
  .sheet__face--front { padding-left: max(3%, 10px); }
  .sheet__face--back  { padding-right: max(3%, 10px); }
  .sheet__face--front { border-left: 0; background-image: linear-gradient(to right, rgba(22,20,15,0.06), transparent 9%); }
  .sheet__face--back  { border-right: 0; background-image: linear-gradient(to left,  rgba(22,20,15,0.06), transparent 9%); }
  .book__hit { width: 50%; }
    }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .reveal { opacity: 1; transform: none; transition: none; }
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}


/* ---------------------------------------------------------------------------
   Animation — the wall of televisions
   Two mockups: a 360x720 phone page and a 1920x1080 desktop one. The wall is
   placed as a percentage of whichever page is in play, and each screen as a
   percentage of the wall. Both coordinate sets are inline on the screens.
   --------------------------------------------------------------------------- */

.vh {                                  /* heard, not seen */
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; border: 0; overflow: hidden; white-space: nowrap;
  clip-path: inset(50%);
}

.animation { padding: 0; }
.animation .wrap {
  max-width: none;
  position: relative;
  aspect-ratio: 360 / 720;             /* the phone mockup's page */
}

.tvwall {
  position: absolute;
  left: 6.04%; top: 2.93%; width: 83.73%;
  aspect-ratio: 900 / 1981;            /* the wall artwork's own ratio */
}
.tvwall__screens { position: absolute; inset: 0; }

/* The artwork sits ON TOP. Its screens are real holes, so the bezels do the
   masking and no clip needs rounded corners of its own. */
.tvwall__art {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;
  z-index: 1;
}

/* No exceptions. A screen lifted above .tvwall__art stops being masked by it,
   and the artwork is the only thing that masks anything here — so the NO SIGNAL
   video was painting its own square corners out over its bezel and onto the set
   drawn in front of it. The overhang was the real fault: the hole in the art is
   61.56%/23.22% wide and the video was placed at 61.44%/23.44%, about two pixels
   proud on each side. It is measured to the hole now and stays behind the art,
   like every other screen. (.scr--front is kept as a hook but carries no lift.) */

/* a transparent hit area over the tube: the clip itself must not be wrapped in
   a button, and the bezel would eat the click otherwise */
.scr__open {
  position: absolute; inset: 0; z-index: 3;
  border: 0; background: none; padding: 0; cursor: pointer;
}
.scr__open:focus-visible { outline: 3px solid var(--paper); outline-offset: -3px; }

.scr {
  position: absolute;
  left: var(--l); top: var(--t); width: var(--w); height: var(--h);
  overflow: hidden;
  background: #0a0304;                 /* the tube, before anything loads */
}
.scr > * {
  display: block; width: 100%; height: 100%;
  object-fit: cover; border: 0;
}
/* The YouTube player is 16:9 and the opening is not, so it is driven off the
   opening's height and the bezel crops its width — same as the still it
   replaces. */
.scr--yt iframe {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  height: 100%; width: 178%;
}

@media (min-width: 900px) {
  .animation .wrap {
    aspect-ratio: 1920 / 1080;         /* the desktop mockup's page */
    max-width: 1920px;
    margin-inline: auto;
  }
  .tvwall {
    left: 11.33%; top: 31.02%; width: 76.90%;
    aspect-ratio: 1477 / 729;
  }
  .scr { left: var(--dl); top: var(--dt); width: var(--dw); height: var(--dh); }
}


/* ==========================================================================
   DESKTOP — from DESKTOP_NZ.com (6).pdf
   A 2135x2808 plate: the same characters re-spaced, one of them dropped, and
   the headline sitting down in the field. Both coordinate sets are inline on
   each image; these rules only choose which pair to read.
   Every placement here was measured off the plate rather than eyeballed — see
   qa/ for the composites the numbers were checked against.
   ========================================================================== */
@media (min-width: 900px) {
  .hero .wrap {
    max-width: 1366px;               /* the mockup's own page width */
    position: relative;              /* the headline is placed against this */
    container-type: inline-size;     /* ...and sized from it */
  }
  .hero { padding-top: 0; }          /* the mockup's top band IS the masthead */
  .wall {
    max-width: none;
    aspect-ratio: 2135 / 2808;       /* DESKTOP_NZ.com (6).pdf, whole page */
  }
  .wall > img, .wall > .chip { left: var(--dl); top: var(--dt); width: var(--dw); transform: rotate(var(--drot, 0deg)); }
  .wall .chip:focus-visible { transform: rotate(calc(var(--drot, 0deg) + var(--turn))) scale(1.06); }
  .wall img[data-only="mobile"], .wall .chip[data-only="mobile"] { display: none; }

  /* the headline stops being a stacked block above the wall and becomes part
     of the composition, top-left inside the field */
  .hero__name {
    position: absolute;
    left: 10.44%; top: 12.58%;       /* tuned so the INK lands at 10.40/12.50 */
    margin: 0;
    font-size: 6.53cqw;              /* "Zachos" runs 17.19% of the page wide */
    line-height: 1.184;              /* 5.88% of the page between baselines */
  }

  /* a tablet is wide enough to land here and still have no pointer */
  @media (hover: hover) and (pointer: fine) {
    .wall > img:hover, .wall .chip:hover { transform: rotate(calc(var(--drot, 0deg) + var(--turn))) scale(1.06); }
  }

  /* the squares are drawn larger on a desktop page: 29px, not 20px */
  .band[data-ground="graph-paper"]::before { background-size: 29px 29px; }
}

@media (prefers-reduced-motion: reduce) {
  .wall > img:hover, .wall .chip:hover, .wall .chip:focus-visible { transform: rotate(var(--drot, var(--rot, 0deg))); }
}

/* ==========================================================================
   S3 · LOCKERS — six polaroids taped to the doors
   Two mockups: a 360x906 phone page and a 1366x338 desktop strip. Same six
   cards, both placements inline on each one.
   ========================================================================== */

.lockers { padding: 0; }
.lockers .wrap {
  max-width: none;
  position: relative;
  aspect-ratio: 360 / 906;             /* the phone mockup's page */
}
.lockerwall { position: absolute; inset: 0; overflow: hidden; }

/* Fitted to the mockups rather than left to `cover`: on the phone page the wall
   is drawn at 316% of the page width, on the desktop strip at 102%. Those two
   numbers were solved by matching the wall against the bands of each mockup that
   the polaroids don't cover. */
.lockerwall__bg {
  position: absolute;
  left: -53.9%; top: -19.1%; width: 316%; height: auto;
  max-width: none;                   /* the global img cap would clamp this to
                                        the container and leave the wall blank */
}

.pola {
  position: absolute;
  left: var(--cx); top: var(--cy);
  width: var(--w);
  aspect-ratio: 822 / 956;             /* the polaroid inside the footage */
  transform: translate(-50%, -50%) rotate(var(--rot));
}
/* The clip is the polaroid — its white frame is in the footage — so it fills
   the card. There is no separate frame image. */
.pola video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
  display: block;
}

@media (min-width: 900px) {
  /* full bleed at any width: capping the wrap left the wall inset with
     paper either side on a wide screen */
  .lockers .wrap { aspect-ratio: 1366 / 338; max-width: none; }
  .lockerwall__bg { left: -0.70%; top: -219.7%; width: 102%; }
  .pola {
    left: var(--dcx); top: var(--dcy); width: var(--dw);
    transform: translate(-50%, -50%) rotate(var(--drot));
  }
}

/* ==========================================================================
   THE CHARACTER SHEETS — a full-page lightbox behind each sticker

   Frosted paper rather than a black void: the page stays faintly visible
   underneath, which keeps it feeling like a sheet lifted off the wall instead
   of a slideshow in a different application. The boards are mostly white
   inside, so they get a hairline and a real shadow or they melt into it.
   ========================================================================== */
.lightbox {
  /* 100svh is the SMALL viewport: when Safari's toolbar slides away the visual
     viewport grows past it and the page underneath showed along the bottom
     edge, which is why the stickers were visible under an open board. Fixed to
     all four edges at the DYNAMIC height instead, so it tracks the toolbar and
     always covers. The backdrop is given the panel's own colour as well, so a
     rounding sliver cannot read as a gap. */
  position: fixed; inset: 0;
  width: 100vw; max-width: 100vw; height: 100dvh; max-height: 100dvh;
  margin: 0; padding: 0; border: 0;
  background: rgba(250, 250, 250, 0.86);
  -webkit-backdrop-filter: blur(22px) saturate(1.05);
  backdrop-filter: blur(22px) saturate(1.05);
  color: var(--ink);
  overscroll-behavior: contain;
}
.lightbox::backdrop {
  background: rgba(250, 250, 250, 0.86);
  -webkit-backdrop-filter: blur(22px) saturate(1.05);
  backdrop-filter: blur(22px) saturate(1.05);
}
/* A focus ring belongs to a keyboard user and nobody else. showModal() parks
   focus on the first control in the dialog and Safari paints its ring there,
   which is where the black circle round the close X and the blue frame round a
   film came from — both of them outlines that were supposed to be gone. Focus
   goes to the dialog itself now (script.js); this makes sure that neither it
   nor a tapped control draws anything. */
.lightbox:focus { outline: none; }
.lightbox :focus:not(:focus-visible) { outline: none; }
.lightbox[open] { display: grid; grid-template-rows: 1fr auto; }

.lightbox__stage {
  display: grid; place-items: center;
  min-height: 0;
  padding: clamp(1rem, 4vw, 3.5rem);
  overflow: auto;
  scrollbar-width: none;
}
.lightbox__stage::-webkit-scrollbar { display: none; }

/* The boards are 1920x1080 stills and the films are 1280x720, so `width: auto`
   drew every film at its intrinsic 1280 while a board filled to 1680 — one size
   for pictures and a smaller one for video, which is exactly what it looked
   like. `width: 100%` makes both scale UP to the same box; height stays auto so
   the frame still hugs each item's own aspect rather than a hard-coded 16:9,
   and max-height pulls the width back in when the window is short. */
.lightbox__img {
  width: 100%; height: auto;
  max-width: 1680px; max-height: 100%;
  object-fit: contain;
  background: #fff;
  border: 1px solid rgba(22, 20, 15, 0.10);
  box-shadow: 0 2px 4px rgba(22,20,15,0.06), 0 18px 50px rgba(22, 20, 15, 0.16);
  transition: opacity 0.18s ease;
}
.lightbox.is-swapping .lightbox__img { opacity: 0; }

/* zoomed: the board fills the height and the stage pans sideways, which is the
   only way a sheet this dense is readable on a phone held upright */
.lightbox.is-zoomed .lightbox__stage { place-items: center start; padding: 0; }
.lightbox.is-zoomed .lightbox__img {
  max-width: none; max-height: none;
  height: 100svh; width: auto;             /* svh, not %: a percentage height
                                              inside an auto-sized grid area
                                              falls back to the natural size */
  border: 0; box-shadow: none;
  cursor: zoom-out;
}
.lightbox__img { cursor: zoom-in; }

.lightbox__cap {
  margin: 0; padding: 0 1rem 1.1rem;
  text-align: center;
  font-size: var(--t-label); font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase;
}
.lightbox__count { color: var(--ink-55); margin-left: 0.9em; }
/* work that lives on YouTube gets a way out rather than a dead player */
.lightbox__watch {
  display: inline-block; margin-left: 1.1em;
  text-decoration: none; border-bottom: 1px solid currentColor;
  padding-bottom: 1px;
}
.lightbox__watch::after { content: " \2197"; }
.lightbox__watch[hidden] { display: none; }
.lightbox.is-zoomed .lightbox__cap { display: none; }

.lightbox__close, .lightbox__nav {
  position: fixed; z-index: 2;
  display: grid; place-items: center;
  width: 3rem; height: 3rem;                 /* a real 48px target */
  border: 0; border-radius: 999px;
  background: rgba(250, 250, 250, 0.72);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  color: var(--ink);
  font: 400 1.5rem/1 var(--f-body);
  cursor: pointer;
  transition: background 0.2s ease, transform 0.2s ease;
}
.lightbox__close:hover, .lightbox__nav:hover { background: var(--paper); transform: scale(1.06); }
.lightbox__close:focus-visible, .lightbox__nav:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; }
.lightbox__close { top: 1rem; right: 1rem; font-size: 1.9rem; }

/* The arrows sit just outside the artwork, not out at the window's edge. A
   film is 16:9 and a board is not, so the gap between the media and the frame
   changes with every item — script.js measures what is actually on screen and
   publishes it as --media-w, and these fall back to the old edge position if it
   is ever missing. max() keeps them on screen when the media fills the stage. */
.lightbox { --media-w: 100vw; --nav-gap: 3.25rem; }
.lightbox__nav { top: 50%; transform: translateY(-50%); }
.lightbox__nav--prev { left:  max(0.75rem, calc(50% - var(--media-w) / 2 - var(--nav-gap))); }
.lightbox__nav--next { right: max(0.75rem, calc(50% - var(--media-w) / 2 - var(--nav-gap))); }
.lightbox__nav--prev:hover { transform: translateY(-50%) scale(1.06); }
.lightbox__nav--next:hover { transform: translateY(-50%) scale(1.06); }

/* The boards are 16:9. On a phone held upright one is about 210px tall, which
   is useless for a sheet of head studies — so we say so, once, and get out of
   the way the moment the phone turns. */
.lightbox__turn {
  position: fixed; left: 50%; bottom: 4.2rem; transform: translateX(-50%);
  margin: 0; padding: 0.55rem 1rem;
  border-radius: 999px;
  background: rgba(22, 20, 15, 0.86); color: var(--paper);
  font-size: var(--t-label); letter-spacing: 0.1em; text-transform: uppercase;
  white-space: nowrap; pointer-events: none;
  opacity: 0; transition: opacity 0.35s ease;
}
/* An upright phone: the artwork gets the whole screen, and the title and the
   arrows travel with it rather than living in the corners of the window. The
   caption stops reserving a grid row (so nothing is subtracted from the
   picture) and both it and the arrows are placed off --media-h, the measured
   height of whatever is actually on screen — script.js publishes it, because
   only the browser knows how tall a given board or film ends up. A board and a
   film still get exactly the same furniture. */
@media (orientation: portrait) and (max-width: 820px) {
  .lightbox[open] { grid-template-rows: 1fr; }
  .lightbox__stage { padding: 0; }
  .lightbox__img, .lightbox__vid { max-width: 100%; border: 0; box-shadow: none; }

  /* the foot of the artwork, whatever it is */
  .lightbox { --below: calc(50% + var(--media-h, 32svh) / 2); }

  .lightbox__nav {
    display: grid; top: calc(var(--below) + 0.45rem); bottom: auto; transform: none;
    width: 2.6rem; height: 2.6rem;
  }
  .lightbox__nav--prev { left:  max(0.5rem, calc(50% - var(--media-w, 94vw) / 2)); }
  .lightbox__nav--next { right: max(0.5rem, calc(50% - var(--media-w, 94vw) / 2)); }
  .lightbox__nav--prev:hover, .lightbox__nav--next:hover { transform: scale(1.06); }

  .lightbox__cap {
    position: fixed; left: 0; right: 0;
    top: calc(var(--below) + 1.15rem);
    padding: 0 4rem;                 /* clear of the two arrows */
  }
  .lightbox.is-fresh .lightbox__turn { opacity: 1; }
  .lightbox__turn { bottom: 2.2rem; }
  /* zoom is retired here: the board already fills the width, and a tap is for
     closing (script.js) — so it must not also toggle something */
  .lightbox__img { cursor: default; }
}
/* Turn the phone and the board should fill the screen — that is the whole
   point of turning it. A 16:9 board on a 390px-tall window has no height to
   spare, so in short landscape the caption stops occupying a grid row and
   floats over the picture instead, the stage loses its padding, and the board
   is given the full 100svh. (Height-fill, not crop: 16:9 inside 2.16:1 means
   the picture runs edge to edge top and bottom, which is as large as a sheet
   of head studies can be shown without cutting any of it off.) */
@media (orientation: landscape) and (max-height: 560px) {
  .lightbox[open] { grid-template-rows: 1fr; }
  .lightbox__stage { padding: 0; }
  .lightbox__img {
    max-width: 100vw; max-height: 100svh;
    height: 100svh; width: auto;
    border: 0; box-shadow: none;
    cursor: default;                 /* it already fills the height */
  }
  /* zoom has nothing left to do here, so it is made a no-op rather than
     left to yank a full-height board over to the left edge */
  .lightbox.is-zoomed .lightbox__stage { place-items: center; }
  .lightbox.is-zoomed .lightbox__cap { display: block; }
  .lightbox__cap {
    position: fixed; left: 0; right: 0; bottom: 0;
    padding: 1.6rem 1rem 0.5rem;
    background: linear-gradient(to top,
      rgba(250, 250, 250, 0.94), rgba(250, 250, 250, 0));
    pointer-events: none;            /* the picture under it stays reachable */
  }
  .lightbox__cap a { pointer-events: auto; }
  .lightbox__close { top: 0.5rem; right: 0.5rem; width: 2.4rem; height: 2.4rem; font-size: 1.5rem; }
  .lightbox__nav { width: 2.4rem; height: 2.4rem; }
}
@media (prefers-reduced-motion: reduce) {
  .lightbox__img, .lightbox__turn, .lightbox__close, .lightbox__nav { transition: none; }
}

/* --- film, on the same sheet ---------------------------------------------
   One room for everything. A board, a looping gif and a finished film are all
   work on a wall, and giving the films their own dark theatre made the set
   feel like a different application — so the frosted paper carries all three
   and only the behaviour differs: a film keeps its controls, loses the zoom
   and the swipe (the controls own the horizontal gesture), and gets a dark mat
   of its own so the blacks in the picture still read as black. */
.lightbox__vid, .lightbox__yt {
  width: 100%; height: auto;
  max-width: 1680px; max-height: 100%;
  object-fit: contain;
  background: #16140f;
  border: 0;                       /* a picture needs no frame drawn round it */
  box-shadow: 0 18px 50px rgba(22, 20, 15, 0.16);
}
/* the YouTube frame has no intrinsic size, so it is given the 16:9 the player
   actually is and left to fit the stage like any other film */
/* an iframe has no intrinsic ratio to follow, so it is told the one the player is */
.lightbox__yt { aspect-ratio: 16 / 9; }
.lightbox__yt[hidden] { display: none; }
/* A film is just as cramped on an upright phone as a board is, so it gets the
   same hint; it used to be the one item in the set without it. */
/* the films fill the turned screen the same way the boards do */
@media (orientation: landscape) and (max-height: 560px) {
  .lightbox__vid, .lightbox__yt {
    max-width: 100vw; max-height: 100svh;
    height: 100svh; width: auto;
    box-shadow: none;
  }
}

/* ==========================================================================
   "THAT ONE'S CLICKABLE" — a glow that comes round now and then
   Nothing on this page looks like a button, which is the point of it, but it
   does mean a visitor has no way of telling a sticker that opens a sheet from
   one that is only decoration. So the interactive ones breathe: every few
   seconds a couple of the ones currently on screen glow once and settle again.

   Two shapes of hint, because one does not fit both. A sticker is an irregular
   cut-out, so it gets drop-shadow, which follows the artwork's own alpha — a
   box-shadow would draw a rectangle around a character. A television screen is
   a rectangle cut into the wall, so it gets an inset ring instead.

   Only a few at a time, and only while they are on screen: this page already
   died once from handing the compositor too much at once, and `filter` is not
   a free property. Nothing animates at all for prefers-reduced-motion.
   ========================================================================== */
@keyframes hint-glow {
  0%, 100% { filter: drop-shadow(0 0 0 rgba(22, 20, 15, 0)); }
  45%      { filter: drop-shadow(0 0 10px rgba(22, 20, 15, 0.45)); }
}
@keyframes hint-ring {
  0%, 100% { box-shadow: inset 0 0 0 0 rgba(250, 250, 250, 0); }
  45%      { box-shadow: inset 0 0 14px 2px rgba(250, 250, 250, 0.75); }
}
.hint-glow { animation: hint-glow 1.7s ease-in-out; }
.scr__open.hint-glow { animation-name: hint-ring; }
/* a hint must never be what makes a thing move: no transform here, so it cannot
   push a sticker onto its neighbour the way :hover did */
@media (prefers-reduced-motion: reduce) {
  .hint-glow { animation: none; }
}

/* ==========================================================================
   S6 · GRAPHICS — printed work in a perspective room
   Two mockups: a 360x577 phone page and a 1366x809 desktop one. The room is a
   repeating rule pattern so it covers the band; the pile is placed.
   ========================================================================== */
.graphics { padding: 0; }
.graphics .wrap {
  max-width: none;
  position: relative;
  aspect-ratio: 360 / 577;
  overflow: hidden;
}
.gfx { position: absolute; inset: 0; }

/* The room is not a cover crop. Measured off both mockup pages by the extent of
   its ink: on the phone it bleeds edge to edge, and on desktop it sits inset
   2.81% either side with the page's own aspect, so cover-scaling it would blow
   the perspective up by about 1.9x and cut the vanishing point off-centre. */
.gfx__room {
  position: absolute; left: 0; top: 0;
  width: 100%; height: 100%;
  max-width: none;
}
.gfx__pile {
  position: absolute;
  left: 17.07%; top: 13.97%; width: 64.36%; height: auto;
}

@media (min-width: 900px) {
  .graphics .wrap { aspect-ratio: 1366 / 809; }
  .gfx__room { left: 2.81%; width: 94.38%; }
  .gfx__pile { left: 26.42%; top: 18.98%; width: 51.44%; }
}

