/* ═════════════════════════════════════════════════════════════════════════
   KYLE McCARTHY · UX DESIGN LEAD
   One stylesheet. No build step. Open the HTML, edit the HTML.

   ── THE CONTRACT ──────────────────────────────────────────────────────────
   Every value below section 2 resolves to a token. There are no loose
   numbers in the component sections. If a rule needs a size, a space, a
   color, a duration or a tracking value that section 2 does not already
   name, the fix is to add a step to the ramp — never to type a literal.

   The exceptions, and they are the only ones:
     · 0.5 / 1 / 2 / 3px      rule weights, the gate's one inked cell, and
                              the optical offset under a drawn underline.
                              The spacing ramp starts at 4px because a rule
                              is not a space.
     · 4 / 8px transforms     the two motion offsets: how far a thing travels
                              on arrival, and how far an arrow nudges.
     · %  fr  ch  em  vw      proportional units, which are relationships
                              rather than measurements.

   ── THE RULES ─────────────────────────────────────────────────────────────
     · 14 type steps, 14 spacing steps. Nothing between them.
     · One ground, one ink, two accents. Every tint is composed from five
       channel tokens, so re-coloring the site is a five-line edit.
     · Hairline borders. Never a shadow.
     · No border-radius, except the fluency pills and the gate's clear disc.
     · Numerals are digits, and tabular wherever they are compared.
     · One italic accent in the fold, and at most one more below it.
       <em> set to font-style:normal (the ratio and status labels) is a
       label, not an accent, and does not count against that budget.
     · Card art is evidence, never a beauty shot.
     · A color is declared only where it CHANGES. Nothing restates the
       value it already inherits.

   ── CONTRAST (2026-09-09 redesign) ────────────────────────────────────────
   Measured against the page ground #1b1917, a soft warm black. Every text
   pair clears WCAG AA; most clear AAA.
     ink      #f3efea   15.31 : 1      acc      #ff7e70    7.07 : 1  (marks AND text)
     ink-80             10.11 : 1      acc-ink  #ff9385    8.15 : 1  (small accent text)
     ink-72              8.30 : 1      acc2     #2dd4bf    9.42 : 1  (teal marks; "state changed")
                                       acc2-ink #5eead4   11.85 : 1  (teal text, rare)
   The salmon clears 4.5 on its own, so it may carry text; --acc-ink is a
   lifted step for small sizes. The teal never carries body copy: it marks
   a state that changed (a filled key, the visitor's cursor on the gate).

   ── SECTIONS ──────────────────────────────────────────────────────────────
     1  Fonts                     8  Work grid          15  Responsive
     2  Tokens                    9  About              16  Redesign motion (home)
     3  Reset and base           10  Case study         17  Case-page motion
     4  Type                     11  Ask band and footer
     5  Nav                      12  Gate
     6  Hero and milestone rail  13  Motion
     7  Facts and fluency        14  The system page
   Sequential. Contiguous. There is exactly ONE :root block and exactly ONE
   responsive section, so there is one place to look when a token is wrong
   and one place to look when something reflows.
   ═════════════════════════════════════════════════════════════════════ */

/* ══ 1 · FONTS ═══════════════════════════════════════════════════════════
   Self-hosted. No third-party request on any page load.
   ─────────────────────────────────────────────────────────────────────── */
@font-face{font-family:'Newsreader';font-style:normal;font-weight:400;font-display:swap;
  src:url('../fonts/newsreader-400.woff2') format('woff2')}
@font-face{font-family:'Newsreader';font-style:italic;font-weight:400;font-display:swap;
  src:url('../fonts/newsreader-400-italic.woff2') format('woff2')}
@font-face{font-family:'Geist';font-style:normal;font-weight:400;font-display:swap;
  src:url('../fonts/geist-400.woff2') format('woff2')}
@font-face{font-family:'Geist';font-style:normal;font-weight:500;font-display:swap;
  src:url('../fonts/geist-500.woff2') format('woff2')}
@font-face{font-family:'Geist Mono';font-style:normal;font-weight:400;font-display:swap;
  src:url('../fonts/geist-mono-400.woff2') format('woff2')}

/* ══ 2 · TOKENS ══════════════════════════════════════════════════════════
   Three layers. Primitives are raw values with no opinion. Semantics give
   them a job. Components (sections 5 onward) only ever reference semantics.
   ─────────────────────────────────────────────────────────────────────── */
:root{
  color-scheme:dark;

  /* ── 2.1 · COLOR PRIMITIVES ────────────────────────────────────────
     Five channel tokens and the ground. Every tint on the site is composed
     from these, which is why re-coloring is a five-line edit.
     2026-09-09 REDESIGN: the ground is a soft, warm black rather than a hard
     one, the ink is a warm off-white, the accent is salmon, and a teal second
     accent exists for the first time, for marks that must stand apart from
     the salmon (a live dot, a second series, the cursor on the gate).
     NOTE: --ground is duplicated in each page's <meta name="theme-color">,
     because a meta tag cannot read a custom property. Change both together. */
  --ground:#1b1917;              /* soft warm black, never #000 */
  --ink-rgb:243,239,234;         /* #f3efea  warm off-white */
  --acc-rgb:255,126,112;         /* #ff7e70  salmon, marks AND text */
  --acc-ink-rgb:255,147,133;     /* #ff9385  salmon lifted a step for small text */
  --acc2-rgb:45,212,191;         /* #2dd4bf  teal, the second accent */

  /* ── 2.2 · COLOR SEMANTICS ─────────────────────────────────────────
     Named by job, not by value. The number on each line is its measured
     contrast against --ground; text roles clear 4.5, marks clear 3. */
  --bg:var(--ground);
  --ink:rgb(var(--ink-rgb));               /* headings, values      15.31 */
  --ink-80:rgba(var(--ink-rgb),.8);        /* body copy             10.11 */
  --ink-72:rgba(var(--ink-rgb),.715);      /* secondary labels       8.30 */
  --ink-30:rgba(var(--ink-rgb),.3);        /* underline on a link      -- */
  --ink-28:rgba(var(--ink-rgb),.28);       /* an unfinished dashed edge  -- */
  --ink-22:rgba(var(--ink-rgb),.22);       /* border on hover          -- */
  --ink-10:rgba(var(--ink-rgb),.12);       /* every hairline (12% on a dark ground reads as 10% did on paper) */
  --ink-02:rgba(var(--ink-rgb),.035);      /* the only fill on the site */
  --ink-015:rgba(var(--ink-rgb),.025);     /* fainter still, under a placeholder */

  --acc:rgb(var(--acc-rgb));               /* marks and graphics     7.07 */
  --acc-ink:rgb(var(--acc-ink-rgb));       /* accent TEXT            8.15 */
  --acc-70:rgba(var(--acc-rgb),.7);        /* the drawn rule           -- */
  --acc-40:rgba(var(--acc-rgb),.4);        /* a live underline         -- */
  --acc-10:rgba(var(--acc-rgb),.14);       /* selection                -- */

  --acc2:rgb(var(--acc2-rgb));             /* teal marks             9.42 */
  --acc2-ink:#5eead4;                      /* teal TEXT, rare       11.85 */
  --acc2-40:rgba(var(--acc2-rgb),.4);
  --acc2-10:rgba(var(--acc2-rgb),.12);

  --hair:1px solid var(--ink-10);          /* THE border. There is one. */
  --grid-line:rgba(var(--ink-rgb),.045);   /* the ground's plan grid */
  --grid-cell:96px;

  /* Two roles the channels cannot cover, both on the gate only.
     --peer is the visitor's cursor: it must NOT be the salmon, because the
     salmon is already carrying the selected frame and its handles, and a
     cursor that matches the selection stops reading as a separate participant.
     The teal is exactly the second participant. Both carry --bg text, and
     both clear 4.5 against the ground. */
  --peer:var(--acc2);                      /* the cursor tag       9.42 */
  --warn:#ff8a80;                          /* a refused password   7.68 */

  /* ── 2.3 · TYPE FAMILIES ────────────────────────────────────────── */
  --serif:'Newsreader',Georgia,'Times New Roman',serif;
  --sans:'Geist',system-ui,-apple-system,sans-serif;
  --mono:'Geist Mono',ui-monospace,SFMono-Regular,Menlo,monospace;

  /* ── 2.4 · TYPE RAMP ─────────────────────────────────────────
     Not a geometric scale. These are the exact sizes the reference design
     uses, in order, and nothing on this site is allowed to be a size that
     is not on this list. A ramp's job is to be closed, not to be pretty. */
  --fs-01:10px;
  --fs-03:11px;
  --fs-04:11.5px;
  --fs-05:12px;
  --fs-06:12.5px;
  --fs-07:13px;
  --fs-08:13.5px;
  --fs-09:14px;
  --fs-10:14.5px;
  --fs-11:15px;
  --fs-12:17px;
  --fs-13:17.5px;
  --fs-14:18px;
  --fs-15:19px;
  --fs-16:20px;
  --fs-17:21px;
  --fs-18:23px;
  --fs-19:24px;
  --fs-20:26px;
  --fs-21:27px;
  --fs-22:28px;
  --fs-23:32px;
  --fs-24:clamp(34px,3.6vw,46px);
  --fs-25:clamp(38px,4.4vw,56px);

  /* ── 2.4b · (the old scale's rationale) ────────────────────────────────────────────────
     Fourteen steps. Ratios tighten through the reading sizes (1.07–1.13)
     and open up above them (1.14–1.17), which is why 14 and 15 can share a
     page without looking like a mistake and 24 and 28 cannot be confused.
     Two steps are fluid; the rest are fixed, because a caption that
     breathes is a caption you notice. */

  /* ── 2.5 · SPACING RAMP ─────────────────────────────────────────────
     Every step is a multiple of 4. Nothing on this site is 7px or 22px. */
  --s-01:4px;   --s-02:8px;   --s-03:12px;  --s-04:16px;
  --s-05:20px;  --s-06:24px;  --s-07:28px;  --s-08:32px;
  --s-09:40px;  --s-10:48px;  --s-11:56px;  --s-12:72px;
  --s-13:96px;  --s-14:112px;

  /* ── 2.6 · LINE HEIGHT ─────────────────────────────────────────
     Nine steps, tightening as the type gets larger. */
  --lh-01:1.08;
  --lh-02:1.15;
  --lh-03:1.2;
  --lh-04:1.35;
  --lh-05:1.4;
  --lh-06:1.45;
  --lh-07:1.5;
  --lh-08:1.55;
  --lh-09:1.62;

  /* ── 2.6b · ──────────────────────────────────────────── */

  /* ── 2.7 · TRACKING ─────────────────────────────────────────
     Serif headings tighten; mono labels open. Ordered smallest to widest. */
  --ls-01:.04em;
  --ls-02:.08em;
  --ls-03:.1em;
  --ls-04:.12em;
  --ls-05:.14em;
  --ls-06:.16em;
  --ls-07:.2em;

  /* ── 2.7b · ─────────────────────────────────────────────────
     Serif headings tighten. Mono labels open. Nothing in between. */
  --ls-tight:-.02em;   /* serif display */
  --ls-snug:-.01em;    /* serif at reading size */

  /* ── 2.8 · LAYOUT ───────────────────────────────────────────────────
     1180 lines the nav up with the case column. Wider made every card a
     billboard. --measure caps prose; --measure-tight caps a serif lede,
     which runs shorter because it is set larger. */
  --maxw:1180px;
  --gutter:clamp(28px,5vw,72px);
  --measure:66ch;
  --measure-tight:54ch;
  --pad-pill:6px 14px;   /* OPTICAL. Two values the 4px ramp cannot hold, because
                            a pill's padding is typographic inset rather than
                            layout. Matching the reference exactly matters more. */
  --gap-unit:6px;        /* between a numeral and the unit beside it */
  --s-08b:36px;          /* the contact sheet's one off-ramp step */
  --rail-year:64px;      /* the fixed year column in the milestone rail */
  --toc-w:200px;         /* the case sidebar */
  --radius-pill:999px;   /* the only radius on the site, plus the gate disc */
  --embed-h:auto;        /* a framed prototype's measured height. Declared here so
                            the reference always resolves; the real value is set on
                            the frame itself, in px, once it has measured itself. */
  /* THE GATE'S OWN GEOMETRY. Component dimensions, not spacing steps, which
     is why they are named rather than taken from the --s- scale: a selection
     handle is 8px because that is what a handle is, not because 8 is next. */
  --hnd:8px;             /* a selection handle */
  --hnd-off:-4px;        /* half of it, to sit astride the corner */
  --cur-w:17px;          /* the pen-name cursor */
  --cur-h:24px;
  --insp:158px;          /* the inspector rail */
  --dim-drop:-26px;      /* the dimension badge, below the frame */
  --sw:12px;             /* a colour swatch in the inspector */
  --cut-w:420px;         /* the About portrait, capped so it cannot become
                            a column of dead space beside the copy */

  /* ── 2.9 · MOTION ───────────────────────────────────────────────────
     One curve. Four durations. Everything that moves picks from these. */
  --ease:cubic-bezier(.16,1,.3,1);
  --dur-fast:.22s;   /* a dot sliding across a plate */
  --dur:.3s;         /* color and border on hover */
  --dur-slow:.5s;    /* an element arriving */
  --dur-exit:.72s;   /* the gate dissolving */
  --dur-sig:1.6s;    /* the signature writing itself */

  /* ── 2.10 · REDESIGN MOTION (2026-09-09) ────────────────────────────
     The handful of new values section 16's interactions needed and the
     existing ramp had no step for. Everything else in that section
     resolves to a token already declared above. */
  --blur-skim:1.5px;    /* the skim lede's unread blur */
  --glow-r:220px;       /* the case well's pointer glow, outer radius */
  --ring-r:180px;       /* the case well's lit-hairline radius, tighter than the glow */
  --mq-h:28px;          /* the platform marquee's row height */
  --dur-vt-root:.45s;   /* cross-document view-transition root cross-fade */
  --dur-vt-group:.5s;   /* a named view-transition (a card's cover or title) morphing */
}

/* ══ 3 · RESET AND BASE ══════════════════════════════════════════════════ */
*{margin:0;padding:0;box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  background:var(--bg);
  color:var(--ink-80);
  font-family:var(--sans);
  font-size:var(--fs-11);
  line-height:var(--lh-07);
  -webkit-font-smoothing:antialiased;
}
/* height:auto is load-bearing, not tidiness. An <img> that carries intrinsic
   width and height attributes, which it must, so the page reserves its space
   before the file lands, will otherwise keep the height attribute while
   max-width shrinks the width, and render stretched. The components that set
   both axes themselves override this and are unaffected. */
img,svg,iframe,video,canvas{display:block;max-width:100%;height:auto}
/* THE GROUND'S GRID. 2026-09-09: a very faint plan grid behind everything,
   for depth. Two repeating gradients on a fixed pseudo-element, 96px cells,
   the ink at 4%, faded out toward the bottom so the footer stays clean.
   Nothing on the site is positioned against it; it is texture, not layout. */
body{position:relative;isolation:isolate}
body::before{content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;
  background:
    linear-gradient(to right,var(--grid-line) 1px,transparent 1px) 0 0/var(--grid-cell) var(--grid-cell),
    linear-gradient(to bottom,var(--grid-line) 1px,transparent 1px) 0 0/var(--grid-cell) var(--grid-cell);
  -webkit-mask-image:linear-gradient(to bottom,rgba(0,0,0,1) 0,rgba(0,0,0,.55) 60%,rgba(0,0,0,0) 100%);
  mask-image:linear-gradient(to bottom,rgba(0,0,0,1) 0,rgba(0,0,0,.55) 60%,rgba(0,0,0,0) 100%)}

a{color:inherit;text-decoration:none}
::selection{color:var(--acc-ink);background:var(--acc-10)}
:focus-visible{outline:2px solid var(--acc);outline-offset:3px}

/* the page measure, used by every section that is not full-bleed */
.wrap{max-width:var(--maxw);margin-inline:auto;padding-inline:var(--gutter)}

/* text for screen readers only. clip-path is the modern half; clip stays for
   older WebKit, which is the only reason two properties are here. */
.sr{position:absolute;width:1px;height:1px;overflow:hidden;white-space:nowrap;
  clip:rect(0 0 0 0);clip-path:inset(50%)}

/* ══ 4 · TYPE ════════════════════════════════════════════════════════════
   Three families, three jobs. Serif carries every heading and every claim
   set large. Sans carries running copy. Mono carries anything that is a
   label, a year, a unit, or a number being compared.
   ─────────────────────────────────────────────────────────────────────── */
h1,h2,h3{font-family:var(--serif);font-weight:400;letter-spacing:var(--ls-tight);
  color:var(--ink)}
h1{font-size:var(--fs-25);line-height:var(--lh-01)}
h2{font-size:var(--fs-23);line-height:var(--lh-03)}
h3{font-size:var(--fs-12);line-height:var(--lh-04)}
em{font-style:italic}

/* the mono voice: uppercase, tracked, at reading size */
.mono{font-family:var(--mono);text-transform:uppercase;font-size:var(--fs-11)}

/* the accent label that opens a section */
.eyebrow{font-family:var(--mono);text-transform:uppercase;font-size:var(--fs-03);
  letter-spacing:var(--ls-06);color:var(--acc-ink)}

/* the serif paragraph under a heading */
.lede{font-family:var(--serif);font-size:var(--fs-15);line-height:var(--lh-07);
  letter-spacing:var(--ls-snug);color:var(--ink);max-width:var(--measure-tight)}

/* A link inside a lede draws its own rule once the fold has settled. The link
   is held on one line because an absolutely positioned ::after attaches to the
   first fragment of a wrapped inline only, and would otherwise draw into
   nothing at all. */
.lede a{position:relative;white-space:nowrap;transition:color var(--dur) var(--ease)}
.lede a::after{content:"";position:absolute;left:0;right:0;bottom:-3px;height:2px;
  background:var(--acc-70);transform:scaleX(0);transform-origin:left center;
  animation:draw var(--dur-exit) var(--ease) var(--dur-slow) forwards}
.lede a:hover{color:var(--acc-ink)}
.lede a:hover::after{background:var(--acc)}

/* the standing rule for a link inside body prose. The reset strips underlines,
   so prose links get a hairline and take the accent on hover. */
.tie{border-bottom:1px solid var(--ink-30);
  transition:color var(--dur) var(--ease),border-color var(--dur) var(--ease)}
.tie:hover{color:var(--acc-ink);border-bottom-color:var(--acc-ink)}

/* ══ 5 · NAV ═════════════════════════════════════════════════════════════ */
.nav{border-bottom:var(--hair);padding-block:var(--s-05);background:var(--bg)}
.nav .wrap{display:flex;justify-content:space-between;align-items:center;gap:var(--s-06)}
.nav .lock{display:flex;gap:var(--s-04);align-items:baseline}
.nav .lock b{color:var(--ink);font-weight:400}
.nav .links{display:flex;gap:var(--s-08)}
.nav .links a{transition:color var(--dur) var(--ease)}
.nav .links a:hover{color:var(--acc-ink)}
.nav .links a[aria-current]{color:var(--acc-ink);cursor:default}

/* ══ 6 · HERO AND MILESTONE RAIL ═════════════════════════════════════════
   Two columns sharing a baseline. The claim on the left, the receipts on
   the right, so the fold answers "who is this" and "what has he shipped"
   at the same time.
   ─────────────────────────────────────────────────────────────────────── */
.hero{display:grid;grid-template-columns:1fr 1fr;gap:var(--s-10);
  padding-block:var(--s-14) var(--s-11);align-items:end}
/* 2026-09-09 review: one ramp step down from the default h1, and capped so
   the shorter headline still holds a predictable 3 lines at 1440 and 4 at
   a phone's width rather than reflowing unpredictably with the copy. */
.hero h1{font-size:var(--fs-24);max-width:20ch}
.hero .lede{margin-top:var(--s-05)}

.rail-note{font-family:var(--mono);text-transform:uppercase;font-size:var(--fs-01);
  letter-spacing:var(--ls-04);color:var(--ink-72);margin-bottom:var(--s-04)}
.rail{display:flex;flex-direction:column;gap:var(--s-02);list-style:none}
.rail .row{display:flex;gap:var(--s-03);align-items:baseline}
/* 2026-09-09 review: the salmon budget tightened, so the year drops to a
   quiet --ink-72 rather than doubling as an accent label. */
.rail .y{font-family:var(--mono);font-size:var(--fs-07);color:var(--ink-72);
  width:var(--rail-year);min-width:var(--rail-year);font-variant-numeric:tabular-nums}
.rail .t{color:var(--ink)}
/* 2026-09-09 (round 5): every milestone holds to ONE line on a desktop. The
   hero keeps its original two equal columns (the lede's measure depends on
   it); the longest milestone was shortened instead, the row is set one step
   down, and .t refuses to wrap. Below the tablet breakpoint the site's own
   max width (1180) the rows may wrap again; a narrow laptop or a phone
   cannot hold the longest line, and an overflow would be worse. */
@media (min-width:1180px){
  .rail .t{font-size:var(--fs-09);white-space:nowrap}
}
@media (min-width:901px) and (max-width:1179px){
  .rail .t{font-size:var(--fs-09)}
}

/* ══ 7 · FACTS AND FLUENCY ═══════════════════════════════════════════════
   Four numbers, no more. Each wears its mechanism underneath, because a
   number without a mechanism is a claim and a number with one is evidence.
   ─────────────────────────────────────────────────────────────────────── */
.facts{border-top:var(--hair);border-bottom:var(--hair);
  padding-block:var(--s-07) var(--s-06);
  display:grid;grid-template-columns:repeat(4,1fr);gap:var(--s-06)}
.facts .f b{display:block;font-family:var(--mono);font-weight:400;font-size:var(--fs-03);
  letter-spacing:var(--ls-06);text-transform:uppercase;color:var(--acc-ink);
  margin-bottom:var(--s-02)}
.facts .f .v{font-family:var(--mono);font-size:var(--fs-22);line-height:var(--lh-02);
  color:var(--ink);font-variant-numeric:tabular-nums}
.facts .f .v .u{font-size:var(--fs-07);color:var(--ink-80);margin-left:var(--gap-unit);
  letter-spacing:var(--ls-01);white-space:nowrap}
.facts .f small{display:block;font-size:var(--fs-07);line-height:var(--lh-06);
  margin-top:var(--s-02)}

/* the fluency row shares the facts grid and spans it */
.pills{grid-column:1/-1;border-top:1px dashed var(--ink-10);padding-top:var(--s-04);
  display:flex;align-items:center;gap:var(--s-03);flex-wrap:wrap}
.pills .lab{font-family:var(--mono);font-size:var(--fs-03);letter-spacing:var(--ls-06);
  text-transform:uppercase;color:var(--acc-ink);margin-right:var(--gap-unit)}
.pill{font-family:var(--mono);font-size:var(--fs-04);letter-spacing:var(--ls-02);
  text-transform:uppercase;color:var(--ink);border:var(--hair);
  border-radius:var(--radius-pill);padding:var(--pad-pill);white-space:nowrap}

/* ══ 8 · WORK GRID ═══════════════════════════════════════════════════════
   Two columns. Height rhythm comes from per-card aspect ratios, never from
   a multi-column span grid: a span grid makes the reading order argue with
   the visual order, and this page has a reading order.

   THE RATIO SET. Three, and only three. Adding a card means choosing one of
   them, not inventing a fourth.
     .a169   16:9   a wide artifact — a console, a flow, a running prototype
     .a45     4:5   a portrait — a person, a phone, a tall document
   ─────────────────────────────────────────────────────────────────────── */
/* THE POOL AND THE DEAL. Every case is authored once, as a direct child of
   .grid, in pool order. That is also the reading order, so with JavaScript off
   the page is a single readable column of every case. The script filters the
   pool by variant, sorts by rank, and deals the survivors alternately into two
   .gcol wrappers, at which point .dealt turns the container into the two-column
   layout. Reading order still equals rank order, which CSS `order` could never
   have given us because it cannot move a card between columns. */
.grid{display:grid;grid-template-columns:1fr 1fr;gap:var(--s-06);
  padding-block:var(--s-11) var(--s-06);align-items:start}
.gcol{display:flex;flex-direction:column;gap:var(--s-07)}

/* THE POOL, BEFORE IT IS DEALT. Only the home page's work grid is a pool: its
   cards are authored as flat siblings in rank order and the script deals them
   into two .gcol wrappers. Until then it is one readable column, which is what
   somebody with JavaScript off sees. The About contact sheet is NOT a pool and
   must never inherit this: it is two columns, always, with no script involved. */
.grid.pool:not(.dealt){display:flex;flex-direction:column;gap:var(--s-07)}
.card{display:flex;flex-direction:column}

/* THE WELL. One hairline, no fill, no radius, no shadow. Defined once here
   and used by three things: the work grid, the contact sheet, and the About
   portrait. Whatever goes inside fills it edge to edge — every photograph on
   this site is already cut to the well's ratio, so cover crops nothing and is
   here only so a re-cut file can never leave a seam. */
.ph{position:relative;border:var(--hair);overflow:hidden;
  display:flex;align-items:center;justify-content:center;
  transition:border-color var(--dur) var(--ease)}
.ph img{width:100%;height:100%;object-fit:cover}

/* A LIVE ARTIFACT: the real prototype, running as itself inside the well. An
   iframe rather than a redraw or a recording, so it is exact, style-isolated
   and still the thing it claims to be. pointer-events stay off, which leaves
   the card's own link owning the whole surface. */
.ph.live iframe{width:100%;height:100%;border:0;pointer-events:none}
.card:hover .ph{border-color:var(--ink-22)}

.a169 .ph{aspect-ratio:16/9}
.a45  .ph{aspect-ratio:4/5}

/* THE PLACEHOLDER. A well carrying its own brief until the artifact exists.
   Delete the .ghost, drop the artifact into the same .ph, and nothing else
   about the card changes. The crossed diagonals are the printer's mark for
   "picture goes here", drawn in hairlines so the slot reads as unfinished
   rather than as a design element. */
.ghost{position:absolute;inset:0;display:flex;flex-direction:column;
  justify-content:flex-end;gap:var(--s-02);padding:var(--s-05) var(--s-06);
  background:
    linear-gradient(to top right,transparent calc(50% - .5px),var(--ink-10) 50%,transparent calc(50% + .5px)),
    linear-gradient(to bottom right,transparent calc(50% - .5px),var(--ink-10) 50%,transparent calc(50% + .5px)),
    var(--ink-015)}
.ghost b{font-family:var(--mono);font-weight:400;font-size:var(--fs-01);
  letter-spacing:var(--ls-06);text-transform:uppercase;color:var(--acc-ink)}
.ghost p{font-size:var(--fs-06);line-height:var(--lh-08);max-width:44ch}
.ghost em{font-style:normal;font-family:var(--mono);font-size:var(--fs-03);color:var(--ink)}

.card .cap{display:flex;justify-content:space-between;gap:var(--s-02);
  margin-top:var(--s-03);align-items:baseline}
.card .cap .m{font-family:var(--mono);text-transform:uppercase;font-size:var(--fs-06);
  text-align:right;white-space:nowrap}
/* the deck lost its .card scope so the platform list below can use it
   unchanged. Nothing that already carried one renders differently. */
.deck{font-size:var(--fs-09);margin-top:var(--s-01);max-width:60ch}
.deck b{color:var(--ink);font-weight:400;font-variant-numeric:tabular-nums}

/* ── THE PLATFORM LIST ────────────────────────────────────────────────
   The closing section: the whole product span in seven rows. The name and
   the role chip are always on — they are what a reviewer scans for — and
   only the copy folds away, on a native <details> so it needs no script
   and keeps its keyboard and screen-reader behaviour for free.

   No new visual language: .sechead heads it, .pill carries the role,
   .deck carries the copy, and the row is one hairline. The mark is the
   only drawn thing, and it is a mono glyph in the accent, like every
   other caret on the site. */
.plat{border-top:var(--hair)}
.plat > summary{display:grid;grid-template-columns:1fr auto auto;
  gap:var(--s-02) var(--s-04);align-items:baseline;
  padding-block:var(--s-05);cursor:pointer;list-style:none}
.plat > summary::-webkit-details-marker{display:none}
.plat h3{transition:color var(--dur) var(--ease)}
.plat > summary:hover h3{color:var(--acc-ink)}
.plat .mark{font-family:var(--mono);font-size:var(--fs-07);color:var(--acc);
  justify-self:end}
.plat .mark::after{content:"+"}
.plat[open] .mark::after{content:"\2212"}
.plat .deck{margin-top:0;padding-bottom:var(--s-05)}

/* ══ 9 · ABOUT ═══════════════════════════════════════════════════════════
   Two halves on one page. Above the seam, how he thinks about the work, in
   two columns that share rows so the hairlines run straight across. Below
   it, the contact sheet: the home page's grid with a different subject
   inside it. Same tokens, same caption grammar, no new vocabulary.
   ─────────────────────────────────────────────────────────────────────── */

/* the introduction. The portrait sits in the same well, at the same 4:5, as
   every picture in the contact sheet below it — because it is the same kind
   of picture, and giving it a private treatment would be a second visual
   language on one page. It is capped by width rather than height so it can
   never grow taller than the copy beside it. */
.intro{display:grid;grid-template-columns:1.12fr .88fr;gap:0 var(--s-10);
  align-items:center;padding-block:var(--s-12) var(--s-01)}
.intro .lede{margin-top:var(--s-05)}
.intro .cut{justify-self:stretch;display:flex;justify-content:center}
.intro .cut .ph{width:100%;max-width:var(--cut-w);aspect-ratio:4/5}

/* the orienting line. About is the page people deep-link to, so it says what
   he is looking for before it says what he thinks. */
.orient{font-family:var(--mono);text-transform:uppercase;font-size:var(--fs-03);
  letter-spacing:var(--ls-05);margin-top:var(--s-06)}
.orient b{font-weight:400;color:var(--ink)}

.sechead{border-top:var(--hair);margin-top:var(--s-11);padding-top:var(--s-08)}
.sechead h2{max-width:22ch}
.sechead .dek{font-family:var(--serif);font-size:var(--fs-15);line-height:var(--lh-07);
  letter-spacing:var(--ls-snug);max-width:58ch;margin-top:var(--s-04)}

/* Two columns that share rows, so each belief sits level with the thing it
   replaced. DOM order is the mobile reading order — all of column one, then
   all of column two — which is why the placement map below exists rather
   than relying on source order. */
.pairs{display:grid;grid-template-columns:1fr 1fr;gap:0 var(--s-11);padding-top:var(--s-08)}
.pairs > *:nth-child(1){grid-column:1;grid-row:1}
.pairs > *:nth-child(2){grid-column:1;grid-row:2}
.pairs > *:nth-child(3){grid-column:1;grid-row:3}
.pairs > *:nth-child(4){grid-column:1;grid-row:4}
.pairs > *:nth-child(5){grid-column:1;grid-row:5}
.pairs > *:nth-child(6){grid-column:2;grid-row:1}
.pairs > *:nth-child(7){grid-column:2;grid-row:2}
.pairs > *:nth-child(8){grid-column:2;grid-row:3}
.pairs > *:nth-child(9){grid-column:2;grid-row:4}
.pairs > *:nth-child(10){grid-column:2;grid-row:5}

.ulab{font-family:var(--mono);font-size:var(--fs-01);letter-spacing:var(--ls-06);
  text-transform:uppercase;color:var(--ink-72);margin:0 0 var(--s-02)}
.ulab.hot{color:var(--acc-ink)}
.cell{border-top:var(--hair);padding:var(--s-03) var(--s-01) var(--s-04);
  font-size:var(--fs-10);line-height:var(--lh-09)}
.cell:nth-child(5),.cell:nth-child(10){border-bottom:var(--hair)}
.cell b{font-weight:500;color:var(--ink)}

/* the seam. One hairline, one label, one line. The air around it is doing as
   much work as the rule is, so it does not tighten. */
.seam{border-top:var(--hair);margin-top:var(--s-12);padding-top:var(--s-07)}
.seam .eyebrow{display:block;margin-bottom:var(--s-04)}
.turn{font-family:var(--serif);font-size:var(--fs-17);line-height:var(--lh-05);
  letter-spacing:var(--ls-tight);color:var(--ink)}
.grid.tight{padding-top:var(--s-08b)}

/* the unit with no photograph. Same well, same ratio, same caption grammar;
   only the contents changed, so the sheet stays a regular grid of four. */
.card.txt .ph{padding:var(--s-08)}
.quiet{font-family:var(--serif);font-size:var(--fs-18);line-height:var(--lh-05);
  letter-spacing:var(--ls-tight);color:var(--ink)}

.sheet-note{border-top:var(--hair);margin-top:var(--s-06);padding-top:var(--s-04);
  font-family:var(--mono);text-transform:uppercase;font-size:var(--fs-01);
  letter-spacing:var(--ls-05);color:var(--ink-72)}

/* ── THE TWO AUDIENCES ───────────────────────────────────────────────
   Kyle applies for senior IC design roles and for design leadership roles
   at the same time. The evidence is the same; the emphasis and the running
   order are not. Both variants' short copy lives in the DOM at all times and
   CSS reveals exactly one, so nothing is ever painted from an empty state and
   filled in afterwards. <html> ships with data-active-variant="ic" already on
   it, which means the default render is correct with no script at all. */
/* Hide only the audience NOT being served. Never set display on the visible
   half: the first version of this rule did, and `display:block` beat
   `.facts{display:grid}`, which flattened the four-figure stat ribbon into a
   single stacked column. A reveal must not have an opinion about layout.
   Safe because every page hard-codes data-active-variant="ic" on <html>,
   which audit check 15 enforces. */
[data-active-variant="ic"]   [data-v~="lead"]{display:none}
[data-active-variant="lead"] [data-v~="ic"]{display:none}

/* an unconfirmed fact, rendered as a visible hole. A gap is honest; a
   plausible guess is not. These come off before the page ships. */
/* THE TITLES he is open to. It was a dashed hole while the answer was
   missing, because a gap is honest and a plausible guess is not. The answer
   arrived, so it is now what it always should have been: a quiet line under
   the ask, in the same mono the rest of the furniture uses. */
.titles{display:block;font-family:var(--mono);font-size:var(--fs-05);
  letter-spacing:var(--ls-04);text-transform:uppercase;color:var(--ink-72);
  margin-top:var(--s-04)}

/* ── THE DOMAIN BAND ─────────────────────────────────────────────────
   Everything he touched, in one scan, with the only column that matters:
   whether he drew it or directed it. Cases carry depth; this carries span,
   and it costs no case slot to do it. Hairlines only, no prose. */
.band{border-top:var(--hair);margin-top:var(--s-10);padding-top:var(--s-04)}
.band .ulab{margin-bottom:var(--s-04)}
.band ol{list-style:none}
.brow{display:grid;grid-template-columns:minmax(0,10rem) minmax(0,1fr) minmax(0,7rem);
  gap:var(--s-06);align-items:baseline;
  padding-block:var(--s-03);border-bottom:var(--hair)}
.bname{color:var(--ink)}
.bdesc{font-size:var(--fs-09)}
.bkind{font-family:var(--mono);text-transform:uppercase;font-size:var(--fs-01);
  letter-spacing:var(--ls-06);color:var(--acc-ink);text-align:right}

/* ══ 10 · CASE STUDY ═════════════════════════════════════════════════════
   A sticky contents rail and one narrow reading column. The column is capped
   at --measure because a case study is read, not scanned, and 66 characters
   is where a line stops being read and starts being skimmed.
   ─────────────────────────────────────────────────────────────────────── */
.case{display:grid;grid-template-columns:var(--toc-w) minmax(0,1fr);gap:0 var(--s-11);
  max-width:var(--maxw);margin-inline:auto;padding-inline:var(--gutter)}
.side{position:sticky;top:0;height:fit-content;align-self:start;padding-top:var(--s-11)}
.side .back{font-family:var(--mono);text-transform:uppercase;font-size:var(--fs-03);
  letter-spacing:var(--ls-05);transition:color var(--dur) var(--ease)}
.side .back:hover{color:var(--acc-ink)}
.toc{list-style:none;margin-top:var(--s-08);display:flex;flex-direction:column;gap:var(--s-02)}
.toc a{display:block;font-size:var(--fs-08);line-height:var(--lh-04);
  border-left:1px solid transparent;padding-left:var(--s-03);margin-left:calc((var(--s-03) + 1px) * -1);
  transition:color var(--dur) var(--ease),border-color var(--dur) var(--ease)}
.toc a:hover{color:var(--ink)}
.toc a.on{color:var(--ink);border-left-color:var(--acc-ink)}
.body{padding-top:var(--s-11);max-width:var(--measure);min-width:0}

.chead .kick{font-family:var(--mono);text-transform:uppercase;font-size:var(--fs-03);
  letter-spacing:var(--ls-06);color:var(--acc-ink)}
.chead h1{font-size:var(--fs-24);margin-top:var(--s-04)}
.chead .sub{font-family:var(--serif);font-size:var(--fs-16);line-height:var(--lh-06);
  letter-spacing:var(--ls-snug);color:var(--ink);margin-top:var(--s-05)}

.meta{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--s-06);
  margin-top:var(--s-08);padding-block:var(--s-06);
  border-top:var(--hair);border-bottom:var(--hair)}
.meta b{display:block;font-family:var(--mono);font-weight:400;font-size:var(--fs-01);
  letter-spacing:var(--ls-06);text-transform:uppercase;color:var(--acc-ink);
  margin-bottom:var(--s-02)}
.meta span{display:block;font-size:var(--fs-08);line-height:var(--lh-06);color:var(--ink)}

.sec{padding-top:var(--s-11)}
.sec .eyebrow{display:block;margin-bottom:var(--s-03)}
.sec h2{font-size:var(--fs-21);margin-bottom:var(--s-04)}
.sec h3{font-size:var(--fs-13);margin:var(--s-07) 0 var(--s-02)}
.sec p{margin-bottom:var(--s-04)}
.sec p b{font-weight:500;color:var(--ink)}

/* one sentence, set large, used once per case at most */
.pull{font-family:var(--serif);font-size:var(--fs-17);line-height:var(--lh-05);
  letter-spacing:var(--ls-tight);color:var(--ink);
  border-left:2px solid var(--acc);padding:2px 0 2px var(--s-05);margin-block:var(--s-07)}

/* THE ARTIFACT SLOT. The brief for a figure that does not exist yet, sitting
   in the space that figure will fill. Dashed, because dashed means
   unfinished everywhere else on this site too. */
.slot{border:1px dashed var(--ink-28);background:var(--ink-02);
  padding:var(--s-06);margin-block:var(--s-07);
  display:flex;flex-direction:column;justify-content:center;gap:var(--s-02)}
.slot b{font-family:var(--mono);font-weight:400;font-size:var(--fs-01);
  letter-spacing:var(--ls-06);text-transform:uppercase;color:var(--acc-ink)}
.slot p{font-size:var(--fs-06);line-height:var(--lh-08);max-width:62ch;margin:0}
.slot em{font-style:normal;font-family:var(--mono);font-size:var(--fs-03);color:var(--ink)}

/* A WORKING NOTE, deliberately loud. Every one of these is a question only
   Kyle can answer, sitting in the paragraph it blocks. They come off before
   the page ships, and the page is not finished while one is still on it. */

/* A FIGURE. Image, then its callouts, then the caption. The heading above it
   takes the first fixation and the caption carries the decision, which is why
   a figure never appears without both.

   NOTE ON SCALE. A recreated interface from another product is exempt from
   this site's type and spacing ramps. Forcing Triton's console onto Kyle's
   editorial scale would make the recreation a lie about what was built. */
figure{margin-block:var(--s-07)}
figure figcaption{font-size:var(--fs-06);line-height:var(--lh-07);
  color:var(--ink-80);margin-top:var(--s-03)}
figure figcaption b{font-family:var(--mono);font-weight:400;font-size:var(--fs-01);
  letter-spacing:var(--ls-05);text-transform:uppercase;color:var(--acc-ink);
  margin-right:var(--s-02)}

/* AN EMBEDDED ARTIFACT: a running prototype, framed rather than screenshotted.
   It carries its own styles and its own fonts, because it is another product's
   interface, which is the same exemption the console recreations take. No px
   height: aspect-ratio is the honest way to size a frame you do not control. */
/* A CAPTURE of a real screen, framed the same way an embed is: a hairline and
   nothing else. The console draws its own borders, so a second frame around it
   would read as two products. Intrinsic width and height on the tag, so the
   figure reserves its space before the file lands and the callouts below it
   never jump. */
figure .shot{display:block;width:100%;height:auto;border:var(--hair);
  background:var(--ink-02)}

figure .embed{display:block;width:100%;border:0;color-scheme:light;aspect-ratio:4/3}
/* Once the frame has measured its own contents it stops guessing at a ratio
   and takes their real height, so the artifact is whole and never scrolls
   inside the page. The 4:3 above is the fallback for a browser that never
   runs the measurement. */
figure .embed.fit{aspect-ratio:auto;height:var(--embed-h)}

/* CALLOUTS. Numbered, anchored to regions of the artwork, with the legend
   directly beneath it and never in a paragraph above. Four per image, twelve
   words each: nine callouts tells a reader that nothing mattered more than
   anything else. */
.calls{list-style:none;margin-top:var(--s-03);display:flex;flex-direction:column;
  gap:var(--s-02)}
.calls li{display:grid;grid-template-columns:1.25rem minmax(0,1fr);gap:var(--s-03);
  font-size:var(--fs-09);line-height:var(--lh-07);color:var(--ink-80)}
.calls .n{font-family:var(--mono);font-size:var(--fs-01);letter-spacing:var(--ls-06);
  color:var(--acc-ink);padding-top:3px}

/* the next case is a full-width target, not a text link */
.cnext{margin-top:var(--s-12)}
.nxt{display:block;border:var(--hair);padding:var(--s-07);
  transition:border-color var(--dur) var(--ease),background-color var(--dur) var(--ease)}
.nxt:hover{border-color:var(--ink-22);background:var(--ink-02)}
.nxt .k{display:block;font-family:var(--mono);font-size:var(--fs-01);
  letter-spacing:var(--ls-06);text-transform:uppercase;color:var(--acc-ink);
  margin-bottom:var(--s-03)}
.nxt .t{display:block;font-family:var(--serif);font-size:var(--fs-20);
  line-height:var(--lh-03);letter-spacing:var(--ls-tight);color:var(--ink)}
.nxt .d{display:block;font-size:var(--fs-09);line-height:var(--lh-07);
  margin-top:var(--s-02);max-width:56ch}
.nxt .go{display:inline-block;font-family:var(--mono);font-size:var(--fs-03);
  letter-spacing:var(--ls-06);text-transform:uppercase;color:var(--ink);
  margin-top:var(--s-04);border-bottom:1px solid var(--ink-10);padding-bottom:var(--s-01);
  transition:border-color var(--dur) var(--ease)}
.nxt .go i{font-style:normal;display:inline-block;margin-left:var(--s-01);
  transition:transform var(--dur) var(--ease)}
.nxt:hover .go{border-bottom-color:var(--acc-ink)}
.nxt:hover .go i{transform:translateX(4px)}

.cfoot{display:flex;gap:var(--s-07);flex-wrap:wrap;margin-top:var(--s-06);
  padding-bottom:var(--s-13);
  font-family:var(--mono);text-transform:uppercase;font-size:var(--fs-03);
  letter-spacing:var(--ls-03)}
.cfoot a{transition:color var(--dur) var(--ease)}
.cfoot a:hover{color:var(--acc-ink)}

/* ══ 11 · ASK BAND AND FOOTER ════════════════════════════════════════════ */
.ask{border-top:var(--hair);margin-top:var(--s-11);padding-block:var(--s-10) var(--s-02)}
.ask .in{display:flex;justify-content:space-between;align-items:baseline;
  gap:var(--s-06);flex-wrap:wrap}
.ask h2{max-width:30ch}
.ask .links{display:flex;gap:var(--s-07);align-items:baseline}
.ask .links a{font-family:var(--mono);text-transform:uppercase;font-size:var(--fs-07);
  color:var(--ink);border-bottom:1px solid var(--ink-10);padding-bottom:var(--s-01);
  transition:border-color var(--dur) var(--ease)}
.ask .links a:hover{border-bottom-color:var(--ink)}
.ask .links a.hot{color:var(--acc-ink);border-bottom-color:var(--acc-40)}

.foot{border-top:var(--hair);padding-block:var(--s-05) 0;margin-top:var(--s-10)}

/* THE SIGN-OFF. 2026-09-09 restore: Kyle wants it cut off at the bottom,
   deliberately, the way the old site did it -- the mark drawn larger than
   its box, the box clipping it, so it runs off the bottom edge rather than
   shrinking to fit. .foot-sig is the clipping box (overflow:hidden, a
   height a good deal shorter than the mark's own height so the crop
   reads as intentional, not a layout accident); align-items:flex-start
   pins the mark to the top of that box so it is the BOTTOM that runs off,
   not the top. No padding under it: the clipped edge is the end of the
   document. It is still written on once, when the footer enters view (a
   soft-edged wipe over the filled shape; the SVG is filled paths, not
   strokes, so a pen draw is not possible without a re-export). aria-hidden,
   because a signature is not information. */
.foot .wrap.foot-quip{display:block;font-family:var(--mono);font-size:var(--fs-01);
  letter-spacing:var(--ls-06);color:var(--ink-72);margin-top:var(--s-06)}
.foot-sig{margin-top:var(--s-06);display:flex;justify-content:center;align-items:flex-start;
  overflow:hidden;height:clamp(118px,15.5vw,184px);
  pointer-events:none;line-height:0}
.foot-sig img{width:min(720px,90%);max-width:none;height:auto;display:block;
  filter:invert(1) brightness(1.06);
}
/* 2026-09-09 (round 3): the ink wipe is gone at Kyle's request; the mark is static */
.foot .wrap{display:flex;justify-content:space-between;gap:var(--s-06);flex-wrap:wrap}
/* 2026-09-09 review: the footer reads as two groups now, not four loose
   items on one space-between row: the byline on the left, everything that
   is a control or a link grouped on the right with its own even gap. */
.foot-r{display:flex;align-items:center;gap:var(--s-06);flex-wrap:wrap}
/* THE SWITCH. The copyright line is the control that flips the page between
   the IC and the leadership variant. A real button, so it is keyboard
   reachable and announced, styled to be indistinguishable from the text it
   replaced. This is a working affordance for Kyle, not a product feature:
   take it out before the site ships, or leave it in and nobody will find it. */
.vtoggle{font:inherit;letter-spacing:inherit;text-transform:inherit;color:inherit;
  background:none;border:0;padding:0;cursor:pointer;
  transition:color var(--dur) var(--ease)}
.vtoggle:hover{color:var(--acc-ink)}

/* ══ 12 · GATE ════════════════════════════════════════════════════════════════
   THE DESIGN FILE. The gate is the tool the work was made in: a canvas, one
   selected frame, and the visitor's own pointer carrying a name tag the way
   every cursor on a shared file does.

   The grid is two CSS gradients rather than a canvas, so there is no
   per-frame work at all and nothing to throttle on a phone. The only thing
   that moves on this screen is the pointer, which the visitor is moving
   themselves.
   ──────────────────────────────────────────────────────────────────────── */
.lockbody{background:
    radial-gradient(circle at 1px 1px,var(--ink-10) 1px,transparent 0) 0 0/22px 22px,
    var(--bg);
  min-height:100vh;min-height:100dvh;overflow:hidden}

/* THE PEN NAME. Kyle's own cursor, drawn where the system one was. Only on a
   device that has a pointer to replace: a touch screen keeps its own
   behaviour and this never runs. */
.lockbody.live{cursor:none}
.lockbody.live a,.lockbody.live input,.lockbody.live button{cursor:none}
.me{position:fixed;left:0;top:0;z-index:9;display:flex;align-items:flex-start;
  pointer-events:none;opacity:0;transition:opacity var(--dur-fast) linear;
  will-change:transform}
.me.on{opacity:1}
.me svg{width:var(--cur-w);height:var(--cur-h);display:block;flex:none}
.me b{font-family:var(--mono);font-size:var(--fs-01);font-weight:400;
  letter-spacing:var(--ls-06);color:var(--bg);background:var(--peer);
  padding:3px var(--s-02);margin:var(--s-03) 0 0 -2px}

/* the layer chip, top left, and the inspector, top right: the two pieces of
   chrome that make a canvas read as a file rather than as a dotted page */
.gchip{position:fixed;top:var(--s-05);left:var(--s-05);z-index:4;
  display:flex;align-items:center;gap:var(--s-02);background:var(--bg);
  border:var(--hair);padding:var(--s-01) var(--s-03);
  font-family:var(--mono);font-size:var(--fs-01);letter-spacing:var(--ls-04);
  color:var(--ink-72)}
.gchip i{width:var(--hnd);height:var(--hnd);background:var(--ink-30);font-style:normal;
  transition:background var(--dur) var(--ease)}
.gchip b{font-weight:400;color:var(--ink);transition:color var(--dur) var(--ease)}
.lockbody.ok .gchip i{background:var(--acc)}
.lockbody.ok .gchip b{color:var(--acc-ink)}

.ginsp{position:fixed;top:var(--s-05);right:var(--s-05);z-index:4;width:var(--insp);
  background:var(--bg);border:var(--hair);padding:var(--s-03);
  font-family:var(--mono);font-size:var(--fs-01);letter-spacing:var(--ls-04);
  color:var(--ink-72)}
.ginsp h2{margin:0 0 var(--s-02);font-family:var(--mono);font-size:var(--fs-01);
  font-weight:400;letter-spacing:var(--ls-06);text-transform:uppercase;
  color:var(--ink-30)}
.ginsp .r{display:flex;align-items:center;gap:var(--s-02);padding:2px 0}
.ginsp .r i{width:var(--sw);height:var(--sw);flex:none;font-style:normal;
  border:1px solid var(--ink-22)}
.ginsp .r span{margin-left:auto;color:var(--ink-30)}
.ginsp hr{border:0;border-top:var(--hair);margin:var(--s-02) 0}

/* the stack: signature, then the one selected frame */
.di-wrap{position:fixed;inset:0;display:grid;place-items:center;z-index:2;
  padding:var(--s-06);transition:opacity var(--dur-exit) var(--ease)}
.di-wrap.out{opacity:0}
.di-stack{display:flex;flex-direction:column;align-items:center;gap:var(--s-07)}
.di-mark{width:min(360px,62vw);height:auto;display:block;
  filter:invert(1) brightness(1.06)}   /* the signature is drawn black; the ground is not */

.di-plate{position:relative;background:var(--bg);border:var(--hair);
  padding:var(--s-04) var(--s-05) var(--s-03);width:min(320px,80vw);
  transition:border-color var(--dur) var(--ease),box-shadow var(--dur) var(--ease)}
.di-plate.lit{border-color:var(--acc);box-shadow:0 0 0 1px var(--acc)}
.di-lab{display:block;font-family:var(--mono);font-size:var(--fs-01);
  letter-spacing:var(--ls-06);text-transform:uppercase;color:var(--ink-72);
  transition:color var(--dur) var(--ease)}
.di-plate.bad .di-lab{color:var(--warn);text-transform:none;
  letter-spacing:var(--ls-04)}
.di-plate.go .di-lab{color:var(--acc-ink)}
.di-plate input{display:block;width:100%;border:0;background:none;outline:0;
  margin-top:var(--s-02);font-family:var(--mono);font-size:var(--fs-10);
  letter-spacing:var(--ls-07);color:var(--ink);caret-color:var(--acc)}
.di-hnd{position:absolute;width:var(--hnd);height:var(--hnd);background:var(--bg);
  border:1.5px solid var(--acc);opacity:0;
  transition:opacity var(--dur) var(--ease)}
.di-plate.lit .di-hnd{opacity:1}
.di-hnd.tl{top:var(--hnd-off);left:var(--hnd-off)}
.di-hnd.tr{top:var(--hnd-off);right:var(--hnd-off)}
.di-hnd.bl{bottom:var(--hnd-off);left:var(--hnd-off)}
.di-hnd.br{bottom:var(--hnd-off);right:var(--hnd-off)}
.di-dim{position:absolute;left:50%;transform:translateX(-50%);bottom:var(--dim-drop);
  font-family:var(--mono);font-size:var(--fs-01);letter-spacing:var(--ls-04);
  color:var(--bg);background:var(--acc);padding:2px var(--s-02);
  white-space:nowrap;opacity:0;transition:opacity var(--dur) var(--ease)}
.di-plate.lit .di-dim{opacity:1}

.di-ask{position:fixed;left:50%;transform:translateX(-50%);bottom:var(--s-05);
  z-index:3;font-family:var(--mono);font-size:var(--fs-01);
  letter-spacing:var(--ls-04);color:var(--ink-72);margin:0;
  transition:opacity var(--dur-fast)}
.di-ask a{color:var(--ink);border-bottom:1px solid var(--ink-30);padding-bottom:2px}
.di-ask a:hover{color:var(--acc-ink);border-bottom-color:var(--acc)}
.lockbody.typing .di-ask{opacity:0;pointer-events:none}

/* ══ 13 · MOTION ═════════════════════════════════════════════════════════
   Elements arrive on a stagger. The delay is a token on the element rather
   than an :nth-child rule, because an :nth-child stagger silently re-times
   itself the moment anything is inserted above it.
   ─────────────────────────────────────────────────────────────────────── */
.rv{opacity:0;transform:translateY(8px);
  animation:rise var(--dur-slow) var(--ease) var(--rv-delay,0s) forwards}
.rv-2{--rv-delay:.08s}
.rv-3{--rv-delay:.16s}
.rv-4{--rv-delay:.24s}
@keyframes rise{to{opacity:1;transform:none}}
@keyframes draw{to{transform:scaleX(1)}}


/* ══ 14 · THE SYSTEM PAGE ════════════════════════════════════════════════
   The colophon is built out of the system it documents. The contents rail,
   the section shell, the band of hairline rows, the wells, the placeholder
   and the next-case target are sections 8 and 10, unchanged.

   Documenting a hundred selectors cost five more, and these are they. Each
   exists for the same reason: a specimen has to BE the value it names. A
   swatch has to be painted in the token, a bar has to be as wide as the step
   it stands for. No component on this site does that, because no other page
   has ever needed a value to describe itself.
   ─────────────────────────────────────────────────────────────────────── */

/* The reading column opens up so a specimen row can be a table. Prose keeps
   the 66-character cap, because the cap is about reading and a band is
   scanned. The standfirst keeps the shorter serif measure, same as a lede. */
.body.wide{max-width:none}
.wide .sec > p,.wide .calls,.wide .two p{max-width:var(--measure)}
.wide .chead .sub{max-width:var(--measure-tight)}

/* A band row on this page carries a token name and its value in the kind
   column, where the domain band carries one or two words. The track widens;
   nothing else about the component changes. */
.wide .brow{grid-template-columns:minmax(0,10rem) minmax(0,1fr) minmax(0,12rem)}

/* A color, painted in itself. The background is set on the element from the
   token it documents, which is what makes this a specimen rather than a
   picture of one: change the channel and every chip on the page follows. */
.chip{display:inline-block;width:var(--s-06);height:var(--s-04);
  border:var(--hair);vertical-align:-3px;margin-right:var(--s-03)}

/* A spacing step, drawn at its own width, the same way. */
.bar{display:inline-block;height:2px;background:var(--acc);vertical-align:2px}

/* A measurement that does not clear its threshold. Full ink, so it is the
   one number in a teal column that is not teal, and a dashed rule under it,
   because dashed means unfinished everywhere else on this site too. */
.miss{border-bottom:1px dashed var(--acc);color:var(--ink)}

/* Two things placed side by side because they are meant to be compared. */
.two{display:grid;grid-template-columns:1fr 1fr;gap:var(--s-06) var(--s-09);
  padding-top:var(--s-04)}

/* ══ 15 · RESPONSIVE ═════════════════════════════════════════════════════
   Every breakpoint on the site is in this section, largest first, one block
   each. Each thing collapses when IT runs out of room rather than all at
   once, which is why there are five and not two. The work grid is the last
   to give up its second column.
   ─────────────────────────────────────────────────────────────────────── */

/* four numbers stop fitting on one line before anything else does */
/* ── 15 · LANES ─────────────────────────────────────────────────────
   A segment, what it broke, and what changed, in three hairline columns.
   This is the deferred hairline-table pattern, built because the PAY case
   needs it: persona cards are decoration, a segment beside the defect it
   caused is evidence. Text stays real text at real sizes, which a diagram
   drawn as an image cannot promise at this column width. */
.lanes{margin-block:var(--s-07);border-top:var(--hair)}
.lanes .lrow{display:grid;
  grid-template-columns:minmax(0,.9fr) minmax(0,1.15fr) minmax(0,1.15fr);
  gap:var(--s-06);padding-block:var(--s-05);border-bottom:var(--hair)}
.lanes .lhead{padding-block:var(--s-03)}
.lanes .lhead span{font-family:var(--mono);text-transform:uppercase;
  font-size:var(--fs-01);letter-spacing:var(--ls-06);color:var(--ink-72)}
.lanes .who{display:block;font-family:var(--mono);font-size:var(--fs-05);
  letter-spacing:var(--ls-01);color:var(--acc-ink);margin-bottom:var(--s-02)}
.lanes p{font-size:var(--fs-08);line-height:var(--lh-07);color:var(--ink-80);margin:0}

/* ── 16 · PHONES ───────────────────────────────────────────────────
   Two phone captures side by side. A 786x1704 capture is 2.17 times
   taller than it is wide, so one across the measure runs 1431px tall and
   nothing else fits on screen with it. Two across is 694px and the pair
   is the point anyway: these figures are comparisons. */
.phones{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--s-05);
  align-items:start}

/* ── 17 · CASE NUMBERS ──────────────────────────────────────────────
   The home page's facts row, re-columned for the case measure. Three
   numbers fit where four crush. The <small> under each is not decoration:
   it carries the denominator, which is the first thing a reviewer asks. */
.case .facts{grid-template-columns:repeat(3,1fr);border-top:0;
  margin-block:0 var(--s-08);padding-block:var(--s-07) var(--s-06)}

/* ── 18 · PROSE LINK ────────────────────────────────────────────────
   The first link the case body has needed. Underlined by a rule rather
   than text-decoration so it sits on the accent ramp like every other
   drawn line on the site. */
.sec p a{border-bottom:1px solid var(--acc-40);
  transition:color var(--dur) var(--ease),border-color var(--dur) var(--ease)}
.sec p a:hover{color:var(--acc-ink);border-bottom-color:var(--acc-ink)}

/* ── 19 · DIAGRAM ───────────────────────────────────────────────────
   An inline SVG figure. Text inside it is real SVG text on the site's
   own ramp, so it scales with the well instead of blurring like a raster
   would, and it carries no colour that is not a token. */
.dia{display:block;width:100%;height:auto;border:var(--hair)}
.dia .box{fill:none;stroke:var(--ink-10)}
.dia .fillbox{fill:var(--ink-02);stroke:var(--ink-10)}
.dia .rule{stroke:var(--ink-10)}
.dia .tick{stroke:var(--acc)}
.dia .lbl{font-family:var(--mono);text-transform:uppercase;fill:var(--ink-72)}
.dia .acc{font-family:var(--mono);text-transform:uppercase;fill:var(--acc-ink)}
.dia .ttl{font-family:var(--serif);fill:var(--ink)}
.dia .txt{font-family:var(--sans);fill:var(--ink-80)}

/* A fixed viewBox cannot reflow, so at 1600 units in a 334px well the
   labels land at 5.8px. Below 620 the drawing is replaced by the same
   four facts as text rather than shrunk past reading. */
.dia-alt{display:none;list-style:none;margin:0;padding:0;border-top:var(--hair)}
.dia-alt li{display:grid;grid-template-columns:minmax(0,1fr) auto;
  gap:var(--s-02) var(--s-04);padding-block:var(--s-04);border-bottom:var(--hair)}
.dia-alt b{font-family:var(--serif);font-weight:400;font-size:var(--fs-14);color:var(--ink)}
.dia-alt i{font-style:normal;font-family:var(--mono);text-transform:uppercase;
  font-size:var(--fs-01);letter-spacing:var(--ls-06);color:var(--ink-72);align-self:center}
.dia-alt .own i,.dia-alt li.own i{color:var(--acc-ink)}
.dia-alt span{grid-column:1/-1;font-size:var(--fs-08);line-height:var(--lh-06);color:var(--ink-80)}

@media(max-width:1040px){
  .facts{grid-template-columns:1fr 1fr}
  .nav .links{gap:var(--s-05)}
}

/* the case sidebar loses its sticky rail before the reading column narrows */
@media(max-width:900px){
  .two{grid-template-columns:1fr}
  .case{grid-template-columns:1fr;gap:0}
  .side{position:static;padding-top:var(--s-08)}
  .toc{display:none}
  .body{padding-top:var(--s-07)}
  .meta{grid-template-columns:1fr 1fr;gap:var(--s-05)}
}

/* the hero and the About intro give up their second column together */
@media(max-width:860px){
  .hero{grid-template-columns:1fr;gap:var(--s-08);padding-block:var(--s-10) var(--s-08)}
  .intro{grid-template-columns:1fr;gap:var(--s-07) 0;align-items:start;
    padding-block:var(--s-09) 0}
  .intro .cut{justify-content:flex-start}
  .sechead{margin-top:var(--s-09)}
  .pairs{grid-template-columns:1fr}
  .pairs > *{grid-column:1 !important;grid-row:auto !important}
  .pairs > *:nth-child(6){margin-top:var(--s-08)}
  .seam{margin-top:var(--s-10)}
}

/* the work grid, last to fold. Aspect ratios are NOT flattened here: a
   portrait well that becomes 16:9 on a phone crops the head off whatever is
   inside it, and a square diagram just puts the dead space back on the
   other axis. Each well keeps the ratio it was given. */
@media(max-width:760px){
  .lanes .lrow{grid-template-columns:minmax(0,1fr);gap:var(--s-03)}
  .lanes .lhead{display:none}
  .lanes .lrow > div + div::before{content:attr(data-l);display:block;
    font-family:var(--mono);text-transform:uppercase;font-size:var(--fs-01);
    letter-spacing:var(--ls-06);color:var(--ink-72);margin-bottom:var(--s-01)}
  .grid{grid-template-columns:1fr}
}

/* phone. The lockup drops its title line, the facts stay two-by-two rather
   than stacking (four numbers stacked is most of a screen before the work
   even starts), and the milestone year stays beside its line instead of
   sitting on top of it. */
@media(max-width:620px){
  .dia{display:none}
  .dia-alt{display:block}
  .case .facts{grid-template-columns:minmax(0,1fr);gap:var(--s-05)}
  /* A three-column hairline row needs 320px of fixed track before the
     description gets a pixel, which is more than a 375px phone has after
     the gutter. Scoped to the colophon, the only page carrying one. */
  .wide .brow{grid-template-columns:minmax(0,1fr);gap:var(--s-01)}
  .wide .bkind{text-align:left}
  /* the role chip is a sentence, not a word. The name keeps the mark beside
     it and the chip drops to its own line rather than squeezing both. */
  .plat > summary{grid-template-columns:1fr auto}
  .plat .mark{grid-row:1;grid-column:2}
  .plat .pill{grid-column:1;justify-self:start;white-space:normal}
  .nav .lock span{display:none}
  .facts{gap:var(--s-06) var(--s-05);padding-block:var(--s-06)}
  .facts .f .v{font-size:var(--fs-19)}
  .rail{gap:var(--s-03)}
  .rail .y{--rail-year:40px;font-size:var(--fs-05)}
  .rail .t{font-size:var(--fs-09);line-height:var(--lh-04)}
  .ginsp{display:none}
  .di-stack{gap:var(--s-06)}
}

/* a short screen — a landscape phone, or a laptop with the keyboard up */
@media(max-height:560px){
  .di-mark{width:min(260px,50vw)}
  .di-ask{display:none}
}

/* Motion is a courtesy, not a message: nothing here carries meaning that is
   lost when it is switched off. */
@media(prefers-reduced-motion:reduce){
  .rv{animation:none;opacity:1;transform:none}
  .lede a::after{animation:none;transform:none}
  *{transition:none !important}
}

/* 2026-09-08: the home demos are hand-built HTML/CSS/JS, not video. Say so. */
.ph.live{position:relative}
.ph.live.nobadge::after{content:none}   /* the leadership card: a scene, not a product, so no badge */
.ph.live::after{content:"Running code";position:absolute;right:10px;top:10px;z-index:2;
  font-family:var(--mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-72);background:var(--ground);padding:4px 7px;border:1px solid var(--ink-10);
  pointer-events:none}

/* ══ 16 · REDESIGN MOTION (2026-09-09) ══════════════════════════════════
   Eight small interactions layered onto the sections above, added by a
   parallel pass and kept in one block so the whole batch can be found,
   reviewed or pulled in one place. Every value still resolves to a
   section 2 token; the few this batch needed with no existing step
   (a blur amount, two spotlight radii, the marquee's height, and the two
   view-transition durations) were added there, in 2.10, not here. Each
   subsection is independently reduced-motion-safe.
   ─────────────────────────────────────────────────────────────────────── */

/* ── 16.1 · SKIM OR READ ──────────────────────────────────────────────
   The hero lede's .rest spans blur until the reader hovers, focuses
   inside the paragraph, or presses the Read toggle just under it. .key
   phrases (the Sovren link, the headcount figures, Triton, the
   leadership line) never blur, so a five-second skim still gets the
   whole claim. <b> is used for .key only because it needs no separate
   tag of its own weight; font-weight is reset to inherit here so it
   reads as a highlight, not as bold running text. */
.lede.skim .key{font-weight:inherit}
.lede.skim .rest{filter:blur(var(--blur-skim));opacity:.62;
  transition:filter var(--dur-slow) var(--ease),opacity var(--dur-slow) var(--ease)}
.lede.skim:hover .rest,.lede.skim:focus-within .rest,.lede.skim.on .rest{
  filter:blur(0);opacity:1}
/* 2026-09-09 review: styled as a real .pill (hairline, radius-pill, mono)
   so the affordance reads before the first hover, not only after. */
.skim-toggle{display:inline-block;margin-top:var(--s-03);font-family:var(--mono);
  text-transform:uppercase;font-size:var(--fs-04);letter-spacing:var(--ls-02);
  color:var(--ink);background:none;border:var(--hair);border-radius:var(--radius-pill);
  padding:var(--pad-pill);cursor:pointer;
  transition:color var(--dur) var(--ease),border-color var(--dur) var(--ease)}
.skim-toggle:hover{color:var(--acc-ink);border-color:var(--ink-22)}
.skim-toggle[aria-pressed="true"]{color:var(--acc-ink);border-color:var(--acc-40)}
/* touch has no hover to clear the blur with, so it starts clear instead
   and the toggle (redundant there) is hidden */
@media(hover:none){
  .lede.skim .rest{filter:none;opacity:1}
  .skim-toggle{display:none}
}
@media(prefers-reduced-motion:reduce){
  .lede.skim .rest{filter:none;opacity:1;transition:none}
}

/* ── 16.2 · SPOTLIGHT WELL ────────────────────────────────────────────
   A soft radial glow under the pointer inside a case well, plus the
   hairline lighting up toward --ink-22 near it (2026-09-09 review: the
   ring no longer spends salmon, only the glow does, at --acc-10). Two
   plain children (.glow, .ring), appended by script, rather than
   pseudo-elements, because .ph.live already owns ::after for the
   "Running code" tag. Script sets --sx/--sy in px from the well's own
   corner; the 50% fallback here only matters for the one frame before
   the first pointermove ever fires. */
.ph .glow,.ph .ring{position:absolute;inset:0;pointer-events:none;opacity:0;
  transition:opacity var(--dur) var(--ease)}
.ph .glow{background:radial-gradient(var(--glow-r) circle at var(--sx,50%) var(--sy,50%),
  var(--acc-10),transparent 70%)}
.ph .ring{padding:1px;
  background:radial-gradient(var(--ring-r) circle at var(--sx,50%) var(--sy,50%),
  var(--ink-22),transparent 70%);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude}
.card:hover .ph .glow,.card:hover .ph .ring{opacity:1}
/* no pointer to spotlight on touch, and no motion to spend under
   reduced motion: the well falls back to its plain hairline hover */
@media(hover:none){.ph .glow,.ph .ring{display:none}}
@media(prefers-reduced-motion:reduce){.ph .glow,.ph .ring{display:none}}

/* ── 16.3 · SPLIT-FLAP MILESTONE RAIL ─────────────────────────────────
   2026-09-09 review: plays once, then hands the row back to its normal
   typography rather than staying a permanent mono readout. Script turns
   each .rail .t into an inline-grid host stacking two children in the
   same cell (grid, not absolute positioning, so nothing can overflow or
   mis-size against an inline ancestor the way an absolutely positioned
   overlay did): .flap-real, the genuine text, always in the accessible
   tree and invisible only by opacity; and the aria-hidden .flap-ov built
   from one tile per character, grouped into one .flap-word span per word
   so the board can only wrap between words, never mid-word. Once the
   last tile lands, .settled crossfades the two: the mono readout fades
   out, the real serif/sans text fades in, and the rail is left exactly
   as it was before any of this ran. */
.flap-host{display:inline-grid}
.flap-host>*{grid-area:1/1;min-width:0}
/* 2026-09-09 (round 3): the real text is on screen from the first paint
   and stays there for three seconds; only then does the board flip, top
   row first, trickling down, and it is back to the real text in three
   seconds. The overlay inherits the row's own face and size so the two
   layers share exact metrics: nothing wraps differently, nothing jumps,
   and the rail keeps the same rhythm before, during and after. */
.flap-real{opacity:1;transition:opacity var(--dur-fast) linear}
.flap-ov{font:inherit;color:var(--ink-80);opacity:0;transition:opacity var(--dur-fast) linear}
.flap-host.flipping .flap-real{opacity:0}
.flap-host.flipping .flap-ov{opacity:1}
.flap-word{display:inline-block}
.flap-word:not(:last-child){margin-right:.3em}
.flap-ch{display:inline-block;text-align:center}
@media(prefers-reduced-motion:reduce){.flap-ov{display:none}.flap-real{opacity:1;transition:none}}

/* ── 16.4 · PLATFORM MARQUEE ──────────────────────────────────────────
   One quiet row above "Seven platforms.", the same seven names already
   in the list below it, scrolling slowly enough to read as a divider
   rather than a feature. Hairlines above and below match every other
   band edge on the page. 2026-09-09 review: shorter (28px), slower (90s)
   and off the salmon entirely (--ink-30 for both the words and the
   dots) -- a rule with words on it, not a banner. */
.marquee{height:var(--mq-h);display:flex;align-items:center;overflow:hidden;
  border-top:var(--hair);border-bottom:var(--hair);
  mask-image:linear-gradient(to right,transparent,#000 8%,#000 92%,transparent);
  -webkit-mask-image:linear-gradient(to right,transparent,#000 8%,#000 92%,transparent)}
.mq-track{display:flex;width:max-content;animation:mq-scroll 90s linear infinite}
/* .off is set by script when the row scrolls out of view; :hover is the
   visitor's own pause and needs no script at all */
.marquee:hover .mq-track,.marquee.off .mq-track{animation-play-state:paused}
.mq-set{display:flex;align-items:center;flex:none}
.mq-set span:not(.mq-dot){font-family:var(--mono);text-transform:uppercase;
  font-size:var(--fs-04);letter-spacing:var(--ls-05);color:var(--ink-30);
  white-space:nowrap;padding:0 var(--s-04)}
.mq-dot{width:4px;height:4px;border-radius:var(--radius-pill);background:var(--ink-30);flex:none}
@keyframes mq-scroll{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@media(prefers-reduced-motion:reduce){
  .mq-track{animation:none}
  .marquee{mask-image:none;-webkit-mask-image:none}
}

/* ── 16.5 · INSPECT MODE ──────────────────────────────────────────────
   Off by default, a real <button> grouped with the footer's other links
   in .foot-r. 2026-09-09 review: the button itself is styled exactly
   like a footer link (no border, no padding, hover to --acc-ink) so it
   never reads as a control sitting in a row of text -- only the overlay
   it draws is teal (--acc2), so the effect it turns on stays visually
   distinct from the site's own salmon hover state. The gap line to the
   nearest sibling stays in --acc, the same color every other measurement
   mark on the site already uses (the case diagrams' .tick is the
   identical choice for the identical reason). Pure SVG, appended once by
   script and positioned fixed over the whole page. */
.inspect-btn{font:inherit;letter-spacing:inherit;text-transform:inherit;color:inherit;
  background:none;border:0;padding:0;cursor:pointer;transition:color var(--dur) var(--ease)}
.inspect-btn:hover{color:var(--acc-ink)}
.inspect-btn[aria-pressed="true"]{color:var(--acc-ink)}
.insp-ov{position:fixed;inset:0;z-index:999;pointer-events:none}
.insp-box{fill:none;stroke:var(--acc2)}
.insp-hnd{fill:var(--acc2)}
.insp-dist{stroke:var(--acc)}
.insp-badge-bg{fill:var(--acc2)}

/* ── 16.6 · VIEW TRANSITIONS ──────────────────────────────────────────
   Cross-document, so a card's cover art and title carry into the case
   page rather than the browser hard-cutting between them. Each case
   page names its header figure and title with the matching pair below
   (triton-cover/triton-title, and so on for pay, leadership, ads and
   crushsuite); the three cases with no page (Social, Ads-adjacent slots
   not in the pool, etc.) simply never get a name and cross-fade as root.
   Confirmed nothing else on this page carries a view-transition-name --
   a duplicate silently aborts the whole transition -- so these ten
   declarations are the only ones site-wide.
   2026-09-09 review, choppy-transition pass:
   (a) a card's .ph is a fixed-ratio crop (16:9 or 4:5); the case header's
       .shot is the image at its own natural ratio. Morphing one group box
       into the other used to stretch the snapshot to fit -- worst on Pay,
       whose card is 4:5 against a landscape header shot. object-fit:cover
       on both snapshots keeps the crop instead of distorting it.
   (b) .rv's rise, the wire sweep and the o500 key-fill all key off
       IntersectionObserver/on-load and do not know a transition is
       running. Since the pseudo-element tree sits over the live page
       until the transition ends, an animation that starts underneath is
       invisible until the overlay lifts, then pops in mid-flight. Freezing
       each to its current frame for the life of the transition (below)
       removes the pop; the real reveal plays right after, motion intact.
   (c) root cross-fade .45s, named groups .5s, both on --ease so a group
       morph does not out-pace or lag the root it sits inside. */
@view-transition{navigation:auto}
::view-transition-old(root),::view-transition-new(root){
  animation-duration:var(--dur-vt-root);animation-timing-function:var(--ease)}
::view-transition-group(*){animation-duration:var(--dur-vt-group);animation-timing-function:var(--ease)}
::view-transition-old(triton-cover),::view-transition-new(triton-cover),
::view-transition-old(pay-cover),::view-transition-new(pay-cover),
::view-transition-old(leadership-cover),::view-transition-new(leadership-cover),
::view-transition-old(ads-cover),::view-transition-new(ads-cover),
::view-transition-old(crushsuite-cover),::view-transition-new(crushsuite-cover){
  height:100%;width:100%;object-fit:cover;overflow:hidden}
.card[data-case="1"] .ph{view-transition-name:triton-cover}
.card[data-case="1"] h3{view-transition-name:triton-title}
.card[data-case="2"] .ph{view-transition-name:pay-cover}
.card[data-case="2"] h3{view-transition-name:pay-title}
.card[data-case="3"] .ph{view-transition-name:leadership-cover}
.card[data-case="3"] h3{view-transition-name:leadership-title}
.card[data-case="6"] .ph{view-transition-name:ads-cover}
.card[data-case="6"] h3{view-transition-name:ads-title}
.card[data-case="9"] .ph{view-transition-name:crushsuite-cover}
.card[data-case="9"] h3{view-transition-name:crushsuite-title}
/* :active-view-transition matches <html> for the life of the transition,
   cross-document included -- true on the outgoing page and, once
   navigation lands, true again on the incoming one until it settles.
   Freeze every reveal that would otherwise animate unseen behind the
   transition overlay (suspect b), and stop the signature wipe / flap
   overlay from being sampled mid-frame for the very same reason, rather
   than only the named groups. Shorthand `transition` reset (not `none`
   on the property list) so a rule added later here does not silently
   keep animating through a transition by accident. */
html:active-view-transition .rv{animation:none;opacity:1;transform:none}
html:active-view-transition .wire-wrap .wire-mask,
html:active-view-transition .wire-wrap .wire-line{transition:none}
html:active-view-transition .o500-key{transition:none}
html:active-view-transition .foot-sig img{transition:none}
html:active-view-transition .flap-real,html:active-view-transition .flap-ov{transition:none}
@media(prefers-reduced-motion:reduce){
  ::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation:none}
}

/* ── 16.7 · FONT-GATED REVEAL ─────────────────────────────────────────
   .rv (section 13) already rises on load; it now waits for
   html.fonts-ready, which script sets on document.fonts.ready or a
   1.2s fallback, so the arrival never races the swap from the
   fallback face to Newsreader/Geist. A no-JS visitor never gets that
   class; the <noscript> override in index.html's <head> covers that
   case by disabling the animation outright rather than leaving .rv
   paused at opacity:0 forever.
   2026-09-09 (later): the pause is opt-in per page. Only a page whose
   <head> sets html.rv-wait synchronously (index.html) holds .rv back;
   every other page (About, the cases, the system page) rises on load as
   section 13 always did. Without this scope About's hero flashed once
   and then sat at opacity 0, because nothing there ever set fonts-ready. */
html.rv-wait .rv{animation-play-state:paused}
html.rv-wait.fonts-ready .rv{animation-play-state:running}


/* ══ 17 · CASE-PAGE MOTION (2026-09-09), the wire sweep, the 500 keys, the case headers ══ */
/* ═════════════════════════════════════════════════════════════════════════
   CASE MOTION · staged 2026-09-09
   Three additions to the case-study pages, kept out of site.css until the
   review agent folds them in. Every value resolves to a section 2 token;
   nothing here introduces a literal color, size or duration.

     1 · View transitions   the cross-document morph from a home card into
                             a case header (cover art up, title above it).
     2 · Wire sweep          a figure drawn twice, hairline wireframe over
                             the shipped render, with a draggable divider
                             between them that auto-plays partway on enter.
     3 · One in five hundred a grid of 500 keys filling in to back the PAY
                             seed-phrase stat.
   ═════════════════════════════════════════════════════════════════════ */

/* ── 1 · VIEW TRANSITIONS ────────────────────────────────────────────────
   Opts every same-origin navigation into the cross-document transition.
   The home page names each card's cover and title (…-cover / …-title); this
   page names the matching header elements inline, since each name is a
   one-off per document rather than a reusable rule. Harmless if the home
   page already declares this. */
/* @view-transition is declared once in section 16 */

/* ── 2 · WIRE SWEEP ───────────────────────────────────────────────────────
   A hairline wireframe sits over the figure's own content on an opaque
   mask, and one custom property, --wire-pct (0-100), is the whole state:
   it drives the mask's clip-path and the position of the line, its handle
   and its label together. Drag, the handle's arrow keys and the auto-play
   on enter all just move that number; only the mover differs. Reduced
   motion never plays the entry animation, and the site-wide transition
   kill (section 15) means the park at 88% lands with nothing to animate. */
.wire-wrap{--wire-pct:0%;position:relative;overflow:hidden;touch-action:none;cursor:ew-resize}
.wire-wrap .wire-mask{position:absolute;inset:0;z-index:2;background:var(--ground);
  clip-path:inset(0 0 0 var(--wire-pct));transition:clip-path var(--dur-slow) var(--ease)}
.wire-wrap .wire-svg{position:absolute;inset:0;width:100%;height:100%;display:block}
.wire-wrap .wire-svg rect,.wire-wrap .wire-svg line,.wire-wrap .wire-svg circle{
  fill:none;stroke:var(--ink-30);stroke-width:1;vector-effect:non-scaling-stroke}
.wire-wrap .wire-svg .f{fill:var(--ink-30);stroke:none;opacity:.6}
.wire-wrap .wire-line{position:absolute;top:0;bottom:0;left:var(--wire-pct);width:1px;
  background:var(--acc);z-index:3;transition:left var(--dur-slow) var(--ease)}
/* the label rides the line (it is an absolutely-positioned child of it, so
   its own left:0 means "at the line"); near the right edge it flips to sit
   left of the divider instead, so it can never run past the frame */
.wire-wrap .wire-label{position:absolute;top:var(--s-02);left:0;
  transform:translateX(-50%);font-family:var(--mono);font-size:var(--fs-01);
  letter-spacing:var(--ls-04);text-transform:uppercase;color:var(--ground);
  background:var(--acc);padding:2px var(--s-02);white-space:nowrap;transition:transform var(--dur) var(--ease)}
.wire-wrap.label-flip .wire-label{transform:translateX(calc(-100% - var(--s-02)))}
/* the handle: a small circle astride the line, carrying a two-arrow glyph
   drawn in hairlines so it reads as a drag control rather than a mark */
.wire-wrap .wire-handle{position:absolute;top:50%;left:var(--wire-pct);z-index:4;
  width:28px;height:28px;margin:-14px 0 0 -14px;border-radius:var(--radius-pill);
  background:var(--ground);border:1px solid var(--ink-30);padding:0;font:inherit;
  display:flex;align-items:center;justify-content:center;cursor:ew-resize;
  transition:left var(--dur-slow) var(--ease),border-color var(--dur) var(--ease)}
.wire-wrap .wire-handle svg{width:14px;height:14px}
.wire-wrap .wire-handle svg path{fill:none;stroke:var(--ink-80);stroke-width:1.4;
  stroke-linecap:round;stroke-linejoin:round}
.wire-wrap .wire-handle:hover{border-color:var(--ink-22)}
.wire-wrap .wire-handle:focus-visible{outline:2px solid var(--acc);outline-offset:3px}
/* while dragging, --wire-pct is following the pointer directly; a
   transition here would lag it, so drag suspends the same rules the
   auto-play and the keyboard use */
.wire-wrap.dragging .wire-mask,.wire-wrap.dragging .wire-line,
.wire-wrap.dragging .wire-handle{transition:none}

/* ── 3 · ONE IN FIVE HUNDRED ──────────────────────────────────────────────
   500 <use> elements reference one key glyph. currentColor carries the
   fill, so recoloring a key is one property, not five hundred edits. Each
   key gets a row-based --d delay (8ms per row) so the grid fills row by
   row; one key then dims to --ink-30 to mark the fraction that did not
   keep its keys. Grid on the left, the claim on the right: two columns in
   the case column's own gutter, one below 720px where a row that narrow
   would squeeze both halves to nothing. */
.o500{margin-block:var(--s-06) var(--s-08);display:grid;
  grid-template-columns:minmax(0,360px) minmax(0,1fr);gap:var(--s-08);align-items:center}
.o500-media{min-width:0}
.o500-svg{width:min(360px,100%);height:auto;overflow:visible;display:block}
.o500-key{color:var(--ink-22);opacity:.35;transform-box:fill-box;transform-origin:center;
  transform:scale(.5);cursor:default;
  transition:opacity var(--dur-fast) var(--ease),transform var(--dur-fast) var(--ease),
             color var(--dur-fast) var(--ease);
  transition-delay:var(--d,0ms)}
/* 2026-09-09 review: teal, not salmon. Teal's one job on this site is
   "state changed", and a key filling in is exactly that; salmon stays for
   everything else. */
.o500-grid.o500-filled .o500-key{color:var(--acc2);opacity:1;transform:scale(1)}
.o500-key.o500-dim{color:var(--ink-30) !important}
.o500-text h3{margin:0 0 var(--s-03)}
.o500-text p{font-size:var(--fs-09);line-height:var(--lh-07);color:var(--ink-80);
  margin:0;max-width:38ch}
@media(max-width:720px){
  .o500{grid-template-columns:minmax(0,1fr);gap:var(--s-05)}
}


/* ══ 18 · THE SYSTEM PAGE, EXTENDED (2026-09-09): specimens, the role table, the token assembly ══ */
/* ═════════════════════════════════════════════════════════════════════════
   SYSTEM PAGE · ADDITIONS (2026-09-09, staged)
   Everything system.html needed that section 14 did not already have a
   pattern for. Staged in its own file rather than site.css so the review
   agent can fold it into section 14 in one pass; every declaration here
   references a token already in site.css section 2, none invented. Loaded
   after site.css on system.html only.
   ─────────────────────────────────────────────────────────────────────── */

/* ── THE ROLE TABLE ─────────────────────────────────────────────────────
   Four columns where .brow has three: a painted chip, the token and its
   composed value (filled at runtime, never typed), where it is used, and
   the measured contrast. Falls back to .brow's grid under 620px the same
   way the rest of the page's bands do. */
.roletable{list-style:none;border-top:var(--hair)}
.rt-row{display:grid;grid-template-columns:minmax(0,9rem) minmax(0,1fr) minmax(0,14rem) minmax(0,7rem);
  gap:var(--s-04) var(--s-06);align-items:baseline;padding-block:var(--s-03);
  border-bottom:var(--hair)}
.rt-row .rt-name{display:flex;align-items:center;font-family:var(--mono);font-size:var(--fs-07)}
.rt-row .rt-val{font-family:var(--mono);font-size:var(--fs-06);color:var(--ink-72)}
.rt-row .rt-use{font-size:var(--fs-08);line-height:var(--lh-06);color:var(--ink-80)}
.rt-row .rt-use code{font-family:var(--mono);font-size:var(--fs-06);color:var(--ink)}
.rt-row .rt-c{font-family:var(--mono);font-size:var(--fs-07);color:var(--ink-72);text-align:right}
.rt-row .rt-c.pass{color:var(--acc2)}
.rt-row .rt-c.na{color:var(--ink-30)}
@media(max-width:620px){
  .rt-row{grid-template-columns:minmax(0,1fr);gap:var(--s-01)}
  .rt-row .rt-c{text-align:left}
}

/* ── COLOR RULES ─────────────────────────────────────────────────────── */
.maylist{list-style:none;margin-top:var(--s-03);display:flex;flex-direction:column;gap:var(--s-02)}
.maylist li{display:grid;grid-template-columns:1.25rem minmax(0,1fr);gap:var(--s-03);
  font-size:var(--fs-09);line-height:var(--lh-07);color:var(--ink-80)}
.maylist .y{font-family:var(--mono);color:var(--acc2)}
.maylist .n{font-family:var(--mono);color:var(--warn)}

/* ── TYPE SPECIMEN SHEET ─────────────────────────────────────────────── */
.specimen{border-top:var(--hair)}
.sp-row{display:grid;grid-template-columns:minmax(0,6.5rem) repeat(3,minmax(0,1fr));
  gap:var(--s-04) var(--s-05);align-items:center;padding-block:var(--s-03);
  border-bottom:var(--hair)}
.sp-row.sp-head{padding-block:var(--s-02);border-bottom:1px dashed var(--ink-10)}
.sp-row.sp-head span{font-family:var(--mono);text-transform:uppercase;font-size:var(--fs-01);
  letter-spacing:var(--ls-06);color:var(--ink-72)}
.sp-row .sp-tok{font-family:var(--mono);font-size:var(--fs-01);letter-spacing:var(--ls-01);
  color:var(--ink-72);overflow-wrap:break-word;word-break:break-word;line-height:var(--lh-06)}
.sp-cell{overflow:hidden;color:var(--ink);white-space:nowrap;text-overflow:ellipsis}
.sp-cell.serif{font-family:var(--serif);letter-spacing:var(--ls-tight)}
.sp-cell.sans{font-family:var(--sans)}
.sp-cell.mono{font-family:var(--mono);text-transform:uppercase;letter-spacing:var(--ls-06)}
@media(max-width:760px){
  .sp-row{grid-template-columns:minmax(0,1fr)}
  .sp-row.sp-head{display:none}
  .sp-cell::before{content:attr(data-l);display:block;font-family:var(--mono);
    text-transform:uppercase;font-size:var(--fs-01);letter-spacing:var(--ls-06);
    color:var(--ink-72);margin-bottom:var(--s-01);white-space:normal}
}

/* the ten text-role specimens: the real markup, once each, with its recipe
   printed beside it in the same mono the rest of the furniture uses */
.roles{display:flex;flex-direction:column;gap:0;border-top:var(--hair)}
.role-row{display:grid;grid-template-columns:minmax(0,1.3fr) minmax(0,1fr);
  gap:var(--s-06);align-items:center;padding-block:var(--s-06);border-bottom:var(--hair)}
.role-row .rc{font-family:var(--mono);font-size:var(--fs-06);line-height:var(--lh-08);
  color:var(--ink-72);white-space:pre-wrap;word-break:break-word}
.role-row .rc b{color:var(--acc-ink);font-weight:400}
@media(max-width:760px){.role-row{grid-template-columns:minmax(0,1fr)}}

/* ── SPACE RULER ─────────────────────────────────────────────────────── */
.ruler-row{display:grid;grid-template-columns:minmax(0,9rem) 1fr minmax(0,5rem);
  gap:var(--s-04);align-items:center;padding-block:var(--s-02);border-bottom:var(--hair)}
.ruler-row .rk{font-family:var(--mono);font-size:var(--fs-06);color:var(--ink-72)}
.ruler-track{height:8px;background:var(--ink-02);position:relative}
.ruler-bar{height:100%;background:var(--acc)}

/* ── MOTION INVENTORY TABLE ──────────────────────────────────────────── */
.tbl-scroll{overflow-x:auto;margin-block:var(--s-06);border-top:var(--hair)}
.motable{border-collapse:collapse;width:100%;min-width:760px;font-size:var(--fs-08)}
.motable th{font-family:var(--mono);text-transform:uppercase;font-size:var(--fs-01);
  letter-spacing:var(--ls-06);color:var(--acc-ink);text-align:left;
  padding:var(--s-03) var(--s-04) var(--s-02) 0;border-bottom:var(--hair);white-space:nowrap}
.motable td{padding:var(--s-03) var(--s-04) var(--s-03) 0;border-bottom:var(--hair);
  color:var(--ink-80);vertical-align:top}
.motable td.mv{font-family:var(--mono);white-space:nowrap;color:var(--ink)}
.motable td.mn{color:var(--ink-72);font-family:var(--mono);white-space:nowrap}
.motable td.mrm{color:var(--acc2)}

/* the easing demo: a dot travels the track on click, timed on the real
   --ease curve and --dur-sig so it reads at a legible speed rather than
   the .3s a hover would use */
.ease-demo{border:var(--hair);padding:var(--s-05) var(--s-06);margin-top:var(--s-05);
  display:flex;flex-direction:column;gap:var(--s-04)}
.ease-track{position:relative;height:var(--s-08);background:var(--ink-02)}
.ease-dot{position:absolute;top:50%;left:0;width:var(--s-04);height:var(--s-04);
  margin-top:calc(var(--s-04) / -2);border-radius:var(--radius-pill);background:var(--acc);
  transform:translateX(0)}
.ease-dot.go{transition:transform var(--dur-sig) var(--ease)}
.ease-dot.go.at-end{transform:translateX(calc(100cqw - var(--s-04)))}
.ease-track{container-type:inline-size}
.ease-btn{align-self:flex-start;font-family:var(--mono);text-transform:uppercase;
  font-size:var(--fs-04);letter-spacing:var(--ls-02);color:var(--ink);background:none;
  border:var(--hair);border-radius:var(--radius-pill);padding:var(--pad-pill);cursor:pointer;
  transition:color var(--dur) var(--ease),border-color var(--dur) var(--ease)}
.ease-btn:hover{color:var(--acc-ink);border-color:var(--ink-22)}

/* ── COMPONENT SHOWCASE ──────────────────────────────────────────────── */
.showcase{display:flex;flex-direction:column;gap:0;border-top:var(--hair)}
.sc-row{display:grid;grid-template-columns:minmax(0,11rem) minmax(0,1fr);
  gap:var(--s-06);padding-block:var(--s-06);border-bottom:var(--hair);align-items:start}
.sc-row .sk{font-family:var(--mono);text-transform:uppercase;font-size:var(--fs-01);
  letter-spacing:var(--ls-06);color:var(--ink-72)}
.sc-row .sk b{display:block;color:var(--acc-ink);font-weight:400;margin-bottom:var(--s-01)}
.sc-live{display:flex;flex-wrap:wrap;gap:var(--s-05);align-items:center}
.sc-item{display:flex;flex-direction:column;gap:var(--s-01);align-items:flex-start}
.sc-item .sc-lab{font-family:var(--mono);font-size:var(--fs-01);letter-spacing:var(--ls-05);
  text-transform:uppercase;color:var(--ink-30)}
@media(max-width:760px){.sc-row{grid-template-columns:minmax(0,1fr)}}

/* a small stand-in card + well for the showcase, built from the same
   classes as the work grid so the specimen IS the component */
.sc-card{width:180px}
.sc-card .ph{aspect-ratio:16/9}

/* the gate plate states: real .di-plate markup, forced-static, no fixed
   positioning (the gate itself is position:fixed; a specimen cannot be) */
.sc-plates{display:flex;flex-wrap:wrap;gap:var(--s-06)}
.sc-plates .di-plate{position:relative;width:220px}

/* ── TOKEN ASSEMBLY HERO (F) ─────────────────────────────────────────────
   Eight chips fly from scattered start points into a left-hand column;
   each landing applies one more real declaration to the wireframe on the
   right until it is an actual .pill and an actual .card. FLIP: chips are
   positioned with transform only, read live from the stylesheet so the
   labels can never drift out of sync with a token edit. */
.assembly{border-top:var(--hair);border-bottom:var(--hair);margin-block:var(--s-08) var(--s-10);
  padding-block:var(--s-07)}
.asm-stage{position:relative;min-height:260px}
.asm-scatter{position:absolute;inset:0;z-index:2;pointer-events:none}
.asm-chip{position:absolute;top:0;left:0;font-family:var(--mono);font-size:var(--fs-03);
  letter-spacing:var(--ls-01);padding:3px var(--s-03);border:var(--hair);background:var(--bg);
  color:var(--ink-72);white-space:nowrap;will-change:transform,opacity}
.asm-rig{position:relative;display:grid;grid-template-columns:200px 1fr;gap:var(--s-09);
  align-items:center;min-height:260px}
.asm-col{display:flex;flex-direction:column;gap:var(--s-02)}
.asm-slot{font-family:var(--mono);font-size:var(--fs-03);letter-spacing:var(--ls-01);
  padding:3px var(--s-03);border:var(--hair);color:var(--ink-30);opacity:.35;
  transition:opacity var(--dur) var(--ease),color var(--dur) var(--ease)}
.asm-slot.filled{opacity:1;color:var(--ink-72)}
.asm-preview{display:flex;flex-direction:column;align-items:flex-start;gap:var(--s-05)}
/* the wireframe pill and card: bare hairline boxes at rest, each class
   below is one of the eight chips landing, in the order the figure names
   them. Order: --ground, --acc, --s-04, --fs-11, --hair, --ink-02,
   --mono, --dur -- the last enables the hover transition, which is the
   only property a finished component has that a static one does not. */
.asm-pill{display:inline-block;border:1px dashed var(--ink-28);width:96px;height:20px;
  font-size:0;color:transparent;box-sizing:border-box;
  transition:font-size var(--dur) var(--ease),color var(--dur) var(--ease)}
.asm-card{border:1px dashed var(--ink-28);width:220px;padding:0;min-height:64px;
  box-sizing:border-box}
.asm-card-h{font-size:0;color:transparent;transition:font-size var(--dur) var(--ease)}
/* 1 · --ground: the card gets a real surface instead of showing through
   to the page */
.asm-card.step-bg{background:var(--bg)}
/* 2 · --acc: the accent rule that marks this as a live component */
.asm-card.step-accent{border-top:2px solid var(--acc)}
/* 3 · --s-04: real inset instead of zero padding */
.asm-card.step-space{padding:var(--s-04)}
/* 4 · --fs-11: the heading and the pill's label both become legible */
.asm-card.step-type .asm-card-h{font-size:var(--fs-11);color:var(--ink)}
.asm-pill.step-type{font-size:var(--fs-11);color:var(--ink);border-style:solid}
/* 5 · --hair: the dashed wireframe edge becomes the site's one border */
.asm-card.step-hair{border-color:var(--ink-10);border-style:solid;border-width:1px}
.asm-pill.step-hair{border:var(--hair)}
/* 6 · --ink-02: the card's fill becomes the site's one fill, not a flat bg */
.asm-card.step-fill{background:var(--ink-02)}
/* 7 · --mono: the pill's label takes the mono voice, uppercase and tracked */
.asm-pill.step-mono{font-family:var(--mono);text-transform:uppercase;
  letter-spacing:var(--ls-02);width:auto;height:auto;padding:var(--pad-pill)}
/* 8 · --dur: the last property a finished component has and a static one
   does not -- something to transition when the visitor's pointer arrives */
.asm-pill.step-final{transition:border-color var(--dur) var(--ease)}
.asm-card.step-final{transition:border-color var(--dur) var(--ease)}
.asm-card.step-final:hover,.asm-pill.step-final:hover{border-color:var(--ink-22)}
.asm-caption{font-family:var(--mono);font-size:var(--fs-01);letter-spacing:var(--ls-05);
  text-transform:uppercase;color:var(--ink-72);margin-top:var(--s-05)}
.asm-caption a{color:var(--ink);border-bottom:1px solid var(--ink-10);cursor:pointer}
.asm-caption a:hover{color:var(--acc-ink);border-bottom-color:var(--acc-ink)}
@media(max-width:760px){
  .asm-rig{grid-template-columns:1fr}
}
@media(prefers-reduced-motion:reduce){
  .asm-chip{transition:none !important}
}
