/* ============================================================
   GLOSS project page
   One committed visual world: a dark studio ground with a
   green/orange accent pair. Both accents sit at the same oklch
   lightness and chroma (L .76 / C .148) and differ only in hue,
   so they read as one system rather than two colours fighting.
   Green carries structure; orange is reserved for emphasis.
   ============================================================ */

:root {
  /* ground — neutrals carry a slight green bias so they read as chosen */
  --bg:        #0d120e;
  --bg-alt:    #161b17;
  --panel:     #1c221e;
  --line:      #2c342e;
  --ink:       #e9eeea;
  --ink-2:     #b0b8b1;
  --ink-3:     #7b857d;

  /* accents */
  --accent:      #5dcb81;   /* structure: numbering, active state, links */
  --accent-soft: #85e3a0;
  --accent-deep: #2a9754;
  --accent-2:      #f69746; /* emphasis: best results, hotspots, wordmark */
  --accent-2-soft: #ffb471;
  --accent-ink:  #041107;

  --stage:  #0f1510;        /* model-viewer backdrop */
  --plate:  transparent;    /* result plates are pre-composited on --bg */
  --radius: 6px;

  --font-display: "Space Grotesk", system-ui, sans-serif;
  --font-body:    "IBM Plex Sans", system-ui, sans-serif;
  --font-mono:    "IBM Plex Mono", ui-monospace, monospace;
  --display-weight: 700;
  --display-tracking: -0.045em;

  /* ---- type scale ------------------------------------------------
     One ladder for the whole page; no rule sets a raw px size.
     Display steps are fluid and ordered so the GLOSS title is always
     the largest type on the page — a section heading can never
     out-size it at any viewport width. Text and mono steps are fixed:
     small type does not need to scale, and fluid small type is what
     made the old page read differently at every breakpoint.
     ---------------------------------------------------------------- */
  --fs-hero:     clamp(23px, 2.7vw, 38px);   /* GLOSS title */
  --fs-h2:       clamp(19px, 2.1vw, 30px);   /* section titles */
  --fs-h3:       clamp(17px, 1.6vw, 22px);   /* tl;dr claim, sub-titles */
  --fs-lead:     17px;                       /* abstract */
  --fs-body:     16px;                       /* base copy, ledes */
  --fs-sm:       14px;                       /* tables */
  --fs-xs:       13px;                       /* notes, footer */
  --fs-mono-lg:  clamp(13px, 1.4vw, 17px);   /* the tl;dr swap line */
  --fs-mono:     12px;                       /* buttons, numbering */
  --fs-mono-sm:  11px;                       /* captions, labels */
  --fs-mono-xs:  10px;                       /* credits, badges */
  --fs-mono-2xs:  9px;                       /* in-plate overlays */
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

.wrap { max-width: 1200px; margin: 0 auto; padding: 0 40px; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .pipe-flow.on { animation: none; }
}

/* ============================ HERO ============================ */
.hero {
  position: relative;
  min-height: min(92vh, 900px);
  display: flex; align-items: flex-end;
  overflow: hidden;
  border-bottom: 1px solid var(--line);
}
.hero-bg {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 68% 42%;   /* keep the head clear of the title block */
  filter: saturate(1.02) contrast(1.04);
}
/* Keeps the title legible over a moving image: darkest at the bottom where
   the text sits, and a flat floor of shade everywhere else. */
.hero-scrim {
  position: absolute; inset: 0;
  background:
    /* dark enough to read the title against, light enough to see the dragon */
    linear-gradient(to top, var(--bg) 2%, rgba(13,18,14,0.80) 26%, rgba(13,18,14,0.34) 62%, rgba(13,18,14,0.30) 100%),
    linear-gradient(to right, rgba(13,18,14,0.78) 0%, rgba(13,18,14,0.30) 46%, transparent 78%);
}
.hero-inner { position: relative; width: 100%; padding-top: 72px; padding-bottom: 54px; }

.eyebrow {
  display: flex; justify-content: space-between; align-items: baseline; gap: 16px;
  font-family: var(--font-mono); font-size: var(--fs-mono); letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--ink-2);
  border-bottom: 1px solid rgba(255,255,255,0.16); padding-bottom: 14px;
}
.eyebrow .venue { color: var(--accent); }

.hero-title {
  font-family: var(--font-display);
  letter-spacing: var(--display-tracking);
  /* both parts share this size; the wordmark line holds, the tagline wraps */
  font-size: var(--fs-hero);
  line-height: 1.14;
  margin: 44px 0 0;
  max-width: 30ch;
  text-wrap: pretty;
  font-weight: var(--display-weight);
  color: var(--ink-2);
}
/* One continuous title: both halves are the same face, size, weight and
   colour, and the whole thing wraps as a single sentence. */
.title-line, .title-tag { display: inline; font-weight: inherit; color: inherit; }

/* Each of the five letters carries the colour of its slot in the assembled
   wordmark, so a letter and its destination are visibly the same thing. */
.hero-title .gl:nth-of-type(1), .gl-prefix i:nth-child(1) { color: #5dcb81; }
.hero-title .gl:nth-of-type(2), .gl-prefix i:nth-child(2) { color: #95c355; }
.hero-title .gl:nth-of-type(3), .gl-prefix i:nth-child(3) { color: #c0b52d; }
.hero-title .gl:nth-of-type(4), .gl-prefix i:nth-child(4) { color: #e0a624; }
.hero-title .gl:nth-of-type(5), .gl-prefix i:nth-child(5) { color: #f69746; }

.gl-phrase { color: var(--ink-2); }
.gl {
  font-style: normal;
  transition: color 0.7s ease 0.15s;
}
/* the source letters keep their colour after the wordmark assembles, so the
   link between each letter and its slot in GLOSS stays visible */

.gl-prefix { font-style: normal; }
.gl-prefix i {
  display: inline-block;
  font-style: normal;
  opacity: 0;
  will-change: transform, opacity;
}
.gl-prefix .colon { color: var(--ink-3); }
.gl-prefix + .gl-phrase { margin-left: 0.34em; }

/* No animation: show the finished title immediately. */
.hero-title.static .gl-prefix i { opacity: 1; transform: none; }

.authors { margin-top: 30px; font-size: var(--fs-body); color: var(--ink); }
.authors sup { color: var(--ink-3); font-size: var(--fs-mono-sm); }
.authors .au-o { color: var(--accent-2); }        /* Chenyue, Szymon */
.authors .au-g { color: var(--accent); }          /* Anita, James, Masha */
.authors a.au-o:hover { color: var(--accent-2-soft); }
.affils { font-family: var(--font-mono); font-size: var(--fs-mono); color: var(--ink-2); margin-top: 7px; line-height: 1.75; }

.links { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 28px; }
.btn {
  font-family: var(--font-mono); font-size: var(--fs-mono); letter-spacing: 0.05em;
  padding: 11px 22px; border: 1px solid rgba(255,255,255,0.22); color: var(--ink);
  border-radius: var(--radius); background: rgba(13,18,14,0.42);
  backdrop-filter: blur(6px);
  transition: all 0.18s ease; text-decoration: none; display: inline-block; cursor: pointer;
}
.btn:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }
.btn.primary { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.btn.primary:hover { filter: brightness(1.12); color: var(--accent-ink); }
.btn.soon { opacity: 0.5; cursor: default; }
.btn.soon:hover { border-color: rgba(255,255,255,0.22); color: var(--ink-2); }

.hero-credit {
  font-family: var(--font-mono); font-size: var(--fs-mono-xs); letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-3); margin-top: 30px;
}

/* ============================ SECTIONS ============================ */
.section { padding: 92px 0 0; }
.section-head { display: flex; align-items: baseline; gap: 20px; border-top: 1px solid var(--line); padding-top: 30px; }
.section-num { font-family: var(--font-mono); font-size: var(--fs-mono); letter-spacing: 0.14em; color: var(--accent); }
.section-title {
  font-family: var(--font-display); font-weight: var(--display-weight);
  letter-spacing: var(--display-tracking);
  font-size: var(--fs-h2); line-height: 1.08; margin: 0;
}
.section-lede { font-size: var(--fs-body); line-height: 1.7; color: var(--ink-2); max-width: 760px; margin: 16px 0 0 32px; text-wrap: pretty; }
.caption { font-family: var(--font-mono); font-size: var(--fs-mono-sm); color: var(--ink-3); margin-top: 11px; letter-spacing: 0.05em; }

/* Result plates carry the page ground baked into the image, so no card is
   painted behind them. Diagrams keep their white sheet and sit on a card. */
.plate { background: transparent; border: none; border-radius: var(--radius); overflow: hidden; }
.figure-plate { background: var(--panel); border: 1px solid var(--line); padding: 10px; }
/* Figures whose greyscale is the point - metallic/roughness maps, method
   comparisons - keep their white sheet: inverting them would falsify the
   values being shown. They are presented as paper, deliberately. The method
   diagrams are recoloured onto the page palette instead (recolor-figures.py),
   which is only safe because their labels are vector and their renders are
   left untouched. */
.plate.sheet { background: #fff; padding: 10px; border: 1px solid var(--line); }
.plate img { width: 100%; display: block; }

/* ============================ 3D VIEWER ============================ */
.viewer-row { display: grid; grid-template-columns: minmax(0, 1fr) 268px; gap: 26px; margin-top: 34px; align-items: start; }

model-viewer {
  width: 100%; height: 520px;
  background: var(--stage);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  --poster-color: transparent;
  transition: background 0.35s ease;
}

.viewer-side { display: flex; flex-direction: column; gap: 22px; }
.side-label {
  font-family: var(--font-mono); font-size: var(--fs-mono-xs); letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--ink-3); margin-bottom: 11px;
}

/* brush / method swatches */
.swatches { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 9px; }
.swatch {
  border: 2px solid var(--line); border-radius: var(--radius); overflow: hidden;
  cursor: pointer; padding: 0; background: none; transition: border-color 0.16s ease, transform 0.16s ease;
  position: relative; aspect-ratio: 1;
}
.swatch img { width: 100%; height: 100%; display: block; object-fit: cover; }
.swatch:hover { transform: translateY(-2px); }
.swatch[aria-pressed="true"] { border-color: var(--accent); }
.swatch .tag {
  position: absolute; inset: auto 0 0 0; font-family: var(--font-mono); font-size: var(--fs-mono-2xs);
  letter-spacing: 0.06em; text-transform: uppercase; background: rgba(0,0,0,0.68); color: #fff;
  padding: 3px 4px; text-align: center;
}

/* segmented control (PBR channels / methods) */
.seg { display: flex; flex-wrap: wrap; gap: 6px; }
.seg button {
  font-family: var(--font-mono); font-size: var(--fs-mono-sm); letter-spacing: 0.05em;
  padding: 8px 13px; border: 1px solid var(--line); background: transparent;
  color: var(--ink-2); cursor: pointer; border-radius: var(--radius); transition: all 0.16s ease;
}
.seg button:hover { border-color: var(--accent); }
.seg button[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }

.hint { font-family: var(--font-mono); font-size: var(--fs-mono-xs); color: var(--ink-3); line-height: 1.6; letter-spacing: 0.03em; }

/* hotspots */
.hotspot {
  width: 22px; height: 22px; border-radius: 50%;
  border: 2px solid var(--accent-2); background: rgba(255,255,255,0.14);
  backdrop-filter: blur(3px); cursor: pointer; padding: 0;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-mono); font-size: var(--fs-mono-xs); color: var(--accent-2);
  transition: transform 0.18s ease;
}
.hotspot:hover { transform: scale(1.2); }
.hotspot-label {
  position: absolute; top: 26px; left: 50%; transform: translateX(-50%);
  background: var(--panel); border: 1px solid var(--line); color: var(--ink);
  font-family: var(--font-mono); font-size: var(--fs-mono-xs); letter-spacing: 0.04em;
  padding: 6px 10px; border-radius: var(--radius); white-space: nowrap;
  opacity: 0; pointer-events: none; transition: opacity 0.18s ease;
}
.hotspot:hover + .hotspot-label, .hotspot:focus + .hotspot-label { opacity: 1; }

/* ============================ PIPELINE (scroll-scrubbed) ============================ */
.pipeline-stage { position: relative; margin-top: 38px; }
.pipeline-sticky {
  position: sticky; top: 0; height: 100vh;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 22px;
}
.pipeline-svg { width: 100%; max-width: 980px; }
.pipeline-track { height: 260vh; }

.pipe-step { opacity: 0.18; transition: opacity 0.4s ease; }
.pipe-step.on { opacity: 1; }
.pipe-flow { stroke-dasharray: 5 5; opacity: 0.2; transition: opacity 0.4s ease; }
.pipe-flow.on { opacity: 0.85; animation: flow 1s linear infinite; }
@keyframes flow { to { stroke-dashoffset: -20; } }

.pipe-caption {
  min-height: 62px; max-width: 720px; text-align: center;
  font-size: var(--fs-body); line-height: 1.6; color: var(--ink-2); text-wrap: pretty;
}
.pipe-caption b { color: var(--ink); font-family: var(--font-mono); font-size: var(--fs-mono); letter-spacing: 0.1em; display: block; margin-bottom: 5px; color: var(--accent); }

.pipe-dots { display: flex; gap: 7px; }
.pipe-dots i { width: 7px; height: 7px; border-radius: 50%; background: var(--line); transition: background 0.3s ease; }
.pipe-dots i.on { background: var(--accent); }

/* ============================ VIDEO ============================ */
.video-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 20px; margin-top: 34px; }
.video-grid.two-up { grid-template-columns: repeat(auto-fit, minmax(470px, 1fr)); }
.video-grid.one-up { grid-template-columns: minmax(0, 520px); }
.video-card { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; background: var(--panel); }
.video-card video { width: 100%; display: block; background: #000; }
.video-card .vc-label { font-family: var(--font-mono); font-size: var(--fs-mono-sm); color: var(--ink-2); padding: 13px 15px 0; letter-spacing: 0.05em; text-transform: uppercase; }
.video-card .vc-note { font-size: var(--fs-xs); line-height: 1.6; color: var(--ink-3); padding: 7px 15px 14px; margin: 0; text-wrap: pretty; }

/* ============================ TABLE ============================ */
.table-wrap { overflow-x: auto; margin-top: 30px; border: 1px solid var(--line); border-radius: var(--radius); }
table { border-collapse: collapse; width: 100%; font-size: var(--fs-sm); background: var(--panel); }
th, td { padding: 11px 15px; text-align: right; border-bottom: 1px solid var(--line); white-space: nowrap; }
th:first-child, td:first-child { text-align: left; }
thead th { font-family: var(--font-mono); font-size: var(--fs-mono-xs); letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-3); }
tbody tr:last-child td { border-bottom: none; }
tr.ours td { background: color-mix(in oklab, var(--accent) 11%, transparent); font-weight: 600; }
td.best { color: var(--accent-2); font-weight: 600; }

/* ============================ BIBTEX / FOOTER ============================ */
pre.bib {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 20px 24px; overflow-x: auto; font-family: var(--font-mono);
  font-size: var(--fs-mono); line-height: 1.65; color: var(--ink-2); margin-top: 26px;
}
.footer { margin-top: 90px; padding: 34px 0 60px; border-top: 1px solid var(--line); font-size: var(--fs-xs); color: var(--ink-3); }


/* ============================ TL;DR ============================
   The claim in one sentence, over the work itself, and under it the two
   swaps stated plainly: each old phrase struck through beside the one that
   replaces it. */
/* the section is a lead-in, not a chapter: it sits tighter than the rest */
.tldr-sec { padding: 40px 0 30px; }
.tldr-sec .section-head { padding-top: 20px; }

.tldr-claim {
  font-family: var(--font-display); font-weight: 500;
  letter-spacing: -0.025em; line-height: 1.25;
  font-size: var(--fs-h3);
  color: var(--ink-2); max-width: 42ch;   /* wider measure, so it wraps to two lines */
  margin: 12px 0 0 32px; text-wrap: pretty;
}
.tldr-claim b { color: var(--ink); font-weight: 700; }
.tldr-claim .hl-ref { color: var(--accent-2); }
.tldr-claim .hl-geo { color: var(--accent); }

/* Both pairs stand at once, side by side: what is taken out, struck through,
   next to what replaces it. Each pair keeps its own accent. */
/* One claim per line. The words are static; only the strike is animated, so
   the reader sees the old idea being crossed out rather than text appearing. */
.tldr-swaps {
  margin: 14px 0 0 32px; display: flex; flex-direction: column; gap: 8px;
  align-items: flex-start;
}
.swap {
  font-family: var(--font-mono); font-size: var(--fs-mono-lg);
  line-height: 1.5; margin: 0; color: var(--ink-3);
  display: flex; align-items: baseline; gap: 9px;
}
/* the rule is drawn rather than declared, so it can sweep across on cue.
   text-decoration cannot be animated, hence the pseudo-element. */
.swap-out { position: relative; text-decoration: none; }
.swap-out::after {
  content: ""; position: absolute; left: 0; right: 0; top: 0.62em; height: 1.5px;
  background: currentColor; transform: scaleX(0); transform-origin: left center;
  transition: transform .75s cubic-bezier(.4,.05,.2,1);
}
.tldr-swaps.struck .swap-out::after { transform: scaleX(1); }
.tldr-swaps.struck .swap:nth-child(2) .swap-out::after { transition-delay: .28s; }
.swap .swap-out { color: var(--accent-2); }
.swap.alt .swap-out { color: var(--accent); }
@media (prefers-reduced-motion: reduce) {
  .swap-out::after { transition: none; }
}
.swap-sep { color: var(--ink-3); }
.swap-in { color: var(--accent-2); }
.swap.alt .swap-in { color: var(--accent); }

.tldr-swaps + .caption { margin-top: 9px; margin-left: 32px; }


@media (max-width: 760px) {
  .tldr-claim, .tldr-swaps, .tldr-swaps + .caption { margin-left: 0; max-width: none; }
}

/* ============================ SUB-SECTIONS ============================ */
.sub-title {
  font-family: var(--font-display); font-weight: 500;
  font-size: var(--fs-h3); letter-spacing: -0.02em;
  margin: 62px 0 0; display: flex; align-items: baseline; gap: 14px;
}
.sub-num { font-family: var(--font-mono); font-size: var(--fs-mono); color: var(--accent); letter-spacing: 0.08em; }

/* Compact abstract: one column, held to a readable measure and set a little
   smaller than body copy so the long paper text stays quick to scan. */
.abstract-body {
  font-size: 15.5px; line-height: 1.66; color: var(--ink-2);
  margin: 20px 0 0 32px; max-width: 74ch;
  text-wrap: pretty;
}
.abstract-body p { margin: 0 0 0.9em; break-inside: avoid-column; }
.abstract-body p:last-child { margin-bottom: 0; }
.abstract-body b { color: var(--ink); font-weight: 600; }

/* ---- the three ideas the method rests on, as tiles ----
   Cards rather than bare points, so the three read as a set of claims you can
   take one at a time. No icons: the titles carry them. */
.concepts {
  list-style: none; padding: 0;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px; margin: 30px 0 0 32px; max-width: 74ch;   /* matches the abstract measure */
}
.concept {
  margin: 0;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 16px 17px 17px;
  transition: border-color .2s ease, transform .2s ease;
}
.concept:hover { border-color: var(--accent); transform: translateY(-2px); }
.concept-title {
  margin: 0 0 9px;
  font-family: var(--font-display); font-weight: 600;
  font-size: var(--fs-body); letter-spacing: -0.01em; line-height: 1.2;
  color: var(--ink); text-wrap: balance;
}
.concept-body {
  margin: 0;
  font-size: var(--fs-mono-sm); line-height: 1.5;
  color: var(--ink-2); text-wrap: pretty;
}

@media (max-width: 1000px) {
  .abstract-body { font-size: 16px; max-width: none; }
  .concepts { margin-left: 0; gap: 14px; }
}

.figure-plate { margin-top: 30px; }
.figure-plate img { width: 100%; display: block; }

.figure-duo { display: grid; grid-template-columns: repeat(auto-fit, minmax(330px, 1fr)); gap: 22px; margin-top: 22px; }
.figure-duo figure { margin: 0; }
/* a figure that is taller than it is wide would swamp the column at full
   width, so it keeps a reading measure instead */
.figure-solo { max-width: 760px; margin: 26px 0 0; }
.figure-duo figcaption,
.figure-solo figcaption,
.lib-grid figcaption {
  font-family: var(--font-mono); font-size: var(--fs-mono-sm); color: var(--ink-3);
  margin-top: 9px; letter-spacing: 0.05em;
}

.lib-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 20px; margin-top: 30px; }
.lib-grid.three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.lib-grid figure { margin: 0; }

.placeholder-note { color: var(--accent-2); opacity: 0.85; }

.swatches.two { grid-template-columns: repeat(2, minmax(0, 1fr)); }

@media (max-width: 900px) {.hero { min-height: 78vh; }
  .hero-title { font-size: clamp(21px, 4.2vw, 30px); }
  .eyebrow { flex-direction: column; gap: 6px; }
  .eyebrow-right { display: none; }
  .abstract-body { margin-left: 0; }
}

@media (max-width: 900px) {
  .wrap { padding: 0 22px; }model-viewer { height: 400px; }
  .section-lede { margin-left: 0; }
  .pipeline-sticky { height: auto; padding: 60px 0; position: static; }
  .pipeline-track { height: auto; }
}

/* Below tablet the one-liner would shrink past readability, so let it wrap.
   The assembly animation measures real positions, so it works either way. */
@media (max-width: 660px) {
  .hero-title { font-size: clamp(20px, 5.4vw, 28px); line-height: 1.2; max-width: none; }
  .gl-prefix + .gl-phrase { margin-left: 0.28em; }
}

/* ============================================================
   Versatile applications — shape pickers, PBR controls, transfer flow,
   and the twelve-tile comparison grid
   ============================================================ */

/* Swatch rows size themselves to how many references a shape has: two get a
   generous half each, more fall back to the three-up grid above. */
.swatches.two { grid-template-columns: repeat(2, minmax(0, 1fr)); }

.mini-title {
  font-family: var(--font-mono); font-size: var(--fs-mono-sm); letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--ink-3);
  margin: 46px 0 -8px; padding-bottom: 10px; border-bottom: 1px solid var(--line);
}

.section-lede.tight { margin-top: 12px; }

/* ---- toggle button (rotating light) ---- */
.toggle-btn {
  display: inline-flex; align-items: center; gap: 9px; width: 100%;
  padding: 9px 12px; border-radius: var(--radius); cursor: pointer;
  background: var(--panel); border: 1px solid var(--line); color: var(--ink-2);
  font-family: var(--font-mono); font-size: var(--fs-mono-sm); letter-spacing: 0.04em;
  transition: border-color .18s, color .18s;
}
.toggle-btn:hover { border-color: var(--accent); }
.toggle-btn .dot {
  width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--ink-3); transition: background .18s, box-shadow .18s;
}
.toggle-btn[aria-pressed="true"] { color: var(--ink); border-color: var(--accent-deep); }
.toggle-btn[aria-pressed="true"] .dot {
  background: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
  animation: pulse 2.4s ease-in-out infinite;
}
@keyframes pulse { 50% { opacity: .45; } }
@media (prefers-reduced-motion: reduce) { .toggle-btn .dot { animation: none; } }

/* ---- the two live PBR maps, shown small beside the model ---- */
.map-strip { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.map-strip figure { margin: 0; }
.map-strip img {
  width: 100%; display: block; border-radius: 4px;
  border: 1px solid var(--line); background: var(--stage);
}
.map-strip figcaption {
  font-family: var(--font-mono); font-size: var(--fs-mono-2xs); color: var(--ink-3);
  margin-top: 5px; letter-spacing: 0.05em;
}
.map-strip .off { opacity: .3; }              /* channel currently switched out */
.map-strip .off figcaption { text-decoration: line-through; }

/* ---- transfer: naming the two shapes before showing the result ---- */
.transfer-head {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 26px;
}
.chip {
  font-family: var(--font-mono); font-size: var(--fs-mono-sm); letter-spacing: 0.04em;
  padding: 7px 12px; border-radius: 999px; border: 1px solid var(--line);
  background: var(--panel); color: var(--ink-2);
}
.chip-from { border-color: var(--accent-deep); color: var(--accent-soft); }
.chip-to   { border-color: var(--accent-2); color: var(--accent-2-soft); }
.chip-note { border-style: dashed; color: var(--ink-3); }
.chip-arrow { color: var(--ink-3); font-size: var(--fs-body); line-height: 1; }

/* ---- the source → references → inference strip ---- */
.flow-strip {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 16px; margin-top: 24px;
}
.flow-strip figure { margin: 0; }
.flow-strip .plate { display: flex; align-items: center; justify-content: center; }
.flow-strip img { width: 100%; display: block; }
.flow-strip figcaption {
  font-family: var(--font-mono); font-size: var(--fs-mono-xs); line-height: 1.55;
  color: var(--ink-3); margin-top: 9px; letter-spacing: 0.02em;
}
.flow-strip figcaption b { color: var(--accent); font-weight: 500; }
.flow-strip .hero-panel .plate { border-color: var(--accent-deep); }
.flow-strip .hero-panel figcaption { color: var(--ink-2); }

/* ---- twelve results on one geometry, in one scene ---- */
.grid-viewer { margin-top: 30px; }
.grid-viewer model-viewer {
  width: 100%; height: min(66vh, 620px);
  background: var(--stage); border: 1px solid var(--line);
  border-radius: var(--radius); display: block;
}
.hint.centred { text-align: center; margin-top: 12px; }

.cond-legend { margin-top: 26px; }
.cond-grid {
  display: grid; grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 12px; margin-top: 12px;
}
.cond-grid figure { margin: 0; }
.cond-grid img {
  width: 100%; display: block; border-radius: 4px;
  border: 1px solid var(--line); background: var(--plate);
}
.cond-grid figcaption {
  font-family: var(--font-mono); font-size: var(--fs-mono-2xs); color: var(--ink-3);
  margin-top: 6px; letter-spacing: 0.02em;
}

@media (max-width: 900px) {.grid-viewer model-viewer { height: 58vh; }
}


/* ============================================================
   Turntable clips — one full turn of a shape, replacing the
   live viewers. Sized like the viewers they stand in for, so
   the surrounding layout is unchanged.
   ============================================================ */
.turntable { margin: 0; position: relative; }
.turntable video {
  width: 100%; display: block; border-radius: var(--radius);
  background: var(--stage); border: 1px solid var(--line);
  aspect-ratio: 900 / 560; object-fit: cover;
}
.turntable-cap {
  position: absolute; left: 12px; bottom: 12px;
  font-family: var(--font-mono); font-size: var(--fs-mono-sm);
  letter-spacing: 0.05em; color: var(--ink-2);
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  border: 1px solid var(--line); border-radius: 999px; padding: 5px 12px;
  backdrop-filter: blur(3px); pointer-events: none;
}
.turntable.small video { aspect-ratio: 900 / 560; }
.turntable.small figcaption:not(.turntable-cap) {
  font-family: var(--font-mono); font-size: var(--fs-mono-sm);
  color: var(--ink-3); margin-top: 10px; letter-spacing: 0.03em;
}
.turntable.small figcaption b { color: var(--ink-2); font-weight: 500; }
.pbr-ab { margin-top: 26px; }

/* The grid clip is the one that wants width: twelve shapes across three rows. */
.grid-viewer .turntable video { aspect-ratio: 900 / 560; }

/* The teaser sits between the tl;dr and the abstract, on the page ground:
   the figure is pre-composited on --bg, so it needs no plate behind it. */
.teaser-flow { margin-top: 58px; }

/* Two authoring sessions share one subsection, chosen with a segmented
   control. Three across and two down keeps a whole session on one screen
   instead of turning it into a scroll. */
/* Two sessions, so the chooser is a step control rather than a pair of tabs:
   an arrow either side of the current name, with dots for position. */
.creature-pick { margin-top: 26px; }
/* [hidden] is a UA rule; .video-grid's own display:grid outranks it, so the
   put-away grid would still take up space. Restate it at class specificity. */
.video-grid[hidden] { display: none; }
.video-grid.three-up { grid-template-columns: repeat(3, minmax(0, 1fr)); }



/* ==================== 4 · VERSATILE APPLICATIONS ====================
   Every result in this section is a turnaround, and every one of them was
   driven by references the reader should be able to see. So the unit here is
   not a video but a video plus the references that produced it: the evidence
   and its cause in one card. */

/* --- the reference strip that sits under a result ------------------ */
.ref-row { display: flex; align-items: center; gap: 9px; padding: 12px 15px 14px; flex-wrap: wrap; }
.ref-row .rr-lab {
  font-family: var(--font-mono); font-size: var(--fs-mono-2xs); color: var(--ink-3);
  letter-spacing: 0.09em; text-transform: uppercase; margin-right: 2px;
}
.ref-thumb { width: 54px; height: 54px; border-radius: 4px; overflow: hidden;
  border: 1px solid var(--line); background: #000; flex: none; }
.ref-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ref-plus { color: var(--ink-3); font-size: var(--fs-mono); line-height: 1; }
/* A material map is a data channel, not a picture — label it so the reader
   knows which of the three they are looking at. */
.ref-chan { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.ref-chan span { font-family: var(--font-mono); font-size: var(--fs-mono-2xs);
  color: var(--ink-3); letter-spacing: 0.06em; }

/* --- 4.3: the brush grid is the reference, so it gets equal billing - */
.xfer-card { display: grid; grid-template-columns: 0.85fr 1fr; gap: 0; align-items: stretch; }
.xfer-card .xc-brush { border-right: 1px solid var(--line); background: #000; display: flex; }
.xfer-card .xc-brush img { width: 100%; object-fit: contain; display: block; }
@media (max-width: 620px) {.xfer-card .xc-brush { border-right: none; border-bottom: 1px solid var(--line); } }
.xfer-group { margin-top: 30px; }
.xfer-group + .xfer-group { margin-top: 40px; }

/* --- 4.4: filmstrip comparison -------------------------------------
   The deck ships each comparison as one 7-panel filmstrip. Cropping it in the
   browser rather than slicing it into seven files keeps the seven methods
   frame-locked to each other — which is the whole point of a comparison — and
   costs one video instead of seven. Two canvases read different source rects
   out of a single decoded frame, so the panes cannot drift apart. */
.cmp { margin-top: 30px; }
.cmp-stage { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }

.cmp-pane { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; background: var(--panel); }
.cmp-pane canvas { width: 100%; display: block; background: #000; aspect-ratio: 1 / 1; }
.cmp-pick { display: flex; flex-wrap: wrap; gap: 5px; padding: 11px 12px; border-top: 1px solid var(--line); }
.cmp-pick button {
  /* seven of these have to sit on one line beside each other, so the chip is
     sized to the longest label rather than to a comfortable default */
  font-family: var(--font-mono); font-size: var(--fs-mono-sm); letter-spacing: 0;
  padding: 5px 8px; border-radius: 999px; cursor: pointer;
  background: transparent; border: 1px solid var(--line); color: var(--ink-3);
  transition: border-color .16s ease, color .16s ease;
}
.cmp-pick button:hover { color: var(--ink-2); border-color: var(--ink-3); }
.cmp-pick button.on { border-color: var(--accent); color: var(--accent); }
/* Ours is the claim being made, so it is the one chip that carries the
   emphasis accent rather than the structural one. */
.cmp-pick button.on[data-ours] { border-color: var(--accent-2); color: var(--accent-2-soft); }
.cmp-shape { display: flex; gap: 8px; margin-top: 18px; align-items: center; }
.cmp-shape .cs-lab { font-family: var(--font-mono); font-size: var(--fs-mono-2xs);
  color: var(--ink-3); letter-spacing: 0.09em; text-transform: uppercase; }
.cmp-shape button {
  font-family: var(--font-mono); font-size: var(--fs-mono-sm); padding: 4px 11px;
  border-radius: 999px; cursor: pointer; background: transparent;
  border: 1px solid var(--line); color: var(--ink-3);
}
.cmp-shape button.on { border-color: var(--accent); color: var(--accent); }

/* --- 4.4: reference-driven completion gallery ---------------------- */
.auto-gal { display: grid; grid-template-columns: repeat(auto-fit, minmax(184px, 1fr)); gap: 14px; margin-top: 28px; }
.auto-cell { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; background: var(--panel); position: relative; }
.auto-cell video { width: 100%; display: block; background: #000; }
/* The reference rides in the corner of its own result, the way the deck
   composites it — the pairing is the point and a caption would separate them. */
.auto-cell .ac-ref {
  position: absolute; top: 7px; right: 7px; width: 46px; height: 46px;
  border-radius: 3px; overflow: hidden; border: 1px solid rgba(233,238,234,0.35);
  background: #000; box-shadow: 0 2px 8px rgba(0,0,0,0.5);
}
.auto-cell .ac-ref img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* --- 4.4: the finetune strips stay whole ---------------------------- */
.ft-strip { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
  background: #000; margin-top: 14px; }
.ft-strip video { width: 100%; display: block; }

/* The completion gallery runs to forty cells, so it is broken by shape — a
   flat grid that long reads as one undifferentiated wall. */
.gal-head {
  font-family: var(--font-mono); font-size: var(--fs-mono-sm); color: var(--ink-3);
  letter-spacing: 0.09em; text-transform: uppercase; font-weight: 400;
  margin: 30px 0 0; padding-bottom: 8px; border-bottom: 1px solid var(--line);
}
.gal-head + .auto-gal { margin-top: 14px; }
/* Nine shapes will not sit on one line beside the "shape" label. */
.cmp-shape { flex-wrap: wrap; }

/* --- 4.1 · eight blends, four across ---------------------------------
   The turnarounds are square, so four to a row reads as a contact sheet of
   one experiment repeated rather than as eight separate figures. Each result
   carries its references directly beneath it as centre-cropped discs: round,
   because a circle reads as a swatch of a look rather than as another
   rendering competing with the turnaround above it. */
.blend-grid {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 18px; margin-top: 34px;
}


.blend-cell {
  border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; background: var(--panel);
}
.blend-cell video { width: 100%; display: block; background: #000; }
.blend-cell .vc-label { padding: 10px 13px 0; }
.blend-cell .vc-note  { padding: 6px 13px 13px; }
.ref-orbs { display: flex; justify-content: center; gap: 12px; padding: 13px 10px 2px; }
.orb {
  width: 48px; height: 48px; border-radius: 50%; overflow: hidden; flex: none;
  border: 1px solid var(--line); background: #000;
}
/* cover + centre is the crop: the middle of each reference, nothing letterboxed */
.orb img { width: 100%; height: 100%; object-fit: cover; object-position: center; display: block; }

/* --- 4.2 / 4.3 · one object per cell, stepped through its results ------
   Each object was textured several times from different references. Stacking
   those results in one frame and stepping between them holds the geometry
   still, so what changes between presses is only the reference — which is the
   comparison the section is making. The controls carry no labels: the arrows
   sit on the frame they act on and the dots report position. */
.pbr-grid { display: grid; gap: 20px; margin-top: 34px; }
.pbr-grid.three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.pbr-grid.four  { grid-template-columns: repeat(4, minmax(0, 1fr)); }



.pbr-cell { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; background: var(--panel); }
.pbr-stage { position: relative; background: #000; }
.pbr-stage video { width: 100%; display: block; background: #000; }
/* only the active result occupies the frame; the rest stay in the DOM so a
   press swaps instantly instead of re-fetching */
.pbr-stage video { display: none; }
.pbr-stage video.on { display: block; }

.pbr-arrow {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 32px; height: 32px; padding: 0; cursor: pointer; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%; border: 1px solid rgba(233,238,234,0.28);
  background: rgba(13,18,14,0.55); color: var(--ink-2);
  opacity: 0; transition: opacity .16s ease, border-color .16s ease, color .16s ease;
}
.pbr-arrow.prev { left: 8px; } .pbr-arrow.next { right: 8px; }
.pbr-cell:hover .pbr-arrow, .pbr-arrow:focus-visible { opacity: 1; }
.pbr-arrow:hover { border-color: var(--accent); color: var(--accent); }
.pbr-arrow svg { width: 16px; height: 16px; fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
/* touch has no hover, so the arrows stay visible there */
@media (hover: none) { .pbr-arrow { opacity: 1; } }

.pbr-refs { display: none; align-items: flex-start; justify-content: center; gap: 12px; padding: 13px 12px 4px; }
.pbr-refs.on { display: flex; }
.pbr-refs .ref-thumb { width: 52px; height: 52px; }
.pbr-refs .xc-brush-wide { display: block; width: 100%; }
.pbr-refs .xc-brush-wide img { width: 100%; display: block; border-radius: 4px; border: 1px solid var(--line); background: #000; }

.pbr-dots { display: flex; justify-content: center; gap: 6px; padding: 9px 0 12px; }
.pbr-dots i { width: 6px; height: 6px; border-radius: 50%; background: var(--line); transition: background .2s ease; }
.pbr-dots i.on { background: var(--accent); }

.swap-chip { display: flex; align-items: center; justify-content: center; gap: 7px;
  flex-wrap: wrap; padding: 0 10px 14px; }
.swap-chip .chip { font-size: var(--fs-mono-2xs); padding: 3px 9px; }


/* --- 4.4 · the comparison row ---------------------------------------
   Seven panels arrive as one video, so the legend is a seven-column grid
   pinned under it rather than labels placed by hand. */
.cmp-strip { border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; background: #000; margin-top: 16px; }
.cmp-strip video { width: 100%; display: block; }
.cmp-legend { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); margin-top: 9px; }
.cmp-legend span {
  font-family: var(--font-mono); font-size: var(--fs-mono-sm); color: var(--ink-3);
  text-align: center; letter-spacing: 0; padding: 0 2px;
}
/* the leftmost panel is the input, not a competitor */
.cmp-legend .is-ref  { color: var(--ink-2); }
.cmp-legend .is-ours { color: var(--accent-2-soft); }
@media (max-width: 720px) { .cmp-legend span { font-size: var(--fs-mono-2xs); } }

/* --- 4.4 · one object per slide, five results across ------------------
   Every slide is the same shape filled five times from five references, so
   the row is the comparison and stepping between rows changes only the shape.
   The finetuned results arrive as a single five-panel video; the per-shape
   ones as five files laid out to match, so both read as the same figure. */
.auto-swap { margin-top: 22px; }
.as-slide { display: none; }
.as-slide.on { display: block; }
.as-row { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 8px; }

.as-name {
  font-family: var(--font-mono); font-size: var(--fs-mono-sm); color: var(--ink-2);
  letter-spacing: 0.06em; text-transform: uppercase; margin-top: 11px; text-align: center;
}
.as-name span { color: var(--ink-3); text-transform: none; letter-spacing: 0.03em; }
.as-nav { display: flex; align-items: center; justify-content: center; gap: 14px; margin-top: 14px; }
.as-dots { display: flex; gap: 6px; }
.as-dots i { width: 6px; height: 6px; border-radius: 50%; background: var(--line); transition: background .2s ease; }
.as-dots i.on { background: var(--accent); }

/* --- 4.1 / 4.2 / 4.3 · reference sizes -------------------------------
   The references are the argument these sections make, so they are sized to
   be read rather than to sit politely under the result. */
.pbr-refs.orbs { gap: 16px; padding: 15px 12px 4px; }
.pbr-refs.orbs .orb { width: 82px; height: 82px; }
.pbr-refs .ref-thumb { width: 78px; height: 78px; }
.pbr-refs .ref-chan span { font-size: var(--fs-mono-xs); }
/* 4.3 keeps a single sampled patch, not the whole brush sheet */
.pbr-refs.patch { padding: 13px 12px 4px; }
.xc-patch { display: block; width: 132px; }
.xc-patch img { width: 100%; display: block; border-radius: 4px;
  border: 1px solid var(--line); background: #000; }

/* --- 4.4 · pick the shape by name ------------------------------------ */
.as-pick { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 16px; }
.as-pick button {
  font-family: var(--font-mono); font-size: var(--fs-mono-sm); letter-spacing: 0.03em;
  padding: 5px 11px; border-radius: 999px; cursor: pointer;
  background: transparent; border: 1px solid var(--line); color: var(--ink-3);
  transition: border-color .16s ease, color .16s ease;
}
.as-pick button:hover { color: var(--ink-2); border-color: var(--ink-3); }
.as-pick button.on { border-color: var(--accent); color: var(--accent); }
/* the finetuned shapes are a different experiment, so they are marked rather
   than mixed silently into the same list */
.as-pick button[data-ft]::before {
  content: ''; display: inline-block; width: 5px; height: 5px; border-radius: 50%;
  background: var(--accent-2); margin-right: 6px; vertical-align: middle;
}
.as-pick button[data-ft].on { border-color: var(--accent-2); color: var(--accent-2-soft); }

/* --- the metrics table, compact and last ----------------------------- */
.table-wrap.compact { margin-top: 26px; }
.table-wrap.compact table { font-size: var(--fs-xs); }
.table-wrap.compact th, .table-wrap.compact td { padding: 6px 14px; }

/* --- 4.1 · five objects, one row -------------------------------------
   At five across a cell is ~215 px, so a fixed 82 px disc would overflow the
   three-reference blends. The discs keep their size where there is room and
   otherwise divide the row, staying circular via aspect-ratio. */
.pbr-grid.five { grid-template-columns: repeat(5, minmax(0, 1fr)); }


.pbr-grid.five .pbr-refs.orbs { gap: 10px; padding: 13px 10px 4px; }
.pbr-grid.five .pbr-refs.orbs .orb {
  width: min(82px, calc((100% - 20px) / 3)); height: auto; aspect-ratio: 1;
}

/* --- 4.2 · six across, split into its two halves ---------------------
   The section makes two separate points: what the channels do on three
   objects, and why they have to follow the geometry. Six cells in one row
   with a rule between them keeps both visible at once without letting them
   read as one undifferentiated set. */
.pbr-split { display: grid; grid-template-columns: 1fr 1fr; gap: 34px; margin-top: 34px; }
.pbr-split .pbr-grid { margin-top: 0; }
.pbr-split .tri-side { position: relative; }
.pbr-split .tri-side::before {
  content: ''; position: absolute; left: -17px; top: 4px; bottom: 4px;
  border-left: 1px solid var(--line);
}
@media (max-width: 1100px) {
  .pbr-split { gap: 28px; }
  .pbr-split .tri-side::before { display: none; }
}
.pbr-cell.tri .tri-lab {
  font-family: var(--font-mono); font-size: var(--fs-mono-2xs); color: var(--ink-3);
  letter-spacing: 0.05em; text-transform: uppercase;
  text-align: center; padding: 10px 6px 12px;
}


/* --- 4.3 · six results across ----------------------------------------
   Two fills per brush source, laid out as one row so the three sources can
   be compared without stepping. The chip stays because a transfer only means
   something if you can see where the brushes came from. */
.pbr-grid.six { grid-template-columns: repeat(6, minmax(0, 1fr)); }


.pbr-grid.six .xc-patch { width: 84px; }
.pbr-grid.six .swap-chip { gap: 4px; padding: 0 6px 11px; }
.pbr-grid.six .swap-chip .chip { font-size: 8px; padding: 2px 6px; letter-spacing: 0; }

/* Three channel references have to share a cell that is now a sixth of the
   row, so they divide it rather than holding a fixed width that overflows. */
.pbr-split .pbr-refs { gap: 8px; padding: 12px 8px 4px; }
.pbr-split .pbr-refs .ref-chan { min-width: 0; flex: 1 1 0; }
.pbr-split .pbr-refs .ref-thumb {
  width: min(78px, 100%); height: auto; aspect-ratio: 1; margin: 0 auto;
}
.pbr-split .pbr-refs .ref-chan > span { font-size: 8px; letter-spacing: 0; }
/* cells size to their own content instead of stretching to the tallest */
.pbr-grid { align-items: start; }

/* Every 4.1 cell now carries a dot row, including the single-sample ones, so
   the five cells end at the same height instead of stepping down wherever a
   shape happened to have one result. */
.pbr-grid.five { align-items: stretch; }
.pbr-grid.five .pbr-cell { display: flex; flex-direction: column; }
.pbr-grid.five .pbr-refs { margin-top: auto; }

/* 4.2 · the two halves carry different content heights, so they are centred
   against each other rather than both hanging from the top. */
.pbr-split { align-items: center; }


/* ---- artist intent: the decision behind each clip, in the artist's voice ---- */
.vc-intent {
  display: flex; align-items: flex-start; gap: 8px;
  margin: 7px 13px 13px;
  font-size: var(--fs-xs); line-height: 1.5; font-style: italic;
  color: var(--ink-2); text-wrap: pretty;
}
.intent-icon {
  flex: none; width: 15px; height: 15px; margin-top: 2px;
  fill: none; stroke: var(--accent-2); stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}

/* ---- affiliation marks under the byline ----
   The Princeton shield ships as dark ink on transparent, so it is restated in
   --ink for this ground; NVIDIA keeps its own green. Heights are set per mark
   rather than uniformly — a tall shield and a wide wordmark do not balance at
   the same height. */
.affil-logos {
  display: flex; align-items: center; gap: 22px; margin-top: 14px;
  opacity: 0.9;
}
.logo-princeton { height: 30px; width: auto; display: block; }
.logo-nvidia    { height: 17px; width: auto; display: block; }


/* the lede should not crowd the figure it introduces */
.section-lede + .plate, .section-lede + .figure-duo { margin-top: 30px; }


/* ============================================================
   PHONE  (≤ 700px)
   -------------------------------------------------------------
   Everything above this width is deliberately left alone: the
   desktop and tablet layout is locked and no rule in this block
   can reach it. Below it the fixed column counts stop being a
   layout and start being a problem — on a 393px screen the five
   blend cells measure 54px each, the six transfer cells 42px, and
   the three demo videos 103px. Nothing at that size is watchable,
   so this is the one tier where the grids are allowed to reflow.
   ============================================================ */
@media (max-width: 700px) {

  /* --- one thing at a time, full width -----------------------
     These are the cases where the reader is meant to watch a
     single clip rather than compare several, so a column split
     buys nothing and costs all the resolution. */
  .video-grid.three-up,
  .video-grid,
  .concepts,
  .viewer-row { grid-template-columns: minmax(0, 1fr); }

  /* --- contact sheets, two up --------------------------------
     Here the comparison IS the content, so the grid survives —
     just at a count that leaves each cell big enough to read. */
  .as-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* --- 4.1 – 4.3: one row, swiped ----------------------------
     Folding these to two columns still left every cell at 163px,
     which is not enough of a result to be worth looking at. A
     single scrolling row gives each cell most of the screen and
     keeps the set in the order it was authored — you swipe from
     one reference to the next instead of reading a wall.

     The track bleeds to both screen edges by exactly the page
     gutter, so the row reads as continuing past the frame while
     the first cell still lines up with the text above it. The
     cell is sized under a full screen on purpose: the sliver of
     the next one is what says the row can be swiped. */
  .pbr-split,
  .pbr-grid.three,
  .pbr-grid.four,
  .pbr-grid.five,
  .pbr-grid.six {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: 39vw;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    scroll-padding-left: 22px;
    -webkit-overflow-scrolling: touch;
    margin-left: -22px;
    margin-right: -22px;
    padding: 0 22px 4px;
  }

  /* 4.2 is two groups of three inside a split. On a desktop the
     split sets them side by side and they read as one band; left
     to stack they would become two separate rows on a phone.
     Dissolving the two inner grids makes their six cells direct
     children of the split, so the section is the single row it
     looks like at full width. */
  .pbr-split > .pbr-grid { display: contents; }
  .pbr-split .tri-side::before { display: none; }
  /* the tri cells are shorter than the rest; without this they
     centre themselves and sit half a card lower than their row */
  .pbr-split { align-items: start; }

  .pbr-split .pbr-cell,
  .pbr-grid.three > *,
  .pbr-grid.four  > *,
  .pbr-grid.five  > *,
  .pbr-grid.six   > * { scroll-snap-align: start; }

  /* --- the stepper controls ----------------------------------
     A 32px disc that sat comfortably on a 260px desktop cell
     covers a third of a phone cell. Shrink the disc, but keep the
     tap target at 44px with a transparent halo — the control gets
     smaller to look at, not harder to hit. */
  .pbr-arrow { width: 22px; height: 22px; }
  .pbr-arrow svg { width: 11px; height: 11px; }
  .pbr-arrow.prev { left: 4px; }
  .pbr-arrow.next { right: 4px; }
  .pbr-arrow::after { content: ""; position: absolute; inset: -11px; }
  .pbr-dots { padding: 7px 0 9px; }

  /* --- the baseline filmstrip --------------------------------
     Seven methods in one frame cannot be squeezed into 349px and
     still be a comparison, and the legend has to stay locked to
     the panels it labels. So the strip and its legend scroll
     together instead of shrinking. */
  .cmp-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .cmp-scroll .cmp-strip,
  .cmp-scroll .cmp-legend { min-width: 620px; }

  /* --- hero --------------------------------------------------
     vh on iOS counts the URL bar that is about to slide away, so
     the hero jumps as you scroll. svh is the stable height. */
  .hero { min-height: 78svh; }
  /* the wide framing puts the dragon off the left edge of a phone, leaving
     the hero looking like an empty dark panel - pull the crop back toward
     the subject so there is something behind the title */
  .hero-bg { object-position: 42% 44%; }
  .hero-inner { padding-top: 54px; padding-bottom: 40px; }
  /* the scrim was aimed left for a wide screen; on a phone the
     title spans the full width, so shade the whole bottom */
  .hero-scrim {
    background: linear-gradient(to top, var(--bg) 2%, rgba(13,18,14,0.88) 40%,
                                rgba(13,18,14,0.66) 72%, rgba(13,18,14,0.52) 100%);
  }

  /* the wide result tables already scroll inside .table-wrap;
     keep them from setting a floor under the page width */
  .table-wrap { max-width: 100%; }
}


/* ============================================================
   3.2.2 · Geometry conditioned local inpainting
   -------------------------------------------------------------
   Four shapes, each a five-column strip: the reference pair, then
   the target pair and the fill. The source figure arrived as one
   sheet on white, so it is sliced into its five columns and laid
   out here instead — the gaps are then page ground rather than
   paper, and the labels are real text that follows the palette.
   The wider gap between column two and three is the seam in the
   original: everything left of it describes the reference,
   everything right of it the target.

   One row, scrolled sideways, at every width. Four five-column
   panels cannot be read stacked, and shrinking them to fit is
   what would make the texture detail worthless.
   ============================================================ */
.geo-row {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 400px;   /* five tiles at ~80px, matching the patch size in 3.2.1 */
  gap: 26px;
  margin-top: 30px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-padding-left: 2px;
  padding-bottom: 8px;
}
.geo-panel { margin: 0; scroll-snap-align: start; }

/* the images and their labels share one track list, so a label
   cannot drift out from under the column it names */
.geo-cols,
.geo-labels {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
}
/* the five columns are stitched flush, the way they came off the
   render, so the strip reads as one frame rather than five cards */
.geo-cols img {
  width: 100%; display: block;
  background: var(--bg);
}

/* the last column is the output, so it is bracketed rather than
   merely labelled - drawn inside the column so the stitch holds */
.geo-hi { position: relative; display: block; }
.geo-hi::before,
.geo-hi::after {
  content: ""; position: absolute; top: 0; bottom: 0; width: 9px;
  border: 2px solid var(--accent-2);
  pointer-events: none;
}
.geo-hi::before { left: 0;  border-right: 0; border-radius: 3px 0 0 3px; }
.geo-hi::after  { right: 0; border-left: 0;  border-radius: 0 3px 3px 0; }
.geo-labels { margin-top: 10px; }
.geo-labels span {
  font-family: var(--font-mono); font-size: var(--fs-mono-xs);
  letter-spacing: 0.03em; color: var(--ink-3);
  text-align: center; white-space: nowrap;
}
.geo-labels span:last-child { color: var(--accent-2-soft); }  /* the result is the claim */
.geo-gap { display: block; }

@media (max-width: 700px) {
  /* same full-bleed treatment as the 4.x rows */
  .geo-row {
    margin-left: -22px; margin-right: -22px;
    padding-left: 22px; padding-right: 22px;
    scroll-padding-left: 22px;
    gap: 20px;
  }
}

/* A statement about a co-author's own standing, not an affiliation —
   it gets its own line so it is not read as one more institution. */
.affil-note { display: block; margin-top: 5px; color: var(--ink-3); }

/* 4.2 and 4.3 carry sentences, not labels. Same face and scale as the
   4.4 mini-titles so the section reads as one family, but set in
   sentence case — and without the wide tracking, which exists to open
   up uppercase and only loosens lowercase past reading. */
.mini-title.sentence { text-transform: none; letter-spacing: 0.02em; }
