/* ═══════════════════════════════════════════════════════════════════════════
   THOMAS LEVENE — risograph separation
   Plates: Fluorescent Pink · Federal Blue · Yellow · Key
   ═══════════════════════════════════════════════════════════════════════════ */

:root{
  /* ── Inks ───────────────────────────────────────────────────────────── */
  --stock:        #DEDDD6;
  --stock-hi:     #EFEEE8;
  --ink-pink:     #FF48B0;
  --ink-blue:     #2A2DC4;
  --ink-yellow:   #FFE800;
  --ink-key:      #16161A;

  /* Multiply results, documented — never author these as new colours */
  --overprint-a:  #2A0D87;   /* pink × blue  */
  --overprint-b:  #FF4100;   /* pink × yellow */

  /* ── Secondary text ─────────────────────────────────────────────────
     Tinted from each ground, never gray-by-opacity. Measured ratios:  */
  --on-stock:      #16161A;  /* 14.8:1 on stock */
  --on-stock-soft: #4B4B54;  /*  6.1:1 on stock */
  --on-key:        #DEDDD6;  /* 14.8:1 on key   */
  --on-key-soft:   #A9A8A2;  /*  7.6:1 on key   */
  --on-blue:       #EFEEE8;  /*  8.1:1 on blue  */
  --on-blue-soft:  #C9CAF0;  /*  6.0:1 on blue  */
  --on-pink:       #16161A;  /*  6.4:1 on pink  */
  --on-pink-soft:  #3D0E28;  /*  5.2:1 on pink  */

  /* ── Type ───────────────────────────────────────────────────────────── */
  --display: "Bricolage Grotesque", ui-sans-serif, system-ui, sans-serif;
  --text:    "Archivo", ui-sans-serif, system-ui, sans-serif;
  --mono:    "Martian Mono", ui-monospace, "SFMono-Regular", monospace;

  /* ── Registration ───────────────────────────────────────────────────── */
  --reg-x: 0px;          /* live misregistration, driven by pointer   */
  --reg-y: 0px;

  /* Resting plate offset on set type.
     A real press misregisters by a physical distance, so the offset used to
     be a flat 3–4px at every size. That is faithful and it is also why small
     type died: at 90px a 3px fringe is 3% of the cap height and reads as
     ink, at 21px it is 14% and reads as a second, wrong word. These clamps
     make the offset proportional with a floor and a ceiling, so display type
     keeps exactly the fringe it has now and small type gets a fringe scaled
     to it. Set in em: on a registered <length> property em resolves against
     the element's own font-size, which is precisely the quantity at issue. */
  --plate-o:  clamp(1.2px, 0.045em, 3px);   /* [data-plate] headings     */
  --plate-oj: clamp(1.6px, 0.075em, 4px);   /* the work index, set large */
  --plate-y:  0.55;                          /* vertical share of offset  */

  --gut: clamp(20px, 5vw, 72px);
  --ease: cubic-bezier(.16, 1, .3, 1);
}

/* ═══════════════════════════ RESET ═══════════════════════════ */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; padding: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* full-viewport fixed layers (.sheet, .grain) span the scrollbar gutter and
     would otherwise leave a few px of horizontal scroll. `clip`, not
     `hidden`: no scroll container is created. */
  overflow-x: clip;
}
@media (prefers-reduced-motion: reduce){ html { scroll-behavior: auto; } }

body{
  background: var(--stock);
  color: var(--ink-key);
  font-family: var(--text);
  font-size: clamp(16px, 0.5vw + 14px, 18px);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  /* `clip`, for the same reason html uses it, and for one more: `hidden` on
     one axis forces the other to compute to `auto`, which makes body a scroll
     container. A view-timeline binds to its subject's nearest scrollport, so
     the showcase's timeline attached to a body that never scrolls and sat
     frozen at one position. `clip` creates no scrollport. */
  overflow-x: clip;
}

img, canvas, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
h1, h2, h3 { font-weight: 700; line-height: 0.9; letter-spacing: -0.03em; }
ul, ol { list-style: none; }

::selection { background: var(--ink-pink); color: var(--ink-key); }

/* Pulling the sheet is a gesture on the press, not a text selection. The
   class is only set once a pointer has actually travelled a few pixels with
   the button down, so click, double-click and triple-click still select —
   it is drag-to-select, and only that, which gives way to the registration. */
html.is-dragging, html.is-dragging *{
  -webkit-user-select: none;
          user-select: none;
}

:focus-visible{
  outline: 2px solid var(--ink-pink);
  outline-offset: 4px;
}

.wrap{
  max-width: 1560px;
  margin-inline: auto;
  padding-inline: var(--gut);
}

/* Mono is for measurement — plate names, densities, angles, time.
   Sized to be read, not to be decorative. */
.mono{
  font-family: var(--mono);
  font-size: clamp(11px, 0.2vw + 10px, 12.5px);
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  line-height: 1.6;
}

/* ═══════════════════════════ SHEET FURNITURE ═══════════════════════════ */
.sheet{ position: fixed; inset: 0; pointer-events: none; z-index: 90; }

.trim{
  position: absolute;
  width: 26px; height: 26px;
  opacity: .55;
}
.trim::before, .trim::after{
  content: ""; position: absolute; background: var(--ink-key);
}
.trim::before { width: 100%; height: 1px; top: 50%; }
.trim::after  { height: 100%; width: 1px; left: 50%; }

.trim--tl { top: 16px;    left: 16px; }
.trim--tr { top: 16px;    right: 16px; }
.trim--bl { bottom: 16px; left: 16px; }
.trim--br { bottom: 16px; right: 16px; }

/* Paper tooth */
.grain{
  position: fixed; inset: -50%;
  z-index: 95;
  pointer-events: none;
  opacity: .34;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ═══════════════════════════ NAV ═══════════════════════════ */
.nav{
  position: fixed; top: 0; left: 0; right: 0;
  z-index: 80;
  display: flex; align-items: center; justify-content: space-between;
  gap: 24px;
  padding: 22px var(--gut);
  transition: transform .5s var(--ease), color .4s var(--ease),
              background-color .4s var(--ease), border-color .4s var(--ease);
  border-bottom: 1px solid transparent;
}

/* Past the hero the nav lands on its own paper strip */
.nav[data-stuck="true"]{
  background: var(--stock);
  border-bottom-color: rgba(22, 22, 26, .18);
}

/* ── Reading the bar over the live field ─────────────────────────────────
   Over the hero the bar is deliberately transparent, and the field behind it
   inks up under the pointer — so reaching for the nav is the one gesture
   guaranteed to thicken the dots directly behind these words. The field is
   the best thing on the page and stays exactly as it is. The words get a
   knockout instead: a tight halo in the ground colour, which is what a press
   does when type has to sit on a dense tint.
   ───────────────────────────────────────────────────────────────────────── */
.nav{ --halo: var(--stock); }
.nav[data-invert="true"]{ --halo: var(--ink-key); }

.nav:not([data-stuck="true"]) .nav__name,
.nav:not([data-stuck="true"]) .nav__links a,
.nav:not([data-stuck="true"]) .nav__toggle{
  text-shadow:
    /* solid ring first — this is the knockout, and it has to be opaque */
     2px  0   0 var(--halo), -2px  0   0 var(--halo),
     0    2px 0 var(--halo),  0   -2px 0 var(--halo),
     1.5px  1.5px 0 var(--halo), -1.5px  1.5px 0 var(--halo),
     1.5px -1.5px 0 var(--halo), -1.5px -1.5px 0 var(--halo),
    /* then a soft falloff so the patch has an edge rather than a cut */
     0 0 4px var(--halo), 0 0 8px var(--halo), 0 0 8px var(--halo);
}

/* Over the key flood the nav reverses out */
.nav[data-invert="true"]{ color: var(--stock); }
.nav[data-invert="true"][data-stuck="true"]{
  background: var(--ink-key);
  border-bottom-color: rgba(222, 221, 214, .22);
}
/* On the key flood the solid chip flips, or it would be key on key */
.nav[data-invert="true"] .nav__cta{
  background: var(--stock);
  border-color: var(--stock);
  color: var(--ink-key);
}
.nav[data-invert="true"] .nav__cta:hover{
  background: var(--ink-yellow); border-color: var(--ink-yellow); color: var(--ink-key);
}
.nav[data-invert="true"] .nav__cta-dot{ background: var(--ink-pink); }
/* multiply over the key flood turns both dots black — they need normal blend */
.nav[data-invert="true"] .nav__dots i,
.nav[data-invert="true"] .nav__toggle-dots i{ mix-blend-mode: normal; }

.nav__mark{ display: flex; align-items: center; gap: 10px; }

/* ── The mark ─────────────────────────────────────────────────────────────
   Taken from Brandmark.svg rather than approximated. There the two circles
   are radius r with the blue offset (0.5r, -0.21r) from the pink — half a
   radius across and a fifth up, so they overlap by most of their area and
   sit on a diagonal. The old pair was the same idea drawn by eye: level with
   each other and barely touching, which read as two dots rather than as the
   mark. Ratios below are the file's, divided through by the diameter.
   ───────────────────────────────────────────────────────────────────────── */
.nav__dots{
  --d: 16px;                             /* circle diameter */
  position: relative;
  width: calc(var(--d) * 1.25);          /* 303.85 / 243.08 */
  height: calc(var(--d) * 1.105);        /* 268.67 / 243.08 */
  flex: none;
}
.nav__dots i{
  position: absolute;
  width: var(--d); height: var(--d);
  border-radius: 50%;
  mix-blend-mode: multiply;
  transition: transform .55s var(--ease);
}
.nav__dots i:first-child{ left: 0; top: calc(var(--d) * .105); background: var(--ink-pink); }
.nav__dots i:last-child { left: calc(var(--d) * .25); top: 0; background: var(--ink-blue); }
/* Still separates on hover, but along the diagonal the mark is built on
   rather than straight sideways. */
.nav__mark:hover .nav__dots i:first-child{ transform: translate(-3px, 2px); }
.nav__mark:hover .nav__dots i:last-child { transform: translate(3px, -2px); }

/* The wordmark in the supplied lockup is the display face, not the text
   face. No plate offset on it here: at 15px that is the thing that makes
   small type unreadable, and this sits over a live dot field. */
.nav__name{
  font-family: var(--display);
  font-weight: 800;
  font-variation-settings: "wdth" 94, "opsz" 24;
  font-size: 15px;
  letter-spacing: -0.025em;
}

.nav__links{ display: flex; gap: 28px; font-size: 14px; }
.nav__links a{ position: relative; padding-block: 2px; }
.nav__links a::after{
  content: ""; position: absolute; left: 0; bottom: 0;
  width: 100%; height: 2px; background: var(--ink-pink);
  transform: scaleX(0); transform-origin: left;
  transition: transform .4s var(--ease);
}
.nav__links a:hover::after{ transform: scaleX(1); }

/* Solid, not outlined: the primary action sits over a live dot field and an
   outline alone disappears into it. */
.nav__cta{
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 14px; font-weight: 600;
  padding: 10px 18px;
  background: var(--ink-key);
  color: var(--stock);
  border: 1.5px solid var(--ink-key);
  border-radius: 999px;
  transition: background .35s var(--ease), color .35s var(--ease),
              transform .35s var(--ease);
}
.nav__cta:hover{ background: var(--ink-pink); border-color: var(--ink-pink); color: var(--ink-key); transform: translateY(-1px); }
.nav__cta-dot{
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--ink-pink);
  animation: throb 2.4s var(--ease) infinite;
}
.nav__cta:hover{ background: var(--ink-key); color: var(--stock); }
.nav__cta:hover .nav__cta-dot{ background: var(--ink-yellow); }

@keyframes throb{
  0%, 100% { transform: scale(1);   opacity: 1; }
  50%      { transform: scale(.55); opacity: .5; }
}

/* ═══════════════════════════ 01 · HERO ═══════════════════════════ */
.hero{
  position: relative;
  min-height: 100svh;
  display: grid;
  grid-template-rows: auto 1fr auto;
  padding: 104px var(--gut) 34px;
  overflow: hidden;
  isolation: auto;
}

.hero__field{
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  z-index: 1;
  mix-blend-mode: multiply;
}

.hero__inner{
  position: relative;          /* no z-index: the canvas must sit between
                                  the ink field and the type */
  grid-row: 1 / -1;
  display: grid;
  grid-template-rows: auto 1fr;
  gap: 24px;
}

/* The blue is a real box in the layout, so the reversed wordmark can never
   land on bare paper — no JS, no percentages, nothing to fall out of sync. */
.hero__ink{
  position: relative;
  align-self: end;
  display: flex;
  flex-direction: column;
  gap: clamp(26px, 4vw, 52px);
  margin-inline: calc(var(--gut) * -1);
  margin-bottom: -34px;
  padding: clamp(20px, 2.4vw, 34px) var(--gut) 34px;
}
.hero__ink::before{
  content: "";
  position: absolute; inset: 0;
  background: var(--ink-blue);
  z-index: 0;
}

/* Type paints above the halftone canvas and stays crisp */
.plates, .wordmark, .hero__foot{ position: relative; z-index: 2; }

/* Plate manifest */
.plates{
  display: flex; flex-wrap: wrap; gap: 8px 26px;
  font-family: var(--mono);
  font-size: clamp(10.5px, 0.2vw + 9.5px, 12px);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--on-stock);
}
.plates span{ display: inline-flex; align-items: center; gap: 7px; }
.plates b{
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--patch);
  mix-blend-mode: multiply;
}

/* ── The wordmark: three plates, out of register ──────────────────────── */
.wordmark{
  font-family: var(--display);
  font-weight: 800;
  font-variation-settings: "wdth" 96, "opsz" 96;
  text-transform: none;
  line-height: 0.82;
  letter-spacing: -0.035em;
  user-select: none;
}

.wordmark__row{
  position: relative;
  display: block;
  font-size: clamp(3.2rem, 12.5vw, 10rem);
}
.wordmark__row--lg{
  font-size: clamp(4.6rem, 23vw, 19.5rem);
  letter-spacing: -0.045em;
  margin-top: -0.04em;
}

.plate{ display: block; white-space: nowrap; }

.plate--pink, .plate--blue{
  position: absolute; inset: 0;
  mix-blend-mode: multiply;
  user-select: none;
  color: transparent;
  -webkit-background-clip: text;
  background-clip: text;
  will-change: transform;
}

/* Halftone screens — different pitch per plate reads as a different angle */
.plate--pink{
  background-image:
    radial-gradient(var(--ink-pink) 41%, transparent 43%),
    radial-gradient(var(--ink-pink) 41%, transparent 43%);
  background-size: 7px 7px;
  background-position: 0 0, 3.5px 3.5px;
  transform: translate3d(calc(var(--reg-x) * -1), calc(var(--reg-y) * -1), 0);
}
.plate--blue{
  background-image:
    radial-gradient(var(--ink-blue) 44%, transparent 46%),
    radial-gradient(var(--ink-blue) 44%, transparent 46%);
  background-size: 9px 9px;
  background-position: 0 0, 4.5px 4.5px;
  transform: translate3d(var(--reg-x), var(--reg-y), 0);
}

/* Key plate carries the readable text: solid, opaque, never blended.
   The whole wordmark sits on the flood, so it reverses out of the ink. */
.plate--key{
  position: relative;
  color: var(--stock);
}

/* Vertical density bar */
.hero__bar{
  position: absolute; left: 0; top: 30%;
  z-index: 2;
  display: flex; flex-direction: column;
}
.hero__bar i{
  width: clamp(10px, 1.4vw, 18px);
  height: clamp(22px, 3.4vw, 44px);
  background: var(--patch);
  mix-blend-mode: multiply;
}

/* Registration targets */
.reg{
  position: absolute; z-index: 2;
  width: 34px; height: 34px;
  fill: none; stroke: var(--ink-key); stroke-width: 1.25;
  opacity: .5;
  animation: spin 26s linear infinite;
}
.reg--a { top: 92px; right: var(--gut); }
/* Sits in the gap between the role copy and the trim block */
.reg--b { bottom: 44px; right: 46%; stroke: var(--stock); opacity: .6; }

@keyframes spin{ to { transform: rotate(360deg); } }

.hero__foot{
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 40px; flex-wrap: wrap;
}

.hero__role{
  max-width: 36ch;
  font-size: clamp(16px, 1vw + 11px, 21px);
  line-height: 1.4;
  color: var(--on-blue);
  text-wrap: balance;
}

.hero__cue{
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--mono);
  font-size: clamp(10.5px, 0.2vw + 9.5px, 12px);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--on-blue);
  padding: 10px 16px 10px 12px;
  border: 1px solid rgba(222, 221, 214, .5);
  border-radius: 999px;
}
.hero__cue-icon{
  position: relative; width: 15px; height: 15px; flex: none;
}
.hero__cue-icon::before, .hero__cue-icon::after{
  content: ""; position: absolute; top: 2px;
  width: 11px; height: 11px; border-radius: 50%;
}
.hero__cue-icon::before{ left: 0; background: var(--ink-pink); }
.hero__cue-icon::after { left: 4px; background: var(--ink-yellow); mix-blend-mode: multiply; }

.hero__foot-right{
  display: flex; flex-direction: column; align-items: flex-end; gap: 14px;
}

.hero__meta{
  display: flex; gap: 18px;
  font-family: var(--mono);
  font-size: clamp(10.5px, 0.2vw + 9.5px, 12px);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--on-blue-soft);
}

/* ═══════════════════════════ COLOUR BAR ═══════════════════════════ */
.colourbar{
  background: var(--ink-key);
  color: var(--stock);
  padding: 15px 0;
  overflow: hidden;
}
.colourbar__track{ display: flex; width: max-content; }
.colourbar__group{
  display: flex; align-items: center; gap: 20px;
  padding-right: 20px;
  font-family: var(--mono);
  font-size: clamp(11px, 0.2vw + 10px, 12.5px);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--on-key);
}
.colourbar__group em{
  width: 30px; height: 13px; flex: none;
  background: var(--patch);
  font-style: normal;
}

/* ═══════════════════════════ STATEMENT ═══════════════════════════ */
.statement{ padding: clamp(90px, 13vw, 190px) 0 clamp(60px, 8vw, 110px); }

.statement__copy{
  font-family: var(--display);
  font-weight: 600;
  font-variation-settings: "wdth" 92, "opsz" 40;
  font-size: clamp(1.6rem, 4.4vw, 4rem);
  line-height: 1.06;
  letter-spacing: -0.03em;
  max-width: 19ch;
  text-wrap: pretty;
}
.statement__copy mark{
  background: var(--ink-pink);
  color: var(--ink-key);
  mix-blend-mode: multiply;
  padding: 0 .06em;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

/* word-by-word ink-down */
[data-reveal] .wd{
  display: inline-block;
  opacity: .16;
  transition: opacity .5s var(--ease);
}
[data-reveal] .wd.on{ opacity: 1; }

/* ═══════════════════════════ SECTION HEADS ═══════════════════════════ */
.sec-head{
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 24px; flex-wrap: wrap;
  padding-bottom: 26px;
  border-bottom: 1px solid rgba(22, 22, 26, .22);
  margin-bottom: 8px;
}
.sec-head h2{
  font-family: var(--display);
  font-variation-settings: "wdth" 94, "opsz" 48;
  font-size: clamp(1.5rem, 2.6vw, 2.6rem);
}
.sec-head .mono{ color: var(--on-stock-soft); }

/* ═══════════════════════════ WORK ═══════════════════════════ */
.work{ padding-bottom: clamp(80px, 11vw, 150px); }

.index__row{ border-bottom: 1px solid rgba(22, 22, 26, .22); }

.index__row a{
  --o: 0px;
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr) auto;
  align-items: center;
  gap: 20px;
  padding: clamp(20px, 2.6vw, 38px) 0;
}

.index__name{
  font-family: var(--display);
  font-weight: 700;
  font-variation-settings: "wdth" 96, "opsz" 72;
  font-size: clamp(2rem, 5.6vw, 5rem);
  line-height: 0.92;
  letter-spacing: -0.035em;
  /* Misregistration on hover — pink and blue fringe either side of key */
  text-shadow:
    calc(var(--o) * -1) 0 0 var(--ink-pink),
    var(--o) 0 0 var(--ink-blue);
  transition: text-shadow .55s var(--ease), transform .55s var(--ease);
}
.index__row a:hover .index__name,
.index__row a:focus-visible .index__name{ --o: 5px; }

.index__cat{
  font-size: clamp(14px, 0.7vw + 10px, 17px);
  color: var(--on-stock-soft);
  transition: transform .55s var(--ease);
}

/* The row leans in — transform, so nothing reflows */
.index__row a:hover .index__name,
.index__row a:focus-visible .index__name,
.index__row a:hover .index__cat,
.index__row a:focus-visible .index__cat{
  transform: translateX(clamp(10px, 1.6vw, 30px));
}
.index__year{
  font-family: var(--mono); font-size: 12px;
  letter-spacing: .14em; color: var(--on-stock-soft);
}

/* Floating separation proof */
.proof{
  position: fixed; top: 0; left: 0;
  z-index: 85;
  width: clamp(220px, 21vw, 320px);
  aspect-ratio: 520 / 660;
  pointer-events: none;
  opacity: 0;
  /* A proof is ink on paper, not ink in mid-air */
  background: var(--stock-hi);
  box-shadow: 10px 14px 34px rgba(22, 22, 26, .22);
  transition: opacity .4s var(--ease);
  will-change: transform;
}
.proof[data-on="true"]{ opacity: 1; }
.proof canvas{
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  mix-blend-mode: multiply;
  will-change: transform;
}

/* ═══════════════════════════ CAPABILITIES ═══════════════════════════ */
.caps{ padding-bottom: clamp(90px, 12vw, 170px); }

.spec li{
  display: grid;
  grid-template-columns: 26px minmax(0, 20ch) minmax(0, 1fr);
  gap: 20px clamp(20px, 3vw, 56px);
  align-items: start;
  padding: clamp(22px, 2.4vw, 34px) 0;
  border-bottom: 1px solid rgba(22, 22, 26, .16);
}
.spec b{
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--patch);
  mix-blend-mode: multiply;
  margin-top: .35em;
}
.spec h3{
  font-family: var(--display);
  font-variation-settings: "wdth" 94, "opsz" 32;
  font-size: clamp(1.15rem, 1.9vw, 1.75rem);
  line-height: 1;
}
.spec p{ max-width: 62ch; color: var(--on-stock-soft); }

/* ═══════════════════════════ APPROACH · PINK FLOOD ═══════════════════════════ */
.approach{
  background: var(--ink-pink);
  color: var(--ink-key);
  padding: clamp(80px, 11vw, 150px) 0 clamp(80px, 11vw, 150px);
}

.plate-label{
  display: inline-flex; align-items: center; gap: 9px;
  font-family: var(--mono);
  font-size: clamp(10.5px, 0.2vw + 9.5px, 12px);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin-bottom: clamp(40px, 6vw, 80px);
}
.plate-label b{
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--patch);
  border: 1px solid rgba(22,22,26,.35);
}

.approach__lead{
  font-family: var(--display);
  font-weight: 800;
  font-variation-settings: "wdth" 90, "opsz" 72;
  font-size: clamp(2rem, 6.2vw, 5.6rem);
  line-height: 0.94;
  letter-spacing: -0.04em;
  max-width: 16ch;
  text-wrap: balance;
  margin-bottom: clamp(56px, 8vw, 110px);
}

.principles{
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: clamp(32px, 4vw, 64px);
  border-top: 1.5px solid var(--ink-key);
  padding-top: 32px;
}
.principles li > span{
  display: block;
  font-family: var(--mono);
  font-size: 12.5px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--on-pink-soft);
  margin-bottom: 18px;
}
.principles p{ color: var(--on-pink-soft); }
.principles h3{
  font-family: var(--display);
  font-variation-settings: "wdth" 94, "opsz" 32;
  font-size: clamp(1.2rem, 2vw, 1.7rem);
  margin-bottom: 12px;
}
.principles p{ max-width: 42ch; }

/* ═══════════════════════════ CONTACT · KEY FLOOD ═══════════════════════════ */
.contact{
  background: var(--ink-key);
  color: var(--stock);
  padding-top: clamp(80px, 11vw, 150px);
}

.cta{
  display: block;
  position: relative;
  font-family: var(--display);
  font-weight: 800;
  font-variation-settings: "wdth" 92, "opsz" 96;
  font-size: clamp(2.6rem, 11vw, 10rem);
  line-height: 0.86;
  letter-spacing: -0.045em;
  padding-bottom: clamp(50px, 7vw, 90px);
}
.cta__row{ display: block; }

.cta__row .ch{
  display: inline-block;
  transition: transform .5s var(--ease), color .5s var(--ease);
}
.cta:hover .ch{ color: var(--ink-yellow); }
.cta .ch[data-sp]{ width: .28em; }

.cta__arrow{
  display: inline-block;
  font-family: var(--text);
  font-size: clamp(1.4rem, 3vw, 2.6rem);
  vertical-align: super;
  transition: transform .5s var(--ease);
}
.cta:hover .cta__arrow{ transform: translateX(.35em); }

.contact__grid{
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 190px), 1fr));
  gap: clamp(26px, 3vw, 48px);
  border-top: 1px solid rgba(222, 221, 214, .28);
  padding-top: 30px;
  padding-bottom: clamp(60px, 8vw, 110px);
}
.contact__grid dt{
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--on-key-soft);
  margin-bottom: 10px;
}
.contact__grid dd{ font-size: clamp(15px, 1vw + 11px, 18px); }
.contact__grid a{ border-bottom: 1px solid rgba(222,221,214,.35); }
.contact__grid a:hover{ color: var(--ink-pink); border-color: var(--ink-pink); }

/* ── The close ────────────────────────────────────────────────────────────
   The last thing on every page. A headline and an arrow was not an ask — it
   needs a real action, a second way through, and the two facts that decide
   whether someone writes: whether there's room, and how fast a reply comes.
   ───────────────────────────────────────────────────────────────────────── */
.close__act{
  display: flex; flex-wrap: wrap; align-items: center;
  gap: clamp(12px, 1.6vw, 20px);
  margin-top: clamp(30px, 4vw, 52px);
}
.btn{
  display: inline-flex; align-items: center; gap: 12px;
  font-family: var(--text); font-weight: 600;
  font-size: clamp(15px, 0.6vw + 12px, 18px);
  padding: clamp(14px, 1.5vw, 19px) clamp(22px, 2.4vw, 32px);
  border-radius: 999px;
  border: 1.5px solid transparent;
  transition: background .35s var(--ease), color .35s var(--ease),
              border-color .35s var(--ease), transform .35s var(--ease);
}
.btn span{ transition: transform .4s var(--ease); }
.btn:hover span{ transform: translateX(5px); }
.btn:hover{ transform: translateY(-2px); }

.btn--primary{ background: var(--ink-yellow); color: var(--ink-key); border-color: var(--ink-yellow); }
.btn--primary:hover{ background: var(--ink-pink); border-color: var(--ink-pink); }

.btn--ghost{ color: var(--on-key); border-color: rgba(222, 221, 214, .45); }
.btn--ghost:hover{ border-color: var(--on-key); background: rgba(222, 221, 214, .07); }

.close__act .btn--ghost{
  background: var(--stock);
  border-color: var(--stock);
  color: var(--ink-key);
}
.close__act .btn--ghost:hover{
  background: var(--stock-hi);
  border-color: var(--stock-hi);
  color: var(--ink-key);
}

.close__status{
  display: flex; align-items: center; gap: 11px; flex-wrap: wrap;
  margin-top: clamp(24px, 3vw, 36px);
  font-family: var(--mono);
  font-size: clamp(10.5px, 0.2vw + 9.5px, 12px);
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--on-key-soft);
}
.close__status i{
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--ink-yellow); flex: none;
  animation: throb 2.4s var(--ease) infinite;
}

/* the plate number, overprinted like every other panel */
.contact .svc__num{ color: var(--ink-pink); mix-blend-mode: normal; }

/* ── The close, as the hero's negative ────────────────────────────────────
   The hero is ink printed on paper. This is the same press run inverted:
   paper-bright dots laid onto solid key, drifting, blooming under the
   cursor. Same machinery, opposite polarity — which is what makes it the
   second real moment on the site rather than a footer with big type.
   ───────────────────────────────────────────────────────────────────────── */
.contact{
  position: relative;
  isolation: isolate;
  overflow: hidden;
}
.contact > .wrap,
.contact > .foot{ position: relative; z-index: 2; }

/* normal blend, not multiply: multiply over near-black erases everything.
   Typed as canvas.* deliberately — the base .field rule is declared far
   later in this file, so a bare class would lose on source order. */
canvas.field--close{
  mix-blend-mode: normal;
  z-index: 0;
}

/* the headline carries the live registration, like every other plated head */
.contact .cta{
  --pl-a: var(--ink-pink);
  --pl-b: var(--ink-blue);
}

@media (prefers-reduced-motion: reduce){
  .close__status i{ animation: none; }
}

/* ── Footer ───────────────────────────────────────────────────────────────
   A proper close: sitemap, real detail, and a density bar. The CTA above it
   does the asking; this does the answering.
   ───────────────────────────────────────────────────────────────────────── */
.foot{
  border-top: 1px solid rgba(222, 221, 214, .28);
  margin-top: clamp(50px, 7vw, 90px);
  padding-top: clamp(34px, 4.5vw, 56px);
  color: var(--on-key-soft);
  /* solid ground: the closing field is densest at the foot of the panel and
     would otherwise run straight through the sitemap */
  background: var(--ink-key);
}
.foot__grid{
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 175px), 1fr));
  gap: clamp(28px, 3.5vw, 54px);
  padding-bottom: clamp(34px, 4.5vw, 56px);
}
.foot__col{ display: grid; gap: 9px; align-content: start; }
.foot__h{
  font-family: var(--mono); font-size: 10.5px;
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--ink-yellow);
  margin-bottom: 5px;
}
.foot__col a,
.foot__col span{ font-size: 15px; color: var(--on-key-soft); }
.foot__col a{ width: fit-content; border-bottom: 1px solid transparent; transition: color .3s var(--ease), border-color .3s var(--ease); }
.foot__col a:hover{ color: var(--on-key); border-bottom-color: var(--ink-pink); }
.foot__col strong{ color: var(--on-key); font-weight: 600; }

/* density bar, same furniture as the hero */
.foot__bar{
  display: flex; height: 12px;
  margin-bottom: clamp(18px, 2vw, 26px);
}
.foot__bar i{ flex: 1; background: var(--patch); }

.foot__legal{
  display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  padding-bottom: 24px;
  color: var(--on-key-soft);
}
.foot__legal a:hover{ color: var(--ink-yellow); }

/* ═══════════════════════════ RESPONSIVE ═══════════════════════════ */

/* Lateral registration on small screens.
   A press misregisters in two named ways: laterally, across the web, and
   circumferentially, around the cylinder. The site normally shows both at
   once — a diagonal offset. On a phone the vertical half is the half that
   destroys the type: it drops the ghost into the x-height band, filling the
   counters and welding each line to the one under it. So below 900px the
   plates go lateral only. It is not a watered-down version of the effect,
   it is the other real one, and it leaves the horizontal strokes clean.
   The live --reg shear is untouched: pulling the registration still moves
   the plates in both axes, because that is the visitor's own hand. */
@media (max-width: 900px){
  :root{ --plate-y: 0; }
}

@media (max-width: 900px){
  .nav__links{ display: none; }
  .hero__bar{ display: none; }
  .reg--a{ top: 78px; }
  .reg--b{ display: none; }
  .index__row a{ grid-template-columns: 1fr auto; row-gap: 6px; }
  .index__cat{ grid-column: 1; }
  .index__year{ grid-column: 2; grid-row: 1; }
  .spec li{ grid-template-columns: 18px 1fr; }
  .spec p{ grid-column: 2; }
  .proof{ display: none; }
}

@media (max-width: 560px){
  .hero{ padding-top: 96px; }
  .hero__foot{ flex-direction: column; align-items: flex-start; gap: 22px; }
  .hero__meta{ display: none; }
  .plates span:nth-child(3){ display: none; }
  .trim{ width: 18px; height: 18px; }
}

/* ═══════════════════════════ REDUCED MOTION ═══════════════════════════ */
@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  :root{ --reg-x: 2px; --reg-y: 1px; }   /* plates rest slightly off, as they do */
  [data-reveal] .wd{ opacity: 1; }
  .proof{ display: none; }
}

/* No-JS: everything is already visible; reveal spans simply never appear */


/* ═══════════════════════════════════════════════════════════════════════════
   NAV TOGGLE + FULL-SHEET MENU
   ═══════════════════════════════════════════════════════════════════════════ */
.nav__toggle{
  display: none;
  align-items: center; gap: 10px;
  font-family: var(--mono);
  font-size: 11.5px; letter-spacing: .14em; text-transform: uppercase;
  background: none; border: 1.5px solid currentColor; color: inherit;
  border-radius: 999px; padding: 9px 15px;
  cursor: pointer;
}
.nav__toggle-dots{ position: relative; width: 20px; height: 10px; flex: none; }
.nav__toggle-dots i{
  position: absolute; top: 0; width: 10px; height: 10px; border-radius: 50%;
  mix-blend-mode: multiply;
  transition: transform .45s var(--ease);
}
.nav__toggle-dots i:first-child{ left: 0;  background: var(--ink-pink); }
.nav__toggle-dots i:last-child { left: 8px; background: var(--ink-blue); }
.nav__toggle[aria-expanded="true"] .nav__toggle-dots i:first-child{ transform: translateX(-3px); }
.nav__toggle[aria-expanded="true"] .nav__toggle-dots i:last-child { transform: translateX(3px); }

/* The sheet is the only navigation a phone gets, so it is built as a page of
   the site rather than a list on a black rectangle: a plate label at the
   head, the five pages numbered and specced like the ink chart, a two-ink
   screen ramping up out of the bottom edge, and the CTA that .nav__cta has
   to give up at this width. */
.menu{
  position: fixed; inset: 0; z-index: 100;
  background: var(--ink-key);
  color: var(--on-key);
  display: flex; flex-direction: column;
  gap: clamp(18px, 4vh, 34px);
  padding: 96px var(--gut) max(28px, env(safe-area-inset-bottom));
  clip-path: inset(0 0 100% 0);
  transition: clip-path .78s var(--ease);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.menu[data-open="true"]{ clip-path: inset(0 0 0 0); }
.menu[aria-hidden="true"]{ pointer-events: none; }
.menu[data-open="true"]{ pointer-events: auto; }
/* tabindex="-1" so it can be focused on open; it is not tab-reachable, and a
   ring around a full-screen sheet tells nobody anything. */
.menu:focus{ outline: none; }

/* ── Two-ink screen, on the real angles, ramping out of the bottom ──────
   Same halftone language as the dissolves, but built in CSS: it costs no
   canvas and no frame budget, which matters on the one surface that is
   always a phone. It shears with the registration spring like everything
   else, so dragging the sheet pulls the screen apart too. */
.menu__screen{
  position: absolute; inset: 0;
  pointer-events: none;
  overflow: hidden;
  opacity: 0;
  transition: opacity .9s var(--ease) .18s;
  /* A band, not a wash. Ramped in behind the lower half of the list and out
     again before the foot, so the screen reads as ink laid across the sheet
     and the CTA and contact line still sit on clean key. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 26%, #000 60%, #000 72%, transparent 93%);
          mask-image: linear-gradient(to bottom, transparent 26%, #000 60%, #000 72%, transparent 93%);
}
/* Kept low enough that the 7.6:1 mono over it still clears AA */
.menu[data-open="true"] .menu__screen{ opacity: .28; }
.menu__screen::before,
.menu__screen::after{
  content: ""; position: absolute; inset: -50%;
  background-image: radial-gradient(circle, currentColor 32%, transparent 33%);
}
/* Different pitch on each ink as well as different angle: two identical
   grids 60° apart beat against each other into visible blotches, which is
   real moiré but reads as noise rather than as a screen. */
.menu__screen::before{
  color: var(--ink-pink);
  background-size: 15px 15px;
  transform: rotate(15deg) translate3d(var(--reg-x), var(--reg-y), 0);
}
.menu__screen::after{
  color: var(--ink-blue);
  background-size: 19px 19px;
  transform: rotate(75deg) translate3d(calc(var(--reg-x) * -1), calc(var(--reg-y) * -1), 0);
}

/* ── Plate label ─────────────────────────────────────────────────────── */
.menu__head{
  position: relative; z-index: 1;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  border-bottom: 1px solid rgba(222, 221, 214, .26);
  padding-bottom: 12px;
}
.menu__plate{
  font-family: var(--mono);
  font-size: 10.5px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--on-key-soft);
}
.menu__inks{ display: flex; gap: 5px; flex: none; }
.menu__inks i{ width: 9px; height: 9px; border-radius: 50%; background: var(--patch); }

/* ── The five pages ──────────────────────────────────────────────────── */
.menu__list{
  position: relative; z-index: 1;
  display: flex; flex-direction: column;
  margin-block: auto;              /* centred in the sheet, foot stays pinned */
}
.menu__list li{
  overflow: hidden;                /* the words rise out from behind the rule */
  border-bottom: 1px solid rgba(222, 221, 214, .16);
}
.menu__list li:last-child{ border-bottom: 0; }

.menu__list a{
  --o: 0px;
  display: grid;
  grid-template-columns: 2.4em 1fr auto;
  align-items: baseline;
  gap: 0 .5em;
  padding-block: clamp(9px, 1.6vh, 16px);
  color: var(--on-key);
  transform: translateY(105%);
  transition: transform .7s var(--ease);
}
.menu[data-open="true"] .menu__list a{ transform: translateY(0); }

.menu__n{
  font-family: var(--mono);
  font-size: 11px; letter-spacing: .1em;
  color: var(--n-ink, var(--on-key-soft));
  transform: translateY(-.15em);   /* mono baseline sits low against display */
}
/* Pink and yellow only. Federal Blue on a Key flood measures 2:1 — it is a
   real ink on this sheet but it cannot carry text at 11px, so it stays in
   the screen behind and out of the numbering. */
.menu__list li:nth-child(odd)  a{ --n-ink: var(--ink-pink); }    /* 5.9:1  */
.menu__list li:nth-child(even) a{ --n-ink: var(--ink-yellow); }  /* 14.7:1 */

.menu__w{
  font-family: var(--display);
  font-weight: 800;
  font-variation-settings: "wdth" 94, "opsz" 72;
  /* min() against svh so the five words never push the CTA off a short
     phone — the sheet scrolls if it has to, but it should not have to */
  font-size: clamp(1.9rem, min(12vw, 6.4svh), 5rem);
  line-height: 1.0;
  letter-spacing: -0.04em;
  transition: text-shadow .45s var(--ease), color .45s var(--ease);
  text-shadow:
    calc(var(--o) * -1) 0 0 var(--ink-pink),
    var(--o) 0 0 var(--ink-blue);
}
.menu__m{
  font-family: var(--mono);
  font-size: 10px; letter-spacing: .13em; text-transform: uppercase;
  color: var(--on-key-soft);
  transform: translateY(-.2em);
}

@media (hover: hover){
  .menu__list a:hover .menu__w{ --o: 6px; }
  .menu__list a:hover .menu__m{ color: var(--on-key); }
}
.menu__list a:focus-visible .menu__w{ --o: 6px; }

/* The page you are on is already printed: yellow, its number filled, and the
   two other inks pulled out either side of it. Three plates on one word is
   the loudest thing the sheet can say, which is the point — it is the only
   item in the list you cannot navigate to. */
.menu__list a[aria-current="page"] .menu__w{
  --o: 0.13em;
  color: var(--ink-yellow);
}
.menu__list a[aria-current="page"] .menu__n{
  color: var(--ink-key);
  background: var(--ink-yellow);
  padding: .25em .45em;
  margin-left: -.45em;
}

/* ── Foot: the CTA the top bar gives up at this width, then the details ── */
.menu__foot{
  position: relative; z-index: 1;
  display: flex; flex-direction: column; gap: 18px;
}
.menu__cta{
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  background: var(--ink-yellow);
  color: var(--ink-key);
  font-weight: 600;
  font-size: clamp(15px, 4.2vw, 18px);
  border-radius: 999px;
  padding: 15px 22px;
  transition: transform .45s var(--ease), background .3s var(--ease);
}
.menu__cta span{ transition: transform .45s var(--ease); }
@media (hover: hover){
  .menu__cta:hover{ background: var(--ink-pink); }
  .menu__cta:hover span{ transform: translateX(5px); }
}
.menu__cta:active{ transform: scale(.985); }

.menu__meta{
  display: flex; flex-wrap: wrap; gap: 10px 28px;
  justify-content: space-between; align-items: flex-end;
  border-top: 1px solid rgba(222, 221, 214, .26);
  padding-top: 16px;
  font-size: 13px;
  color: var(--on-key-soft);
}
.menu__meta a{ color: var(--on-key); border-bottom: 1px solid rgba(222,221,214,.4); }
.menu__meta a:hover{ color: var(--ink-yellow); border-color: var(--ink-yellow); }

/* An SE-sized screen fits all five words, the CTA and the contact line with
   about two pixels to spare — which is to say it doesn't. Buy the room back
   from the margins rather than the type. */
@media (max-height: 720px){
  .menu{ padding-top: 82px; gap: 14px; }
  .menu__list a{ padding-block: 8px; }
}

@media (prefers-reduced-motion: reduce){
  .menu__list a{ transform: none; }
  .menu__screen{ opacity: .28; transition: none; }
}

body[data-menu="open"]{ overflow: hidden; }

/* The sheet covers the nav, so lift the toggle above it — otherwise there is
   no visible way back out. Reversed to read on the key flood. */
body[data-menu="open"] .nav{
  z-index: 110;
  color: var(--on-key);
  transform: none;
}
body[data-menu="open"] .nav[data-stuck="true"]{
  background: transparent;
  border-bottom-color: transparent;
}
body[data-menu="open"] .nav__dots i,
body[data-menu="open"] .nav__toggle-dots i{ mix-blend-mode: normal; }

@media (max-width: 900px){
  .nav__toggle{ display: inline-flex; }
  .nav__cta{ display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   RISO IMAGE — any photograph, separated into spot-ink plates
   ═══════════════════════════════════════════════════════════════════════════ */
.riso{
  position: relative;
  display: block;
  aspect-ratio: var(--ratio, 4 / 5);   /* holds layout whether or not the
                                          image file exists yet */
  background: var(--stock-hi);
  overflow: hidden;
  isolation: isolate;
}
.riso img{
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}
/* The image element stays in the DOM so its alt text stays in the a11y tree */
.riso[data-ready="true"] img{ opacity: 0; }

.riso__plate{
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  mix-blend-mode: multiply;
  pointer-events: none;
}
/* --sep is scroll-driven: the plates drift apart as the image travels the
   viewport, so a still photograph is never quite still. */
.riso__plate--pink{ transform: translate3d(calc(var(--reg-x) * -0.45 - var(--sep, 0px)), calc(var(--reg-y) * -0.45), 0); }
.riso__plate--blue{ transform: translate3d(calc(var(--reg-x) *  0.45 + var(--sep, 0px)), calc(var(--reg-y) *  0.45), 0); }

/* ═══════════════════════════════════════════════════════════════════════════
   SCREEN CAPTURE — mounted, not separated
   A website screenshot put through the halftone becomes unreadable, and the
   whole point of the image is to show the work. So the capture stays clean
   and the world is carried by the mount: paper margin, key rule, and the two
   ink plates sitting behind it, which pull out into view on the drag.
   ═══════════════════════════════════════════════════════════════════════════ */
.shot{
  --pad: clamp(9px, 1.1vw, 16px);
  position: relative;
  display: block;
  aspect-ratio: var(--ratio, 4 / 5);
  background: var(--stock-hi);
  padding: var(--pad);
  border: 1px solid rgba(22, 22, 26, .22);
  isolation: isolate;
}
.shot::before, .shot::after{
  content: "";
  position: absolute; inset: var(--pad);
  z-index: 0;
  mix-blend-mode: multiply;
  will-change: transform;
}
.shot::before{
  background: var(--ink-pink);
  transform: translate3d(calc(var(--reg-x) * -1 - var(--sep, 0px)), calc(var(--reg-y) * -1), 0);
}
.shot::after{
  background: var(--ink-blue);
  transform: translate3d(calc(var(--reg-x) + var(--sep, 0px)), var(--reg-y), 0);
}
.shot img{
  position: relative; z-index: 1;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: top center;
  display: block;
}
.workcard .shot{ transition: transform .6s var(--ease); }
.workcard a:hover .shot, .workcard a:focus-visible .shot{ transform: translateY(-6px); }

/* No file yet: the procedural sheet is drawn into the mount instead. Inset to
   the padding so the paper margin and rule still read as a mount. */
.shot .riso__plate{ inset: var(--pad); width: auto; height: auto; }
.shot[data-ready="true"] img{ opacity: 0; }

/* ═══════════════════════════════════════════════════════════════════════════
   THE CLIENT SHEET — work index
   No cards, no frames. Every mark is burned to a stencil and pulled in two
   spot inks straight onto the paper, deliberately out of register. Colour is
   assigned per panel, the way a screenprinter picks ink, not taken from the
   client's brand guidelines.
   ═══════════════════════════════════════════════════════════════════════════ */
.wall{
  padding-bottom: clamp(70px, 9vw, 130px);
  /* Un-printed plates rest 34px off-register, which would otherwise widen the
     document. `clip` rather than `hidden`: no scroll container is created. */
  overflow-x: clip;
}

.sheet-head__run{
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--mono); font-size: 11.5px;
  letter-spacing: .14em; text-transform: uppercase;
  background: none; color: inherit; cursor: pointer;
  border: 1.5px solid var(--ink-key);
  border-radius: 999px; padding: 11px 19px;
  transition: background .35s var(--ease), color .35s var(--ease);
}
.sheet-head__run:hover{ background: var(--ink-key); color: var(--stock); }
.sheet-head__run i{
  width: 9px; height: 9px; border-radius: 50%; flex: none;
  background: var(--ink-pink);
}
.sheet-head__run:hover i{ background: var(--ink-yellow); }

.wall__band{
  position: relative;
  padding-block: clamp(96px, 11vw, 160px) clamp(56px, 8vw, 120px);
}
/* the marks must sit above the band's plate number and dissolve */
.wall__band > .wrap{ position: relative; z-index: 2; }
.wall__band--flood{
  background: var(--ink-blue);
  margin-block: clamp(40px, 6vw, 90px);
}

.jobs{
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: clamp(56px, 8vw, 130px) clamp(34px, 5vw, 80px);
  align-items: end;
}
.job{ grid-column: span var(--span, 6); }
.job--drop{ margin-top: clamp(34px, 7vw, 110px); }

/* ── The impression ───────────────────────────────────────────────────── */
.mark{
  position: relative;
  display: block;
  line-height: 0;
}
/* The stencil stays in the DOM for its alt text, but never prints itself */
.mark > img{
  width: 100%; height: auto;
  visibility: hidden;
}
/* size and offset are set inline — the canvas is grown past the mark so
   edge dots are never sliced */
.mark__plate{
  position: absolute;
  mix-blend-mode: multiply;
  opacity: 0;
  will-change: transform;
  transition: transform 1.05s cubic-bezier(.14, .9, .2, 1), opacity .45s linear;
}
.mark__plate--norm{ mix-blend-mode: normal; }

/* Off the press: the two screens arrive from opposite sides and converge.
   Fixed px, not %, or a wide wordmark throws its plates into the next column. */
.mark__plate--0{ transform: translate3d(-34px, -16px, 0) rotate(-2.4deg) scale(1.03); }
.mark__plate--1{ transform: translate3d( 34px,  16px, 0) rotate( 2.4deg) scale(1.03); }

.job.is-printed .mark__plate{ opacity: 1; }
.job.is-printed .mark__plate--0{
  transform: translate3d(calc(var(--reg-x) * -1 - 2px), calc(var(--reg-y) * -1 - 1px), 0);
}
.job.is-printed .mark__plate--1{
  transform: translate3d(calc(var(--reg-x) + 2px), calc(var(--reg-y) + 1px), 0);
}
.job.is-printed .mark__plate--1{ transition-delay: .09s; }

/* Hover pulls the registration apart — the misprint, on purpose */
.job a:hover .mark__plate--0,
.job a:focus-visible .mark__plate--0{
  transform: translate3d(-26px, -12px, 0) rotate(-1.1deg);
  transition-duration: .5s;
}
.job a:hover .mark__plate--1,
.job a:focus-visible .mark__plate--1{
  transform: translate3d(26px, 12px, 0) rotate(1.1deg);
  transition-duration: .5s; transition-delay: 0s;
}

/* ── Caption ──────────────────────────────────────────────────────────── */
.job__line{
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 8px 20px; flex-wrap: wrap;
  margin-top: clamp(20px, 2.4vw, 34px);
  padding-top: 14px;
  border-top: 1.5px solid currentColor;
}
.job__name{
  font-family: var(--display); font-weight: 700;
  font-variation-settings: "wdth" 96, "opsz" 32;
  font-size: clamp(1.15rem, 1.7vw, 1.7rem);
  line-height: 1; letter-spacing: -0.03em;
}
.job__cat{ display: block; font-size: 14.5px; margin-top: 7px; opacity: .78; }
.job__ink{
  font-family: var(--mono); font-size: 10.5px;
  letter-spacing: .14em; text-transform: uppercase;
  display: inline-flex; align-items: center; gap: 7px;
  opacity: .78;
}
.job__ink i{ width: 9px; height: 9px; border-radius: 50%; background: var(--sw); }

.wall__band--flood .job{ color: var(--on-blue); }
.wall__band--flood .job__cat,
.wall__band--flood .job__ink{ opacity: .92; }

/* ── A job, set in type ───────────────────────────────────────────────────
   The whole index is typographic. Client marks were tried and pulled: seven
   logos in seven visual languages read as seven brands, not one sheet, and
   the stencils looked stuck-on. The name set large in two inks is the mark.
   ───────────────────────────────────────────────────────────────────────── */
@property --to {
  syntax: "<length>";
  inherits: false;
  initial-value: 4px;
}

.job__type{
  --to: var(--plate-oj);
  display: block;
  font-family: var(--display);
  font-weight: 800;
  font-variation-settings: "wdth" 90, "opsz" 96;
  font-size: var(--tsize, clamp(2.1rem, 5.5vw, 4.6rem));
  line-height: 0.84;
  letter-spacing: -0.05em;
  text-wrap: balance;
  color: var(--type-fg, var(--ink-key));
  text-shadow:
    calc((var(--reg-x) * -1) - var(--to)) calc((var(--reg-y) * -1) - var(--to) * var(--plate-y)) 0 var(--pl-a, var(--ink-pink)),
    calc(var(--reg-x) + var(--to))        calc(var(--reg-y) + var(--to) * var(--plate-y))        0 var(--pl-b, var(--ink-blue));
  transition: --to .55s var(--ease);
}
/* Hover only where hovering exists. On a touch screen :hover latches on tap,
   so this used to fire the 17px pull apart as the visitor left for the case
   study — the last thing they saw of the index was it falling out of joint. */
@media (hover: hover){
  .job a:hover .job__type{ --to: 17px; }
}
.job a:focus-visible .job__type{ --to: 17px; }

.wall__band--flood .job__type{
  --type-fg: var(--on-blue);
  --pl-a: var(--ink-yellow);
  --pl-b: var(--ink-pink);
}

@media (prefers-reduced-motion: reduce){
  .job__type{ transition: none; }
  .job a:hover .job__type{ --to: var(--plate-oj); }
}

/* ── Set in type, where there is no mark yet ──────────────────────────── */
.job__set{
  display: block;
  font-family: var(--display); font-weight: 800;
  font-variation-settings: "wdth" 90, "opsz" 96;
  font-size: clamp(2.6rem, 7vw, 5.4rem);
  line-height: 0.88; letter-spacing: -0.045em;
  color: var(--ink-key);
  --so: var(--plate-oj);
  text-shadow:
    calc(var(--reg-x) * -1 - var(--so)) calc(var(--reg-y) * -1 - var(--so) * var(--plate-y)) 0 var(--ink-pink),
    calc(var(--reg-x) + var(--so))      calc(var(--reg-y) + var(--so) * var(--plate-y))      0 var(--ink-blue);
}

@media (max-width: 900px){
  .job{ grid-column: span 12; }
  .job--drop{ margin-top: 0; }
  .jobs{ gap: clamp(48px, 12vw, 80px) 0; }
  /* 0.84 is a display leading. At phone sizes the names wrap to two lines and
     the ghost of one line landed inside the next; open it up enough to part
     them without losing the stacked, set-solid look. */
  .job__type{ line-height: 0.92; }

  /* One ink behind the name instead of two.
     Which pair a job pulls is authored per entry, and of the five inks only
     Fluorescent Pink stays legible as a fringe at phone sizes: Federal Blue
     is dark enough on stock to read as a blurred second word, and Key behind
     Key type is not an impression at all, just a thickening. So on a phone
     every job pulls pink alone, against the key.
     !important is load-bearing here — the pair is set in a style attribute
     on each entry, and nothing but !important outranks inline. */
  .job__type{
    --pl-a: var(--ink-pink) !important;
    --pl-b: transparent !important;
  }
}

@media (prefers-reduced-motion: reduce){
  .mark__plate{ opacity: 1; transform: none !important; }
  .job.is-printed .mark__plate--0{ transform: translate3d(-2px, -1px, 0) !important; }
  .job.is-printed .mark__plate--1{ transform: translate3d( 2px,  1px, 0) !important; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE INK CHART — services
   One service per spot ink, each owning a whole flooded panel. Between panels
   the ink doesn't stop at a line: it breaks into a halftone gradient and
   dissolves into the next colour, the way a riso duct runs dry.
   Namespace is its own (svc-) — this page shares nothing but the shell.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Chart head ───────────────────────────────────────────────────────── */
.svc-head{ padding: clamp(128px, 16vw, 200px) 0 clamp(40px, 5vw, 66px); }
.svc-head h1{
  font-family: var(--display); font-weight: 800;
  font-variation-settings: "wdth" 90, "opsz" 96;
  font-size: clamp(2.9rem, 11.5vw, 8.5rem);
  line-height: 0.86; letter-spacing: -0.045em;
  margin-bottom: clamp(22px, 3vw, 38px);
}
.svc-head p{
  max-width: 46ch;
  font-size: clamp(17px, 0.8vw + 13px, 21px);
  line-height: 1.45; color: var(--on-stock-soft);
}

/* The chart itself: five ducts, and the page's contents list */
.chart{
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 2px;
  margin-top: clamp(40px, 6vw, 76px);
  border: 1.5px solid var(--ink-key);
  background: var(--ink-key);
}
.chart a{
  display: block;
  background: var(--stock);
  padding: 0;
}
.chart__patch{
  display: block; height: clamp(64px, 11vw, 128px);
  background: var(--duct);
  /* scaleY, not height: animating height reflows the whole chart every frame */
  transform-origin: top;
  transition: transform .5s var(--ease);
}
.chart a:hover .chart__patch,
.chart a:focus-visible .chart__patch{ transform: scaleY(1.07); }
.chart__cap{
  display: block; padding: 15px clamp(8px, 1vw, 14px) 13px;
  font-family: var(--mono); font-size: clamp(8.5px, 0.55vw, 11px);
  letter-spacing: .1em; text-transform: uppercase;
  line-height: 1.45; color: var(--on-stock);
}
.chart__cap b{ display: block; font-weight: 500; }
.chart__cap span{ display: block; color: var(--on-stock-soft); }

@media (max-width: 700px){
  .chart{ grid-template-columns: repeat(2, 1fr); }
  .chart a:last-child{ grid-column: span 2; }
}

/* ── A flooded service panel ──────────────────────────────────────────── */
.svc{
  position: relative;
  background: var(--ink);
  color: var(--fg);
  padding: clamp(96px, 13vw, 190px) 0 clamp(70px, 10vw, 140px);
  overflow: hidden;
}
.svc__grid{
  position: relative; z-index: 2;
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: clamp(30px, 5vw, 90px);
  align-items: start;
}
@media (max-width: 880px){ .svc__grid{ grid-template-columns: 1fr; } }

/* The plate number, printed as a second ink over the flood */
/* Fully inside the panel — the panel clips for the dissolve, so a numeral
   hanging off the top edge gets sliced and breaks the illusion. */
.svc__num{
  position: absolute;
  top: clamp(16px, 2.4vw, 38px);
  right: var(--gut);
  z-index: 1;
  font-family: var(--display); font-weight: 800;
  font-variation-settings: "wdth" 100, "opsz" 96;
  font-size: clamp(5rem, 16vw, 14rem);
  line-height: 0.74; letter-spacing: -0.06em;
  color: var(--num);
  mix-blend-mode: multiply;
  pointer-events: none; user-select: none;
  /* Scroll drift only. It was wired to the registration drag once and pulled
     back out: a numeral this size sliding under the cursor reads as a bug,
     not as ink. Free-standing marks stay put; only plates misregister. */
  transform: translate3d(0, var(--drift, 0px), 0);
}
.svc__num--over{ mix-blend-mode: normal; }

.svc__label{
  display: inline-flex; align-items: center; gap: 9px;
  font-family: var(--mono); font-size: clamp(10.5px, 0.2vw + 9.5px, 12px);
  letter-spacing: .14em; text-transform: uppercase;
  margin-bottom: clamp(18px, 2.4vw, 30px);
}
.svc__label i{
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--fg);
}
.svc__title{
  font-family: var(--display); font-weight: 800;
  font-variation-settings: "wdth" 88, "opsz" 96;
  font-size: clamp(2.4rem, 6.4vw, 5.6rem);
  line-height: 0.88; letter-spacing: -0.045em;
  margin-bottom: clamp(20px, 2.6vw, 34px);
}
.svc__lead{
  max-width: 34ch;
  font-size: clamp(17px, 0.9vw + 13px, 22px);
  line-height: 1.38;
}

/* Spec list — hairline rules in the panel's own foreground */
.svc__list{ border-top: 1.5px solid currentColor; }
.svc__list li{
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 16px;
  align-items: baseline;
  padding: clamp(13px, 1.5vw, 20px) 0;
  border-bottom: 1px solid currentColor;
  font-size: clamp(15px, 0.5vw + 13px, 18px);
  line-height: 1.4;
}
.svc__list li > b{
  font-family: var(--mono); font-weight: 400;
  font-size: 11px; letter-spacing: .1em;
  opacity: .75;
}

/* Coverage note, set against the right edge */
/* writing-mode, not rotate(): rotating a long nowrap line pivots it about its
   own centre, which drags it hundreds of pixels into the content column. */
.svc__edge{
  position: absolute; z-index: 2;
  /* left gutter — the duct rail owns the right edge */
  left: clamp(4px, 0.8vw, 14px); top: 50%;
  transform: translateY(-50%) rotate(180deg);
  writing-mode: vertical-rl;
  font-family: var(--mono); font-size: 10.5px;
  letter-spacing: .22em; text-transform: uppercase;
  white-space: nowrap;
  opacity: .7;
}
@media (max-width: 1100px){ .svc__edge{ display: none; } }

/* ── The dissolve between two inks ────────────────────────────────────── */
.dissolve{
  position: absolute; top: 0; left: 0; right: 0;
  height: clamp(90px, 11vw, 170px);
  z-index: 1;
  pointer-events: none;
}
.dissolve canvas{ width: 100%; height: 100%; display: block; }

/* ── Sticky ink duct rail ─────────────────────────────────────────────── */
.rail{
  position: fixed; z-index: 70;
  right: clamp(14px, 2vw, 30px); top: 50%;
  transform: translateY(-50%);
  display: grid; gap: 9px;
}
/* Fixed box, scaled contents — no width/height animation */
.rail a{
  display: grid; place-items: center;
  width: 22px; height: 46px;
}
.rail a::before{
  content: ""; display: block;
  width: 22px; height: 46px;
  background: var(--duct);
  border: 1px solid rgba(22, 22, 26, .35);
  transform: scale(.58);
  transition: transform .45s var(--ease);
}
.rail a[data-on="true"]::before{ transform: scale(1); }
@media (max-width: 1100px){ .rail{ display: none; } }

/* ── About: portrait + story ──────────────────────────────────────────── */
.about-intro{
  display: grid;
  grid-template-columns: minmax(0, 4fr) minmax(0, 7fr);
  gap: clamp(28px, 5vw, 80px);
  align-items: start;
}
@media (max-width: 880px){ .about-intro{ grid-template-columns: 1fr; } }

.about-lede{
  font-family: var(--display);
  font-weight: 600;
  font-variation-settings: "wdth" 92, "opsz" 40;
  font-size: clamp(1.35rem, 2.6vw, 2.1rem);
  line-height: 1.1;
  letter-spacing: -0.03em;
  color: var(--on-stock);
  margin-bottom: 6px;
}

/* ── What's on the desk ───────────────────────────────────────────────── */
.kit{ border-top: 1.5px solid var(--ink-key); }
.kit li{
  display: grid;
  grid-template-columns: minmax(0, 8rem) minmax(0, 1fr);
  gap: 14px clamp(20px, 3vw, 44px);
  align-items: baseline;
  padding: clamp(15px, 1.8vw, 24px) 0;
  border-bottom: 1px solid rgba(22, 22, 26, .2);
}
.kit b{ color: var(--on-stock-soft); }
.kit span{ color: var(--on-stock); max-width: 62ch; }
@media (max-width: 620px){
  .kit li{ grid-template-columns: 1fr; gap: 5px; }
}

/* ── Dockets ──────────────────────────────────────────────────────────── */
.dockets{
  background: var(--ink-key); color: var(--on-key);
  padding: clamp(72px, 10vw, 130px) 0;
}
.dockets__head{
  font-family: var(--display); font-weight: 800;
  font-variation-settings: "wdth" 90, "opsz" 72;
  font-size: clamp(1.9rem, 5vw, 4rem);
  line-height: 0.92; letter-spacing: -0.04em;
  max-width: 16ch;
  margin-bottom: clamp(40px, 6vw, 76px);
}
.docket-grid{
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
  gap: 1px; background: rgba(222, 221, 214, .3);
  border: 1px solid rgba(222, 221, 214, .3);
}
.docket{
  background: var(--ink-key);
  padding: clamp(26px, 3vw, 40px);
  display: grid; gap: 14px; align-content: start;
}
.docket__no{
  font-family: var(--mono); font-size: 11px;
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--ink-yellow);
}
.docket h3{
  font-family: var(--display);
  font-variation-settings: "wdth" 94, "opsz" 32;
  font-size: clamp(1.2rem, 1.9vw, 1.6rem); line-height: 1;
}
.docket p{ color: var(--on-key-soft); font-size: 15.5px; }

@media (prefers-reduced-motion: reduce){
  .chart a:hover .chart__patch,
  .chart a:focus-visible .chart__patch{ height: clamp(64px, 11vw, 128px); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   ARRIVALS
   Two motions, not one applied everywhere. Rows print (a wipe across, the way
   a line lands). Blocks register (they arrive out of alignment and settle).
   Everything is visible by default and only hidden once JS confirms it can
   bring it back.
   ═══════════════════════════════════════════════════════════════════════════ */
[data-ink] > *{ will-change: auto; }

/* Rows — the wipe, plus the rule drawing ahead of the text */
html.ink [data-ink="rows"] > *{
  clip-path: inset(0 100% 0 0);
  transition: clip-path .8s var(--ease);
}
html.ink [data-ink="rows"] > *.is-in{ clip-path: inset(0 0 0 0); }

/* Blocks — arrive off-register and settle */
html.ink [data-ink="blocks"] > *{
  opacity: 0;
  transform: translate3d(0, 20px, 0);
  transition: opacity .55s linear, transform .95s var(--ease);
}
html.ink [data-ink="blocks"] > *.is-in{ opacity: 1; transform: none; }

/* ── Plated headings ──────────────────────────────────────────────────────
   The offset pink/blue impression, live. It arrives far out of register and
   pulls in, but never all the way to zero: it settles onto the registration
   spring, so it keeps drifting with the pointer and shearing with the scroll
   for as long as the page is open.
   @property is what makes this possible — transitioning the *variable* lets
   the shadow recompute from live --reg every frame, instead of a text-shadow
   transition fighting the per-frame updates.
   ───────────────────────────────────────────────────────────────────────── */
@property --po {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}

[data-plate]{
  --po: var(--plate-o);
  text-shadow:
    calc((var(--reg-x) * -1) - var(--po))      calc((var(--reg-y) * -1) - var(--po) * var(--plate-y)) 0 var(--pl-a, var(--ink-pink)),
    calc(var(--reg-x) + var(--po))             calc(var(--reg-y) + var(--po) * var(--plate-y))        0 var(--pl-b, var(--ink-blue));
}
html.ink [data-plate]{
  --po: 20px;
  transition: --po 1.15s var(--ease);
}
html.ink [data-plate].is-in{ --po: var(--plate-o); }

@media (prefers-reduced-motion: reduce){
  html.ink [data-ink="rows"] > *,
  html.ink [data-ink="blocks"] > *{
    clip-path: none; opacity: 1; transform: none;
  }
  html.ink [data-plate]{ --po: var(--plate-o); transition: none; }
}

/* ── Ink field ────────────────────────────────────────────────────────────
   The homepage hero's live halftone, generalised. Any section can carry one.
   ───────────────────────────────────────────────────────────────────────── */
.field{
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  z-index: 0;
  mix-blend-mode: multiply;
  pointer-events: none;
}
.page-head, .svc-head, .cs-head, .wall__band, .statement, .dockets{ position: relative; }

/* Copy always sits above the decorative canvases.
   A positioned canvas — even at z-index 0 — paints above static text in the
   same stacking context, so every content wrapper has to claim a layer.
   This is the fix for text being lost behind the dots. */
.page-head  > .wrap,
.svc-head   > .wrap,
.cs-head    > .wrap,
.dockets    > .wrap,
.statement  > .wrap,
.section    > .wrap,
.wall__band > .wrap,
.svc        > .wrap{
  position: relative;
  z-index: 2;
}

/* A dissolve is up to 170px tall and hangs from the section's top edge, so
   any section carrying one needs enough head-room to clear its own copy. */
section:has(> .dissolve){ padding-top: clamp(130px, 15vw, 230px); }

/* Flooded panels centre their content in the space left below the dissolve.
   Same specificity as the rule above and declared after it, so it wins.
   Without this the copy sits jammed under the dissolve with a large dead
   area beneath it. */
section.svc{
  display: grid;
  align-content: center;
  min-height: min(88svh, 880px);
  /* Top padding is derived, not guessed: it is the bottom padding PLUS the
     dissolve's own height, so the copy ends up optically centred in the space
     the visitor actually sees rather than centred behind the dot band. Both
     halves use the same clamps as .dissolve, so they track together. */
  padding-top: calc(clamp(90px, 10vw, 150px) + clamp(90px, 11vw, 170px));
  padding-bottom: clamp(90px, 10vw, 150px);
}

/* ── Paper halo ───────────────────────────────────────────────────────────
   Small copy sitting over a live dot field needs the ink masked off behind
   it. A blurred patch of the stock colour reads as the sheet being kept
   clear, rather than as a box drawn around the text.
   ───────────────────────────────────────────────────────────────────────── */
.page-head .page-lead,
.page-head .plate-label,
.page-head .page-head__act,
.cs-head   .page-lead,
.cs-head   .plate-label,
.svc-head  p,
.svc-head  .plate-label,
.dockets   .dockets__head{
  position: relative;
  z-index: 1;
}
.page-head .page-lead::before,
.page-head .plate-label::before,
.page-head .page-head__act::before,
.cs-head   .page-lead::before,
.cs-head   .plate-label::before,
.svc-head  p::before,
.svc-head  .plate-label::before{
  content: "";
  position: absolute;
  inset: -0.75em -1.4em;
  background: var(--stock);
  filter: blur(19px);
  opacity: .93;
  z-index: -1;
  pointer-events: none;
}
/* the label is a short pill, so it wants a tighter clearing */
.page-head .plate-label::before,
.cs-head   .plate-label::before,
.svc-head  .plate-label::before{ inset: -0.6em -1em; }

/* A whole column of body copy over a field gets one clearing, not a halo per
   paragraph — per-paragraph blurs stack into a muddy patchwork. */
.about-intro .prose,
.about-intro > div:first-child{
  position: relative;
  z-index: 1;
}
.about-intro .prose::before,
.about-intro > div:first-child::before{
  content: "";
  position: absolute;
  inset: -1.4em -1.8em;
  background: var(--stock);
  filter: blur(26px);
  opacity: .94;
  z-index: -1;
  pointer-events: none;
}

/* ── Page transitions ─────────────────────────────────────────────────────
   The page itself is screened. A halftone mask is laid over every top-level
   layer and its dot radius animated: at 9.5px on a 13px grid the dots overlap
   into a solid sheet and the page is simply itself; shrink the radius and the
   page breaks into a dot screen and disperses. Reverse it and the next page
   re-prints from dots.

   Nothing overlays the page — this IS the page, screened. Which is why it had
   to be a mask rather than a curtain.

   The mask only exists while a transition runs (class-gated), so there is no
   compositing cost at rest.
   ───────────────────────────────────────────────────────────────────────── */
html.dots-in{ --dotr: 0px; }

html.dots    :is(body > main, body > header, body > .menu, body > .rail, body > .sheet, body > .grain),
html.dots-in :is(body > main, body > header, body > .menu, body > .rail, body > .sheet, body > .grain){
  -webkit-mask-image: radial-gradient(circle, #000 var(--dotr, 9.5px), rgba(0,0,0,0) calc(var(--dotr, 9.5px) + 0.7px));
          mask-image: radial-gradient(circle, #000 var(--dotr, 9.5px), rgba(0,0,0,0) calc(var(--dotr, 9.5px) + 0.7px));
  -webkit-mask-size: var(--dotp, 13px) var(--dotp, 13px);
          mask-size: var(--dotp, 13px) var(--dotp, 13px);
  -webkit-mask-repeat: repeat;
          mask-repeat: repeat;
  -webkit-mask-position: center;
          mask-position: center;
}

@media (prefers-reduced-motion: reduce){
  html.dots, html.dots-in{ --dotr: 9.5px; }
  html.dots    :is(body > main, body > header, body > .menu, body > .rail, body > .sheet, body > .grain),
  html.dots-in :is(body > main, body > header, body > .menu, body > .rail, body > .sheet, body > .grain){
    -webkit-mask-image: none; mask-image: none;
  }
}



/* ═══════════════════════════════════════════════════════════════════════════
   INNER PAGE HEAD
   ═══════════════════════════════════════════════════════════════════════════ */
.page-head{ padding: clamp(130px, 17vw, 210px) 0 clamp(44px, 6vw, 80px); }
.page-head h1{
  font-family: var(--display);
  font-weight: 800;
  font-variation-settings: "wdth" 92, "opsz" 96;
  font-size: clamp(2.8rem, 11vw, 8rem);
  line-height: 0.88;
  letter-spacing: -0.04em;
  margin-bottom: clamp(22px, 3vw, 40px);
}
/* Deliberately class-scoped: a bare `.page-head p` outranks .plate-label
   and silently resizes every label in the header. */
.page-head .page-lead{
  max-width: 54ch;
  font-size: clamp(17px, 0.8vw + 13px, 21px);
  line-height: 1.45;
  color: var(--on-stock-soft);
}
.page-head .plate-label{ margin-bottom: clamp(26px, 4vw, 48px); }
.page-head__act{ margin-top: clamp(24px, 3vw, 38px); }

/* ═══════════════════════════════════════════════════════════════════════════
   404 — THE SPOILED SHEET
   Every run begins by throwing sheets away: they come off during make-ready
   with the plates not yet locked on, and the pressman pulls them until the
   colour registers. Those are spoils. This page is one, so it is the only
   sheet on the site allowed to be badly out of register.
   ═══════════════════════════════════════════════════════════════════════════ */
.nf__h{ display: block; margin-bottom: 0; }

.nf__num{
  /* Far further out than anything else here — set through --plate-o, which
     [data-plate] already reads, so the reveal and the live registration
     shear come along for free. */
  --plate-o: clamp(6px, 1.7vw, 16px);
  --plate-y: 0.42;                    /* holds its diagonal even on a phone,
                                         where 30vw of numeral can afford it */
  display: block;
  font-family: var(--display);
  font-weight: 800;
  font-variation-settings: "wdth" 86, "opsz" 96;
  font-size: clamp(6rem, 29vw, 19rem);
  line-height: 0.76;
  letter-spacing: -0.055em;
  margin-left: -0.05em;               /* optical: 4 carries a wide left side */
  color: var(--ink-key);
}

.nf__title{
  display: block;
  font-family: var(--display);
  font-weight: 800;
  font-variation-settings: "wdth" 92, "opsz" 96;
  font-size: clamp(1.9rem, 6.2vw, 4.2rem);
  line-height: 0.94;
  letter-spacing: -0.04em;
  margin-top: clamp(12px, 1.8vw, 24px);
}

.nf__act{
  display: flex; flex-wrap: wrap; align-items: center;
  gap: clamp(12px, 1.6vw, 20px);
}
/* .btn--ghost is authored for the dark closing panel; on stock it needs the
   opposite pair or it prints stock-on-stock and disappears. */
.nf__act .btn--ghost{
  color: var(--ink-key);
  border-color: rgba(22, 22, 26, .4);
  background: transparent;
}
.nf__act .btn--ghost:hover{
  background: var(--ink-key);
  border-color: var(--ink-key);
  color: var(--stock);
}

/* ═══════════════════════════════════════════════════════════════════════════
   WORK GRID
   ═══════════════════════════════════════════════════════════════════════════ */
.workgrid{
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 330px), 1fr));
  gap: clamp(30px, 4vw, 60px);
  padding-bottom: clamp(80px, 11vw, 150px);
}
.workcard a{ display: block; }
.workcard .riso{ transition: transform .6s var(--ease); }
.workcard a:hover .riso, .workcard a:focus-visible .riso{ transform: translateY(-6px); }
.workcard__meta{
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; padding-top: 16px;
  border-top: 1px solid rgba(22, 22, 26, .22);
  margin-top: 16px;
}
.workcard__name{
  font-family: var(--display);
  font-weight: 700;
  font-variation-settings: "wdth" 96, "opsz" 32;
  font-size: clamp(1.3rem, 2.2vw, 2rem);
  line-height: 1;
  letter-spacing: -0.03em;
}
.workcard__cat{ display: block; color: var(--on-stock-soft); font-size: 15px; margin-top: 6px; }

/* ═══════════════════════════════════════════════════════════════════════════
   SERVICE ROWS
   ═══════════════════════════════════════════════════════════════════════════ */
.service{
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: clamp(28px, 5vw, 80px);
  align-items: start;
  padding: clamp(44px, 6vw, 86px) 0;
  border-top: 1px solid rgba(22, 22, 26, .22);
}
.service__label{
  display: inline-flex; align-items: center; gap: 9px;
  font-family: var(--mono); font-size: 11.5px;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--on-stock-soft);
  margin-bottom: 18px;
}
.service__label b{ width: 11px; height: 11px; border-radius: 50%; background: var(--patch); mix-blend-mode: multiply; }
.service h2{
  font-family: var(--display);
  font-variation-settings: "wdth" 92, "opsz" 64;
  font-size: clamp(1.9rem, 4.4vw, 3.6rem);
  line-height: 0.94;
  margin-bottom: 20px;
}
.service__lead{ max-width: 46ch; font-size: clamp(16px, 0.6vw + 13px, 19px); }
.service__list{ display: grid; gap: 12px; }
.service__list li{
  display: grid; grid-template-columns: 13px 1fr; gap: 14px;
  align-items: baseline;
  padding-bottom: 12px;
  border-bottom: 1px solid rgba(22, 22, 26, .14);
  color: var(--on-stock-soft);
  max-width: 62ch;
}
.service__list li::before{
  content: ""; width: 9px; height: 9px; border-radius: 50%;
  background: var(--patch, var(--ink-key)); mix-blend-mode: multiply;
  transform: translateY(1px);
}

@media (max-width: 820px){
  .service{ grid-template-columns: 1fr; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   CONTACT FORM (mailto — there is no backend)
   ═══════════════════════════════════════════════════════════════════════════ */
.form{ display: grid; gap: 22px; max-width: 640px; }
.form label{
  display: block;
  font-family: var(--mono); font-size: 11.5px;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--on-key-soft);
  margin-bottom: 9px;
}
.form input, .form textarea, .form select{
  width: 100%;
  font-family: var(--text); font-size: 17px;
  color: var(--on-key);
  background: transparent;
  border: 0; border-bottom: 1.5px solid rgba(222, 221, 214, .4);
  padding: 10px 0;
  border-radius: 0;
}
.form select option{ color: var(--ink-key); }
.form textarea{ resize: vertical; min-height: 110px; }
.form input:focus, .form textarea:focus, .form select:focus{
  outline: none; border-bottom-color: var(--ink-pink);
}
.form button{
  justify-self: start;
  font-family: var(--mono); font-size: 12px;
  letter-spacing: .14em; text-transform: uppercase;
  background: var(--ink-yellow); color: var(--ink-key);
  border: 0; border-radius: 999px;
  padding: 15px 26px;
  cursor: pointer;
  transition: transform .4s var(--ease);
}
.form button:hover{ transform: translateY(-2px); }

/* ═══════════════════════════════════════════════════════════════════════════
   SHARED SECTION UTILITIES
   ═══════════════════════════════════════════════════════════════════════════ */
.section{ padding-bottom: clamp(80px, 11vw, 150px); }
.section--pad{ padding-top: clamp(70px, 9vw, 130px); }
.lede{
  font-family: var(--display);
  font-weight: 600;
  font-variation-settings: "wdth" 92, "opsz" 40;
  font-size: clamp(1.5rem, 3.6vw, 3rem);
  line-height: 1.08;
  letter-spacing: -0.03em;
  max-width: 22ch;
  text-wrap: pretty;
}
.prose{ max-width: 68ch; display: grid; gap: 18px; color: var(--on-stock-soft); }
.prose strong{ color: var(--on-stock); font-weight: 600; }

/* ═══════════════════════════════════════════════════════════════════════════
   CASE STUDY
   ═══════════════════════════════════════════════════════════════════════════ */
.cs-head{ padding: clamp(120px, 15vw, 190px) 0 clamp(30px, 4vw, 52px); }
.cs-head h1{
  font-family: var(--display);
  font-weight: 800;
  font-variation-settings: "wdth" 92, "opsz" 96;
  font-size: clamp(2.6rem, 10vw, 7.5rem);
  line-height: 0.88;
  letter-spacing: -0.04em;
  margin-bottom: clamp(18px, 2.5vw, 30px);
}
.cs-head .page-lead{
  max-width: 52ch;
  font-size: clamp(17px, 0.8vw + 13px, 21px);
  line-height: 1.45;
  color: var(--on-stock-soft);
}

/* Full-bleed plate */
.cs-bleed{
  margin-inline: calc(var(--gut) * -1);
  margin-block: clamp(40px, 6vw, 90px);
}
.cs-bleed .riso{ aspect-ratio: var(--ratio, 16 / 10); }

/* ═══════════════════════════════════════════════════════════════════════════
   THE SHOWCASE — a project's frames, run through the press
   ───────────────────────────────────────────────────────────────────────────
   Vertical scrolling drives a horizontal narrative. The stage pins to the
   viewport and the run of frames pans across it, with the progress rule,
   frame counter and three halftone plates all reading from the same scroll
   timeline. No JavaScript: it is one `view-timeline` and a handful of
   animations hung off it.

   Frame count is data, not layout. --frames comes from the markup, and the
   run height, track width and pan distance are all derived from it, so a
   project with three images and one with six use the same rules.

   Each frame centres its own content well inside its 100vw slot. That is
   the difference between this and the usual pinned-pan gallery: with copy
   pinned to a slot's edge, every moment between frames shows a half-cut
   heading. Centred and inset, a frame slides out complete and the next
   slides in complete, and the pan reads as one move rather than a smear.

   Everything degrades to the same markup as a native horizontal snap
   gallery: below 860px, without scroll-timeline support, and for anyone who
   asked for reduced motion.
   ═══════════════════════════════════════════════════════════════════════════ */
@property --pull {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}

.showcase{ --frames: 3; }

.sc-run{
  position: relative;
  background: var(--ink-key);
}

.sc-stage{
  position: relative;
  min-height: 100svh;
  overflow: hidden;
  isolation: isolate;
  color: var(--on-key);
  background: var(--ink-key);
}

/* The stage ground is a canvas, not CSS. It carries the same
   .field--close halftone the closing panel does — dots that open toward the
   foot of the panel, on the live registration spring. An earlier version
   approximated it with repeating radial-gradients and masks; a repeating
   gradient cannot ramp its dot SIZE, which is the one thing that makes a
   halftone look printed rather than tiled. Reusing the real renderer means
   the two grounds match by construction. */

/* ── Stage furniture ─────────────────────────────────────────────────── */
.sc-head{
  position: absolute; inset: 0 0 auto 0;
  z-index: 12;
  display: grid;
  grid-template-columns: minmax(180px, auto) minmax(90px, 1fr) auto;
  gap: clamp(16px, 3vw, 40px);
  align-items: center;
  /* Derived from the nav, not guessed at it. The nav is fixed over the stage
     for the whole pinned run, and a hard 84px was 5px short of a bar that
     measures 89 once the web font lands — so it was clipping the top of this
     row. --nav-h is published by measureNav(); the fallback is only for the
     frame before the script runs. */
  padding: calc(var(--nav-h, 90px) + clamp(15px, 2vh, 26px)) var(--gut) 14px;
  border-bottom: 1px solid rgba(222, 221, 214, .38);
  pointer-events: none;
}
/* Both ends of the head row sit on the halftone. Same clearing the copy gets:
   a blurred patch of the ground, not a box. */
.sc-head .plate-label,
.sc-head .sc-counter{ position: relative; z-index: 1; }
.sc-head .plate-label::before,
.sc-head .sc-counter::before{
  content: "";
  position: absolute;
  inset: -.7em -1.1em;
  background: var(--ink-key);
  filter: blur(16px);
  opacity: .86;
  z-index: -1;
  pointer-events: none;
}

.sc-head .plate-label{ color: var(--on-key-soft); margin: 0; }
.sc-head .plate-label b{ background: var(--ink-yellow); }

.sc-progress{
  height: 3px;
  background: rgba(222, 221, 214, .25);
}
.sc-progress span{
  display: block; height: 100%;
  background: var(--ink-yellow);
  transform: scaleX(0);
  transform-origin: left center;
}

.sc-counter{
  display: grid; grid-template-columns: auto 2.2em auto;
  gap: 7px; align-items: center;
  margin: 0;
  font-family: var(--mono);
  font-size: 10.5px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--on-key-soft);
}
.sc-counter__window{
  display: block; height: 1.4em; overflow: hidden;
  color: var(--ink-yellow);
}
.sc-counter__reel{ display: flex; flex-direction: column; }
.sc-counter__reel b{ height: 1.4em; font: inherit; color: inherit; }

.sc-hint{
  position: absolute; right: var(--gut); bottom: 20px;
  z-index: 13; margin: 0;
  font-family: var(--mono);
  font-size: 10.5px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-yellow);
  pointer-events: none;
  isolation: isolate;
}
/* Bottom-right corner is where the halftone is densest — this is the one
   piece of furniture that always lands in the thick of it. */
.sc-hint::before{
  content: "";
  position: absolute;
  inset: -.9em -1.2em;
  background: var(--ink-key);
  filter: blur(15px);
  opacity: .88;
  z-index: -1;
}
.sc-hint .sc-hint--pinned{ display: none; }

/* ── The run ─────────────────────────────────────────────────────────────
   Base state is the fallback: a real horizontal scroller. Everything below
   860px, and every browser without scroll-driven animation, gets this. */
.sc-track{
  position: relative; z-index: 4;
  display: flex;
  min-height: 100svh;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x mandatory;
  overscroll-behavior-inline: contain;
  scrollbar-width: none;
}
.sc-track::-webkit-scrollbar{ display: none; }

.sc-frame{
  position: relative;
  flex: 0 0 min(90vw, 940px);
  scroll-snap-align: center;
  isolation: isolate;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: clamp(16px, 2.2vw, 30px);
  /* Top padding clears the nav and the stage head; content stays well inside
     the slot so a frame is never cut by its own edge during the pan. */
  padding: clamp(150px, 20vh, 210px) clamp(24px, 5vw, 74px) clamp(72px, 10vh, 112px);
}

.sc-frame__giant{
  position: absolute; z-index: 0;
  top: 5%; right: 3%;
  font-family: var(--display);
  font-weight: 800;
  font-variation-settings: "wdth" 92, "opsz" 96;
  font-size: clamp(11rem, 32vw, 38rem);
  line-height: .7; letter-spacing: -.08em;
  color: var(--ink-blue);
  mix-blend-mode: screen;
  opacity: .58;
  pointer-events: none;
}
/* Every other frame draws its numeral rather than flooding it, so the run
   alternates weight instead of stamping the same block nine times. */
.sc-frame__giant--outline{
  color: transparent;
  -webkit-text-stroke: 2px var(--ink-pink);
  opacity: .85;
}

/* ── A frame's artwork ───────────────────────────────────────────────────
   Sized by height so mixed proportions sit in one band — the captures run
   from a 1.35 portrait-ish grab to a 3.29 panorama, and on a fixed width
   that is a tower next to a letter slot.

   The two plates live outside the mount rather than under the image, so the
   pull on hover parts them from the paper instead of from the photograph.
   --reg is the site's own registration spring, so these drift with every
   other plate on the page rather than running their own animation. */
.sc-art{
  --pull: 0px;
  position: relative; z-index: 6;
  display: block;
  appearance: none; border: 0; padding: 0; background: none;
  font: inherit; color: inherit;
  height: min(50svh, 540px);
  width: auto;
  aspect-ratio: var(--ratio, 16 / 10);
  max-width: min(82vw, 1120px);
  cursor: zoom-in;
  transform: rotate(var(--tilt, 0deg));
  transition: --pull .42s var(--ease), transform .42s var(--ease);
}
.sc-art::before, .sc-art::after{
  content: ""; position: absolute; inset: 0; z-index: -1;
  transition: transform .42s var(--ease);
}
.sc-art::before{
  background: var(--ink-pink);
  transform: translate(calc(-7px - var(--reg-x) - var(--pull)), calc(6px + var(--reg-y))) rotate(-.45deg);
}
.sc-art::after{
  background: var(--ink-blue);
  transform: translate(calc(7px + var(--reg-x) + var(--pull)), calc(-6px - var(--reg-y))) rotate(.45deg);
}
.sc-art:hover, .sc-art:focus-visible{
  --pull: 17px;
  z-index: 10;
  transform: rotate(0deg) scale(1.015);
  outline: none;
}
.sc-art:focus-visible .sc-art__mount{ outline: 3px solid var(--ink-yellow); outline-offset: 6px; }

.sc-art__mount{
  position: relative; z-index: 2;
  display: block; height: 100%;
  padding: clamp(5px, .55vw, 10px);
  background: var(--stock-hi);
  border: 1px solid var(--ink-key);
  box-shadow: 0 16px 38px rgba(0, 0, 0, .3);
}
.sc-art__mount img{
  display: block; width: 100%; height: 100%;
  /* contain, and paper behind: the artwork is finished work, so the frame
     never decides the crop, and a transparent SVG never reveals the plates */
  object-fit: contain; object-position: center;
  background: var(--stock-hi);
  border: 1px solid rgba(22, 22, 26, .45);
}

/* ── A frame's copy ──────────────────────────────────────────────────── */
.sc-frame__copy{
  position: relative; z-index: 9;
  max-width: 42ch;
  text-align: center;
}
.sc-frame__eyebrow{
  display: block;
  font-family: var(--mono);
  font-size: 10.5px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-yellow);           /* 14.7:1 on key */
}
.sc-frame__title{
  margin-top: .45em;
  font-family: var(--display);
  font-weight: 800;
  font-variation-settings: "wdth" 92, "opsz" 96;
  font-size: clamp(2rem, 5vw, 5.2rem);
  line-height: .92; letter-spacing: -.04em;
  color: var(--on-key);               /* 14.8:1 on key */
  text-wrap: balance;
}
/* The ground under a frame is a real halftone whose dots open toward the foot
   of the panel, and a frame's copy often lands right in the thick of it. Same
   clearing the page heads use over their fields, in key rather than stock:
   one blurred patch behind the whole block, because a halo per line stacks
   into a patchwork. */
.sc-frame__copy::before{
  content: "";
  position: absolute;
  inset: -1.2em -1.7em;
  background: var(--ink-key);
  filter: blur(28px);
  opacity: .9;
  z-index: -1;
  pointer-events: none;
}

.sc-frame__note{
  margin-top: .9em;
  max-width: 30ch;
  font-size: clamp(14px, 1vw + 8px, 17px);
  line-height: 1.45;
  color: var(--on-key-soft);          /* 7.6:1 on key */
}

/* ── Print furniture ─────────────────────────────────────────────────────
   Cycled by frame index so no two neighbouring frames carry the same mark.
   All of it is real press furniture, so none of it says anything untrue
   about the work it sits next to. */
.sc-fx{
  position: absolute; z-index: 7;
  font-family: var(--mono);
  font-size: 10px; letter-spacing: .16em; text-transform: uppercase;
  pointer-events: none;
}
/* Text furniture gets the same treatment; the drawn marks (crosses, bars,
   rule) are ink on ink and want no clearing at all. */
.sc-fx--edge, .sc-fx--stamp{ isolation: isolate; }
.sc-fx--edge::before{
  content: "";
  position: absolute;
  inset: -.9em -.6em;
  background: var(--ink-key);
  filter: blur(14px);
  opacity: .8;
  z-index: -1;
}

.sc-fx--edge{
  top: 17%; right: 2.5%;
  color: var(--ink-yellow);
  writing-mode: vertical-rl;
}
.sc-fx--stamp{
  top: 16%; right: 5%;
  padding: 10px 13px;
  color: var(--ink-key);
  background: var(--ink-yellow);
  transform: rotate(3deg);
}
.sc-fx--signal{
  right: 7%; bottom: 9%;
  display: flex; align-items: end; gap: 7px;
  height: 150px;
}
.sc-fx--signal i{ width: clamp(7px, .9vw, 13px); background: var(--ink-pink); }
.sc-fx--signal i:nth-child(1){ height: 21%; }
.sc-fx--signal i:nth-child(2){ height: 55%; }
.sc-fx--signal i:nth-child(3){ height: 83%; }
.sc-fx--signal i:nth-child(4){ height: 38%; }
.sc-fx--signal i:nth-child(5){ height: 100%; }
.sc-fx--signal i:nth-child(6){ height: 65%; }
.sc-fx--signal i:nth-child(7){ height: 31%; }
.sc-fx--signal i:nth-child(8){ height: 76%; }

/* Registration marks, the same ones the sheet carries in its corners */
.sc-fx--cross{
  width: clamp(90px, 12vw, 200px);
  aspect-ratio: 1;
  border: 2px solid var(--ink-pink);
  border-radius: 50%;
}
.sc-fx--cross::before, .sc-fx--cross::after{
  content: ""; position: absolute; top: 50%; left: 50%;
  background: var(--ink-pink);
  transform: translate(-50%, -50%);
}
.sc-fx--cross::before{ width: 132%; height: 2px; }
.sc-fx--cross::after { width: 2px; height: 132%; }
/* Clear of the copy block, which starts at 26% on the layout these pair with */
.sc-fx--cross-a{ top: 46%; left: 5%; }
.sc-fx--cross-b{ right: 6%; bottom: 11%; scale: .5; }

/* Right-hand side: on the layout this pairs with, the artwork owns the left */
.sc-fx--rule{
  right: 3.5%; top: 20%; bottom: 20%;
  width: 1px;
  background: linear-gradient(to bottom, transparent, var(--ink-pink), transparent);
}

/* ── Compositions ────────────────────────────────────────────────────────
   Six layouts cycle by index. The earlier version placed artwork and copy
   absolutely against opposite gutters, which is what made a title stop
   looking like it belonged to the image next to it: on a 1400px frame the
   two ended up a screen apart with nothing between them.

   They are columns of one grid now, and always adjacent ones. A caption
   cannot drift away from its artwork because the grid will not let it.
   Variety comes from where the split falls (4/8, 7/5, 5/5) and from how the
   two align vertically, not from distance.

   Below this width the base grid takes over: one column, centred, stacked.
   ───────────────────────────────────────────────────────────────────────── */
@media (min-width: 860px){
  .sc-frame{
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    align-content: center;
    column-gap: clamp(20px, 2.6vw, 46px);
    row-gap: clamp(14px, 1.8vw, 26px);
    justify-items: stretch;
    /* Clears the fixed nav and the stage head above it, both derived */
    padding: calc(var(--nav-h, 90px) + clamp(86px, 11vh, 118px))
             clamp(28px, 4vw, 64px)
             clamp(58px, 8vh, 96px);
  }

  /* Height-driven, then capped by its column. Sizing by width instead made
     the box the column's shape rather than the image's, so a square capture
     in a wide band came out with a hand's width of paper down either side.
     This way most images get a box that is exactly their own proportion, and
     only a panorama too wide for its column gets clamped.

     Each artwork hugs the side its copy is on, so the pair reads as one
     unit rather than as two things that happen to share a row. */
  .sc-frame .sc-art{
    height: var(--art-h, 48svh);
    width: auto;
    max-width: 100%;
    grid-row: 1;
  }
  .sc-frame__copy{
    width: auto; max-width: none;
    grid-row: 1;
    text-align: left;
  }

  /* 01 — copy narrow left, artwork wide right */
  .sc-frame[data-layout="1"] .sc-frame__copy{ grid-column: 1 / 5;  align-self: end; }
  .sc-frame[data-layout="1"] .sc-art       { grid-column: 5 / 13; --art-h: 56svh; align-self: center; justify-self: start; }

  /* 02 — artwork left, copy right */
  .sc-frame[data-layout="2"] .sc-art       { grid-column: 1 / 8;  --art-h: 44svh; align-self: center; justify-self: end; }
  .sc-frame[data-layout="2"] .sc-frame__copy{ grid-column: 8 / 13; align-self: center; }

  /* 03 — the big one. Copy drops to a second row directly beneath the
     artwork's left edge, so the two still read as one unit. */
  .sc-frame[data-layout="3"] .sc-art       { grid-column: 2 / 12; --art-h: 54svh; align-self: end; justify-self: start; }
  .sc-frame[data-layout="3"] .sc-frame__copy{ grid-column: 2 / 9;  grid-row: 2; align-self: start; }

  /* 04 — the small one, held in the middle with air either side */
  .sc-frame[data-layout="4"] .sc-frame__copy{ grid-column: 2 / 7;  align-self: center; }
  .sc-frame[data-layout="4"] .sc-art       { grid-column: 7 / 12; --art-h: 34svh; align-self: center; justify-self: start; }

  /* 05 — artwork wide left, copy tucked low right */
  .sc-frame[data-layout="5"] .sc-art       { grid-column: 1 / 9;  --art-h: 50svh; align-self: start; justify-self: end; }
  .sc-frame[data-layout="5"] .sc-frame__copy{ grid-column: 9 / 13; align-self: end; }

  /* 06 — copy high left, artwork low right */
  .sc-frame[data-layout="6"] .sc-frame__copy{ grid-column: 1 / 5;  align-self: start; }
  .sc-frame[data-layout="6"] .sc-art       { grid-column: 5 / 12; --art-h: 46svh; align-self: end; justify-self: start; }

  /* The numeral is behind everything and free to overlap, but it still moves
     with the composition rather than sitting in one corner all the way
     through. Each layout puts it on the side the artwork is not. */
  .sc-frame[data-layout="1"] .sc-frame__giant{ top: 9%;  left: 2%;  right: auto; }
  .sc-frame[data-layout="2"] .sc-frame__giant{ top: 8%;  right: 3%; left: auto; }
  .sc-frame[data-layout="3"] .sc-frame__giant{ top: 5%;  right: 3%; left: auto; }
  .sc-frame[data-layout="4"] .sc-frame__giant{ bottom: 5%; left: 4%; top: auto; right: auto; }
  .sc-frame[data-layout="5"] .sc-frame__giant{ top: 7%;  right: 3%; left: auto; }
  .sc-frame[data-layout="6"] .sc-frame__giant{ bottom: 6%; right: 4%; top: auto; left: auto; }
}

/* ═══ Pinned sequence ═══════════════════════════════════════════════════
   Only where there is room for it, support for it, and no request against
   it. Everything above stays as written for every other case. */
@supports (animation-timeline: view()){
  @media (min-width: 860px) and (prefers-reduced-motion: no-preference){
    .sc-run{
      height: calc(var(--frames) * 100svh);
      view-timeline-name: --sc;
      view-timeline-axis: block;
    }

    .sc-stage{
      position: sticky; top: 0;
      height: 100svh; min-height: 0;
    }

    .sc-track{
      width: calc(var(--frames) * 100vw);
      height: 100%; min-height: 0;
      overflow: visible;
      scroll-snap-type: none;
      will-change: transform;
    }

    /* Longhands, because animation-name is the one thing these two do not
       set here — it comes from the frame-count variant further down, and a
       shorthand would reset it back to none. */
    .sc-track{ animation-name: sc-pan; }
    .sc-counter__reel{ animation-name: sc-reel; }
    .sc-track,
    .sc-counter__reel{
      animation-duration: 1ms;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: --sc;
      animation-range: entry 100% exit 0%;
    }

    .sc-frame{
      flex: 0 0 100vw; width: 100vw;
      height: 100%; min-height: 0;
      scroll-snap-align: none;
    }

    .sc-progress span{
      animation: sc-progress 1ms linear both;
      animation-timeline: --sc;
      animation-range: entry 100% exit 0%;
    }


    .sc-frame__giant{
      animation: sc-giant 1ms linear both;
      animation-timeline: --sc;
      animation-range: entry 100% exit 0%;
    }

    .sc-hint .sc-hint--free{ display: none; }
    .sc-hint .sc-hint--pinned{ display: inline; }

    /* One continuous move. An earlier version held each frame and then
       jumped between them, which fixed a clipping problem and created a
       worse one: the run stopped dead on every image. The pan is linear —
       the composition is what has to survive being mid-move, not the timing.

       The counter is the one thing that should not be continuous, so it
       steps as each frame lands. steps() will not take a variable, so the
       counts that exist are written out; everything else reads --frames. */
    .showcase[data-frames="2"] .sc-counter__reel{ animation-timing-function: steps(1, end); }
    .showcase[data-frames="3"] .sc-counter__reel{ animation-timing-function: steps(2, end); }
    .showcase[data-frames="4"] .sc-counter__reel{ animation-timing-function: steps(3, end); }
    .showcase[data-frames="5"] .sc-counter__reel{ animation-timing-function: steps(4, end); }
    .showcase[data-frames="6"] .sc-counter__reel{ animation-timing-function: steps(5, end); }
    .showcase[data-frames="7"] .sc-counter__reel{ animation-timing-function: steps(6, end); }
    .showcase[data-frames="8"] .sc-counter__reel{ animation-timing-function: steps(7, end); }

    @keyframes sc-pan{
      from{ transform: translate3d(0, 0, 0); }
      to  { transform: translate3d(calc(-100vw * (var(--frames) - 1)), 0, 0); }
    }
    @keyframes sc-reel{
      from{ transform: translateY(0); }
      to  { transform: translateY(calc(-1.4em * (var(--frames) - 1))); }
    }

    @keyframes sc-progress{
      from{ transform: scaleX(0); }
      to  { transform: scaleX(1); }
    }
    /* The numeral trails its own frame slightly, so the depth reads */
    @keyframes sc-giant{
      from{ translate: 10vw 0; }
      to  { translate: -12vw 0; }
    }
  }
}

/* ── Small screens ───────────────────────────────────────────────────── */
@media (max-width: 859px){
  .sc-stage{ min-height: 0; padding-bottom: 26px; }
  .sc-head{
    grid-template-columns: 1fr auto;
    gap: 10px 16px;
  }
  .sc-progress{ grid-column: 1 / -1; order: 3; }
  .sc-frame{
    flex: 0 0 min(86vw, 560px);
    padding: clamp(84px, 15vh, 120px) 18px 26px;
    gap: 18px;
  }
  .sc-art{ height: min(38svh, 360px); max-width: 82vw; }
  /* Down out of the stage head: with no pinning the frame starts right under
     it, and at 5% the numeral sat behind the frame counter. */
  .sc-frame__giant{ top: 15%; font-size: clamp(7rem, 40vw, 14rem); opacity: .34; }
  .sc-frame__title{ font-size: clamp(1.7rem, 8vw, 2.4rem); }
  .sc-frame__note{ max-width: 34ch; }
  .sc-hint{ position: static; text-align: right; padding: 0 var(--gut); }

  /* ── Composed here too ─────────────────────────────────────────────────
     One narrow column cannot hold copy beside artwork, so the desktop
     layouts do not apply. What it can hold is the thing that made them
     work: scale contrast, and a composition that is not the same every
     frame. Artwork height runs 26svh to 46svh — the same near-factor-of-two
     as the desktop run — and the block shifts left, right or centre, so
     three frames in a row never sit identically.

     Only the furniture that fits comes with it. The stamp does; the
     vertical ink names and the density bars are placed against desktop
     compositions and would land on the work here, so they stay out.
     ─────────────────────────────────────────────────────────────────── */
  .sc-fx{ display: none; }
  /* Bottom of the frame, not the top: the stage head is absolutely placed
     over the first ~140px and the stamp was landing on it. */
  .sc-fx--stamp{
    display: block;
    top: auto; right: auto;
    bottom: 18px; left: 18px;
    font-size: 9px; padding: 7px 10px;
  }

  .sc-frame[data-layout="1"] .sc-art{ height: min(40svh, 330px); }
  .sc-frame[data-layout="2"] .sc-art{ height: min(29svh, 240px); }
  .sc-frame[data-layout="3"] .sc-art{ height: min(46svh, 380px); }
  .sc-frame[data-layout="4"] .sc-art{ height: min(26svh, 215px); }
  .sc-frame[data-layout="5"] .sc-art{ height: min(38svh, 310px); }
  .sc-frame[data-layout="6"] .sc-art{ height: min(33svh, 270px); }

  /* Off-centre, alternating, so the run has a rhythm rather than a column */
  .sc-frame[data-layout="1"]{ justify-items: start; }
  .sc-frame[data-layout="2"]{ justify-items: end; }
  .sc-frame[data-layout="3"]{ justify-items: center; }
  .sc-frame[data-layout="4"]{ justify-items: start; }
  .sc-frame[data-layout="5"]{ justify-items: end; }
  .sc-frame[data-layout="6"]{ justify-items: center; }

  .sc-frame[data-layout="2"] .sc-frame__copy,
  .sc-frame[data-layout="5"] .sc-frame__copy{ text-align: right; }
  .sc-frame[data-layout="1"] .sc-frame__copy,
  .sc-frame[data-layout="4"] .sc-frame__copy{ text-align: left; }

  .sc-frame[data-layout="2"] .sc-frame__giant,
  .sc-frame[data-layout="5"] .sc-frame__giant{ left: 4%; right: auto; }
  .sc-frame[data-layout="4"] .sc-frame__giant{ top: auto; bottom: 6%; }
}

@media (prefers-reduced-motion: reduce){
  .sc-art{ transition: none; }
  .sc-art:hover, .sc-art:focus-visible{ transform: rotate(var(--tilt, 0deg)); }
}

/* ── Zoom — one sheet, close up ───────────────────────────────────────────
   The overlay is the key flood the rest of the site closes on, so looking
   closer at a sheet feels like the page turning over rather than a viewer
   opening on top of it.
   ───────────────────────────────────────────────────────────────────────── */
.zoom{
  position: fixed; inset: 0; z-index: 200;
  display: grid;
  grid-template-rows: auto 1fr auto;
  gap: clamp(14px, 2vw, 26px);
  padding: clamp(16px, 2.4vw, 30px) var(--gut);
  background: var(--ink-key);
  color: var(--on-key);
  opacity: 0;
  visibility: hidden;
  transition: opacity .4s var(--ease), visibility .4s;
}
.zoom[data-open="true"]{ opacity: 1; visibility: visible; }
body[data-zoom="open"]{ overflow: hidden; }

.zoom__close{
  justify-self: end;
  appearance: none; border: 1.5px solid currentColor; background: none;
  color: inherit; cursor: pointer;
  border-radius: 999px; padding: 9px 16px;
  font-size: 11px; letter-spacing: .14em; text-transform: uppercase;
}
.zoom__close:hover{ background: var(--ink-yellow); border-color: var(--ink-yellow); color: var(--ink-key); }

.zoom__stage{
  display: grid; align-content: center; justify-items: center;
  gap: 16px;
  min-height: 0;                       /* let the figure shrink inside 1fr */
}
.zoom__fig{
  --pad: clamp(8px, 1vw, 14px);
  display: block;
  max-width: min(100%, 1180px);
  max-height: 100%;
  aspect-ratio: auto;                  /* the image decides, not the mount */
  border-color: rgba(222, 221, 214, .3);
}
.zoom__fig img{
  display: block;
  width: auto; height: auto;
  max-width: 100%;
  max-height: min(72svh, 780px);
  object-fit: contain;
  position: relative; z-index: 1;
}
.zoom__cap{
  font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--on-key-soft);
  text-align: center;
}

.zoom__bar{
  display: flex; align-items: center; justify-content: center;
  gap: clamp(18px, 3vw, 34px);
}
.zoom__nav{
  appearance: none; border: 1.5px solid rgba(222, 221, 214, .3); background: none;
  color: inherit; cursor: pointer;
  width: 46px; height: 46px; border-radius: 50%;
  font-size: 15px;
}
.zoom__nav:hover{ background: var(--ink-pink); border-color: var(--ink-pink); color: var(--ink-key); }
.zoom__count{ font-size: 11px; letter-spacing: .16em; color: var(--on-key-soft); min-width: 8ch; text-align: center; }

@media (prefers-reduced-motion: reduce){
  .zoom{ transition: none; }
}

/* Fact table — client, sector, scope, live link */
.cs-facts{
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 175px), 1fr));
  gap: clamp(22px, 3vw, 42px);
  border-top: 1.5px solid var(--ink-key);
  border-bottom: 1px solid rgba(22, 22, 26, .22);
  padding: 28px 0;
}
.cs-facts dt{
  font-family: var(--mono); font-size: 11.5px;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--on-stock-soft);
  margin-bottom: 9px;
}
.cs-facts dd{ font-size: 16px; line-height: 1.45; }
.cs-facts dd a{ border-bottom: 1.5px solid var(--ink-pink); }
.cs-facts dd a:hover{ color: var(--ink-pink); }

/* Two-up and three-up image rows */
.cs-pair{
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
  gap: clamp(18px, 2.5vw, 34px);
  margin-block: clamp(36px, 5vw, 70px);
}
.cs-cap{
  margin-top: 12px;
  font-family: var(--mono); font-size: 11px;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--on-stock-soft);
}

/* Body blocks */
.cs-block{
  display: grid;
  grid-template-columns: minmax(0, 4fr) minmax(0, 7fr);
  gap: clamp(24px, 4vw, 70px);
  padding: clamp(34px, 4.5vw, 60px) 0;
  border-top: 1px solid rgba(22, 22, 26, .22);
}
.cs-block h2{
  font-family: var(--display);
  font-variation-settings: "wdth" 94, "opsz" 40;
  font-size: clamp(1.4rem, 2.6vw, 2.2rem);
  line-height: 1;
}
@media (max-width: 820px){ .cs-block{ grid-template-columns: 1fr; } }

/* Next project */
.cs-next{
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 20px; flex-wrap: wrap;
  padding: clamp(34px, 5vw, 64px) 0;
  border-top: 1.5px solid var(--ink-key);
}
.cs-next__name{
  --o: 0px;
  font-family: var(--display); font-weight: 800;
  font-variation-settings: "wdth" 94, "opsz" 72;
  font-size: clamp(2rem, 6vw, 4.6rem);
  line-height: 0.94; letter-spacing: -0.035em;
  text-shadow:
    calc(var(--o) * -1) 0 0 var(--ink-pink),
    var(--o) 0 0 var(--ink-blue);
  transition: text-shadow .5s var(--ease);
}
.cs-next a:hover .cs-next__name, .cs-next a:focus-visible .cs-next__name{ --o: 5px; }

/* ═══════════════════════════════════════════════════════════════════════════
   CLIENT STRIP
   ═══════════════════════════════════════════════════════════════════════════ */
.clients{
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
  gap: 1px;
  background: rgba(22, 22, 26, .22);
  border: 1px solid rgba(22, 22, 26, .22);
}
.clients li{
  background: var(--stock);
  padding: clamp(18px, 2.2vw, 28px);
  display: flex; flex-direction: column; gap: 5px;
}
.clients b{
  font-family: var(--display);
  font-weight: 700;
  font-variation-settings: "wdth" 96, "opsz" 24;
  font-size: clamp(1rem, 1.4vw, 1.25rem);
  line-height: 1.1;
  letter-spacing: -0.02em;
}
.clients span{
  font-family: var(--mono); font-size: 10.5px;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--on-stock-soft);
  line-height: 1.5;
}

/* ═══════════════════════════════════════════════════════════════════════════
   CONTACT PAGE
   ═══════════════════════════════════════════════════════════════════════════ */
.contact--page{
  min-height: 100svh;
  display: flex; flex-direction: column;
  padding-top: clamp(130px, 16vw, 200px);
}
.contact--page > .wrap{ flex: 1; }

/* It's a heading here, not a link — so it wears the hover state permanently */
.cta--static{ cursor: default; }
.cta--static .ch{ color: var(--ink-yellow); }

.contact__split{
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.85fr);
  gap: clamp(38px, 5vw, 84px);
  padding-bottom: clamp(60px, 8vw, 110px);
}
.contact__grid--stack{
  grid-template-columns: 1fr;
  border-top: 0;
  padding-top: 0;
  padding-bottom: 0;
  gap: 22px;
}
.contact__aside{ border-left: 1px solid rgba(222, 221, 214, .22); padding-left: clamp(20px, 3vw, 44px); }

/* ── "See all work →" ─────────────────────────────────────────────────── */
.section-more{ padding-top: clamp(28px, 3.5vw, 46px); }
.link-arrow{
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--mono);
  font-size: 12px; letter-spacing: .14em; text-transform: uppercase;
  padding-bottom: 5px;
  border-bottom: 1.5px solid var(--ink-key);
}
.link-arrow span{ transition: transform .45s var(--ease); }
.link-arrow:hover{ color: var(--ink-pink); border-color: var(--ink-pink); }
.link-arrow:hover span{ transform: translateX(5px); }

@media (max-width: 820px){
  .contact__split{ grid-template-columns: 1fr; }
  .contact__aside{ border-left: 0; padding-left: 0; border-top: 1px solid rgba(222,221,214,.22); padding-top: 30px; }
}
