/* ============================================================================
   Ethan Sheehan — classic
   Concept: a test-stand log book at full scale. Paper ground, measured type,
   mono data labels, and the only saturated colour on the site is the fire in
   the footage. Register follows seasats.com (spec tables, mono labels,
   full-bleed media) and kononenkogroup.com / paulkalkbrenner.net (quiet
   ground, one enormous name, real negative space).
   ========================================================================= */

/* ---- 1. tokens ---------------------------------------------------------- */
:root {
  color-scheme: light;

  /* ground + ink: warm neutrals, never pure #fff / #000 */
  --paper:      #FBFAF8;
  --paper-2:    #F3F1ED;   /* recessed panels, table zebra */
  --paper-3:    #E9E6E0;   /* pressed / active */
  --ink:        #1A1A18;
  --ink-2:      #5C5C56;
  --ink-3:      #8E8E86;   /* mono labels */
  --line:       rgba(26, 26, 24, 0.14);
  --line-soft:  rgba(26, 26, 24, 0.07);
  --line-hard:  rgba(26, 26, 24, 0.30);

  /* the single accent, sampled from the plume. Used ONLY for the lock-on
     reticle when acquired and for focus rings. Never decorative. */
  --ember:      #C8481A;

  /* type */
  --sans: "Inter Tight", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --t-display: clamp(3.2rem, 11.4vw, 13rem);   /* the name */
  --t-statement: clamp(1.75rem, 3.6vw, 3.5rem);
  --t-h2:      clamp(1.5rem, 3.1vw, 3rem);
  --t-h3:      clamp(1.15rem, 1.5vw, 1.5rem);
  --t-body:    clamp(1rem, 1.05vw, 1.125rem);
  --t-small:   0.875rem;
  --t-label:   0.75rem;      /* mono, uppercase, tracked */

  /* space */
  --gut:   clamp(1.25rem, 3.2vw, 3.5rem);
  --rail:  clamp(1.25rem, 4vw, 5rem);
  --stack: clamp(4.5rem, 11vh, 10rem);   /* between major sections */
  --maxw:  1680px;

  /* motion — bespoke curves, no default ease anywhere */
  --e-out:  cubic-bezier(0.16, 0.84, 0.24, 1);      /* reveals, settle */
  --e-io:   cubic-bezier(0.62, 0.02, 0.16, 1);      /* travel, wipes */
  --e-snap: cubic-bezier(0.22, 1.24, 0.36, 1);      /* the lock-on acquire */
  --d-fast: 0.28s;
  --d-mid:  0.62s;
  --d-slow: 1.05s;
}

/* ---- 2. reset ----------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--t-body);
  line-height: 1.5;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
img, video, canvas, svg { display: block; max-width: 100%; }
img, video { height: auto; }          /* never stretch — the round-3 bug */
h1, h2, h3, h4, p, figure, blockquote { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
:focus-visible { outline: 2px solid var(--ember); outline-offset: 3px; border-radius: 1px; }
::selection { background: var(--ink); color: var(--paper); }

/* ---- 3. primitives ------------------------------------------------------ */
.wrap { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--rail); }
.bleed { width: 100%; }

.label {                                  /* the mono data label, everywhere */
  font-family: var(--mono);
  font-size: var(--t-label);
  font-weight: 400;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--ink-3);
  line-height: 1.2;
}
.rule { height: 1px; background: var(--line); border: 0; margin: 0; }

.statement {
  font-size: var(--t-statement);
  font-weight: 400;
  letter-spacing: -0.028em;
  line-height: 1.08;
  text-wrap: balance;
}
h2.sec-title {
  font-size: var(--t-h2);
  font-weight: 500;
  letter-spacing: -0.03em;
  line-height: 1.02;
}
.lede { font-size: clamp(1.05rem, 1.35vw, 1.3rem); line-height: 1.55; color: var(--ink-2); max-width: 62ch; }
.body-copy p { max-width: 66ch; line-height: 1.62; color: var(--ink-2); }
.body-copy p + p { margin-top: 1.1em; }

/* section head: mono index + title, the recurring page furniture */
.sec-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1rem;
  padding-block: 1.15rem;
  border-top: 1px solid var(--ink);
  margin-bottom: clamp(1.5rem, 3.5vw, 3rem);
}
@media (min-width: 800px) {
  .sec-head { grid-template-columns: 8rem minmax(0, 1fr) auto; align-items: baseline; gap: var(--gut); }
}
.sec-head .count { justify-self: end; }

/* ---- 4. hero: the name as aperture -------------------------------------- */
/* The burn plays inside the letterforms only; the page around them stays
   paper. On scroll the letters open and the video floods the frame. */
.hero {
  position: relative;
  height: 240svh;              /* 100svh stage + 140svh of flood travel */
  min-height: 40rem;
  overflow: clip;
  background: var(--paper);
}
.hero.static { height: 100svh; }
.hero__stage { position: sticky; top: 0; height: 100svh; overflow: clip; }

/* the media layer (video, or the WebGL heat canvas that samples it) */
.hero__media, .hero__gl {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}
.hero__gl { z-index: 1; }
.hero__media { z-index: 0; }
/* when the shader runs it draws the video itself — hide the raw element */
.gl .hero__media { opacity: 0; }

/* the paper mask with the name knocked out of it (SVG cutout) */
.hero__cut { position: absolute; inset: 0; z-index: 2; width: 100%; height: 100%; pointer-events: none; }
.gl .hero__cut { display: none; }         /* shader composites its own mask */

.hero__furniture {
  position: absolute; inset: 0; z-index: 4;
  display: grid;
  grid-template-rows: auto 1fr auto;
  padding: var(--rail);
  pointer-events: none;
}
.hero__furniture > * { pointer-events: auto; }
.hero__top, .hero__bottom { display: flex; justify-content: space-between; align-items: start; gap: var(--gut); }
.hero__bottom { align-items: end; }
.hero__role { max-width: 30ch; }
.hero__cue { display: flex; align-items: center; gap: 0.6rem; }
.hero__cue .tick { width: 1px; height: 2.6rem; background: currentColor; transform-origin: bottom; animation: cue 2.6s var(--e-io) infinite; }
@keyframes cue { 0%, 100% { transform: scaleY(0.25); opacity: 0.35; } 50% { transform: scaleY(1); opacity: 1; } }

/* a scrim appears only once media is behind the furniture, so the mono labels
   stay legible over a bright plume without dimming the paper state */
.hero__furniture::before, .hero__furniture::after {
  content: ""; position: absolute; left: 0; right: 0; height: 24vh;
  opacity: 0; transition: opacity var(--d-mid) var(--e-io); pointer-events: none;
}
.hero__furniture::before { top: 0; background: linear-gradient(to bottom, rgba(12,10,9,0.45), transparent); }
.hero__furniture::after { bottom: 0; background: linear-gradient(to top, rgba(12,10,9,0.5), transparent); }
.hero.flooded .hero__furniture::before, .hero.flooded .hero__furniture::after { opacity: 1; }

/* furniture inverts to paper-on-media once the burn has flooded */
.hero__furniture { color: var(--ink); transition: color var(--d-mid) var(--e-io); }
.hero.flooded .hero__furniture { color: var(--paper); }
.hero.flooded .hero__furniture .label { color: rgba(251, 250, 248, 0.72); }

/* accessible name — the SVG/canvas carries no text for AT */
.hero__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

.hero__cut text {
  font-family: var(--sans);
  font-weight: 700;
  font-size: var(--t-display);
  letter-spacing: -0.035em;
  transform-origin: center;
  transform-box: fill-box;
}
.hero__cut .cut-paper { transition: opacity var(--d-fast) linear; }

/* a plate that carries its own heat shader over the media */
.plate { position: relative; overflow: clip; }
.plate canvas.heat { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; z-index: 2; }
.plate.gl canvas.heat { opacity: 1; }
.plate.gl > video, .plate.gl > img { visibility: hidden; }

/* ---- 5. lock-on: the recurring seeker ----------------------------------- */
/* Four hairline corner brackets that acquire a target and read it out. */
#lock {
  position: fixed;
  top: 0; left: 0;
  z-index: 60;
  width: 0; height: 0;
  pointer-events: none;
  opacity: 0;
  color: var(--ink);
  transition: opacity var(--d-fast) var(--e-io);
  will-change: transform, width, height;
}
#lock.on { opacity: 1; }
#lock.hot { color: var(--ember); }
#lock .c {                                 /* corner bracket */
  position: absolute;
  width: 14px; height: 14px;
  border: 1px solid currentColor;
  /* the brackets travel over paper, dark plates and bright plume alike, so
     they carry their own separation rather than relying on the backdrop */
  filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.55));
}
#lock .c.tl { top: 0; left: 0; border-right: 0; border-bottom: 0; }
#lock .c.tr { top: 0; right: 0; border-left: 0; border-bottom: 0; }
#lock .c.bl { bottom: 0; left: 0; border-right: 0; border-top: 0; }
#lock .c.br { bottom: 0; right: 0; border-left: 0; border-top: 0; }
#lock .read {
  text-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
  position: absolute;
  top: 100%; left: 0;
  margin-top: 7px;
  display: flex; gap: 0.75rem;
  white-space: nowrap;
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: currentColor;
}
#lock .read > span { display: inline-flex; gap: 0.4rem; align-items: baseline; }
#lock .read .k { color: var(--ink-3); }
#lock .read .v { color: var(--ink); }
#lock.hot .read .v { color: var(--ember); }
/* over a dark plate the seeker flips to paper */
#lock.inv, #lock.inv .read .v { color: var(--paper); }

/* ---- 6. work: the featured rows ----------------------------------------- */
.work { margin-block: var(--stack); }
.wk {
  display: grid;
  gap: clamp(1rem, 2.4vw, 2.25rem);
  margin-bottom: clamp(3.5rem, 8vw, 8rem);
}
@media (min-width: 900px) {
  .wk { grid-template-columns: var(--cols, 7fr 5fr); align-items: start; }
  .wk.flip .wk__media { order: 2; }
  .wk.flip .wk__text  { order: 1; }
}
.wk__media {
  position: relative;
  overflow: clip;
  background: var(--paper-2);
  aspect-ratio: 16 / 9;
}
.wk.tall .wk__media { aspect-ratio: 4 / 5; }
.wk__media video, .wk__media img {
  width: 100%; height: 100%;
  object-fit: cover;
}
.wk__media .still { position: absolute; inset: 0; z-index: 1; }
.wk__media .loop  { position: absolute; inset: 0; z-index: 2; opacity: 0; transition: opacity 0.5s var(--e-io); }
.wk:hover .wk__media .loop, .wk:focus-within .wk__media .loop { opacity: 1; }
.wk__text { padding-top: 0.35rem; display: flex; flex-direction: column; gap: 1rem; }
.wk__title { font-size: var(--t-h2); font-weight: 500; letter-spacing: -0.03em; line-height: 1; }
.wk__title .code { font-family: var(--mono); font-size: 0.42em; letter-spacing: 0.06em; color: var(--ink-3); vertical-align: 0.55em; margin-left: 0.75rem; text-transform: uppercase; }
.wk__one { color: var(--ink-2); max-width: 46ch; }

/* the spec strip under a work row — the seasats table, compressed */
.specs { display: grid; grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); border-top: 1px solid var(--line); }
.specs div { padding: 0.7rem 0 0.15rem; border-right: 1px solid var(--line-soft); padding-right: 1rem; }
.specs div:last-child { border-right: 0; }
.specs .v { font-family: var(--mono); font-size: clamp(0.9rem, 1.05vw, 1.05rem); letter-spacing: -0.005em; color: var(--ink); margin-top: 0.4rem; line-height: 1.25; }

/* full-bleed cinema plate between rows */
.cinema { position: relative; width: 100%; height: clamp(60svh, 78svh, 92svh); overflow: clip; background: var(--ink); margin-block: var(--stack); }
.cinema video, .cinema img, .cinema canvas { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.cinema__cap { position: absolute; left: var(--rail); bottom: var(--rail); z-index: 3; color: var(--paper); max-width: 40ch; }
.cinema__cap .label { color: rgba(251, 250, 248, 0.7); }

/* ---- 7. index: the numbered manifest ------------------------------------ */
.idx { border-top: 1px solid var(--ink); }
.idx__row {
  position: relative;
  display: grid;
  grid-template-columns: 3.25rem minmax(0, 1fr);
  gap: 1rem;
  align-items: center;
  padding: clamp(0.85rem, 1.5vw, 1.35rem) 0;
  border-bottom: 1px solid var(--line);
  transition: background var(--d-fast) var(--e-io);
}
@media (min-width: 900px) {
  .idx__row { grid-template-columns: 3.25rem 5.5rem minmax(0, 1.15fr) minmax(0, 1fr) 9rem 7rem; gap: var(--gut); }
}
.idx__row:hover { background: var(--paper-2); }
.idx__n { font-family: var(--mono); font-size: var(--t-label); color: var(--ink-3); letter-spacing: 0.06em; }
.idx__thumb { display: none; position: relative; width: 5.5rem; aspect-ratio: 3/2; overflow: clip; background: var(--paper-3); }
@media (min-width: 900px) { .idx__thumb { display: block; } }
.idx__thumb img, .idx__thumb video { width: 100%; height: 100%; object-fit: cover; }
.idx__thumb video { position: absolute; inset: 0; opacity: 0; transition: opacity 0.42s var(--e-io); }
.idx__row:hover .idx__thumb video { opacity: 1; }
.idx__title { font-size: clamp(1.05rem, 1.6vw, 1.5rem); font-weight: 500; letter-spacing: -0.022em; line-height: 1.1; }
.idx__title .code { font-family: var(--mono); font-size: 0.62em; color: var(--ink-3); margin-left: 0.7rem; letter-spacing: 0.05em; }
.idx__one { color: var(--ink-2); font-size: var(--t-small); display: none; }
@media (min-width: 900px) { .idx__one { display: block; } }
.idx__meta { display: none; }
@media (min-width: 900px) { .idx__meta { display: block; text-align: right; } }
.idx__go { justify-self: end; }
.idx__row .arrow { display: inline-block; transition: transform var(--d-fast) var(--e-snap); }
.idx__row:hover .arrow { transform: translateX(0.35rem); }

/* group heading inside the index */
.idx__group { padding: clamp(2rem, 4vw, 3.5rem) 0 0.75rem; border-bottom: 1px solid var(--ink); }

/* ---- 8. about + spec table ---------------------------------------------- */
.about { display: grid; gap: var(--gut); margin-block: var(--stack); }
@media (min-width: 900px) { .about { grid-template-columns: 5fr 7fr; } }
.about__portrait { position: relative; overflow: clip; background: var(--paper-2); aspect-ratio: 4/5; }
.about__portrait img { width: 100%; height: 100%; object-fit: cover; }

.spec-table { width: 100%; border-collapse: collapse; }
.spec-table th, .spec-table td { text-align: left; padding: 0.85rem 1rem 0.85rem 0; border-bottom: 1px solid var(--line); vertical-align: baseline; }
.spec-table th { width: 12rem; font-weight: 400; }
.spec-table td { font-family: var(--mono); font-size: var(--t-small); }

/* ---- 9. chrome: header, footer, curtain --------------------------------- */
.hdr {
  position: fixed; top: 0; left: 0; right: 0; z-index: 50;
  background: var(--paper);
  border-bottom: 1px solid var(--line-soft);
  display: flex; justify-content: space-between; align-items: center;
  gap: var(--gut);
  padding: 1.1rem var(--rail);
  mix-blend-mode: normal;
  transition: color var(--d-mid) var(--e-io), background var(--d-mid) var(--e-io);
}
.hdr.over-media { color: var(--paper); background: transparent; border-bottom-color: transparent; }
.hdr.over-media .label { color: rgba(251, 250, 248, 0.75); }
.hdr__mark { display: flex; align-items: center; gap: 0.6rem; font-family: var(--mono); font-size: var(--t-label); letter-spacing: 0.12em; text-transform: uppercase; }
.hdr__nav { display: flex; gap: clamp(0.9rem, 2vw, 2rem); }
.hdr__nav a { position: relative; }
.hdr__nav a::after { content: ""; position: absolute; left: 0; right: 0; bottom: -3px; height: 1px; background: currentColor; transform: scaleX(0); transform-origin: right; transition: transform var(--d-fast) var(--e-io); }
.hdr__nav a:hover::after, .hdr__nav a[aria-current]::after { transform: scaleX(1); transform-origin: left; }

.ftr { border-top: 1px solid var(--ink); padding-block: clamp(2.5rem, 6vw, 5rem) 2rem; margin-top: var(--stack); }
.ftr__grid { display: grid; gap: var(--gut); }
@media (min-width: 800px) { .ftr__grid { grid-template-columns: repeat(4, 1fr); } }
.ftr a:hover { text-decoration: underline; text-underline-offset: 0.25em; }

#curtain { position: fixed; inset: 0; z-index: 90; background: var(--paper); transform: scaleY(0); transform-origin: bottom; pointer-events: none; }

/* ---- 10. motion states -------------------------------------------------- */
/* Every latent state is gated behind html.motion so a failed script can never
   leave content hidden. */
.motion .rv { opacity: 0; transform: translateY(1.1rem); }
.motion .rv.in { opacity: 1; transform: none; transition: opacity var(--d-mid) var(--e-out), transform var(--d-mid) var(--e-out); }

.motion .wipe { clip-path: inset(0 0 100% 0); }
.motion .wipe.in { clip-path: inset(0 0 0 0); transition: clip-path var(--d-slow) var(--e-io); }

.motion .line-mask { display: block; overflow: clip; }
.motion .line-mask > span { display: block; transform: translateY(105%); }
.motion .line-mask.in > span { transform: none; transition: transform var(--d-slow) var(--e-out); }

/* the media plate settles: it starts slightly over-scaled and eases to rest */
.motion .settle img, .motion .settle video { transform: scale(1.06); }
.motion .settle.in img, .motion .settle.in video { transform: scale(1); transition: transform 1.4s var(--e-out); }

/* hairline rules draw in rather than appearing */
.motion .draw { transform: scaleX(0); transform-origin: left; }
.motion .draw.in { transform: scaleX(1); transition: transform var(--d-slow) var(--e-io); }

/* ---- 11. reduced motion: a designed static equivalent, not a blank page -- */
@media (prefers-reduced-motion: reduce) {
  .motion .rv, .motion .wipe, .motion .line-mask > span,
  .motion .settle img, .motion .settle video, .motion .draw {
    opacity: 1 !important; transform: none !important; clip-path: none !important; transition: none !important;
  }
  .hero__cue .tick { animation: none; transform: scaleY(0.6); }
  .wk__media .loop, .idx__thumb video { display: none; }   /* posters carry it */
  #lock { display: none; }
  * { scroll-behavior: auto !important; }
}

/* ---- 12. components ------------------------------------------------------ */

/* fixed header needs clearance on inner pages (the home page opens on media) */
body.proj { padding-top: 4.5rem; }

/* statement block */
.statement-sec { padding-block: clamp(4rem, 12vh, 9rem) 0; }
.statement-sec .statement { max-width: 18ch; margin-bottom: clamp(2.5rem, 6vw, 5rem); }
.intro { display: grid; gap: var(--gut); margin-bottom: clamp(3rem, 7vw, 6rem); }
@media (min-width: 900px) { .intro { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.nums { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0; border-top: 1px solid var(--ink); }
@media (min-width: 800px) { .nums { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.num { padding: 1.35rem 1.25rem 1.5rem 0; border-bottom: 1px solid var(--line); border-right: 1px solid var(--line-soft); }
.num:last-child { border-right: 0; }
.num__v { font-size: clamp(1.6rem, 3.1vw, 2.9rem); font-weight: 500; letter-spacing: -0.035em; line-height: 1; margin-bottom: 0.6rem; }

/* work section spacing */
.work .wrap + .cinema, .work .cinema + .wrap { margin-top: 0; }
.work .wk__title a:hover { text-decoration: underline; text-underline-offset: 0.18em; text-decoration-thickness: 1px; }

/* plates on project pages */
.plate { background: var(--paper-2); }
.plate > img, .plate > video { width: 100%; height: auto; }
.plate.fig { background: #fff; border: 1px solid var(--line-soft); }
.plate.fig > img { object-fit: contain; }

/* ---- the figure layer: figures that arrive, and figures that are drawn ----
   A rendered figure animates by revealing a full-opacity copy of itself left
   to right behind a hairline playhead; the frame, axes and tick labels are
   legible from the first frame at low opacity, so nothing the reader needs in
   order to orient is ever hidden. js/plots.js owns --fig-p. */
.fig--anim { position: relative; }
.fig--anim > img:first-of-type { opacity: 0.16; transition: opacity var(--d-mid) var(--e-out); }
.fig--anim.is-done > img:first-of-type { opacity: 1; }
.fig__ink {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain;
  clip-path: inset(0 calc((1 - var(--fig-p, 0)) * 100%) 0 0);
}
.fig__head {
  position: absolute; top: 0; bottom: 0; width: 1px; background: var(--ink);
  left: calc(var(--fig-p, 0) * 100%); opacity: 0.45;
}
.fig--anim.is-done .fig__head { opacity: 0; }

/* a canvas figure drawn from the project's own log */
.fig__canvas { display: block; width: 100%; aspect-ratio: 16 / 9; background: #fcfcfb; }
.fig--live > img { display: none; }
.fig__replay {
  position: absolute; right: 0.5rem; bottom: 0.5rem; z-index: 2;
  padding: 0.32rem 0.6rem; border: 1px solid var(--line); border-radius: 0;
  background: rgba(252, 252, 251, 0.92); color: var(--ink-2); cursor: pointer;
  transition: color var(--d-fast) var(--e-io), border-color var(--d-fast) var(--e-io);
}
.fig__replay:hover { color: var(--ink); border-color: var(--ink); }
.plate.fig:has(.fig__replay) { position: relative; }

/* the opening plate when a project leads on a figure or a drawing: contained
   on a white panel rather than cropped to a full-bleed band */
.cinema--fig {
  height: auto; background: #fff; overflow: visible;
  border-block: 1px solid var(--line-soft);
  padding: clamp(1.5rem, 4vw, 3.25rem) var(--rail);
  display: grid; place-items: center;
}
.cinema--fig > img, .cinema--fig > canvas {
  position: static; inset: auto; width: 100%; height: auto;
  max-width: min(1200px, 100%); max-height: 68svh; object-fit: contain;
}
.cinema--fig > canvas { aspect-ratio: 16 / 9; height: auto; }
.cinema--fig__cap { justify-self: start; max-width: min(1200px, 100%); margin-top: 0.9rem; }
.cinema--fig .fig__ink { position: absolute; inset: clamp(1.5rem, 4vw, 3.25rem) var(--rail); width: auto; height: auto; }
.cinema--fig .fig__head { left: auto; top: clamp(1.5rem, 4vw, 3.25rem); bottom: clamp(1.5rem, 4vw, 3.25rem);
  inset-inline-start: calc(var(--rail) + var(--fig-p, 0) * (100% - 2 * var(--rail))); }
.cap { display: flex; justify-content: space-between; gap: 1rem; padding-top: 0.7rem; }
.cap .credit { color: var(--ink-3); white-space: nowrap; }

/* project page head */
.phead { padding-block: clamp(2.5rem, 7vh, 5rem) clamp(1.5rem, 4vh, 3rem); display: flex; flex-direction: column; gap: 1rem; }
.phead__title { font-size: clamp(2.4rem, 7vw, 6.5rem); font-weight: 600; letter-spacing: -0.042em; line-height: 0.96; }
.phead__meta { display: flex; flex-wrap: wrap; gap: var(--gut); align-items: baseline; margin-top: 0.6rem; padding-top: 1.15rem; border-top: 1px solid var(--line); }
.phead__meta > div { display: flex; flex-direction: column; gap: 0.3rem; }
.phead__links { flex-direction: row !important; gap: 1.25rem; }
.phead__links a:hover { color: var(--ember); }
/* the way back: same anchor the card came from, so the index restores position */
.pback { align-self: start; display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.45rem 0.75rem 0.45rem 0.6rem; margin-bottom: 0.25rem; border: 1px solid var(--line); color: var(--ink-2); transition: color var(--d-fast) var(--e-io), border-color var(--d-fast) var(--e-io); }
.pback .arrow { transition: transform var(--d-fast) var(--e-out); }
.pback:hover { color: var(--ink); border-color: var(--ink); }
.pback:hover .arrow { transform: translateX(-0.2rem); }

/* project page sections */
.psec { display: grid; gap: clamp(1.5rem, 3vw, 2.5rem); padding-block: clamp(3rem, 8vh, 6.5rem); border-top: 1px solid var(--line); }
@media (min-width: 1000px) { .psec { grid-template-columns: 4fr 8fr; gap: var(--gut); } }
.psec__text { display: flex; flex-direction: column; gap: 0.9rem; }
@media (min-width: 1000px) { .psec__text { position: sticky; top: 6rem; align-self: start; } }
.psec__plates { display: grid; gap: clamp(1rem, 2vw, 1.75rem); min-width: 0; }
.psec--two .psec__plates { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.psec--three .psec__plates { grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); }
.psec--strip .psec__plates { grid-auto-flow: column; grid-auto-columns: minmax(16rem, 22rem); overflow-x: auto; scroll-snap-type: x mandatory; padding-bottom: 0.75rem; }
.psec--strip .plate { scroll-snap-align: start; }
@media (max-width: 700px) { .psec--two .psec__plates, .psec--three .psec__plates { grid-template-columns: minmax(0, 1fr); } }

/* documents + code */
.doc { display: flex; align-items: center; gap: 1rem; padding: 0.9rem 1rem; border: 1px solid var(--line); background: var(--paper-2); }
.doc img { width: 3rem; height: auto; }
.doc:hover { border-color: var(--ink); }
.doc__txt { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
.doc__t { font-size: var(--t-small); line-height: 1.3; }
.doc__n { margin-left: auto; white-space: nowrap; }
.codecard { display: flex; justify-content: space-between; gap: 1rem; padding: 0.9rem 1rem; border: 1px solid var(--line); }
.codecard a:hover { color: var(--ember); }

/* related */
.rels { display: flex; flex-wrap: wrap; gap: 0.75rem; padding-bottom: var(--stack); }
.rel { padding: 0.6rem 0.9rem; border: 1px solid var(--line); }
.rel:hover { border-color: var(--ink); }

/* footer copy */
.ftr__grid p { margin-top: 0.5rem; font-size: var(--t-small); color: var(--ink-2); }
.ftr__grid p.label { color: var(--ink-3); }

/* the hero furniture retires once the burn owns the frame, so it can never
   collide with the fixed header as the sticky stage releases */
.hero__furniture { transition: color var(--d-mid) var(--e-io), opacity var(--d-mid) var(--e-io); }
.hero.spent .hero__furniture { opacity: 0; pointer-events: none; }

/* anchored sections must clear the fixed header */
main > section[id], .psec { scroll-margin-top: 5.25rem; }

/* project head: the links are their own line, not baseline-jammed against role */
.phead__meta { flex-direction: column; align-items: stretch; gap: 1rem; }
@media (min-width: 700px) {
  .phead__meta { flex-direction: row; align-items: baseline; gap: var(--gut); }
  .phead__links { margin-left: auto; }
}

/* ---- 13. the run: every project, in order -------------------------------- */
.run { margin-block: var(--stack) 0; }

.grp { padding-block: clamp(3rem, 8vh, 7rem); }
.grp__blurb { max-width: 62ch; margin-bottom: clamp(2rem, 5vw, 4rem); }
.grp__blurb p { font-size: clamp(1.05rem, 1.35vw, 1.3rem); line-height: 1.55; }
.grp__cards { display: flex; flex-direction: column; gap: clamp(3.5rem, 9vw, 8rem); }

/* one project */
.pj { display: grid; gap: clamp(1rem, 2.4vw, 2.25rem); align-items: start; }
@media (min-width: 900px) {
  .pj { grid-template-columns: 7fr 5fr; }
  .pj.flip .pj__media { order: 2; }
  .pj.flip .pj__text  { order: 1; }
}
.pj__media {
  position: relative;
  display: block;
  overflow: clip;
  background: var(--paper-2);
  aspect-ratio: 16 / 9;
}
.pj__media img, .pj__media video { width: 100%; height: 100%; object-fit: cover; }
/* a figure cover is a document, not a photograph: contain it on white */
.pj__media--fig, .grp--invert .pj__media--fig, .grp--panel .pj__media--fig { background: #fff; }
.pj__media--fig img { object-fit: contain; padding: 0.55rem; }
.pj__still { position: absolute; inset: 0; z-index: 1; }
.pj__media video.auto { position: absolute; inset: 0; z-index: 2; opacity: 0; transition: opacity 0.55s var(--e-io); }
.pj__media video.auto.playing { opacity: 1; }
.pj__text { display: flex; flex-direction: column; gap: 1rem; padding-top: 0.35rem; }
.pj__title { font-size: var(--t-h2); font-weight: 500; letter-spacing: -0.03em; line-height: 1; }
.pj__title a:hover { text-decoration: underline; text-underline-offset: 0.18em; text-decoration-thickness: 1px; }
.pj__title .code { font-family: var(--mono); font-size: 0.42em; letter-spacing: 0.06em; color: var(--ink-3); vertical-align: 0.55em; margin-left: 0.75rem; text-transform: uppercase; }
.pj__one { color: var(--ink-2); max-width: 48ch; }
.pj__go { margin-top: 0.35rem; display: inline-flex; gap: 0.5rem; align-items: center; }
.pj__go:hover { color: var(--ember); }
.pj__go .arrow { transition: transform var(--d-fast) var(--e-snap); }
.pj__go:hover .arrow { transform: translateX(0.35rem); }

/* the wide treatment: media runs the full column, copy sits under it */
@media (min-width: 900px) {
  .pj--big { grid-template-columns: minmax(0, 1fr); }
  .pj--big .pj__media { aspect-ratio: 21 / 9; order: 0 !important; }
  .pj--big .pj__text {
    display: grid;
    grid-template-columns: 14rem minmax(0, 1fr) minmax(0, 1.1fr);
    gap: var(--gut);
    align-items: start;
    padding-top: 1.1rem;
    border-top: 1px solid var(--line);
    order: 0 !important;
  }
  .pj--big .pj__title { grid-column: 2; grid-row: 1 / span 3; }
  .pj--big .pj__one { grid-column: 3; grid-row: 1; }
  .pj--big .specs { grid-column: 3; grid-row: 2; border-top: 0; }
  .pj--big .pj__go { grid-column: 1; grid-row: 2; margin-top: 0; }
}

/* ---- the highlighted group: ink ground, media in full colour ------------- */
.grp--invert {
  background: var(--ink);
  color: var(--paper);
  padding-block: clamp(4rem, 11vh, 9rem);
  margin-block: clamp(2rem, 5vh, 4rem);
}
.grp--invert .sec-head { border-top-color: var(--paper); }
.grp--invert .label { color: rgba(251, 250, 248, 0.62); }
.grp--invert .sec-title { color: var(--paper); }
.grp--invert .grp__blurb p { color: rgba(251, 250, 248, 0.82); max-width: 68ch; }
.grp--invert .pj__media { background: #24241f; }
.grp--invert .pj__one { color: rgba(251, 250, 248, 0.72); }
.grp--invert .pj__text { border-top-color: rgba(251, 250, 248, 0.22); }
.grp--invert .specs { border-top-color: rgba(251, 250, 248, 0.22); }
.grp--invert .specs div { border-right-color: rgba(251, 250, 248, 0.12); }
.grp--invert .specs .v { color: var(--paper); }
.grp--invert .pj__go:hover, .grp--invert .pj__title a:hover { color: var(--paper); }

/* reduced motion: the posters carry the run, nothing plays */
@media (prefers-reduced-motion: reduce) {
  .pj__media video.auto { display: none; }   /* the poster layer carries the run */
}

/* ---- 14. the three tracks ------------------------------------------------ */
.hdr__logo { width: auto; height: 0.95rem; flex: none; }

.pillars-sec { padding-block: clamp(3rem, 8vh, 6rem) 0; }
.pillars { display: grid; gap: 0; border-top: 1px solid var(--ink); }
@media (min-width: 860px) { .pillars { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.pil {
  display: flex; flex-direction: column; gap: 0.75rem;
  padding: 1.5rem 1.75rem 1.75rem 0;
  border-bottom: 1px solid var(--line);
  border-right: 1px solid var(--line-soft);
  transition: background var(--d-fast) var(--e-io);
}
.pil:last-child { border-right: 0; }
@media (min-width: 860px) { .pil { padding-right: var(--gut); } }
.pil:hover { background: var(--paper-2); }
.pil__t { font-size: clamp(1.35rem, 2.1vw, 1.9rem); font-weight: 500; letter-spacing: -0.03em; line-height: 1.05; }
.pil__l { color: var(--ink-2); font-size: var(--t-small); line-height: 1.55; flex: 1; }
.pil__go { display: inline-flex; gap: 0.5rem; align-items: center; }
.pil:hover .pil__go { color: var(--ember); }
.pil .arrow { transition: transform var(--d-fast) var(--e-snap); }
.pil:hover .arrow { transform: translateX(0.35rem); }

/* ---- the second highlight treatment: a recessed panel, not a second slab -- */
.grp--panel {
  background: var(--paper-2);
  padding-block: clamp(3.5rem, 9vh, 7.5rem);
  margin-block: clamp(2rem, 5vh, 4rem);
  border-block: 1px solid var(--ink);
}
.grp--panel .sec-head { border-top: 0; padding-top: 0; }
.grp--panel .pj__media { background: var(--paper-3); }

/* ---- 15. the teaser reel ------------------------------------------------- */
.swarm {
  position: relative;
  height: 230svh;                 /* 100svh stage + travel for the fly-out */
  background: var(--ink);
  overflow: clip;
}
.swarm.static { height: 100svh; }
.swarm__stage { position: sticky; top: 0; height: 100svh; overflow: clip; }
.swarm__field { position: absolute; inset: 0; z-index: 1; }

/* tiles fly under the chrome, so the chrome carries its own separation */
.swarm__stage::before, .swarm__stage::after {
  content: ""; position: absolute; left: 0; right: 0; z-index: 2;
  pointer-events: none;
}
.swarm__stage::before { top: 0; height: 9rem; background: linear-gradient(to bottom, rgba(26,26,24,0.92), transparent); }
.swarm__stage::after  { bottom: 0; height: 11rem; background: linear-gradient(to top, rgba(26,26,24,0.92), transparent); }

.swarm__tile {
  position: absolute;
  width: clamp(11rem, 22vw, 27rem);
  aspect-ratio: 16 / 9;
  margin: 0;
  overflow: clip;
  background: #1f1f1b;
  will-change: transform, opacity;
  transform: translate(-50%, -50%);
  box-shadow: 0 1.5rem 3rem rgba(0, 0, 0, 0.45);
}
.swarm__tile img, .swarm__tile video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}
.swarm__tile video { opacity: 0; transition: opacity 0.4s var(--e-io); }
.swarm__tile video.playing { opacity: 1; }

.swarm__label {
  z-index: 3;
  position: absolute;
  left: var(--rail); right: var(--rail); bottom: var(--rail);
  z-index: 3;
  display: flex; justify-content: space-between; gap: var(--gut);
  color: var(--paper);
}
.swarm__label .label { color: rgba(251, 250, 248, 0.62); }

@media (max-width: 700px) {
  .swarm { height: 200svh; }
  .swarm__tile { width: clamp(8rem, 42vw, 14rem); box-shadow: 0 0.75rem 1.5rem rgba(0,0,0,0.5); }
}

@media (prefers-reduced-motion: reduce) {
  .swarm__tile video { display: none; }
  .swarm__tile { box-shadow: none; }
}

/* the header runs out of room at phone widths: the logo carries the identity
   and the name is two lines away in the hero anyway */
.hdr__mark span { white-space: nowrap; }
@media (max-width: 460px) {
  .hdr__mark span { display: none; }
  .hdr__logo { height: 1.1rem; }
  .hdr__nav { gap: 1rem; }
}

/* the particle field that assembles the squares */
.swarm__particles {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  z-index: 1;
  pointer-events: none;
}
.swarm__field { z-index: 2; }
.swarm__stage::before, .swarm__stage::after { z-index: 3; }
.swarm__label { z-index: 4; }
/* with the field running, tiles resolve in place rather than flying */
.swarm.has-field .swarm__tile { transition: none; }
@media (prefers-reduced-motion: reduce) { .swarm__particles { display: none; } }

/* ---- 16. scroll effects -------------------------------------------------- */
/* the picture drifts inside a frame that does not move */
.pj__media img, .pj__media video,
.plate > img, .plate > video {
  transform: translate3d(0, var(--py, 0%), 0) scale(1.09);
  will-change: transform;
}
.pj__media, .plate { --py: 0%; }

/* the section head carries a rule that fills as you move through the section */
.grp { --gp: 0; }
.grp .sec-head { position: relative; }
.grp .sec-head::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: -1px; height: 1px;
  background: var(--ink);
  transform: scaleX(var(--gp)); transform-origin: left;
}
.grp--invert .sec-head::after { background: var(--paper); }

/* each project's text arrives in order rather than all at once */
.motion .pj__text > * { opacity: 0; transform: translateY(0.7rem); }
.motion .pj.in .pj__text > * { opacity: 1; transform: none;
  transition: opacity 0.55s var(--e-out), transform 0.55s var(--e-out); }
.motion .pj.in .pj__text > :nth-child(1) { transition-delay: 0.02s; }
.motion .pj.in .pj__text > :nth-child(2) { transition-delay: 0.09s; }
.motion .pj.in .pj__text > :nth-child(3) { transition-delay: 0.16s; }
.motion .pj.in .pj__text > :nth-child(4) { transition-delay: 0.23s; }
.motion .pj.in .pj__text > :nth-child(5) { transition-delay: 0.30s; }

@media (prefers-reduced-motion: reduce) {
  .pj__media img, .pj__media video, .plate > img, .plate > video { transform: none; }
  .grp .sec-head::after { transform: scaleX(1); }
  .motion .pj__text > * { opacity: 1 !important; transform: none !important; }
}

/* ---- 17. the filterable index -------------------------------------------- */
.fi-sec { padding-block: var(--stack) 0; }
.chips { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: clamp(1.5rem, 3vw, 2.5rem); }
.chip {
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.5rem 0.9rem;
  border: 1px solid var(--line);
  font-family: var(--mono); font-size: var(--t-label);
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-2);
  transition: border-color var(--d-fast) var(--e-io), color var(--d-fast) var(--e-io), background var(--d-fast) var(--e-io);
}
.chip:hover { border-color: var(--ink); color: var(--ink); }
.chip.is-on { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.chip__n { color: var(--ink-3); font-size: 0.9em; }
.chip.is-on .chip__n { color: rgba(251, 250, 248, 0.6); }

.fi { border-top: 1px solid var(--ink); }
.fi__row {
  display: grid;
  grid-template-columns: 2.75rem minmax(0, 1fr);
  gap: 0.35rem 1rem;
  align-items: center;
  padding: 0.9rem 0;
  border-bottom: 1px solid var(--line);
  transition: background var(--d-fast) var(--e-io);
}
@media (min-width: 900px) {
  .fi__row { grid-template-columns: 2.75rem minmax(0, 1.4fr) minmax(0, 1fr) 8rem 7rem; }
}
.fi__row:hover { background: var(--paper-2); }
.fi__row[hidden] { display: none; }
.fi__t { font-size: clamp(1rem, 1.3vw, 1.2rem); font-weight: 500; letter-spacing: -0.02em; }
.fi__t .code { font-family: var(--mono); font-size: 0.68em; color: var(--ink-3); margin-left: 0.6rem; }
.fi__topics { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.tag {
  font-family: var(--mono); font-size: 0.6875rem; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--ink-3);
  border: 1px solid var(--line-soft); padding: 0.15rem 0.45rem;
}
.fi__s, .fi__y { display: none; }
@media (min-width: 900px) { .fi__s { display: block; text-align: right; } .fi__y { display: block; text-align: right; } }
.fi__row .arrow { display: inline-block; transition: transform var(--d-fast) var(--e-snap); }
.fi__row:hover .arrow { transform: translateX(0.35rem); }
