/* ============================================================================
   SALUX — Atelier Dossier
   ----------------------------------------------------------------------------
   Two rules govern everything here.

   1. The page paints the frame's own background colour. salux.js reads a
      per-frame colour out of meta.json and writes --bg/--fg on <html> every
      frame. Nothing in this file may hard-code a section background or put a
      transition on --bg: the film IS the background, and a 200ms ease would
      smear the light/dark inversion across a dozen frames.

   2. Density is the anti-slop. Reference codes, tolerances, frame counters,
      registration marks. Empty space with big words in it is what reads as
      generated; a page that looks like it was measured does not.
   ========================================================================== */

/* ---- tokens ------------------------------------------------------------- */
:root{
  --paper:#f4f1ec;
  --ink:#0a0a0a;
  --void:#000000;
  --bone:#f2efe9;
  --gold:#b8892f;
  --deep:#051622;

  /* Written live by JS. The seed values are act 0 (hero, black). */
  --bg:#000000;
  --fg:#f2efe9;

  --rule:color-mix(in srgb, var(--fg) 15%, transparent);
  --rule-soft:color-mix(in srgb, var(--fg) 8%, transparent);

  /* Secondary ink. The STRENGTH is written live by JS, not fixed here.
     Ranking --fg by contrast fixed the primary ink but left this one riding the
     inversion down with it: at 52% over the mid-grey the film passes through as
     the lights come up (#767676 at act II), the composite reaches 2.35:1 --
     the worst figure on the page. JS now raises the mix only as far as it has
     to to hold 4.5:1. Black keeps its 52%; paper settles at 58%, because 52%
     over paper was quietly failing too -- #0e0e0e at 52% on #f2f0ec is 3.72:1,
     and that is the body copy and the whole spec table. See muteAlpha(). */
  --mute-a:52%;
  --mute:color-mix(in srgb, var(--fg) var(--mute-a), transparent);

  --gut:clamp(18px, 3.6vw, 68px);
  --serif:"Zodiak","Bodoni Moda",Didot,"Times New Roman",serif;
  --sans:"Switzer",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
  --mono:"Geist Mono",ui-monospace,SFMono-Regular,"Roboto Mono",monospace;

  /* Film grain as one inline SVG tile -- no image request, and feTurbulence
     gives real noise rather than a repeating dot pattern you can spot. */
  --grain:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");

  --ease:cubic-bezier(.22,.61,.36,1);
  --ease-io:cubic-bezier(.65,.02,.28,1);
}

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

html{
  background:var(--bg);
  -webkit-text-size-adjust:100%;
  scroll-behavior:auto;      /* the reels ARE the motion; never smooth-scroll over them */
  /* clip, NEVER hidden. `overflow-x:hidden` forces overflow-y to compute as
     `auto`, which turns the element into a scroll container and silently breaks
     `position:sticky` for every descendant -- i.e. every reel in the film stops
     pinning and the whole page just scrolls past the canvases. `clip` gives the
     same horizontal protection and creates no scroll container. */
  overflow-x:clip;
}
body{
  margin:0;
  background:var(--bg);
  color:var(--fg);
  font-family:var(--sans);
  font-weight:300;
  font-size:16px;
  line-height:1.5;
  /* Both html and body: the scrolling root can be either, and clipping only one
     lets a staged-off-canvas reveal open a real horizontal scrollbar. See the
     note on html -- this must be `clip`, not `hidden`. */
  overflow-x:clip;
  -webkit-font-smoothing:antialiased;
}

img,video,canvas{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
::selection{background:var(--gold);color:#000}

/* ---- typography --------------------------------------------------------- */
.display{
  font-family:var(--serif);
  font-weight:300;
  line-height:.84;
  letter-spacing:-.022em;
  margin:0;
}
.h-chapter{
  font-family:var(--serif);
  font-weight:400;
  font-size:clamp(2.4rem,6.4vw,6.6rem);
  line-height:.92;
  letter-spacing:-.018em;
  margin:0;
}
.lede{
  font-size:clamp(1rem,1.25vw,1.28rem);
  line-height:1.58;
  font-weight:300;
  color:color-mix(in srgb, var(--fg) 82%, transparent);
  max-width:44ch;
  margin:0;
}
.body{font-size:.95rem;line-height:1.62;color:var(--mute);max-width:52ch;margin:0}

/* The dossier voice. Tabular figures are not decoration: these numbers sit in
   columns and in a live counter, and proportional digits would jitter. */
.meta{
  font-family:var(--mono);
  font-size:.6875rem;
  font-weight:400;
  letter-spacing:.15em;
  text-transform:uppercase;
  font-variant-numeric:tabular-nums;
  color:var(--mute);
  margin:0;
}
.meta--fg{color:var(--fg)}
.num{font-family:var(--mono);font-variant-numeric:tabular-nums;letter-spacing:.02em}

hr.rule{height:1px;background:var(--rule);border:0;margin:0}

/* ---- fixed chrome ------------------------------------------------------- */
/* Registration marks — four corner crosshairs, the way a print plate is
   trimmed. They read as instrumentation, and they hold the eye still while the
   background inverts underneath them. */
.regs{position:fixed;inset:0;pointer-events:none;z-index:60}
.regs i{position:absolute;width:13px;height:13px;opacity:.45;mix-blend-mode:difference}
.regs i::before,.regs i::after{content:"";position:absolute;background:#fff}
.regs i::before{left:50%;top:0;bottom:0;width:1px;transform:translateX(-50%)}
.regs i::after{top:50%;left:0;right:0;height:1px;transform:translateY(-50%)}
.regs i:nth-child(1){left:calc(var(--gut) - 6px);top:calc(var(--gut) - 6px)}
.regs i:nth-child(2){right:calc(var(--gut) - 6px);top:calc(var(--gut) - 6px)}
.regs i:nth-child(3){left:calc(var(--gut) - 6px);bottom:calc(var(--gut) - 6px)}
.regs i:nth-child(4){right:calc(var(--gut) - 6px);bottom:calc(var(--gut) - 6px)}

/* mix-blend-mode:difference is what carries the fixed chrome across the
   inversion — white on black, black on white, with no JS theme toggle and no
   frame where the type is invisible against its own background. */
.mast{
  position:fixed;top:0;left:0;right:0;z-index:70;
  display:flex;align-items:center;justify-content:space-between;
  padding:calc(var(--gut) * .72) var(--gut);
  mix-blend-mode:difference;
  color:#fff;
}
.mast .wordmark{
  font-family:var(--serif);font-weight:400;
  font-size:.98rem;letter-spacing:.42em;text-indent:.42em;
}
.mast nav{display:flex;gap:clamp(14px,2vw,32px)}
.mast nav a{
  font-family:var(--mono);font-size:.6875rem;letter-spacing:.15em;
  text-transform:uppercase;opacity:.66;transition:opacity .3s var(--ease);
}
.mast nav a:hover{opacity:1}

/* ---- index overlay (phones) --------------------------------------------- */
/* The toggle lives INSIDE .mast so it inherits the difference blend and stays
   legible over black acts and paper acts alike, exactly like the wordmark. */
.mast__key{
  display:none;
  align-items:center;gap:9px;
  background:none;border:0;padding:0;margin:0;
  color:inherit;cursor:pointer;
  font-family:var(--mono);font-size:.625rem;letter-spacing:.15em;
  text-transform:uppercase;
}
.mast__key-r{display:block;width:16px}
.mast__key-r i{
  display:block;height:1px;background:currentColor;
  transition:transform .4s var(--ease), opacity .3s var(--ease);
}
.mast__key-r i:first-child{margin-bottom:4px}
/* Two rules into an X. The rows sit 5px apart (1px + 4px margin), so each
   travels half that to meet on the centre line. */
.mast__key[aria-expanded="true"] .mast__key-r i:first-child{transform:translateY(2.5px) rotate(45deg)}
.mast__key[aria-expanded="true"] .mast__key-r i:last-child{transform:translateY(-2.5px) rotate(-45deg)}

.idx{
  position:fixed;inset:0;z-index:150;
  background:var(--bg);
  opacity:0;visibility:hidden;
  transition:opacity .42s var(--ease), visibility 0s linear .42s;
}
.idx.on{opacity:1;visibility:visible;transition-delay:0s,0s}
/* [hidden] is removed by JS before the class lands, so the transition has
   something to animate from; it is put back after the close finishes. */
.idx[hidden]{display:none}
.idx__in{
  position:absolute;inset:0;
  display:flex;flex-direction:column;justify-content:center;
  padding:calc(var(--gut) * 3.4) var(--gut) calc(var(--gut) * 2);
}
.idx__lab{margin-bottom:auto}
.idx__foot{margin-top:auto}
.idx__in a{
  display:flex;align-items:baseline;gap:16px;
  font-family:var(--serif);font-weight:300;
  font-size:clamp(2rem,11vw,3.2rem);line-height:1.08;
  letter-spacing:-.02em;color:var(--fg);
  padding:10px 0;
  border-bottom:1px solid var(--rule-soft);
  /* Staggered in behind the panel fade. --i is set per link in the markup order
     by JS so the list assembles rather than appearing whole. */
  opacity:0;transform:translateY(10px);
  transition:opacity .5s var(--ease), transform .5s var(--ease);
  transition-delay:calc(90ms + var(--i,0) * 55ms);
}
.idx.on .idx__in a{opacity:1;transform:none}
.idx__in a .meta{flex:0 0 auto;width:2.2em;color:var(--mute)}
.idx__in a:last-of-type{border-bottom:0}

@media (prefers-reduced-motion:reduce){
  .idx,.idx__in a{transition:none}
  .mast__key-r i{transition:none}
}


/* Live readout: chapter left, frame index right, hairline rail along the
   bottom. The counter is driven by the same value that picks the frame, so it
   cannot drift out of sync with what is actually on screen. */
.hud{
  position:fixed;left:0;right:0;bottom:0;z-index:70;
  padding:0 var(--gut) calc(var(--gut) * .6);
  display:flex;align-items:flex-end;justify-content:space-between;gap:16px;
  pointer-events:none;
  mix-blend-mode:difference;color:#fff;
}
.hud .meta{color:#fff;opacity:.7}
.hud__rail{
  position:fixed;left:0;bottom:0;height:1px;width:100%;
  background:rgba(255,255,255,.16);z-index:71;mix-blend-mode:difference;
}
.hud__rail i{display:block;height:100%;width:100%;background:#fff;transform:scaleX(0);transform-origin:left}

/* ---- grade + grain ------------------------------------------------------ */
/* Separate generation sessions produce separate colour temperatures. One grade
   pass over the whole page is what a colourist would do on a real shoot, and it
   is the difference between "a set of clips" and "a film". Kept very low: it
   should be felt, never seen. */
/* Folded into the vignette. This was a THIRD full-viewport blended layer (on top
   of the grain and the difference-blended chrome), and every one of them forces
   the compositor to re-read and re-blend the whole screen. At 0.07 opacity it
   was costing a composite pass to do almost nothing visible. */
/* Half the film now plays on paper, and a vignette tuned for black acts reads
   as dirt on a white page. Kept black (it still has to darken, not glow) but
   pulled back to where it only shapes the corners. */
.vig{
  position:fixed;inset:0;z-index:57;pointer-events:none;
  background:radial-gradient(128% 92% at 50% 46%, transparent 56%, rgba(0,0,0,.26) 100%);
}
/* Compositor-driven, not JS. The drift used to be a transform written from rAF,
   which invalidated this full-viewport blended layer on every frame and made the
   compositor re-blend the whole screen sixty times a second. A keyframe
   animation on transform stays on the compositor thread. */
.grain{
  position:fixed;inset:-120px;z-index:58;pointer-events:none;
  opacity:.045;mix-blend-mode:overlay;
  background-image:var(--grain);
  background-size:200px 200px;
  will-change:transform;
  animation:grain-drift 7s steps(6,end) infinite;
}
@keyframes grain-drift{
  0%  {transform:translate3d(0,0,0)}
  20% {transform:translate3d(-8px,6px,0)}
  40% {transform:translate3d(7px,-9px,0)}
  60% {transform:translate3d(-6px,-5px,0)}
  80% {transform:translate3d(9px,7px,0)}
  100%{transform:translate3d(0,0,0)}
}
@media (prefers-reduced-motion:reduce){ .grain{animation:none} }

/* ---- loader ------------------------------------------------------------- */
.loader{
  position:fixed;inset:0;z-index:200;background:#000;color:#f2efe9;
  display:grid;place-items:center;
  transition:opacity .9s var(--ease), visibility 0s linear .9s;
}
.loader.done{opacity:0;visibility:hidden;pointer-events:none}
.loader__in{width:min(420px,72vw)}
.loader__mark{
  font-family:var(--serif);font-weight:300;
  font-size:clamp(2.4rem,6.4vw,4rem);letter-spacing:.34em;text-indent:.34em;
  margin:0 0 26px;text-align:center;
}
.loader__bar{height:1px;background:rgba(242,239,233,.18);position:relative;overflow:hidden}
.loader__bar i{position:absolute;inset:0 auto 0 0;width:100%;background:#f2efe9;transform:scaleX(0);transform-origin:left}
.loader__pct{margin-top:14px;display:flex;justify-content:space-between}
.loader__pct span{color:rgba(242,239,233,.6)}

/* ---- hero --------------------------------------------------------------- */
/* The wordmark set full width and BOLD, with the watch cut out of its
   background and standing in front of it, so the letters run right up to the
   case edge and disappear behind it.

   One flat photograph, not a matted clip. Cutting the watch out of the hover
   video meant segmenting a black strap against a dark studio floor on every
   frame, which is the least reliable thing you can ask of a matte -- and it
   showed. This is the same piece shot on an even ground, so the matte is a
   border flood fill and the strap survives whole. */
.hero{
  position:relative;min-height:100svh;
  display:grid;place-items:center;
  padding:calc(var(--gut) * 2.4) var(--gut);
  overflow:hidden;
}
.hero__word{
  position:absolute;left:0;right:0;top:50%;
  transform:translateY(-50%);
  margin:0;text-align:center;z-index:1;
  font-family:var(--serif);font-weight:800;
  font-size:clamp(4rem,20.5vw,19rem);
  line-height:.8;letter-spacing:-.045em;
  color:var(--fg);
  white-space:nowrap;
}
.hero__watch{
  /* Sized by HEIGHT so the strap runs off the top and bottom of the screen and
     its cut ends are never visible; the vw cap stops it bleeding sideways on a
     narrow phone. */
  position:absolute;left:50%;top:50%;
  transform:translate(-50%,-50%) scale(1);
  height:min(112svh, 148vw);width:auto;max-width:none;
  z-index:2;
  transition:transform 1.8s var(--ease);
}
.hero:hover .hero__watch{transform:translate(-50%,-50%) scale(1.025)}

/* Instrument readouts flanking the piece, the way a spec sheet annotates it. */
.hero__specs{
  position:absolute;inset:auto var(--gut) 34%;z-index:3;
  display:flex;justify-content:space-between;pointer-events:none;
}
.hero__specs div{text-align:center}
.hero__specs b{
  display:block;font-family:var(--serif);font-weight:400;
  font-size:clamp(1.5rem,3vw,2.6rem);line-height:1;letter-spacing:-.02em;
  color:var(--fg);
}
.hero__specs span{display:block;margin-top:7px}

.hero__foot{
  position:absolute;left:var(--gut);right:var(--gut);
  bottom:calc(var(--gut) * 2.6);z-index:3;
  display:flex;justify-content:space-between;align-items:flex-end;gap:24px;
  flex-wrap:wrap;
}

/* ---- the film (one canvas, one scroll range) --------------------------------------- */
/* position:sticky, not a JS pin. A pin-spacer is a layout the browser did not
   choose, and it is where these builds usually break; sticky is the browser
   doing it natively and it cannot desync from the scroll position. */
.film{position:relative;width:100%}
.film__stage{
  position:sticky;top:0;height:100svh;width:100%;
  display:grid;place-items:center;overflow:hidden;
}
.film__c{width:100%;height:100%}
.film__ui{position:absolute;inset:0;pointer-events:none}
.film__ui > *{position:absolute}

/* Everything inside a reel that is driven by scroll position hides by default.
   This rule used to be scoped to .call, which meant .film__label ignored
   data-show entirely and BOTH act captions rendered on top of each other for the
   whole reel. Scope it to the attribute, not to one class. */
.film__ui [data-show]{
  opacity:0;transform:translateY(6px);
  transition:opacity .5s var(--ease), transform .5s var(--ease);
}
.film__ui [data-show].on{opacity:1;transform:none}

/* Type sits over moving footage, so it needs its own ground. The scrim is mixed
   from --bg, which tracks the frame -- so it darkens under light text on the
   black acts and lightens under dark text on the white ones, without a single
   hand-written per-act override. */
.film__ui::before,
.film__ui::after{
  content:"";position:absolute;left:0;right:0;height:46vh;pointer-events:none;
  z-index:-1;
}
.film__ui::before{
  top:0;
  background:linear-gradient(to bottom,
    color-mix(in srgb, var(--bg) 78%, transparent), transparent);
}
.film__ui::after{
  bottom:0;
  background:linear-gradient(to top,
    color-mix(in srgb, var(--bg) 92%, transparent),
    color-mix(in srgb, var(--bg) 40%, transparent) 46%, transparent);
}
.film__ui{isolation:isolate}

/* Annotated exploded view — a leader line from a label to a part. This is the
   payoff of the dossier idea, and the reason the white plateau can hold on a
   single frame without the scroll feeling stalled. */
.call{display:flex;align-items:center;gap:10px}
.call__l{width:clamp(24px,5vw,84px);height:1px;background:var(--rule);flex:0 0 auto}
.call__t{white-space:nowrap}
.call__t b{
  display:block;font-family:var(--sans);font-weight:500;font-size:.86rem;
  letter-spacing:0;text-transform:none;color:var(--fg);
  text-shadow:0 1px 14px color-mix(in srgb, var(--bg) 80%, transparent);
}
.call__t .meta{color:color-mix(in srgb, var(--fg) 74%, transparent)}
.call--r{flex-direction:row-reverse}
.call--r .call__t{text-align:right}

.film__label{left:var(--gut);bottom:calc(var(--gut) * 3);max-width:min(34ch,40vw)}
/* Not .body -- that is muted 300 for long-form reading on a calm ground. Over
   film it has to be a weight heavier and at full strength or it disappears into
   the footage, which is exactly what happened on the closing acts. */
.film__label .body{
  color:color-mix(in srgb, var(--fg) 92%, transparent);
  font-weight:400;font-size:1rem;line-height:1.55;
  text-shadow:0 1px 18px color-mix(in srgb, var(--bg) 70%, transparent);
}
.film__label .meta--fg{
  text-shadow:0 1px 14px color-mix(in srgb, var(--bg) 70%, transparent);
}
.film__no{
  right:var(--gut);top:50%;
  font-family:var(--serif);font-weight:300;
  font-size:clamp(4rem,13vw,13rem);line-height:1;
  opacity:.1;letter-spacing:-.04em;
}
/* The roman numeral is positioned with top+translate, and [data-show] animates
   transform too -- so it needs the centring baked into its own rule or it jumps
   half its height on reveal. */
.film__ui .film__no{transform:translateY(-50%)}
.film__ui .film__no[data-show]{transform:translateY(calc(-50% + 6px))}
.film__ui .film__no[data-show].on{transform:translateY(-50%)}

/* ---- static chapters ---------------------------------------------------- */
.chap{position:relative;padding:clamp(90px,14vh,190px) var(--gut)}
.chap__head{
  display:grid;grid-template-columns:repeat(12,1fr);
  gap:clamp(16px,2vw,32px);align-items:end;
  padding-bottom:clamp(28px,4vh,54px);
  border-bottom:1px solid var(--rule);
}
.chap__head .n{grid-column:1/2}
.chap__head h2{grid-column:2/9}
.chap__head .lede{grid-column:9/13}

.spec{
  display:grid;grid-template-columns:repeat(12,1fr);
  gap:clamp(16px,2vw,32px);
  padding-top:clamp(34px,5vh,64px);
}
.spec__fig{grid-column:1/7;position:relative;min-width:0}
.spec__fig img{width:100%;height:auto}
.spec__tbl{grid-column:8/13;align-self:start;min-width:0}
.spec__tbl dl{margin:0;display:grid;grid-template-columns:1fr auto}
.spec__tbl dt,.spec__tbl dd{margin:0;padding:13px 0;border-bottom:1px solid var(--rule-soft)}
.spec__tbl dt{
  font-family:var(--mono);font-size:.6875rem;letter-spacing:.13em;
  text-transform:uppercase;color:var(--mute);
}
.spec__tbl dd{
  text-align:right;font-family:var(--mono);font-size:.8125rem;
  font-variant-numeric:tabular-nums;color:var(--fg);
}

.figcap{
  margin-top:12px;display:flex;justify-content:space-between;gap:16px;
  padding-top:9px;border-top:1px solid var(--rule);
}

.chap--say{
  min-height:150svh;
  display:grid;place-items:center;text-align:center;
}
.chap--say .lede{max-width:46ch}

/* ---- plates (collection) ------------------------------------------------ */
/* Deliberately unequal. A 4-up grid of identical tiles is the single most
   template-looking thing a page can do; varying crop and scale breaks that
   without loosening the photography. */
.plates{
  display:grid;grid-template-columns:repeat(12,1fr);
  gap:clamp(16px,2.4vw,44px);
  padding:clamp(70px,10vh,140px) var(--gut);
}
.plate{position:relative;overflow:hidden;min-width:0}
/* The 32px placeholder, inlined per image, held until the real file lands so a
   tile is never an empty rectangle. */
img[data-src]{
  background-image:var(--lqip);
  background-size:cover;
  background-position:center;
}
.plate video{
  width:100%;height:100%;object-fit:cover;
  transform:scale(1.02);
  transition:transform 1.5s var(--ease);
}
.plate--v:hover video{transform:scale(1.06)}
.plate__cap{
  position:absolute;left:14px;bottom:12px;z-index:2;
  color:#fff;mix-blend-mode:difference;
  opacity:0;transform:translateY(4px);
  transition:opacity .5s var(--ease), transform .5s var(--ease);
}
.plate:hover .plate__cap{opacity:.85;transform:none}
.plate img{
  width:100%;height:100%;object-fit:cover;
  transform:scale(1.02);
  transition:transform 1.5s var(--ease), filter 1.5s var(--ease);
  filter:saturate(.92);
}
.plate:hover img{transform:scale(1.06);filter:saturate(1)}
.plate--a{grid-column:1/7;aspect-ratio:16/10}
.plate--b{grid-column:8/13;aspect-ratio:4/5;align-self:end}
.plate--c{grid-column:2/6;aspect-ratio:1/1}
.plate--d{grid-column:6/13;aspect-ratio:16/9}
.plate--e{grid-column:1/13;aspect-ratio:21/9}

/* ---- outro -------------------------------------------------------------- */
.outro{position:relative;min-height:100svh;display:grid;place-items:center;overflow:hidden;isolation:isolate}
.outro video{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:-1;
  filter:saturate(.84) brightness(.66);
}
.outro__in{text-align:center;padding:var(--gut);max-width:min(760px,86vw)}
.cta{
  display:inline-flex;align-items:center;gap:14px;margin-top:34px;
  padding:16px 30px;border:1px solid var(--rule);
  font-family:var(--mono);font-size:.6875rem;letter-spacing:.17em;text-transform:uppercase;
  transition:background .5s var(--ease), color .5s var(--ease), border-color .5s var(--ease);
}
.cta:hover{background:var(--fg);color:var(--bg);border-color:var(--fg)}

.foot{
  padding:clamp(48px,7vh,90px) var(--gut) calc(var(--gut) * 3);
  border-top:1px solid var(--rule);
  display:grid;grid-template-columns:repeat(12,1fr);gap:clamp(16px,2vw,32px);
}
.foot__a{grid-column:1/5}
.foot__b{grid-column:6/9}
.foot__c{grid-column:10/13;text-align:right}
/* Colophon. Set apart by a rule and a full-width row so it reads as a note
   ABOUT the page rather than another line of the dossier ON it. */
.foot__d{
  grid-column:1/13;
  margin-top:clamp(40px,6vh,72px);
  padding-top:18px;
  border-top:1px solid var(--rule-soft);
  display:flex;justify-content:space-between;align-items:baseline;
  gap:18px;flex-wrap:wrap;
}
.foot__me{
  color:var(--fg);
  border-bottom:1px solid color-mix(in srgb, var(--fg) 32%, transparent);
  padding-bottom:1px;
  transition:border-color .3s var(--ease);
}
.foot__me:hover{border-bottom-color:var(--fg)}

/* ---- reveals ------------------------------------------------------------ */
/* A wipe, not a fade-up. Everything on a page rising 20px and fading in is the
   house style of a template; a mask that opens reads like type being set. */
.rv{
  clip-path:inset(0 0 105% 0);
  transition:clip-path 1.05s var(--ease-io);
  transition-delay:var(--d,0ms);
}
.rv.on{clip-path:inset(0 0 -12% 0)}
.rv-f{opacity:0;transition:opacity .9s var(--ease);transition-delay:var(--d,0ms)}
.rv-f.on{opacity:1}

/* ---- responsive --------------------------------------------------------- */
@media (max-width:900px){
  .chap__head{grid-template-columns:repeat(6,1fr)}
  .chap__head .n{grid-column:1/7}
  .chap__head h2{grid-column:1/7;margin:10px 0 16px}
  .chap__head .lede{grid-column:1/7}
  .spec{grid-template-columns:repeat(6,1fr)}
  .spec__fig{grid-column:1/7}
  .spec__tbl{grid-column:1/7;margin-top:34px}
  .plates{grid-template-columns:repeat(6,1fr)}
  .plate--a,.plate--b,.plate--c,.plate--d,.plate--e{grid-column:1/7}
  .plate--b{aspect-ratio:4/5;align-self:auto}
  .foot{grid-template-columns:repeat(6,1fr);gap:30px}
  .foot__a,.foot__b,.foot__c{grid-column:1/7;text-align:left}
  .foot__d{grid-column:1/7;margin-top:8px}
  .mast nav{display:none}
  .mast__key{display:flex}
  .film__no{display:none}
  .film__label{max-width:none;right:var(--gut)}
  /* Portrait plate: same width, 3:4 instead of 16:9, so the watch is 450px tall
     instead of 190. Mirrors the reels' own mobile geometry. */
  /* On a tall phone a centred wordmark and a bottom-anchored watch cannot meet
     unless the watch is wider than the screen -- so it is, and it bleeds off
     both sides. The captions move ABOVE it rather than fighting the dial. */
  /* On a phone the watch is nearly screen-width, so the type cannot read at
     its sides the way it does on desktop. It moves up to cross the strap
     instead of sitting behind the dial, where it was simply invisible. */
  .hero__word{font-size:clamp(3rem,21vw,7rem);top:27%;transform:translateY(-50%)}
  /* On a phone the watch has to leave the wordmark room: at 94svh it covered
     the type outright. Sized off width instead so it scales with the screen
     rather than its height. */
  .hero__watch{height:min(58svh, 104vw)}
  .hero__specs{inset:auto var(--gut) auto;top:16%}
  .hero__specs b{font-size:1.35rem}
  .hero__foot{gap:12px;bottom:calc(var(--gut) * 1.2)}
  .hero__foot .meta{font-size:.625rem;letter-spacing:.1em}
  /* The foot sits over the strap, which is black -- but give it a scrim so it
     never depends on that. */
  .hero__foot::before{
    content:"";position:absolute;left:calc(var(--gut) * -1);right:calc(var(--gut) * -1);
    top:-46px;bottom:calc(var(--gut) * -1.6);z-index:-1;
    background:linear-gradient(to top,
      var(--bg) 58%,
      color-mix(in srgb, var(--bg) 72%, transparent) 82%,
      transparent);
  }
  .hero__foot .meta{text-shadow:0 1px 10px var(--bg)}
}

@media (prefers-reduced-motion:reduce){
  /* The film still tells the story, it just stops being scrubbed. Reel heights
     collapse from ~600vh to a little over one screen, so someone who asked for
     less motion is not made to scroll eighteen thousand pixels; the damping is
     switched off in JS so frames track scroll exactly. The inline height needs
     !important to lose to this. */
  .film{height:220svh !important}
  .rv{clip-path:none;transition:none}
  .rv-f{opacity:1;transition:none}
  .plate img,.hero__slot video{transition:none}
  .grain{display:none}
}
