/* ============================================================
   Close up — scroll-driven storytelling
   Loaded by /close-up/* only.

   A separate stylesheet rather than a section of site.css, because it serves
   one page shape and nothing else on the site shares a line of it. It consumes
   design-system tokens and site.css's rhythm variables and defines almost
   nothing of its own.

   Layout in one line: a sticky stage holding the artwork, beside a column of
   passages that scroll past it. Each passage is a "beat"; the stage answers by
   moving.

   ------------------------------------------------------------------
   THE DIVISION OF LABOUR

   CSS owns everything that does not depend on where the camera is pointing:
   the sticky stage, its shape, the type, the resting state, reduced motion,
   and the whole mobile rearrangement.

   close-up.js owns `translate` and `scale` on .cu__art, and writes them as
   literal values. It CANNOT be done here. The geometry depends on the measured
   frame — fit-bounds means a phone and a laptop reach different scales for the
   same authored region — so the numbers have to be computed after layout.

   Which is why there are no --cu-scale / --cu-x / --cu-y variables any more.
   Routing motion through custom properties looked elegant and cannot reach the
   compositor: unregistered ones do not interpolate at all, and registered ones
   substitute as computed values, so every frame invalidates style.

   ⚠ DO NOT add `transform` to .cu__art. `translate` and `scale` are separate
   properties that apply BEFORE the shorthand, so a transform here would not
   simply also apply — its translate would sit inside the script's scale and be
   multiplied by it, and the error would grow quadratically with zoom. See
   css-transforms-2 §6, and the warning by write() in close-up.js.

   --cu-ar is set inline on .cu by the generator, and holds the artwork's shape
   as a bare ratio (`1747 / 2479`) — the one form that is valid both as
   aspect-ratio and inside a calc().
   ------------------------------------------------------------ */

.cu {
  /* A fallback for a hand-written page that forgot it, and the shape of the
     seed artwork. Portrait, because these are paintings on paper. */
  --cu-ar: 1747 / 2479;

  max-width: 1160px;
  margin-inline: auto;
  padding-inline: clamp(18px, 5vw, 28px);
  padding-block: var(--pad-band-sm) var(--pad-band);

  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  gap: clamp(24px, 5vw, 72px);

  /* Required, not cosmetic. A sticky grid item sticks within its own grid
     area; `align-items: start` is what stops the stage stretching to the full
     row height, which would leave it nothing to travel through. The row is as
     tall as the script column, so that is the distance the stage sticks for. */
  align-items: start;
}

/* ------------------------------------------------------------
   The stage
   ------------------------------------------------------------ */

.cu__stage {
  position: sticky;
  top: calc(var(--header-h) + 20px);

  /* The frame is the artwork's shape, not the column's.

     Sizing this as "full column width, full available height" leaves the
     stage letterboxed by however much the two aspect ratios disagree — on a
     tall window that was 370px of empty frame under a 749px picture, which
     reads as a bug rather than as matting. Deriving the width from the
     available height instead means the frame is only ever as large as the
     artwork can fill, and `min()` hands back to the column when the column is
     the tighter of the two.

     svh, not vh. On iOS the URL bar collapses on scroll and vh is the tall
     measurement, so a vh-sized stage is taller than the visible viewport for
     the entire first screen — the bottom of the artwork sits under the
     browser chrome exactly while someone is looking at it. */
  --cu-room: calc(100svh - var(--header-h) - 40px);
  width: min(100%, calc(var(--cu-room) * var(--cu-ar)));
  aspect-ratio: var(--cu-ar);
  height: auto;
  margin-inline: auto;

  /* The artwork is scaled past its own box on every beat but the first. This
     is what keeps it inside the frame instead of over the text. */
  overflow: hidden;

  display: grid;
  place-items: center;

  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius-xs);
}

.cu__art {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;

  /* No transform, no translate, no scale declared here — see the ⚠ at the top
     of this file. The resting state IS the absence of them: unset means
     identity, so if script never runs (parse error, blocked file, JS off) the
     page shows the whole artwork with every passage readable beside it. That
     is a real page, not a broken one, which is the bar any scroll effect has
     to clear — and it costs no declarations at all.

     transform-origin still applies to the individual properties, and the
     centre is what camera.fit() derives its pan against. */
  transform-origin: 50% 50%;

  /* The artwork is the only thing on the page that moves. Naming the two
     properties actually animated, not `transform`: Chrome builds different
     data structures for the shorthand than for the individual properties, so
     `will-change: transform` is not a substitute here. */
  will-change: translate, scale;
}

/* No transition. Both drivers are continuous — the ViewTimeline one hands the
   whole move to the compositor, the rAF one writes a new value every frame —
   so an easing transition on top would make the artwork chase its own target:
   the scroll sets a value, the transition spends 800ms travelling to it, and
   the next frame moves the goalposts. That reads as lag, not smoothness.

   The easing is in the motion itself. See easeInOut() in close-up.js. */

/* ------------------------------------------------------------
   The script column
   ------------------------------------------------------------ */

.cu__script {
  max-width: 34rem;
}

.cu__title {
  font-family: var(--font-serif);
  font-weight: var(--fw-light, 300);
  font-size: clamp(2rem, 4.4vw, 3.1rem);
  line-height: 1.08;
  letter-spacing: -0.015em;
  color: var(--ink);
  margin: 0 0 0.6em;
  text-wrap: balance;
}

.cu__title em {
  font-style: italic;
  color: var(--rose);
}

.cu__standfirst {
  font-family: var(--font-serif);
  font-size: 1.2rem;
  line-height: 1.55;
  color: var(--ink-soft);
  margin: 0 0 var(--gap-band, 64px);
  max-width: 30rem;
}

/* A beat is one passage and the stage position that belongs with it.

   The height is the pacing control, and it is the single number most worth
   fiddling with while prototyping: it decides how long a reader spends at one
   camera position. Too short and the stage never settles between moves; too
   long and the page feels padded with whitespace. 76svh means roughly one
   move per screen, with the passage vertically centred so it is at rest in
   the middle of the move rather than at either end. */
.cu__beat {
  min-height: 76svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.cu__beat p {
  font-family: var(--font-serif);
  font-size: 1.25rem;
  line-height: 1.6;
  color: var(--ink);
  margin: 0 0 1em;
}

.cu__beat p:last-child { margin-bottom: 0; }

/* The line that carries the beat — set larger and in the pigment, one per
   passage at most. This is the site's existing habit of letting an italic
   serif clause do the emotional work, borrowed from the section headings. */
.cu__say {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: clamp(1.5rem, 2.6vw, 2rem) !important;
  line-height: 1.3 !important;
  color: var(--rose) !important;
  text-wrap: balance;
}

.cu__aside {
  font-family: var(--font-grotesk);
  font-size: 0.82rem !important;
  line-height: 1.6 !important;
  color: var(--ink-faint) !important;
  border-top: 1px solid var(--rule);
  padding-top: 14px;
  margin-top: 1.4em !important;
}

/* ------------------------------------------------------------
   Reduced motion

   site.css §13 already strips every animation and transition with !important.
   What a stylesheet cannot do is stop a script writing `translate` and `scale`
   directly — so close-up.js checks the query itself and builds neither driver,
   leaving both properties unset and the whole artwork visible.

   That is the honest resting state for this page: the artwork entire, every
   passage in order beside it. Nothing is withheld from someone who asked for
   less movement — they get the same words and the same picture, all at once
   instead of one beat at a time.

   The stage stays sticky. Sticky is layout, not animation, and holding the
   artwork in view while its description scrolls is the accessible behaviour,
   not the decorative one.
   ------------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
  /* Beats no longer need a screen each — nothing is waiting for the reader to
     arrive, so the spacing goes back to ordinary prose rhythm. */
  .cu__beat {
    min-height: 0;
    margin-bottom: var(--gap-band, 64px);
  }
}

/* ------------------------------------------------------------
   Phones — the tightening

   Kept in one block at the end, following site.css §17's convention, so the
   mobile shape can be retuned without hunting through the file.

   The layout changes kind rather than degree here: `display: block` instead
   of a grid. A sticky item sticks within its grid area, and in a single
   column that area is one row tall, so the stage would unstick the moment its
   own row ended. In block flow its containing block is .cu itself, which
   spans every passage.
   ------------------------------------------------------------ */

@media (max-width: 860px) {
  .cu {
    display: block;
    padding-block: 12px var(--pad-band-sm);
  }

  .cu__stage {
    top: var(--header-h);

    /* A full-bleed band, NOT a shrink-to-fit frame.

       The desktop stage is sized to the artwork, which is right beside a
       column of text and wrong above one. Sticky lifts the stage out of flow,
       so a frame narrower than the column leaves the passages running visibly
       past both its edges and straight under the picture — the artwork ends up
       a card floating on top of its own paragraph.

       So on a phone the stage stops being a frame and becomes a shelf: edge to
       edge, opaque, with the artwork centred inside it and letterboxed by
       whatever paper is left over. The negative inline margin cancels .cu's
       padding, the same full-bleed move .ws-band makes on /workshops.

       Height, not aspect-ratio, drives it here — vertical is the scarce axis
       on a phone — so aspect-ratio is explicitly released. */
    aspect-ratio: auto;
    height: 48svh;
    width: calc(100% + 2 * clamp(18px, 5vw, 28px));
    margin-inline: calc(-1 * clamp(18px, 5vw, 28px));
    margin-bottom: 28px;

    background: var(--paper);
    border: 0;
    border-bottom: 1px solid var(--rule);
    border-radius: 0;

    /* In block flow the passages come after the stage and would otherwise
       paint over it as they scroll up behind. */
    z-index: 2;
  }

  .cu__script { max-width: none; }

  .cu__beat {
    min-height: 62svh;
  }

  .cu__beat p { font-size: 1.1rem; }
}

/* The smallest phones. Nothing clamps the zoom here and nothing needs to:
   under fit-bounds a narrow screen simply arrives at a larger scale to keep
   the same promise, which is the whole reason a beat stores a rectangle and
   not a number. What this does is hand the artwork a little more of a screen
   that has very little to give. */
@media (max-width: 560px) {
  .cu__stage { height: 45svh; }
  .cu__beat { min-height: 58svh; }
}

/* ------------------------------------------------------------
   The index — /close-up

   Not a story page; a short list of them. It lives in this stylesheet rather
   than site.css because it is only ever loaded beside the stories, and it
   borrows their vocabulary rather than the card grids'.

   Plates, not cards: a close-up is something to read, not something to buy,
   so there is no price, no badge and no basket — and no reason to look like
   the shop.
   ------------------------------------------------------------ */

.cu-index {
  list-style: none;
  margin: var(--gap-grid-top, 40px) 0 0;
  padding: 0;
  display: grid;
  gap: clamp(20px, 4vw, 40px);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 340px), 1fr));
}

.cu-index__link {
  display: grid;
  grid-template-columns: minmax(0, 120px) minmax(0, 1fr);
  gap: 18px;
  align-items: center;
  text-decoration: none;
  color: inherit;
  padding: 14px;
  border: 1px solid var(--rule);
  border-radius: var(--radius-xs);
  background: var(--paper);
  transition: border-color var(--dur-fast) var(--ease-soft);
}

.cu-index__link:hover { border-color: var(--rule-ink); }

.cu-index__plate {
  display: block;
  overflow: hidden;
  border: 1px solid var(--rule);
  background: var(--panel);
}

.cu-index__plate img { display: block; width: 100%; height: auto; }

.cu-index__eyebrow {
  display: block;
  font-family: var(--font-grotesk);
  font-size: 0.66rem;
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 6px;
}

.cu-index__title {
  display: block;
  font-family: var(--font-serif);
  font-size: 1.45rem;
  line-height: 1.15;
  color: var(--ink);
  text-wrap: balance;
}
.cu-index__title em { font-style: italic; color: var(--rose); }

.cu-index__note {
  display: block;
  margin-top: 8px;
  font-family: var(--font-serif);
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--ink-soft);
}

@media (max-width: 560px) {
  .cu-index__link { grid-template-columns: minmax(0, 84px) minmax(0, 1fr); gap: 14px; }
  .cu-index__title { font-size: 1.2rem; }
}
