/* ============================================================
   PALETTE v7 — semantic ground/type layer.
   Added alongside the legacy --cream/--deep/--fluoro literals, which the
   six unmigrated sections still use. New work uses the semantic tokens
   only. Migration is section by section, each one verified by eye.

   The fault this fixes: the old set had three tones but two usable
   pairings. Everything involving fluoro measured 3.0-3.5 in every
   direction, so fluoro could never carry type and became flat planes of
   low-contrast mush. Near-black ink #0D1633 unlocks it. Ink on fluoro is
   4.79, so fluoro is now a ground that holds copy.

   LIGHT ladder   paper -> deep -> fluoro -> paper -> ink -> paper -> cream
   Measured steps 12.08    3.50     3.45      16.51   16.51   1.28 (a settle)

   DARK is a re-anchor, not an inversion. Dark grounds compress: ink to
   deep is only 1.37, so a five-step dark ladder is not physically
   available. Dark mode therefore runs two grounds and lets fluoro carry
   the whole rhythm. Stated rather than faked.
   ============================================================ */
:root{
  /* palette — fixed values, never used directly in a rule */
  --p-paper:#F7F1E3; --p-cream:#E8D6A4; --p-fluoro:#E8251F;
  --p-deep:#173CA8;  --p-ink:#0D1633;   --p-fluoro-deep:var(--accent-type);
  --p-fluoro-lift:var(--accent-type); --p-paper-dim:#E6E0D2;

  /* semantic — what a rule may reference */
  --g:var(--p-paper);        /* section ground            */
  --g-inset:var(--p-cream);  /* card / inset on that ground */
  --t-inset:var(--p-deep);   /* type ON that inset. An inset that changes
                                ground must change its type with it, or a
                                card silently renders ink on ink. */
  --t:var(--p-deep);         /* primary type   12.08 on paper */
  --t-mute:#4A5578;          /* metadata        6.18 on paper */
  --t-accent:var(--p-fluoro-deep); /* small accent type 5.07 */
  --mark:var(--p-fluoro);    /* rules, marks, display type only */
  --rule:rgba(23,60,168,.22);
}
/* ground contexts. Put one of these on a section and the type follows. */
[class*="on-"]{background:var(--g);color:var(--t)}

/* DARK. Grounds re-anchor to ink; the two light grounds fold into the two
   darks. Fluoro is untouched, so the one violent moment survives the
   switch unchanged. Pure paper at 16.51 on ink is harsh at night, so
   dark-mode type is #E6E0D2 at 14.13. */
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){
    --g:var(--p-ink); --g-inset:var(--p-deep); --t-inset:var(--p-paper-dim);
    --t:var(--p-paper-dim); --t-mute:#9AA2C0; --t-accent:var(--p-fluoro-lift);
    --rule:rgba(247,241,227,.18);
  }
  /* .on-fluoro deliberately unchanged */
}
/* manual override hook, so a toggle can be added later without a rewrite */
:root[data-theme="dark"]{ --g:var(--p-ink); --g-inset:var(--p-deep); --t-inset:var(--p-paper-dim);
  --t:var(--p-paper-dim); --t-mute:#9AA2C0; --t-accent:var(--p-fluoro-lift);
  --rule:rgba(247,241,227,.18) }/* Reading inset. The near-white column that body copy sits on. */

/* ---------- PROOF ----------
   The one fluoro plane. Fluoro carries no small type anywhere; on this ground
   the type colour is ink, measured 4.79.
   Kept deliberately short. Evidence read on the way past, not a destination. */

/* The figures count up with the scroll: see PROOF figures in home.js. They are
   never hidden; with no JS the markup already holds the final values. */

:root{
  /* palette, families, --pad and --max are defined in base.css */
  --ground:var(--cream); --text:var(--deep);
  --xs:var(--t-small); --sm:var(--t-small); --base:var(--t-body); --md:var(--t-h3);
  --lg:var(--t-h3); --xl:var(--t-h2); --xxl:var(--t-h1);
  --ease:cubic-bezier(.16,1,.3,1);
  --sec:clamp(var(--s-8),10vw,var(--s-9)); /* 64 -> 96, his numbers. Was --s-10 (128). */
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
html,body{margin:0}
/* the grid moved to a fixed layer in base.css so its strength can be driven
   by the scroll; body only carries the ground colour now */
body{background:var(--ground);color:var(--text);font-family:var(--fb);font-size:var(--t-body);line-height:var(--lh-body);-webkit-font-smoothing:antialiased;overflow-x:hidden}
body::after{content:"";position:fixed;inset:0;pointer-events:none;z-index:80;opacity:.035;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E")}
.wrap{max-width:var(--max);margin:0 auto;padding:0 var(--pad)}
h1,h2,h3{font-family:var(--fd);font-weight:700;margin:0;line-height:.95;letter-spacing:var(--fd-track)}
p{margin:0 0 1rem}
a{color:inherit}
.mono{font-family:var(--fu);font-size:var(--xs);letter-spacing:.08em;text-transform:uppercase}
:focus-visible{outline:2px solid var(--fluoro);outline-offset:3px}

/* ---------- section head ----------
   One left edge (9 Oct, at his instruction). It used to be a two-column grid
   whose first column was sized `auto`, which meant the heading started wherever
   the label beside it happened to end: "03 / Ideas" and "How the work happens"
   are different lengths, so the same device landed on five different left edges
   across the site (measured: 132, 142, 160, 207 and 251px at 1280).
   Now the label, the heading, the sub and the body below all start at the
   content edge, which is what /work, /contact and the five case pages already
   did. The star keeps its place beside the label rather than above it, via
   :has(); where there is no star the label simply starts the row. */
.shead{display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:.7rem;row-gap:.55rem;
  align-items:center;border-top:3px solid var(--deep);padding-top:1rem;margin-bottom:2.6rem}
.shead .no{grid-column:1 / -1;font-family:var(--fu);font-size:var(--xs);letter-spacing:.08em;
  opacity:.82;margin:0;font-weight:inherit}
.shead:has(.smark) .no{grid-column:2;grid-row:1}
.shead h2{grid-column:1 / -1;font-size:var(--xl);margin:0}
.shead .sub{grid-column:1 / -1;font-size:var(--base);opacity:.82;max-width:34em;margin-top:.7rem}

/* ---------- nav ---------- */
/* scoped to the island by id (12 Oct). As a bare element selector this
   caught the first other <nav> on the site, the Explore column in the sign
   off, and threw it to the top of the viewport as a second fixed bar. */
nav#nav{position:fixed;top:0;left:0;right:0;z-index:60;display:flex;justify-content:space-between;align-items:center;
  padding:1.1rem var(--pad);font-family:var(--fu);font-size:var(--xs);letter-spacing:.08em;text-transform:uppercase;
  transition:transform 320ms var(--ease),background 320ms var(--ease)}
nav#nav .sig{font-weight:600}
/* the logo in the island (Nav1, delivered 1 Oct). It is a single-colour mark,
   so it is inverted to the island's cream rather than recoloured per theme. */
nav#nav .sig{display:flex;align-items:center}
nav#nav .links{display:flex;gap:1.5rem}
nav#nav a{text-decoration:none;position:relative;padding:3px 0}
nav#nav .sig{background:none;border:0;padding:0;box-shadow:none}
@media(max-width:820px){nav .links{display:none}}

/* ============================================================
   HERO (rebuilt 21 Sep 2026). One block; it replaces three stacked
   patches that each re-set the same properties.

   A side-by-side split, after a reference Shrey chose.
     Take only: the colour field against the photograph, the photograph
     as a halftone print, one small caption.
     Ignore: the top/bottom stack (it broke the reading), their red, their
     handwriting and scribble. The cut-out name does the scribble's job,
     stuck across the foot of the print.
   Left, the artefact on a fluoro field. Right, the words in one column
   that reads straight down: category, the line, the idea, the sign-off,
   the two actions, the proof.
   ============================================================ */
.hero{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);min-height:100vh;min-height:100svh}

/* ---------- the artefact ---------- */
/* sized so print, name and caption fit one screen: the stack is ~1.5x the width */
/* a real halftone, rendered once from the photograph (dot size follows
   how dark the photo is) and printed in deep ink on cream paper */ /* a label on the print: small type never sits on red */

/* the name: cut-outs stuck across the foot of the print */
.hero-name{position:absolute;left:-7cqw;right:-5cqw;top:calc(125cqw - 26cqw);
  margin:0;font-size:inherit;line-height:normal;letter-spacing:normal}
.hero-name .l1{display:flex;flex-wrap:nowrap;align-items:flex-end;gap:2.5cqw}
.hero-name .l2{display:block;margin-top:1.5cqw}
.tile{display:block;will-change:transform;cursor:grab;user-select:none}
.tile img{display:block;width:100%;height:auto;pointer-events:none;filter:drop-shadow(3px 4px 0 rgba(13,22,51,.45))}
.t-hi{width:17cqw} .t-my{width:19cqw} .t-name{width:34cqw} .t-is{width:12cqw} .t-shrey{width:78cqw}

/* ---------- the words ---------- */
.hero-k{margin:0;font-family:var(--fu);font-size:var(--t-small);line-height:1.6;color:rgba(var(--deep-rgb),.8)}
.roll{display:inline-block;height:1.7em;overflow:hidden;vertical-align:bottom;margin-top:var(--s-1);
  background:var(--deep);color:var(--cream);padding:0 .3em;transform:rotate(-1.2deg)}
.hero-cta{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s-4) var(--s-6);margin-top:var(--s-7)}
.hbtn{display:inline-flex;align-items:center;min-height:48px;padding:0 var(--s-5);background:var(--deep);color:var(--cream);
  font-size:var(--t-body);font-weight:600;text-decoration:none;transition:background-color var(--d-1) ease,color var(--d-1) ease}
.hbtn:hover,.hbtn:focus-visible{background:var(--deep);color:var(--cream)}

/* the words: one column, centred on the screen's height */

/* tablets and phones: the artefact on top, full width, then the words */
@media (max-width:900px){
  .hero{grid-template-columns:minmax(0,1fr);min-height:0}
  /* the name stays on the screen: it overhangs the print, never the edge */
  .hero-name{left:-4cqw;right:auto}
}

/* ---------- ticker ----------
   The transition between identity and proof. One job: other people vouch
   for him. Hierarchy, top to bottom: the quotes are evidence and must be
   READ, so they do not move. The places are recognition, not comprehension,
   so they are the thing that moves. The disciplines are metadata and sit
   still and small. Previously all three were identical marquees, which made
   the only third-party validation on the site the least legible thing on it. */
 /* the tape above is the divider; --sec here was dead air */

/* metadata. Smallest, mono, muted — the bottom of the hierarchy. */
/* ---------- worked with ----------
   A credits line, not a logo wall. Six equal cells ruled like a table, names
   muted until pointed at, when the cell inverts. Only real employers, events and
   projects sit here, with context where it was not a job; spec brands never do. */
/* The eight marks, cut from the board Shrey supplied (2 Oct). They are
   mixed lockups: two sit on their own dark or orange plate, the rest are
   free-standing, so each one is capped by height rather than width and the
   row aligns on the optical centre instead of the box. UN Youth and BCG keep
   their college-project line, per CONTENT. */
/* The marks are exported on one ink and pre-balanced by ink area (2 Oct),
   so a single height is correct for all eight: no per-logo nudging. */
/* Bigger, and on the red (6 Oct feedback): at this size the marks are
   graphics, not small type, so cape red clears the 3:1 that large graphics
   need on the ground. Full breadth of the row, one height for all eight. */
/* Each mark is trimmed to its own ink and sized by height, which is how a
   logo wall is normally set: a square device and a long wordmark then read
   as the same weight. Width follows. */
/* Bliss and Birch is drawn small inside its own lockup, so at a shared cap
   height it still reads light. One nudge, the way a logo wall is normally
   optically corrected. */

/* ---------- the cross ----------
   Two bands meet in an X, from the riyadesign reference (chosen by Shrey,
   17 Sep 2026). What he does runs one way on deep; what it moved runs the
   other way on cream, ruled so it reads where it crosses. Speed follows the
   scroll through Lenis: scrolling pushes the bands, scrolling back reverses
   them, pointing at a band slows it to a stop. The riso star is the separator. */

/* ---------- the strip of marks (13 Oct, at his instruction) ----------
   One comic ticker on this page and it is the quote tape. These are other
   people's marks and they are evidence, so they travel on a ruled line with no
   torn edge, no tilt and no stars between them.
   The whole strip is also a way into the metrics below it: home.js makes a
   click on it open that drawer, which is why it carries a pointer. */
.logo-strip{position:relative;overflow:hidden;cursor:pointer;
  border-top:var(--line) solid var(--deep);border-bottom:var(--line) solid var(--deep)}
/* the shared .xtrack rule went with the crossing bands; each track needs its
   own row now or the four repeat copies stack instead of running */
.logo-strip .xtrack{display:flex;width:max-content;will-change:transform}
.logo-strip:is(:hover,:focus-within){background:rgba(var(--deep-rgb),.04)}

/* ---------- the metrics, collapsed onto a pill ----------
   Closed on arrival. Shield yellow, because this is the one plane on the page
   that has to be found rather than read past, and it is the first time yellow
   carries a surface rather than a highlight. Ink on yellow is 14.73, so the
   type inside is ink and the red accent stays off it. */
.mv{margin-top:var(--s-6)}
.mv-pill{cursor:pointer;list-style:none;display:flex;align-items:center;gap:var(--s-4);
  background:var(--yellow);color:#0D1633;border:var(--line) solid #0D1633;border-radius:999px;
  padding:clamp(.9rem,2vw,1.3rem) clamp(1.6rem,3.5vw,2.6rem);min-height:56px}
.mv-pill::-webkit-details-marker{display:none}
.mv-pill-k{font-family:var(--fd);font-weight:900;font-size:var(--t-h3);line-height:1.1;
  letter-spacing:var(--fd-track)}
.mv-pill-n{margin-left:auto;font-size:var(--xs);letter-spacing:.08em;text-transform:uppercase;opacity:.72}
.mv-chev{flex:none;width:14px;height:14px;border-right:3px solid #0D1633;border-bottom:3px solid #0D1633;
  transform:rotate(45deg) translate(-2px,-2px);transition:transform var(--d-2) var(--e-out)}
.mv[open] .mv-chev{transform:rotate(225deg) translate(-2px,-2px)}
.mv-pill:focus-visible{outline:3px solid var(--deep);outline-offset:3px}
@media (prefers-reduced-motion:reduce){.mv-chev{transition:none}}

/* the drawer sits under the pill on the page's own ground */
.mv .mv-grid{margin-top:var(--s-6)}
@media (max-width:640px){
  .mv-pill{border-radius:28px;flex-wrap:wrap;gap:var(--s-2) var(--s-3)}
  .mv-pill-n{margin-left:0;width:100%}
}

/* ---------- what I do, and what it moved ----------
   This replaces the crossing bands (7 Oct). The X said something true, that
   the disciplines and the results belong to each other, and it said it in
   motion, which is where the seven figures went to not be read. A scanning
   reader takes away "there are numbers" rather than any one of them, and
   these seven are the most persuasive content on the site.
   So the same two ideas, side by side and still. The figure leads at display
   size, the unit sits next to it, and the employer sits under it, because a
   number without a source is a claim and a number with one is evidence. */
.moved{padding:clamp(3rem,7vw,5rem) 0 clamp(2.5rem,6vw,4rem)}
/* align-items:start, or a closed drawer reclaims nothing: grid items stretch
   to the row by default, so the column kept its full height with the list
   hidden inside it and the collapse did nothing visible. */
/* Golden, like .abody and .cblock and the case pages (item 13). It was
   .95 / 1.25, which is 1.316 and is not a ratio anything else on the site
   uses. The figures take the larger column because they are the evidence
   and the disciplines are the context: the hierarchy and the geometry now
   say the same thing. Measured at 1440: 463/609 before, 436/706 after. */
.mv-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.618fr);gap:clamp(2rem,5vw,4.5rem);
  align-items:start;border-top:var(--line) solid var(--deep);padding-top:var(--s-6)}
/* Both columns are native <details> (9 Oct, at his instruction). Native,
   because it is keyboard operable, announced correctly, and works with the
   script switched off, none of which a div and a click handler gives you.
   Both carry `open`: the figures are the most persuasive content on the page
   and a closed drawer buries them. Collapsing is for the second read. */
/* the marker is drawn, so it can be the site’s own line work rather than a
   platform triangle: a rule that loses its upright when the drawer opens */
@media (prefers-reduced-motion:reduce){.mv-col > summary::after{transition:none}}
.mv-k{font-size:var(--xs);letter-spacing:.08em;text-transform:uppercase;opacity:.82;margin:0 0 var(--s-5)}
.mv-do{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:var(--s-3) var(--s-4);line-height:1.5}
.mv-do li{font-family:var(--fu);font-size:var(--t-small);letter-spacing:.04em;text-transform:uppercase;
  color:var(--deep);white-space:nowrap}
.mv-do li::after{content:"·";margin-left:var(--s-4);opacity:.5}
.mv-do li:last-child::after{content:none}
.mv-figs{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:var(--s-5) var(--s-6)}
.mv-figs li{display:grid;grid-template-columns:max-content minmax(0,1fr);align-items:baseline;
  column-gap:var(--s-3);border-top:1.5px solid rgba(var(--deep-rgb),.3);padding-top:var(--s-2)}
.mv-figs b{grid-row:span 2;font-family:var(--fd);font-weight:900;font-size:clamp(1.5rem,2.6vw,2.1rem);
  line-height:1;letter-spacing:var(--fd-track);color:var(--deep)}
.mv-what{font-family:var(--fb);font-size:var(--t-small);line-height:1.3;color:var(--deep)}
.mv-who{font-family:var(--fu);font-size:var(--xs);letter-spacing:.06em;text-transform:uppercase;
  color:var(--accent-type)}
@media (max-width:900px){
  .mv-grid{grid-template-columns:minmax(0,1fr);gap:var(--s-7)}
  .mv-figs{gap:var(--s-4) var(--s-5)}
}
@media (max-width:560px){.mv-figs{grid-template-columns:minmax(0,1fr)}}

/* ---------- what keeps me going: the artefacts float ----------
   Three objects he made, not three images in a row. Each carries its own slow
   loop with a different duration and delay, so they never line up and the
   group never reads as a grid. CSS keyframes on transform only, which the
   compositor handles and which cannot stack or lag the way the hero drift did.
   Under reduced motion they simply sit still, slightly rotated, which is what
   they look like at rest anyway. */
.mot{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:clamp(1rem,3vw,2.6rem);
  align-items:center;margin:var(--s-7) 0 0}
.mot-item{margin:0}
.mot-item img{display:block;width:100%;height:auto}
.mot-item--1{animation:mot-a 7.5s ease-in-out infinite}
.mot-item--3{animation:mot-a 8.2s ease-in-out infinite 1.2s}
@keyframes mot-a{0%,100%{transform:translateY(0) rotate(-1.5deg)}50%{transform:translateY(-10px) rotate(.8deg)}}
@keyframes mot-b{0%,100%{transform:translateY(-6px) rotate(1.6deg)}50%{transform:translateY(6px) rotate(-1deg)}}
@media (prefers-reduced-motion:reduce){.mot-item{animation:none;transform:rotate(-1.2deg)}}
@media (max-width:760px){.mot{grid-template-columns:minmax(0,1fr);gap:var(--s-6)}}

/* ---------- the process: an orange band that runs ----------
   Its own ground, at his instruction, because every section should change
   colour. Orange is new to the palette and earns its place by being the only
   one: ink on it is 9.1, so the type is ink and nothing else sits here. */
.process{--orange:#F0792A;position:relative;overflow:hidden;background:var(--orange);
  border-top:3px solid #0D1633;border-bottom:3px solid #0D1633;margin:var(--s-6) 0 0}
.process .pr-band{position:relative;overflow:hidden}
.process .xtrack{display:flex;width:max-content;will-change:transform}

/* ---------- work: pinned collage ----------
   Scroll and hover mechanics translated from alaris.studio (reference chosen by
   Shrey, 17 Sep 2026), measured from the live site rather than its source:
   - the section is 240vh (was 400vh; cut 22 Sep 2026 on "too much scroll") and its shell sticks for 100vh;
   - progress p runs 0..1 across the scroll; tile i lands over p = reveal..reveal+.13,
     rising 16vh and scaling .9 to 1, tilted;
   - ghost type slides along a curve, trailed by marks, drifting 5vh against the scroll;
   - hover crossfades the poster out in .25s onto footage that plays, and rewinds on leave.
   Differences on purpose: cream/deep/fluoro instead of black, our star as the trailing
   mark, no drop shadow on hover (veto list), no transition on the tile transform (it
   stacked a second ease on top of Lenis and made tiles lag the scroll), and below 760px
   the collage pins as a deck instead of stacking. Only reduced motion stacks. */
.work{--wk-ease:cubic-bezier(.2,.75,.2,1);background:var(--deep);color:var(--cream);position:relative;
  padding:clamp(4.5rem,12vw,6rem) var(--pad) clamp(4rem,10vw,5.5rem)}

.wk-eyebrow,.wk-note,.wk-label{font-family:var(--fu);text-transform:uppercase}
.wk-eyebrow{font-size:var(--xs);letter-spacing:.08em;opacity:.82;margin:0}
.wk-title{font-family:var(--fd);font-weight:700;font-size:var(--t-h2);line-height:.92;
  letter-spacing:var(--fd-track);max-width:9ch;margin:.8rem 0 0;color:var(--cream)}

.wk-ghost{display:none}

.wk-tile{display:block;color:var(--cream);text-decoration:none;transform:rotate(var(--r,0deg))}
.wk-tile:focus-visible{outline:3px solid var(--fluoro);outline-offset:8px}
/* 16:10 again (12 Oct): the covers were portrait A-series when they arrived
   in October and the collage was retuned to match. He has supplied 1600x1000
   masters, so the slot goes back to the shape it was designed for. */
.wk-frame{position:relative;display:block;aspect-ratio:16/10;overflow:hidden;contain:paint;container-type:size;
  border:1px solid rgba(var(--cream-rgb),.22);background:var(--cream)}

/* poster: the still face. Typographic until real imagery exists. */
.wk-poster img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center}
.wk-poster{position:absolute;inset:0;z-index:2;display:flex;flex-direction:column;justify-content:space-between;
  padding:5cqw 5.5cqw;transition:opacity .25s var(--wk-ease)}
.wk-poster::after{content:"";position:absolute;inset:0;pointer-events:none;opacity:.08;
  background-image:radial-gradient(circle at 1px 1px,currentColor 1px,transparent 1.2px);background-size:5px 5px}

.wk-note{display:block;font-size:var(--xs);letter-spacing:.08em;line-height:1.55;color:rgba(var(--cream-rgb),.55);margin:2.4rem 0 0}
a.wk-note{display:inline-flex;align-items:center;gap:14px;padding:6px 0;color:var(--cream);text-decoration:none}
a.wk-note:focus-visible{outline:3px solid var(--fluoro);outline-offset:6px}
.wk-arrow{position:relative;display:inline-block;width:74px;height:1px;background:rgba(var(--cream-rgb),.45);
  transition:width .3s var(--wk-ease)}
.wk-arrow::after{content:"";position:absolute;right:0;top:50%;width:8px;height:8px;
  border-top:1px solid rgba(var(--cream-rgb),.6);border-right:1px solid rgba(var(--cream-rgb),.6);transform:translateY(-50%) rotate(45deg)}
a.wk-note:hover .wk-arrow,a.wk-note:focus-visible .wk-arrow{width:96px}

/* stacked mode: fade up as each tile enters */
/* ============================================================
   POP FROM THE WALL (6 Oct 2026, at his instruction)
   The cards used to slide up and pile on each other, which left the one
   underneath half visible and the label unreadable. Now each card comes
   OUT of the wall: it starts small, flat and clipped to a slot, then
   pushes forward into its own space. No two cards share a position, so
   nothing is ever read through anything else.
   --p is the per-card scroll progress the pin already computes, so the
   pop is scrubbed by the same scroll rather than fired by a timer.
   ============================================================ */
/* The clip is gone (13 Oct). Coming out of a slot was my idea and it fails
   in practice: --p spends most of its life between 0 and 1, so the card spends
   most of its life as a card with its top 20% and its bottom 5% sliced off,
   and a half-cut label does not read as an animation, it reads as a rendering
   bug. That is the choppiness he reported. Opacity, scale and lift have no
   intermediate value that looks broken, which is the property that matters
   for anything scrubbed by a scroll the reader controls.
   Opacity now resolves by --p .45, so the card is solid well before it is
   near enough to read. */
.work.wk-live .wk-tile{
  --p:0;
  opacity:calc(var(--p) * 2.2);
  transform:rotate(var(--r,0deg)) scale(calc(.84 + var(--p) * .16)) translateY(calc((1 - var(--p)) * 34px));
  transition:none;will-change:transform,opacity}
/* the slot it comes out of: a rule on the wall, behind the card */
.work.wk-live .wk-tile::before{content:"";position:absolute;z-index:-1;left:6%;right:6%;top:46%;height:2px;
  background:rgba(var(--cream-rgb),.5);opacity:calc(1 - var(--p));transform:scaleX(calc(.3 + var(--p) * .7))}
.work.wk-live .wk-tile.is-visible{opacity:1}
@media (prefers-reduced-motion:reduce){
  .work.wk-live .wk-tile{opacity:1;transform:rotate(var(--r,0deg));clip-path:none}
  .work.wk-live .wk-tile::before{display:none}
}
@media (min-width:640px) and (max-width:759px){.wk-stage{grid-template-columns:repeat(2,minmax(0,1fr))}}

/* pinned mode */
@media (min-width:760px) and (prefers-reduced-motion:no-preference){
  /* the pin is gone with the collage (24 Oct): the section is its content
     tall now, not 240vh of sticky stage. */
  .work{padding:clamp(4.5rem,12vw,6rem) var(--pad) clamp(4rem,10vw,5.5rem)} /* was 400vh: 41% of the page for three projects (22 Sep 2026) */

  .wk-copy{position:absolute;z-index:15;top:clamp(86px,11vh,118px);
    --wk-left:max(var(--pad),calc((100vw - var(--max)) / 2 + var(--pad)));
    left:var(--wk-left);max-width:calc(31vw - var(--wk-left) - 2rem)}
  .wk-title{font-size:var(--t-h2);max-width:none}

  /* the crosshair is gone for good (13 Oct): it followed no grid line and
     carried nothing. It was set to display:none at the top of this file and
     then switched back on here, which is why it kept coming back. */
  .wk-ghost{display:block;position:absolute;z-index:0;inset:-8vh -7vw -12vh -9vw;width:116vw;height:122vh;
    overflow:visible;pointer-events:none;transform:translate3d(0,calc((var(--wp,0) - .45) * -5vh),0)}
  .wk-ghost-text{font-family:var(--fd);font-weight:700;letter-spacing:var(--fd-track);fill:rgba(var(--cream-rgb),.12);dominant-baseline:middle}
  .wk-ghost [data-ghost-stars] path{fill:rgba(var(--cream-rgb),.12)}

  .work .wk-tile,.work.wk-live .wk-tile{position:absolute;left:var(--x);top:var(--y);width:var(--w);pointer-events:auto;
    transform:translate3d(0,calc(16vh - var(--p,1) * 16vh),0) scale(calc(.9 + var(--p,1) * .1)) rotate(var(--r));
    transition:opacity .42s var(--wk-ease)}
  .work.wk-live .wk-tile{opacity:0}
  .work.wk-live .wk-tile.is-visible{opacity:1;
    transform:translate3d(0,calc(16vh - var(--p,1) * 16vh),0) scale(calc(.9 + var(--p,1) * .1)) rotate(var(--r))}
  .wk-tile:hover,.wk-tile:focus-visible{z-index:12}
    .wk-note{margin:var(--s-6) 0 0}
  .wk-note--left{left:max(var(--pad),calc((100vw - var(--max)) / 2 + var(--pad)))}
  .wk-note--right{left:40%;max-width:320px}
}
/* pinned, narrow: the collage becomes a deck. Same scroll maths over 300vh,
   each tile landing on the one before, offset enough that the pile reads. */
@media (max-width:759px) and (prefers-reduced-motion:no-preference){
    .work{padding:clamp(3.5rem,10vw,4.5rem) var(--pad) clamp(3rem,8vw,4rem)} /* was 300vh */

  .wk-copy{position:absolute;z-index:15;top:clamp(72px,10vh,96px);left:var(--pad);right:var(--pad)}
  .wk-title{font-size:var(--t-h2);max-width:12ch}

  .wk-ghost{display:block;position:absolute;z-index:0;top:30vh;left:-30vw;width:170vw;height:60vh;
    overflow:visible;pointer-events:none;transform:translate3d(0,calc((var(--wp,0) - .45) * -5vh),0)}
  .wk-ghost-text{font-family:var(--fd);font-weight:700;letter-spacing:var(--fd-track);fill:rgba(var(--cream-rgb),.12);dominant-baseline:middle}
  .wk-ghost [data-ghost-stars] path{fill:rgba(var(--cream-rgb),.12)}

  .work .wk-tile,.work.wk-live .wk-tile,.work.wk-live .wk-tile.is-visible{position:absolute;left:var(--dx);
    top:calc(var(--deck-top,43vh) + var(--dy));width:var(--deck-w,min(80vw,440px));pointer-events:auto;
    transform:translate3d(0,calc(16vh - var(--p,1) * 16vh),0) scale(calc(.9 + var(--p,1) * .1)) rotate(var(--r));
    transition:opacity .42s var(--wk-ease)}
  .work.wk-live .wk-tile{opacity:0}
  .work.wk-live .wk-tile.is-visible{opacity:1}

  
  .wk-tile:nth-child(1){--dx:6vw;--dy:0vh}
  .wk-tile:nth-child(2){--dx:13vw;--dy:3vh}
  .wk-tile:nth-child(3){--dx:8vw;--dy:6vh}
    .wk-note{margin:var(--s-5) 0 0;right:auto} /* clear the vinyl button */
  a.wk-note{right:auto}
  .wk-note--left{top:var(--link-top,auto);bottom:auto}
  .wk-note--right{bottom:4vh;letter-spacing:.08em}
}
@media (prefers-reduced-motion:reduce){
  .work.wk-live .wk-tile{opacity:1;transform:rotate(var(--r,0deg));transition:none}
}

/* ---------- brain dump (dark) ---------- */
.dump{background:var(--deep);color:var(--cream);padding:var(--sec) 0}
.steps{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--s-7) var(--s-6);margin-top:var(--s-4)}
.step{border-left:var(--line) solid var(--fluoro);padding:0 0 0 1rem}
.step .n{font-family:var(--fd);font-weight:700;font-size:var(--t-h2);line-height:1;opacity:.82;display:block;margin-bottom:.5rem;letter-spacing:var(--fd-track)}
.step h3{font-size:var(--t-body);margin-bottom:.5rem}
.step p{font-size:var(--sm);line-height:1.5;opacity:.85;margin:0}
.step:hover .n{opacity:1;color:var(--fluoro)}
@media(max-width:1000px){.steps{grid-template-columns:repeat(2,1fr);gap:1.8rem}}
@media(max-width:600px){.steps{grid-template-columns:1fr}}

/* ---------- about ---------- */
/* The jump from the zine into Me was invisible: the same ground, the same
   eyebrow, no change of register (6 Oct feedback). This is the turn: a full
   bleed rule with the sentence that hands over, set in the annotation face so
   it reads as an aside rather than a heading. It is the only place on the
   page where a line of type sits on the rule itself. */
.me-turn{position:relative;margin:var(--s-9) 0 var(--s-8);display:grid;place-items:center}
.me-turn::before{content:"";position:absolute;left:50%;transform:translateX(-50%);width:100vw;height:3px;background:var(--deep)}
.me-turn span{position:relative;background:var(--cream);padding:0 var(--s-5);font-family:var(--fu);
  font-size:var(--t-small);letter-spacing:.14em;text-transform:uppercase;color:var(--deep)}
.shead--me{margin-top:var(--s-6)}
@media (max-width:640px){.me-turn{margin:var(--s-8) 0 var(--s-6)}.me-turn span{padding:0 var(--s-3)}}
.about{padding:var(--sec) 0}
.abody{display:grid;grid-template-columns:minmax(0,1.618fr) minmax(0,1fr);gap:3rem;align-items:start}
.abody .prose p:first-child{font-size:var(--md);line-height:1.3;font-weight:500}
/* tighter paragraph rhythm, at his request 13 Oct: the long version read as
   five separated blocks rather than as one piece of writing. The beats in the
   home monologue come in with it, or the two pages disagree. */
.abody .prose p{max-width:32em;margin-block:0 var(--s-4)}
.prose p{margin-block:0 var(--s-4)}
.beat + .beat{margin-top:var(--s-4)}
.skills h3{font-family:var(--fu);font-size:var(--xs);letter-spacing:.08em;text-transform:uppercase;
  margin:0 0 .8rem;padding-bottom:.4rem;border-bottom:var(--line) solid var(--deep)}
.skills .grp{margin-bottom:1.8rem}
.skills .cat{font-family:var(--fu);font-size:var(--t-small);letter-spacing:.08em;text-transform:uppercase;opacity:.82;margin:0 0 .4rem}
.skills ul{list-style:none;margin:0 0 .9rem;padding:0;display:flex;flex-wrap:wrap;gap:.35rem}
.skills li{font-size:var(--sm);border-bottom:1px solid rgba(var(--deep-rgb),.35);padding:1px 0}
.skills li::after{content:"·";margin-left:.4rem;opacity:.4;border:0}
.skills li:last-child::after{content:""}
.skills .work-k li{opacity:.82}
@media(max-width:900px){.abody{grid-template-columns:1fr;gap:2.4rem}}

/* ---------- faq bento ---------- */
.faq{padding:var(--sec) 0}
.bento{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:.9rem;margin-top:.5rem;align-items:start}
.tile-q{grid-column:span 2;border:3px solid var(--deep);background:var(--cream);padding:1rem 1.1rem;
  cursor:pointer;text-align:left;font:inherit;color:inherit;display:block;width:100%;
  transition:background 200ms var(--ease),color 200ms var(--ease)}
.tile-q:nth-child(1){grid-column:span 3}
.tile-q:nth-child(2){grid-column:span 3}
.tile-q:nth-child(4){grid-column:span 2}
.tile-q:nth-child(5){grid-column:span 4}
.tile-q:nth-child(8){grid-column:span 4}
.tile-q:nth-child(9){grid-column:span 2}
.tile-q .qn{font-family:var(--fu);font-size:var(--t-small);letter-spacing:.08em;opacity:.82;display:block;margin-bottom:.4rem}
.tile-q .qt{font-family:var(--fb);font-weight:700;font-size:var(--t-body);line-height:1.25;display:flex;justify-content:space-between;gap:1rem}
.tile-q .pm{font-family:var(--fd);font-weight:700;flex:none;transition:transform 220ms var(--ease);letter-spacing:var(--fd-track)}
.tile-q[aria-expanded="true"]{background:var(--deep);color:var(--cream)}
.tile-q[aria-expanded="true"] .pm{transform:rotate(45deg)}
.tile-q .ans{display:block;max-height:0;overflow:hidden;transition:max-height 350ms var(--ease),margin 350ms var(--ease);
  font-size:var(--sm);line-height:1.55;font-weight:400;margin:0}
.tile-q[aria-expanded="true"] .ans{max-height:420px;margin-top:.8rem}
.tile-q:hover{box-shadow:5px 5px 0 var(--fluoro)}
@media(max-width:900px){.bento{grid-template-columns:1fr}.tile-q,.tile-q:nth-child(n){grid-column:span 1}}

/* ---------- contact ----------
   The close. The brief asks for a deliberate reduction in intensity, so this
   returns to the ground colour the page opened on - the site starts and ends
   on cream, and the brain dump is left as the single dark moment rather than
   one of two. Was: dark ground, --xxl headline, a decorative star and three
   links competing at the same weight. One accent appears here, on the one
   action that matters. */
.contact{background:var(--cream);color:var(--deep);
  padding:clamp(3rem,7vw,5rem) 0 2rem;position:relative;border-top:3px solid var(--deep)}
.contact .no{font-family:var(--fu);font-size:var(--xs);letter-spacing:.08em;
  text-transform:uppercase;opacity:.82;display:block;margin-bottom:2.4rem}
.cta{font-family:var(--fd);font-weight:700;font-size:var(--t-h1);line-height:.98;
  margin:0 0 2.8rem;max-width:13em;letter-spacing:var(--fd-track)}
.cta em{font-style:normal;color:var(--fluoro)}

/* the action. Typographic, and the largest interactive thing on the page. */
.reach{display:inline-block;text-decoration:none;font-family:var(--fd);font-weight:700;
  font-size:var(--t-h2);line-height:1.05;letter-spacing:var(--fd-track);
  border-bottom:4px solid var(--fluoro);padding-bottom:.2rem;
  transition:border-color 220ms var(--ease)}
.reach:hover,.reach:focus-visible{border-bottom-color:var(--deep)}

/* everything else here is secondary and looks it */
/* The end of the page says what to do and nothing else (6 Oct). Four ways
   out, each marked by its icon, all weighted the same: no one of them gets a
   special treatment, which was his note about the long-version link too. */

/* footer: the Made by Shrey mark (see its own block) and one line of
   metadata. Metadata is #4A5578-weight deep, measured above AA. */
.foot{margin-top:3.4rem;padding-top:1.5rem;border-top:1px solid rgba(var(--deep-rgb),.28);
  display:flex;justify-content:space-between;align-items:flex-end;gap:1.2rem 2rem;flex-wrap:wrap}
.sign{margin:0;display:flex;align-items:baseline;gap:0;color:var(--deep);
  font-family:var(--fd);font-weight:900;line-height:1;font-size:var(--t-body);letter-spacing:var(--fd-track)}
.sign .sw{white-space:nowrap}
.sign-mark{height:clamp(26px,4vw,46px);width:auto;margin:0 .12rem 0 .45rem;transform:translateY(.22em)}
.sign-star{height:clamp(15px,1.9vw,22px);width:auto;margin:0 .1rem 0 0;transform:translateY(-.55em) rotate(-9deg);
  transition:transform 220ms var(--ease)}
.sign:hover .sign-star{transform:translateY(-.55em) rotate(21deg)}
@media(prefers-reduced-motion:reduce){.sign .sign-star,.sign:hover .sign-star{transition:none}}
@media(max-width:640px){.sign-mark{margin-left:.34rem}}
.foot-meta{margin:0;font-family:var(--fu);font-size:var(--xs);letter-spacing:.08em;
  text-transform:uppercase;color:rgba(var(--deep-rgb),.78)}

@media(max-width:640px){.foot{align-items:flex-start}}

/* ---------- vinyl ----------
   Form from the riyadesign reference (chosen by Shrey, 17 Sep 2026): a record
   parked bottom-right with its title above. Ours is a deep record, not black,
   with the hero photograph on the label, a tonearm that drops when it plays,
   and a fluoro ring that tracks the song's position. The ring is the one
   informative use of the accent here. 33 1/3 rpm is 1.8s a turn. */
/* It parks small and opens when you reach for it (8 Oct). At 112px the record
   sat over the bottom-right corner of every screen for the whole page: over a
   figure in What it moved, over the zine, over the last line of Me. A floating
   control may cover the corner it lives in; it may not cover the content. So
   it rests at 64px, which is a control, and grows to 112px, which is a record,
   on hover, focus or while playing. It is anchored bottom-right, so the target
   only ever grows under the pointer.
   It also stands down over the hero, which it used to do and stopped doing when
   the hero was rebuilt: html.at-hero is still set by home.js and nothing was
   reading it. The Listening mark in the hero is the way in, so the player has
   no job until the visitor has passed it. */
:focus-within,.playing){--vd:112px}
/* On a phone there is no corner to spare: a 64px control sits on the body copy
   of whatever section you are reading. So it is not there until it is asked
   for. The Listening mark in the hero links to #vinyl, which makes it the
   target, and once it is playing it stays. Summoned, not parked. */
:focus-within{opacity:1;transform:none;pointer-events:auto}
/* 24px minimum on the target (WCAG 2.5.8). It was 23, which the caption only
   exposed once the player started opening on hover. */
@keyframes vspin{to{transform:rotate(360deg)}}
/* the label is the sleeve he supplied now, so it is not screened or greyed:
   it is artwork, and the halftone treatment was there to make a photograph
   read as a record label */
/* the embed does the playing; it is rendered, never shown */
/* .vinyl-host and its iframe rule are gone with the Spotify embed (13 Oct).
   Checked against the markup AND the scripts before deleting, which is the
   rule this repo learnt the hard way with .rise. */

/* star scroll indicator */
.scrollstar{position:fixed;right:1.1rem;top:50%;transform:translateY(-50%);width:22px;z-index:60}
.scrollstar svg{display:block;width:100%}
@media(max-width:900px){.scrollstar{display:none}}

/* ---------- star marks ---------- */
.smark{grid-column:1;grid-row:1;justify-self:start;align-self:center;width:24px;height:24px;object-fit:contain}

/* dealt-card brain dump */
.step{opacity:0;transform:translateY(20px) rotate(4deg);transition:opacity 520ms var(--ease),transform 620ms var(--ease)}
.step.dealt{opacity:1;transform:none}

/* faq + about stagger */
.tile-q{opacity:0;transform:translateY(14px);transition:opacity 480ms var(--ease),transform 480ms var(--ease),background 200ms var(--ease),color 200ms var(--ease),box-shadow 200ms var(--ease)}
.tile-q.in{opacity:1;transform:none}
/* ---------- Me: the monologue and the deck (8 Oct) ----------
   His paragraph is unchanged to the word. What changed is where it breaks:
   each sentence is its own beat with air after it, which is how a line reads
   when it is spoken rather than printed. The opening beat is set larger
   because it is the thesis, and the closing one is set apart because it is
   the turn. The word-by-word ink still runs over the top; site.js walks text
   nodes, so the beats do not interrupt it. */
.beat{display:block;max-width:30em}
.beat + .beat{margin-top:var(--s-5)}
.beat--open{font-size:var(--md);line-height:1.25;font-weight:500;max-width:22em}
.beat--close{margin-top:var(--s-6);font-style:italic}

/* The right column was empty on every screen wider than 900px, which is the
   white space he asked to use. A deck: one card at a time, thrown left or
   right, with the next two showing behind it so it reads as a stack rather
   than as a box. Every card is real. Nothing here is a placeholder, which is
   why there are three and not five.
   Markup order is a plain ordered list and stays one: a screen reader reads
   all three in sequence and never has to drive the deck. JS adds .is-deck,
   and without JS the list simply stacks down the column. */
.vouch{position:relative}
.vouch-k{font-size:var(--xs);letter-spacing:.08em;text-transform:uppercase;opacity:.82;margin:0 0 var(--s-5)}
.vouch-list{list-style:none;margin:0;padding:0;display:grid;gap:var(--s-4)}
.vouch-card{background:var(--cream);border:var(--line) solid var(--deep);padding:var(--s-5) var(--s-5) var(--s-4)}
.vc-big{margin:0;font-family:var(--fd);font-weight:900;font-size:clamp(1.35rem,2.1vw,1.75rem);
  line-height:1.05;letter-spacing:var(--fd-track);color:var(--deep)}
.vc-sub{margin:var(--s-3) 0 0;font-family:var(--fb);font-size:var(--t-small);line-height:1.45;color:var(--deep)}
.vc-who{margin:var(--s-4) 0 0;font-size:var(--xs);letter-spacing:.08em;text-transform:uppercase;
  color:var(--accent-type)}

/* the deck, once the script is in */
.vouch-list.is-deck{display:block;position:relative;min-height:16.5rem;touch-action:pan-y}
.is-deck .vouch-card{position:absolute;inset:0 0 auto;height:auto;min-height:15rem;
  transform-origin:50% 100%;will-change:transform;
  transition:transform var(--d-2) var(--e-out),opacity var(--d-2) ease}
.is-deck .vouch-card[data-at="0"]{z-index:3;transform:rotate(-1.5deg)}
.is-deck .vouch-card[data-at="1"]{z-index:2;transform:translateY(10px) scale(.965) rotate(1.5deg)}
.is-deck .vouch-card[data-at="2"]{z-index:1;transform:translateY(20px) scale(.93) rotate(-1.5deg)}
.is-deck .vouch-card[data-at="0"]{cursor:grab}
.is-deck .vouch-card[data-at="0"].is-held{cursor:grabbing;transition:none}
.is-deck .vouch-card.is-gone{opacity:0}
.vouch-nav{display:flex;align-items:center;gap:var(--s-3);margin-top:var(--s-5)}
.vouch-nav button{min-width:44px;min-height:44px;display:inline-flex;align-items:center;justify-content:center;
  background:none;border:var(--line) solid var(--deep);color:var(--deep);cursor:pointer;
  font-family:var(--fu);font-size:var(--t-small);transition:background var(--d-1) ease,color var(--d-1) ease}
.vouch-nav button:is(:hover,:focus-visible){background:var(--deep);color:var(--cream)}
.vouch-count{font-family:var(--fu);font-size:var(--xs);letter-spacing:.08em;text-transform:uppercase;opacity:.82}
@media (prefers-reduced-motion:reduce){.is-deck .vouch-card{transition:none}}
@media (max-width:900px){
  .vouch{margin-top:var(--s-7)}
  .vouch-list.is-deck{min-height:15rem}
}

.prose p{opacity:0;transform:translateY(12px);transition:opacity 520ms var(--ease),transform 520ms var(--ease)}
.prose p.in{opacity:1;transform:none}
.cta span.ln{display:block;opacity:0;transform:translateY(18px);transition:opacity 560ms var(--ease),transform 560ms var(--ease)}
.cta span.ln.in{opacity:1;transform:none}

/* reveals */
/* scoped to html.js: see the note in theme.js. Without a script the
   block is simply there, which is what "never hide content by default"
   has to mean. */
.js .rv{opacity:0;transform:translateY(14px);transition:opacity 560ms var(--ease),transform 560ms var(--ease)}
.rv.in{opacity:1;transform:none}.anim [data-t],.anim .bg{opacity:0}
/* THE ONE ORCHESTRATED MOMENT (25 Oct, block E). The five cut-out words
   drop and settle: 60ms apart, 210ms each, so the last one lands at 450ms.
   They settle out of a small rotation rather than a scale, because these
   are paper letters and paper lands crooked and straightens. */
.snap{animation:snap 210ms var(--e-out) forwards}
@keyframes snap{
  from{opacity:0;transform:translateY(-14px) rotate(var(--settle,-3deg))}
  to{opacity:1;transform:translateY(0) rotate(0deg)}}
/* each word takes its own tilt so the row does not land as one object */
.t-hi{--settle:-4deg}.t-my{--settle:3.5deg}.t-name{--settle:-2.5deg}
.t-is{--settle:4deg}.t-shrey{--settle:-2deg}

/* A FAILSAFE, because this exact gate stranded the hero copy invisible for
   four days in October when a sweep removed the class a script applies.
   If the script never runs, everything here reveals itself after two
   seconds instead of staying at opacity 0 forever. His note for this block
   says the content is already visible beneath, and this is what makes that
   true rather than a hope. */
@keyframes failsafe{to{opacity:1}}
/* AND IT MUST STOP MATCHING once a reveal class lands. Written without
   the :not() it was declared later in this file AND was more specific
   than .rise/.snap/.fade, so it won the cascade and the hero claim and
   both calls to action sat at opacity 0 behind a 2s failsafe. That is the
   same source-order trap this file documents three times, caused this
   time by the guard against it. The :not() makes the two mutually
   exclusive rather than competing: script runs, class lands, failsafe no
   longer applies; script never runs, no class, failsafe fires at 2s. */
.anim [data-t]:not(.rise):not(.snap):not(.fade),
.anim .bg:not(.rise):not(.snap):not(.fade){animation:failsafe 1ms linear 2s forwards}
/* .rise is applied by home.js at runtime, never written in the markup, which
   is why the dead-code sweep of 8 Oct removed it after checking only the HTML
   and left the hero claim and both calls to action invisible for four days:
   .anim [data-t] holds them at opacity 0 and this is what brings them back.
   Any class a script adds has to be grepped in assets/*.js too. */
.rise{animation:rise 560ms var(--ease) forwards}
@keyframes rise{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}
.fade{animation:fade 640ms var(--ease) forwards}
@keyframes fade{from{opacity:0}to{opacity:1}}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{animation-duration:1ms!important;animation-iteration-count:1!important;transition-duration:1ms!important}.anim [data-t],.anim .bg{opacity:1}
  .rv{opacity:1;transform:none}
  
}

/* ============================================================
   MULTI-PAGE SHELL — added when the site moved from one
   scrolling page to routed pages. Everything above is the
   original system and is unchanged.
   ============================================================ */

/* page header — the sub-page equivalent of .shead, with air
   above it so it clears the fixed nav */
.phead{padding:calc(var(--sec) + 3.2rem) 0 0}
.phead .wrap{display:block}
.phead .no{font-family:var(--fu);font-size:var(--xs);letter-spacing:.08em;opacity:.82;display:block;margin-bottom:.9rem}
.phead h1{font-size:var(--xxl);max-width:13em;margin:0 0 1.1rem}
.phead .stand{font-size:var(--md);line-height:1.3;font-weight:500;max-width:24em;margin:0}
.phead .rule{margin-top:2.4rem;border-top:3px solid var(--deep)}
.phead .star{width:34px;height:34px;object-fit:contain;display:block;margin-bottom:1.4rem}

/* back link — same grammar as the case studies */

/* page foot — where next, then the signature line */
.pfoot{border-top:1px solid rgba(var(--deep-rgb),.3);margin-top:var(--sec);padding:1.4rem 0 3.5rem;
  display:flex;justify-content:space-between;gap:1rem;flex-wrap:wrap;align-items:baseline}
.pfoot a{text-decoration:none;font-family:var(--fd);font-weight:700;font-size:var(--lg);line-height:1;letter-spacing:var(--fd-track)}
.pfoot a .arw{color:var(--fluoro)}
.pfoot .sig{font-family:var(--fu);font-size:var(--t-small);letter-spacing:.08em;text-transform:uppercase;opacity:.82}

/* home hub — section teaser that points at a full page */
/* The same call to action as every other one on the site (8 Oct, his note:
   no special treatment for just one). It carried a permanent red underline
   while the four at the end of the page carried none until hovered, so the
   one link that leads to a second page shouted and the ones that lead off the
   site whispered. Now it matches .ways a exactly: transparent rule, red on
   hover or focus, 44px of height to hit. */
.more{display:inline-flex;align-items:center;gap:var(--s-2);min-height:44px;margin-top:2.4rem;
  font-family:var(--fu);font-size:var(--t-small);letter-spacing:.06em;text-transform:uppercase;
  color:var(--deep);text-decoration:none;border-bottom:var(--line) solid transparent;
  transition:border-color var(--d-1) ease}
.more:is(:hover,:focus-visible){border-bottom-color:var(--fluoro)}
.more .arw{transition:transform 220ms var(--ease)}
.more:hover .arw{transform:translateX(4px)}

.lede{font-size:var(--md);line-height:1.3;font-weight:500;max-width:20em;margin:0 0 1rem}

/* ideas — index list and the scrolling piece */

/* a single idea, read top to bottom */

/* progress rail for a long read */
.rail{position:fixed;left:0;top:0;height:3px;background:var(--fluoro);width:0;z-index:65}

/* ============================================================
   ROUTED HUBS — /work and /ideas indexes, and the shared
   filter grammar. Namespaced so nothing here can collide with
   the home-page .card / .face / .tag rules above.
   ============================================================ */

/* filter — text toggles, ruled not boxed */
.filter{display:flex;gap:1.4rem;flex-wrap:wrap;align-items:baseline;padding:1.6rem 0 0;
  border-bottom:1px solid rgba(var(--deep-rgb),.25)}
.filter .lab{font-family:var(--fu);font-size:var(--xs);letter-spacing:.08em;text-transform:uppercase;opacity:.82}
.fbtn{font-family:var(--fu);font-size:var(--xs);letter-spacing:.08em;text-transform:uppercase;background:none;
  border:0;color:inherit;cursor:pointer;padding:0 0 .55rem;border-bottom:var(--line) solid transparent;opacity:.82;
  transition:opacity 200ms var(--ease),border-color 200ms var(--ease)}
.fbtn:hover{opacity:1}
.fbtn[aria-pressed="true"]{opacity:1;border-bottom-color:var(--fluoro)}
.fbtn .c{opacity:.5;margin-left:.35em}
.fempty{padding:2.5rem 0;font-family:var(--fu);font-size:var(--xs);letter-spacing:.08em;
  text-transform:uppercase;opacity:.82}

/* work index — an editorial contents page, one row per project */
.windex{border-top:3px solid var(--deep);margin-top:.5rem}
.wrow{display:grid;grid-template-columns:4.5rem minmax(0,1fr) 11rem;gap:1.5rem;align-items:start;
  padding:1.7rem 0;border-bottom:1px solid rgba(var(--deep-rgb),.25);text-decoration:none;position:relative;
  transition:padding-left 260ms var(--ease)}
a.wrow:hover,a.wrow:focus-visible{padding-left:.7rem}
a.wrow::before{content:"";position:absolute;left:0;top:0;bottom:-1px;width:3px;background:var(--fluoro);
  transform:scaleY(0);transform-origin:top;transition:transform 260ms var(--ease)}
a.wrow:hover::before,a.wrow:focus-visible::before{transform:scaleY(1)}
.wrow .n{font-family:var(--fu);font-size:var(--xs);letter-spacing:.08em;opacity:.82;padding-top:.45rem}
.wrow h2{font-family:var(--fd);font-weight:700;font-size:var(--t-h2);line-height:1;
  margin:0 0 .5rem;letter-spacing:var(--fd-track)}
.wrow .teaser{margin:0 0 .7rem;max-width:34em;opacity:.82;font-size:var(--t-small);line-height:1.45}
.wrow .tags{display:flex;gap:.4rem;flex-wrap:wrap}
.wrow .tags span{font-family:var(--fu);font-size:var(--t-small);letter-spacing:.06em;text-transform:uppercase;
  border:1px solid var(--deep);padding:2px 6px}
/* audience reads first, the way the market sorts case studies */
.tags .aud{background:var(--deep);color:var(--cream);border-color:var(--deep);font-weight:600}
.wrow .tags span.on{background:var(--deep);color:var(--cream)}
.wrow .tags span.dev{border-style:dashed;opacity:.82}
.wrow .aside{display:grid;gap:.35rem;justify-items:end;text-align:right;padding-top:.4rem}
.wrow .aside .t{font-family:var(--fu);font-size:var(--xs);letter-spacing:.08em;text-transform:uppercase;opacity:.82}
.wrow .go{font-family:var(--fd);font-weight:700;font-size:var(--t-body);letter-spacing:var(--fd-track)}
a.wrow .go::after{content:" \2192";transition:padding-left 260ms var(--ease)}
a.wrow:hover .go::after{padding-left:.25em}
.wrow.soon .go{font-family:var(--fu);font-size:var(--xs);letter-spacing:.08em;text-transform:uppercase;font-weight:400}
.wrow[hidden]{display:none}
/* same scope as every other reveal: without a script to undo it, this
   hid all five work rows on the homepage and every row on /work/. */
.js .wrow.rv:not(.in){opacity:0}
.wrow.soon.rv.in{opacity:.82}
@media(max-width:760px){
  .wrow{grid-template-columns:3rem minmax(0,1fr);gap:.9rem}
  .wrow .aside{grid-column:2;justify-items:start;text-align:left;padding-top:.6rem;
    grid-auto-flow:column;justify-content:start;gap:1.2rem;align-items:baseline}
}

@media(max-width:900px){.cblock{grid-template-columns:1fr;gap:2rem}}
.creach{display:grid;gap:0;border-top:var(--line) solid var(--deep)}
.creach a{display:flex;justify-content:space-between;align-items:baseline;gap:1rem;padding:1rem 0;
  border-bottom:1px solid rgba(var(--deep-rgb),.3);text-decoration:none;transition:padding 240ms var(--ease)}
.creach a:hover,.creach a:focus-visible{padding-left:.7rem}
.creach .k{font-family:var(--fu);font-size:var(--xs);letter-spacing:.08em;text-transform:uppercase;opacity:.82}
.creach .v{font-family:var(--fd);font-weight:700;font-size:var(--t-h3);line-height:1;letter-spacing:var(--fd-track)}

/* project / idea cross-navigation on a light page */

/* ============================================================
   WORK PLATES — the visual slot for each project.
   Today they are typographic compositions; the markup is a slot,
   so a real artefact image replaces .pw without touching layout
   or the reveal mechanism. Each plate differs in COMPOSITION,
   not merely ground colour — per the section brief.
   ============================================================ */
.plate{position:relative;overflow:hidden;aspect-ratio:16/10;background:var(--deep);
  border:var(--line) solid var(--deep);display:grid;isolation:isolate}
.plate img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center;z-index:1}
.plate .pw{font-family:var(--fd);font-weight:700;text-transform:uppercase;
  color:var(--cream);line-height:.8;letter-spacing:var(--fd-track);margin:0;z-index:1;
  font-size:var(--t-h2)}
.plate .pm{position:absolute;z-index:2;font-family:var(--fu);font-size:var(--t-small);
  letter-spacing:.08em;text-transform:uppercase;opacity:.82}
/* The halftone belonged to the typographic stand-ins. Real artwork is the
   evidence, so nothing is screened over it: the dots now only dress a plate
   that has no image in it (1 Oct 2025, when the posters arrived). */
.plate:not(:has(img))::after{content:"";position:absolute;inset:0;z-index:2;opacity:.09;pointer-events:none;
  background-image:radial-gradient(circle at 1px 1px,#000 1px,transparent 1.15px);background-size:5px 5px}

/* 01 — the V is the hanger rail. Centred, oversized initial. */
.p-vastr{background:var(--deep)}
.p-vastr .pw{align-self:center;justify-self:center;text-align:center}
.p-vastr .pw b{font-size:2.1em;line-height:.7;display:block;font-weight:700}
.p-vastr .pm{left:.6rem;bottom:.5rem;color:var(--cream)}

/* 02 — the campaign is about leaving the feed, so the words walk off it. */
.p-noise{background:var(--fluoro)}
.p-noise .pw{align-self:center;padding-left:.7rem;text-align:left;color:var(--cream)}
.p-noise .pw span{display:block}
.p-noise .pw span:nth-child(2){padding-left:1.2em}
.p-noise .pw span:nth-child(3){padding-left:2.4em;opacity:.45}
.p-noise .pm{right:.6rem;top:.5rem;color:var(--cream)}

/* 03 — a voice print: the same word, three times, decaying. */
.p-voice{background:var(--cream)}
.p-voice .pw{align-self:center;justify-self:center;text-align:center;color:var(--deep);position:relative}
.p-voice .pw span{display:block}
.p-voice .pw span:nth-child(2){opacity:.32;transform:translateX(.18em)}
.p-voice .pw span:nth-child(3){opacity:.14;transform:translateX(.36em)}
.p-voice .pm{left:.6rem;bottom:.5rem;color:var(--deep)}

/* 04 — a shipping plate. Small mark, big rule, industrial. */
.p-vrl{background:var(--deep);grid-template-rows:1fr auto}
.p-vrl .pw{align-self:end;padding:0 .7rem;font-size:var(--t-h1)}
.p-vrl .pm{left:.7rem;top:.5rem;color:var(--cream)}

/* 05 — one word, broken where the name breaks. */
.p-abs{background:var(--fluoro)}
.p-abs .pw{align-self:center;justify-self:center;text-align:center;color:var(--cream);
  font-size:var(--t-h3)}
.p-abs .pm{right:.6rem;bottom:.5rem;color:var(--cream)}

/* 06/07 — not built yet. The plate says so rather than pretending. */
.p-soon{background:transparent;border:var(--line) dashed rgba(var(--deep-rgb),.4);place-items:center}
.p-soon .pw{color:var(--deep);opacity:.82;font-size:var(--t-body);
  letter-spacing:.06em;font-family:var(--fu);font-weight:400;text-align:center;padding:0 .8rem}
.p-soon::after{display:none}

/* row gains a plate column */
.wrow{grid-template-columns:3.2rem 15rem minmax(0,1fr) 8.5rem}
@media(max-width:1040px){.wrow{grid-template-columns:3.2rem 11rem minmax(0,1fr)}
  .wrow .aside{grid-column:3;justify-items:start;text-align:left;padding-top:.6rem;
    grid-auto-flow:column;justify-content:start;gap:1.2rem;align-items:baseline}}
@media(max-width:760px){.wrow{grid-template-columns:3rem minmax(0,1fr)}
  .wrow > :not(.n){grid-column:2} /* everything but the number in the wide column */
  .wrow .plate{grid-column:2;margin-bottom:.9rem}
  .wrow .aside{grid-column:2}}

/* The wipe. Start state applies only when JS will drive it, and the
   transition runs on the compositor so it does not need rAF. */
.js-wipe .plate{clip-path:inset(0 100% 0 0);
  transition:clip-path 780ms cubic-bezier(.16,1,.3,1)}
.js-wipe .plate .pw{transform:translateX(10%);
  transition:transform 900ms cubic-bezier(.16,1,.3,1)}
.js-wipe .plate.wiped{clip-path:inset(0 0% 0 0)}
.js-wipe .plate.wiped .pw{transform:translateX(0)}
@media (prefers-reduced-motion:reduce){
  .js-wipe .plate{clip-path:none;transition:none}
  .js-wipe .plate .pw{transform:none;transition:none}
}

/* ---------- dark mode, for the cream / deep / fluoro system ----------
   Driven by <html data-theme> (set by assets/theme.js when the visitor picks)
   and otherwise by prefers-color-scheme, the same contract the Proof section
   and the case pages already use. Skin-quiet pages carry their own dark rules
   and are excluded here.

   Light sections flip: cream becomes the ink ground, deep becomes the type.
   Sections that are deep by design (Selected work, Brain dump, the record)
   keep their light-mode values, so dark mode still has two grounds and a
   rhythm instead of one flat night. Fluoro does not change. Ink and the
   type tone are the v7 values, so Proof and these sections agree. */
/* ---------- phones: headers stack, nothing outgrows the screen ----------
   Section labels grew from 10px to the 14px floor, so the side-label column
   squeezed long headlines past the edge. Under 640px the label sits above
   the headline instead. The email steps down one size and may break at any
   character rather than push the page sideways. */
@media (max-width:640px){
  .shead{grid-template-columns:minmax(0,1fr);row-gap:var(--s-3)}
  .shead .smark,.shead .no,.shead h2,.shead .sub{grid-column:1;grid-row:auto}
  .shead .no{white-space:normal}
  .shead h2{min-width:0}
  .reach{font-size:var(--t-h3);overflow-wrap:anywhere}
}

.wk-tile:is(:hover,:focus-visible) .wk-label{background:var(--yellow)}
.wk-tile:is(:hover,:focus-visible) .wk-label span:first-child{color:#0D1633}
.wk-note{letter-spacing:.04em}
.work .wk-tile[href*="vrl"]{--y:44vh}
@media (min-width:760px){.wk-note--right{max-width:26rem}}
/* proof: the lede is a sentence to read, so body size */
/* brain dump: steps are body copy (18), so five columns become three then two */
.step p{font-size:var(--t-body);line-height:var(--lh-body)}

/* contrast at 14px: small labels were muted with low opacity, which was
   tolerable at 10px as texture but fails AA now they are reading text.
   Metadata stays quieter than body, at .8, which clears 4.5:1 on cream and
   deep; on the fluoro ground it takes the ink tone, the only one that passes
   there. Small accent text on deep uses the lifted fluoro. */.contact .no,.shead .no{opacity:.82}
.wk-note{color:rgba(var(--cream-rgb),.82)}

/* ---------- work covers: static, with a state change on hover ----------
   Decided 18 Sep 2026: no screen recordings. The cover stays; a bar rises
   into it naming the action, and the label below lifts to full strength.
   Keyboard focus gets the same. */
.wk-open{position:absolute;left:0;right:0;bottom:0;z-index:1;display:flex;justify-content:space-between;
  align-items:center;padding:var(--s-3) var(--s-4);background:var(--deep);color:var(--cream);
  font-family:var(--fu);font-size:var(--t-small);letter-spacing:.04em;
  transition:color var(--d-1) ease}
.wk-open span{transition:transform var(--d-2) var(--e-spring)}
/* the cover lifts to uncover the bar beneath it, so nothing on the cover is hidden */
.wk-poster{transition:opacity .25s var(--wk-ease),transform var(--d-2) var(--e-out)}
.wk-tile:is(:hover,:focus-visible) .wk-poster{transform:translateY(-2.9rem)}
.wk-tile:is(:hover,:focus-visible) .wk-open span{transform:translateX(4px)}

.wk-tile:is(:hover,:focus-visible) .wk-label span:first-child{color:var(--accent-type)}

@media (prefers-reduced-motion:reduce){.wk-open,.wk-open span,.wk-poster{transition:none}}

/* ---------- about: direction and beliefs ----------
   Direction is what he wants next, stated plainly for a recruiter routing
   him to a req. Beliefs are his lines, verbatim, numbered because the last
   one undercuts the other four and the order is the joke. */
.skills .grp-say{font-size:var(--t-small);line-height:1.5;margin:0 0 var(--s-4);max-width:28em;opacity:.85}
.stance{padding:var(--sec) 0 0}
.stance-body{display:grid;gap:var(--s-5);max-width:40em;margin:var(--s-6) 0 0}
.stance-body p{margin:0;font-size:var(--t-body);line-height:var(--lh-body)}
.stance-body .stance-lede{font-family:var(--fd);font-weight:500;font-size:var(--t-h3);line-height:1.25;letter-spacing:var(--fd-track)}

/* ============================================================
   IDEAS: the magazine (built 18 Sep 2026)
   The hero is the base kit: cream ground, deep ink, the cut-out SHREY
   tile, a two-ink photograph, the riso star. Each page is that kit in a
   different setting, the way each home section is. The accent is spent
   once, on the Process page. Pages are 3:4, a spread is 3:2.
   Wide screens: magazine.js binds the pages into leaves on a spine.
   Phones: a swipe track, one page at a time. No JS: pages stack.
   ============================================================ */
.mag{position:relative;margin:var(--s-6) auto 0}

/* ---------- a page ---------- */
.mag-page{position:relative;container-type:inline-size;overflow:hidden;background:var(--cream);color:var(--deep);
  aspect-ratio:3/4;padding:var(--s-7) var(--s-7) var(--s-8);display:flex;flex-direction:column;border:1px solid var(--deep)}

.toc{list-style:none;margin:0;padding:0;border-top:1px solid rgba(var(--cream-rgb),.4)}
.toc a{display:grid;grid-template-columns:2.6rem minmax(0,1fr);grid-template-areas:"n t" ". k";column-gap:var(--s-3);
  padding:var(--s-3) 0;border-bottom:1px solid rgba(var(--cream-rgb),.4);color:inherit;text-decoration:none;
  transition:padding var(--d-2) var(--e-out),background var(--d-1) ease}

.toc a:hover,.toc a:focus-visible{padding-left:var(--s-3);background:rgba(var(--cream-rgb),.08)}

/* 3. manifesto */

/* 4. projects */
.briefs{list-style:none;margin:0;padding:0;border-top:1px solid var(--deep)}
.briefs li{border-bottom:1px solid rgba(var(--deep-rgb),.3)}
.briefs a,.briefs .bf-soon{display:flex;justify-content:space-between;align-items:baseline;gap:var(--s-3);flex-wrap:wrap;
  padding:var(--s-2) 0;color:inherit;text-decoration:none}
.briefs .bf-soon{padding:var(--s-2) 0}

.briefs a{transition:color var(--d-1) ease,padding var(--d-2) var(--e-out)}
.briefs a:hover,.briefs a:focus-visible{color:var(--accent-type);padding-left:var(--s-2)}
.briefs .bf-soon .bf-t{opacity:.82}

.bk-sign{margin:0}.bk-sign img{width:44cqw;height:auto;display:block}

.mag.is-bound .mag-bar,.mag.is-track .mag-bar{display:flex}

/* ---------- bound: leaves on a spine ---------- */
.mag.is-bound{width:min(100%,1120px,calc((100svh - 230px) * 1.5));perspective:2600px}
.mag.is-bound .mag-book{display:block;aspect-ratio:3/2;gap:0;transform:translateX(var(--shift,0));
  transition:transform var(--d-3) var(--e-out)}
.mag-leaf{position:absolute;top:0;left:50%;width:50%;height:100%;transform-style:preserve-3d;transform-origin:0 50%;
  transition:transform 850ms cubic-bezier(.45,.05,.25,1);cursor:pointer}
.mag-leaf.is-flipped{transform:rotateY(-180deg)}
.mag-leaf > .mag-page{position:absolute;inset:0;aspect-ratio:auto;backface-visibility:hidden;-webkit-backface-visibility:hidden}
.mag-leaf > .mag-page.is-back{transform:rotateY(180deg)}
.mag-leaf a,.mag-leaf button{cursor:pointer}
.mag.is-bound .mag-bar{width:min(100%,640px)}

/* ---------- phones: a swipe track ---------- */
.mag.is-track .mag-book{display:flex;gap:var(--s-4);overflow-x:auto;scroll-snap-type:x mandatory;
  overscroll-behavior-x:contain;scrollbar-width:none;padding-bottom:var(--s-1)}
.mag.is-track .mag-book::-webkit-scrollbar{display:none}
.mag.is-track .mag-page{flex:0 0 100%;scroll-snap-align:start;aspect-ratio:auto;min-height:min(132vw,620px)}
.mag.is-track .pg-cover{min-height:min(133vw,620px)}

@media (prefers-reduced-motion:reduce){
  .mag-leaf,.mag.is-bound .mag-book,.toc a,.briefs a,.pg-link span{transition:none}
}

@media (max-width:899px){.mag-page{padding:var(--s-6) var(--s-5) var(--s-8)}.pg-folio{right:var(--s-5)}}

/* ============================================================
   HOME IDEAS: the desk (18 Sep 2026)
   The real issue, set in the home page itself, lying on the same
   cream as the hero with the hero's cut-outs scattered round it. Props
   sit at their own parallax depth (--py from home.js) and never take
   pointer events. Sticker slots stay hidden until tool icons arrive.
   ============================================================ */
.desk{padding:var(--sec) 0 0}
.desk-surface{position:relative;max-width:1400px;margin:var(--s-5) auto 0;padding:var(--s-7) var(--pad) var(--sec)}

.prop{position:absolute;display:block;height:auto;
  transform:translate3d(0,var(--py,0px),0) rotate(var(--rot,0deg));will-change:transform}
.sticker:empty{display:none}
.sticker{width:clamp(40px,4vw,56px)}
.sticker img{display:block;width:100%;height:auto}
.s1{left:14%;top:44%;--rot:-12deg}.s2{right:15%;top:38%;--rot:9deg}.s3{left:22%;bottom:4%;--rot:6deg}

.desk-mag .mag{margin-top:0}
@media (min-width:900px){.desk-mag .mag.is-bound{width:min(78%,1000px,calc((100svh - 230px) * 1.5))}}
/* before the issue arrives, and if it never does: the cover as a link */
@media (max-width:899px){
  .desk-surface{padding:var(--s-5) var(--pad) var(--sec)}
  .sticker{display:none}
}
@media (prefers-reduced-motion:reduce){.prop{transform:rotate(var(--rot,0deg))}}

/* ---------- the magazine, as a magazine (21 Sep 2026) ----------
   Newsstand furniture, all of it doing a job a real cover does: the volume
   line and cover price say "periodical", the starburst says "new", the
   barcode says "printed". Inside, a running head and a shade at the spine.
   Clash, marked: the veto list bans badges. The burst is a cover convention,
   not a UI chip; picked on relevance to a magazine, and it appears once. */
/* running head on every inside page */
.mag-page:not(.pg-cover):not(.pg-back)::before{content:"Ideas \00B7  Issue 01";position:absolute;top:var(--s-5);right:var(--s-7);
  font-family:var(--fu);font-size:var(--t-small);letter-spacing:.06em;text-transform:uppercase;opacity:.7;pointer-events:none}
/* the spine: each page darkens a touch toward the binding */
.mag.is-bound .mag-leaf > .mag-page:not(.is-back){box-shadow:inset 22px 0 26px -20px rgba(13,22,51,.35)}
.mag.is-bound .mag-leaf > .mag-page.is-back{box-shadow:inset -22px 0 26px -20px rgba(13,22,51,.35)}
@media (max-width:899px){.mag-page:not(.pg-cover):not(.pg-back)::before{right:var(--s-5)}}

/* ============================================================
   HOME: EVERY SECTION ITS OWN PERSONALITY (21 Sep 2026)
   Shrey: "the homepage sections should differ by personality". Each one
   gets a different device, so scrolling reads as turning to a new page
   rather than more of the same:
     hero      assembly: the cut-outs snap on, the print sits still
     quotes    a hand-drawn tape between the hero and the page, scroll-driven
     bands     scroll velocity drives the crossing tapes
     proof     a blue comic panel on Ben-Day dots, figures roll up in yellow
     work      pinned collage, scrubbed by scroll
     ideas     the magazine: pages turn on a spine
     me        the paragraph inks in word by word as you read it
     contact   the ask, with a highlighter pulled under "always"
   ============================================================ */

/* ---------- proof: the comic panel ----------
   Yellow, not blue: the work section below is already a blue ground, and two
   blue panels in a row would read as one. Blue Ben-Day dots, ink type (10.6),
   blue figures (6.2). Figures are set wholly in Geist: the trial heading
   face has no digits, and mixing faces inside a number misaligns it. */

/* ---------- me: the paragraph inks in as you read ---------- */
[data-scrub] .w{opacity:.42;transition:opacity 160ms linear}
[data-scrub] .w.on{opacity:1}
@media (prefers-reduced-motion:reduce){[data-scrub] .w{opacity:1}}

/* ---------- contact: a highlighter under "always" ---------- */.contact .cta em{font-style:normal;background:linear-gradient(var(--yellow),var(--yellow)) 0 88% / 0% 34% no-repeat;
  transition:background-size 900ms var(--e-out) 400ms}.contact .cta.lit em{background-size:100% 34%}

/* icons: Phosphor (MIT, phosphoricons.com), bold weight, inlined so they take
   the text colour. Sources in assets/icons/. They sit on the text baseline at
   the text's own size, and only where they add meaning: direction, medium. */
.ico{display:inline-block;width:1em;height:1em;vertical-align:-.14em;flex:none}
.hbtn .ico{margin-left:var(--s-2);transition:transform var(--d-1) var(--e-out)}
.hbtn:hover .ico,.hbtn:focus-visible .ico{transform:translate(2px,2px)}
@media (prefers-reduced-motion:reduce){.hbtn .ico{transition:none}}
/* the cover furniture must never crowd the cover lines: the barcode sits
   top-left under the masthead on small covers, and the dateline stays on one row */
@container (max-width:520px){
}

/* ---------- the quote tape (21 Sep 2026) ----------
   A strip of yellow tape, cut by hand and stuck on at an angle across the
   seam between the hero and the rest of the page. Its edges are drawn, not
   ruled: a wobbly ink line top and bottom. Quotes are set in the display face
   in ink (11.0 on yellow), separated by hand-drawn stars. */

/* ---------- readability pass (21 Sep 2026) ----------
   Muted text is now opacity .82 everywhere, the lowest that holds 4.5:1 on
   cream and on ink; nothing smaller than 14px; no small type straight on a
   photograph or on red. */
@container (max-width:520px){}
/* the dateline wraps on small covers rather than shrinking below 14px */

@container (max-width:520px){}
/* an in-development row is marked by its dashed tag and "Writing", not by
   fading the whole row: fading twice put its text at 3.6:1 */
.wrow.soon.rv.in{opacity:1}
/* plate labels sit on a cream tab, never straight on red or blue */
.plate .pm{opacity:1;background:var(--cream);color:var(--ink);padding:2px 6px}
/* the last belief is the red one; on ink it takes the lifted red (6.4) */

/* ticker pause: small, plain, next to the thing it stops */

/* ---------- loader ----------
   A cold open, not a spinner. Mono leader in the corners, the artefact
   revealed through a widening letterbox, then the star punches and the
   whole thing wipes up into the hero. It also covers the hero image
   decode, so it is doing work as well as setting a tone.
   Restored 22 Sep 2026 at Shrey's request; now plays on every load. */
.loader{position:fixed;inset:0;z-index:120;background:var(--cream);
  display:grid;place-items:center;transition:clip-path 620ms var(--ease)}
.loader.gone{clip-path:inset(0 0 100% 0);pointer-events:none}

.lgrid{position:relative;width:min(88vw,var(--max));height:min(72vh,620px);
  display:grid;grid-template-columns:1fr auto;grid-template-rows:auto 1fr auto;gap:1rem}
.lmeta{font-family:var(--fu);font-size:var(--xs);letter-spacing:.16em;text-transform:uppercase;
  color:var(--deep);opacity:0;transition:opacity 420ms var(--ease)}
.lmeta.in{opacity:.82}
.lmeta.tl{grid-row:1;grid-column:1}
.lmeta.tr{grid-row:1;grid-column:2;text-align:right}
.lmeta.bl{grid-row:3;grid-column:1;align-self:end}
.lmeta.br{grid-row:3;grid-column:2;align-self:end;text-align:right}

/* the letterbox: a slit that opens to reveal the photograph.
   shares the centre cell with the rule, so the rule appears to part */

/* a hairline that draws across before the band opens */
.lrule{grid-row:2;grid-column:1/-1;align-self:center;height:2px;background:var(--deep);
  width:0;transition:width 620ms var(--ease);position:relative;z-index:2;justify-self:start}
.lrule.draw{width:100%}
.lrule.part{opacity:0;transition:opacity 300ms var(--ease)}

@media(max-width:900px){.lgrid{height:60vh}}
@media (prefers-reduced-motion:reduce){.loader{display:none}}

/* ---------- recruiter hooks (22 Sep 2026) ----------
   Copy written by Claude, flagged for Shrey's review (HARD RULE 6): a fast
   lane in the hero, a one-line value claim over each proof stint, and the
   roles he wants, stated once, above the ask. Every claim sits on a verified
   figure; nothing here adds a number. */

/* ============================================================
   FLOATING NAVIGATION MARKS, head start (22 Sep 2026)
   Five floating cut-out marks are the primary navigation, drifting in the
   hero; a label snaps in on hover or focus; the island nav stays as the
   always-visible text menu. Phones hide the marks.
   Clash, marked: the veto list bans offset shadows. The brief asks for a
   hard 3px offset on the cut-out marks, the same surface as the name tiles;
   picked on relevance. It stays on these marks and their labels only.
   ============================================================ */
.hero{position:relative}
/* where each one floats: the red panel's free edges and the cream margin, never over the copy */
/* the marks: paper, ink, one colour each. Fixed colours, so they stay paper in dark mode */
/* the label: a cut-out word that snaps in on intent */

/* the camera date stamp, printed on the picture */

 /* no drift, no hover on touch: the island menu is the navigation */

/* the loader, shortened: a fast rule, a fast wipe */
.loader{transition:clip-path 380ms var(--e-out)}
.lrule{transition:width 340ms var(--e-out)}
.lmeta{transition:opacity 200ms ease}

/* ---------- yellow is an accent, never a plane (Shrey, 22 Sep 2026) ----------
   It marks things (the highlighter, the stars on the tape, the cover
   starburst); it no longer fills a band. The quote tape is paper now. */
/* positions for the full hero: the red panel's free strips either side of the
   print, and the cream corner above the copy */
/* below 1180px the strips beside the print are too narrow to hold the marks
   clear of it; the island nav carries navigation there, as on phones */

/* ============================================================
   REFERENCE BOOK BUILD (23 Sep 2026). Rows marked Take, built here.
   ============================================================ */
/* 01 Hero: the icon slots hold the marks' size and drift; Hero6 is new */

/* 03 Work: covers wait in their frames; cards pick up on hover */
.plate{transition:transform 260ms var(--e-spring),box-shadow 260ms var(--e-out)}
/* the pick-up: lift, a small turn, and the hard 3px offset the cut-outs use
   (veto-list clash, marked: kept to this one hover) */
a.wrow:is(:hover,:focus-visible) .plate{transform:translateY(-6px) rotate(-1.6deg);box-shadow:3px 3px 0 #0D1633}
/* The hover lift moved to motion.js (13 Oct): it is now a three-part move
   on one timeline (the tile straightens, the poster pushes, the red band
   wipes up) and CSS cannot keep those in step. Nothing here drives .wk-frame
   any more, so the two systems cannot fight over its transform. */
@media (prefers-reduced-motion:reduce){.plate,.wk-tile .wk-frame{transition:none}a.wrow:is(:hover,:focus-visible) .plate,.wk-tile:is(:hover,:focus-visible) .wk-frame{transform:none}}

.desk-mag > .mag{position:relative;z-index:1}

/* ============================================================
   CONTACT, the references replicated in full (24 Sep 2026)
   Row 02, up.com.au: the hover that reveals a dark graphic. Row 00, Riya:
   the connect section's double hover, and the scribble. Row 09, the sky pin:
   the section stands on a hill rather than sitting above a band of one.
   One hover, three answers: the address inverts into a plane, the scribble
   draws under it, and the graphic comes up beside it. That is the "double
   hover" take, held to the letter, and it is all one gesture so it does not
   read as three effects competing.
   ============================================================ */
.reach{position:relative;z-index:1;padding:.05em .16em .2rem}
.reach{transition:border-color 220ms var(--ease),background 220ms var(--ease),color 220ms var(--ease)}

.scribble .s2{stroke-width:2.5;opacity:.75;transition-delay:90ms}

/* the dark graphic, row 02. It comes up beside the address, not over it. */

/* the hill, row 09: the section stands on it, full bleed and behind the
   content, with the footer line sitting on it rather than after it. */
/* a third of the height it was: the pill carries the copy, so the section is
   the pill, the sign-off line and the ground. */
.contact{position:relative;overflow:hidden;padding-bottom:clamp(96px,12vw,150px)}
.contact .wrap{position:relative;z-index:1}
/* ============================================================
   THE SIGN OFF (12 Oct 2026, from the sitraaart reference)
   Taken: a page that ends on purpose, three short columns, a giant
   wordmark cropped by the bottom edge, one oversized shape rising behind.
   Left: the near-black ground and the rounded blob. Ours stays on the
   off-white and the shape is our own star, so the ending is written in
   this alphabet rather than borrowed whole from theirs.
   The star is the ground (the Contact2 slot) AND the wordmark's full
   stop. The wordmark is cropped on purpose: a name too big for the page
   is the oldest poster move there is, and it is the one place on the
   site where type is allowed to run off the edge.
   ============================================================ */
.contact{isolation:isolate}
/* its own grid layer, because the section paints an opaque ground over the
   fixed one and would otherwise hide it */
.contact::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background-image:var(--paper);background-size:var(--paper-size);opacity:calc(.25 + var(--paper-in) * .75)}

/* The ending sits on a coloured pill (12 Oct, at his instruction). The veto
   list bans pills, and it means the small metadata capsule, not this: a plane
   big enough to be a room rather than a label. Same job as the reference's
   rounded blob, in our own colour.
   Cape blue and not red, because the block carries small type and red only
   carries type above 24px. Cream on deep is 8.50.
   The hover rule inside turns yellow rather than red: red on blue is unusable,
   and this is the one place yellow gets to be the interactive colour. */
.sign-off{position:relative;z-index:2;display:grid;gap:var(--s-6) var(--s-6);
  grid-template-columns:minmax(0,1.35fr) minmax(0,.6fr) minmax(0,.85fr) auto;
  align-items:start;margin-top:clamp(3.5rem,7vw,5.5rem);
  /* the pill is cape blue in BOTH themes: it is a plane, not a surface that
     follows the UI, and --deep becomes the light cream in dark mode, which put
     the yellow "always" on cream at 1.06. Same trap as the polaroid card.
     Cream on this blue is 8.50 and yellow on it is 8.70. */
  background:#173CA8;color:#F7F4EC;border-radius:999px;
  padding:clamp(3rem,6vw,5rem) clamp(2.4rem,6vw,6rem);align-items:center}
.sign-off .so-cta,.sign-off .so-mail,.sign-off .so-col a,.sign-off .so-k{color:#F7F4EC}
.sign-off .so-mail:is(:hover,:focus-visible),
.sign-off .so-col a:is(:hover,:focus-visible){border-bottom-color:var(--yellow)}
.sign-off :focus-visible{outline-color:var(--yellow)}
/* the pill is a plane, so the grid's own paper does not show through it and
   the star behind must not either */
.sign-off{isolation:isolate}
/* the call to action lives in the pill now and is the biggest thing in it */
.so-cta{margin:0;font-family:var(--fd);font-weight:700;font-size:clamp(1.9rem,3.6vw,3rem);line-height:1;
  letter-spacing:var(--fd-track);color:var(--cream);max-width:9em;text-wrap:balance}
.so-cta em{font-style:normal;color:var(--yellow)}
.so-mail{font-size:var(--t-body);letter-spacing:.01em;text-transform:none}
.so-mail{display:inline-flex;align-items:center;gap:var(--s-2);min-height:44px;margin-top:var(--s-4);
  font-family:var(--fu);font-size:var(--t-small);letter-spacing:.06em;text-transform:uppercase;
  color:var(--deep);text-decoration:none;border-bottom:var(--line) solid transparent;
  transition:border-color var(--d-1) ease}
.so-mail:is(:hover,:focus-visible){border-bottom-color:var(--fluoro)}
.so-k{margin:0 0 var(--s-4);font-size:var(--xs);letter-spacing:.08em;text-transform:uppercase;opacity:.82}
.so-col ul{list-style:none;margin:0;padding:0;display:grid;gap:var(--s-1)}
.so-col a{display:inline-flex;align-items:center;gap:var(--s-2);min-height:36px;min-width:44px;
  font-family:var(--fu);font-size:var(--t-small);color:var(--deep);text-decoration:none;
  border-bottom:var(--line) solid transparent;transition:border-color var(--d-1) ease}
.so-col a:is(:hover,:focus-visible){border-bottom-color:var(--fluoro)}
.so-ico{width:17px;height:17px;flex:none}
.so-social a{letter-spacing:.01em}

/* his own marks, fanned the way a hand of cards sits. Three, not six: the
   ring in the hero already shows all of them and this is a sign-off, not a
   second navigation. */
.so-fan{position:relative;width:clamp(150px,15vw,214px);height:clamp(118px,12vw,168px);justify-self:end}
.so-card{position:absolute;top:0;width:clamp(88px,8.6vw,124px);height:auto;
  transform-origin:50% 100%;transition:transform var(--d-2) var(--e-out)}
.so-card--1{left:0;transform:rotate(-13deg)}
.so-card--2{left:22%;transform:rotate(-1deg) translateY(-6px);z-index:2}
.so-card--3{left:44%;transform:rotate(12deg)}
.so-fan:hover .so-card--1{transform:rotate(-20deg) translateY(-4px)}
.so-fan:hover .so-card--3{transform:rotate(19deg) translateY(-4px)}
@media (prefers-reduced-motion:reduce){.so-card{transition:none}}

/* the wordmark, cropped by the section's own overflow */
.bigmark{position:absolute;z-index:1;left:50%;bottom:0;transform:translate(-50%,22%);
  margin:0;width:100vw;text-align:center;pointer-events:none;user-select:none;
  font-family:var(--fd);font-weight:900;font-size:clamp(3.2rem,15.5vw,15rem);line-height:.78;
  letter-spacing:-.045em;color:var(--deep);white-space:nowrap}

/* the ground: the star, rising, cropped. It is the full stop of the
   wordmark and the hill the section stands on at the same time. */
/* A whole star, most of it below the fold. Cropping it with slice showed
   only the top 30 per cent of the shape, which is one point and reads as an
   arrow; the arms start at about 45 per cent down. So the star keeps its
   proportions and the section edge does the cutting. */
/* The star settles in as you reach it (13 Oct, at his instruction). --star-in
   runs 0 to 1 while the contact section comes up the screen, written by
   home.js on a rAF tick. At 0 it sits lower and turned further; at 1 it is
   home. Transform only, so the compositor does all of it. */
.ground-star{position:absolute;z-index:0;left:50%;bottom:0;
  width:clamp(440px,68vw,1120px);aspect-ratio:1;height:auto;transform-origin:50% 100%;
  transform:translate(-50%,calc(50% + (1 - var(--star-in,1)) * 26%))
            rotate(calc(-4deg + (1 - var(--star-in,1)) * -16deg))
            scale(calc(.88 + var(--star-in,1) * .12));
  fill:var(--fluoro);opacity:calc(.25 + var(--star-in,1) * .67);pointer-events:none}
@media (prefers-reduced-motion:reduce){.ground-star{transform:translate(-50%,50%) rotate(-4deg);opacity:.92}}

@media (max-width:1100px){
  .sign-off{grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:var(--s-6);
    border-radius:clamp(28px,6vw,72px);padding:clamp(2.2rem,5vw,3rem) clamp(1.6rem,4vw,2.6rem)}
  .so-fan{justify-self:start}
}
@media (max-width:640px){
  .sign-off{grid-template-columns:minmax(0,1fr)}
  .bigmark{font-size:clamp(3.4rem,21vw,6rem);transform:translate(-50%,26%)}
}

/* the /contact/ list: the same gesture, one step quieter. No second copy of
   the Contact1 graphic, because one idea per surface. */
.creach .reach-row{position:relative}
.creach .reach-row .v{position:relative;display:inline-block;padding:.02em .14em;transition:background 220ms var(--ease),color 220ms var(--ease)}
.creach .reach-row:is(:hover,:focus-visible) .v{background:var(--deep);color:var(--cream)}
.creach .reach-row .scribble{bottom:-.5rem;height:14px}
.creach .reach-row:is(:hover,:focus-visible) .scribble path{stroke-dashoffset:0}
@media (prefers-reduced-motion:reduce){.scribble path{transition:none;stroke-dashoffset:0}}

/* 06 Me: the floating head, and the globe at the end of the story */
.phead{position:relative}
.me-head{position:absolute;right:max(var(--pad),4vw);top:var(--s-6);width:clamp(140px,18vw,240px);aspect-ratio:1;
  animation:mebob 1.4s var(--e-out) 3 alternate}
/* the floating head is a cut-out with its own transparent margin, so it is
   contained rather than cropped and the slot stops being square-bound */
.me-head{aspect-ratio:auto}
.me-head img{display:block;width:100%;height:auto;object-fit:contain}
@keyframes mebob{to{transform:translateY(-10px) rotate(-3deg)}}
@media (prefers-reduced-motion:reduce){.me-head{animation:none}}
/* it is a <span>, so the stacked branch has to give it a box: inline elements
   ignore width, height and aspect-ratio, which collapsed the slot to a 62px
   band across the column under 900px (found 7 Oct). */
@media (max-width:900px){.me-head{display:block;position:relative;right:auto;top:auto;margin:var(--s-5) 0 0}}
/* ---------- what keeps me going (12 Oct, at his instruction) ----------
   Replaces the globe and the list of places. A polaroid rather than a framed
   image: white card, a deep lip under the picture where a polaroid's chemistry
   sits, and pinned a degree or two off square so it reads as placed by hand
   rather than laid out. It is the only white object on a page of off-white,
   which is what makes it read as a print on a desk. */
.keeps{padding:var(--sec) 0}
/* The card is white in BOTH themes, because a polaroid is white paper and
   not a surface that follows the UI. That means the caption cannot use --deep,
   which becomes the light cream in dark mode: it was 1.26 on the card. Ink and
   the rule are fixed values here for the same reason. This is the --card trap
   from 24 Sep in a new place: anything hard-coded light needs hard-coded ink. */

/* The head that rides the giant star in the sign off. It sits in front of the
   star and behind the wordmark, which is the whole point: the three layers
   overlap the way a collage does rather than clearing each other. */
.star-head{position:absolute;z-index:1;left:50%;bottom:0;
  width:clamp(120px,15vw,250px);height:auto;
  transform:translate(-50%,-38%) rotate(3deg);pointer-events:none;user-select:none}
@media (max-width:640px){.star-head{width:clamp(96px,26vw,150px);transform:translate(-50%,-30%) rotate(3deg)}}
/* ============================================================
   THE HERO STAGE (23 Sep 2026, Reference Book rows 00-02)
   Taken: Balint Horvath's centre-piece with labelled objects floating
   around it, draggable, each one a place to go. Left: his sky wallpaper
   and the XP desktop language (taskbar, system-message window, .exe file
   names) - it reads as someone else's site, and it buried the photograph.
   The ground is cream, blue does the type and line work, red is the one
   accent plane, yellow only marks a hover. The photograph is the
   centre-piece and does not move.
   ============================================================ */
/* The hero ground is lifted towards white (Shrey, 24 Sep 2026): the same
   cream, mixed with paper, so the token relationship holds and dark mode
   still flips. Everything else on the page stays on --cream. */
.stage-hero{--card:color-mix(in srgb,var(--cream) 58%,#fff 42%);position:relative;display:block;min-height:100vh;min-height:100svh;overflow:hidden;
  padding:calc(var(--s-10) + var(--s-4)) 0 var(--s-9);background:var(--cream);color:var(--deep);isolation:isolate}
/* on the dark ground the mount has to go darker, not grey: mixing the dark
   cream towards white lands in the middle and takes the caption with it */
html[data-theme="dark"] .stage-hero{--card:color-mix(in srgb,var(--cream) 90%,#fff 10%)}
.stage-ground{position:absolute;inset:0;z-index:0;background-image:var(--paper);background-size:var(--paper-size);opacity:calc(.25 + var(--paper-in) * .75)}

.stage{position:relative;z-index:2;min-height:calc(100svh - var(--s-10) - var(--s-9));display:grid;place-items:center}

.print{margin:0;width:clamp(168px,17vw,228px);transform:rotate(-2.4deg)}
.print-mount{display:block;position:relative;background:var(--card);border:var(--line) solid var(--deep);padding:10px 10px 0}
/* ============================================================
   THE BRUSH (6 Oct 2026, Shrey, after the Up bank reveal cursor)
   The photograph is printed black and white. The cursor paints the colour
   back in: a soft round mask follows the pointer over a colour copy of the
   same frame, so moving across it feels like wiping a window rather than
   toggling a state. The mask is a CSS mask driven by two custom properties,
   so the paint costs nothing per frame beyond two variable writes.
   Touch and keyboard get the whole thing revealed on focus, because there
   is no pointer to paint with. Reduced motion skips the transition, not the
   reveal: the colour is information, not decoration.
   ============================================================ */
.print-mount{position:relative}
.print-colour{position:absolute;inset:10px 10px 0;display:block;pointer-events:none;
  /* the path is stylesheet-relative on purpose: a url() inside a custom
     property is resolved against this file by the browser, not against the
     page, so an inline override from the markup resolves wrong */
  background-image:url("img/delhi-colour.webp");
  background-size:cover;background-position:center;
  /* the source is the colour original now, not the same halftone pushed, so
     the saturation boost that was faking colour comes off (12 Oct) */
  filter:saturate(1.06);
  -webkit-mask-image:radial-gradient(circle var(--brush,118px) at var(--bx,-999px) var(--by,-999px),#000 42%,rgba(0,0,0,.55) 62%,transparent 78%);
  mask-image:radial-gradient(circle var(--brush,118px) at var(--bx,-999px) var(--by,-999px),#000 42%,rgba(0,0,0,.55) 62%,transparent 78%);
  transition:-webkit-mask-image 120ms linear,mask-image 120ms linear}
.print-mount:is(:focus-within) .print-colour{-webkit-mask-image:none;mask-image:none}
@media (hover:none){.print-colour{-webkit-mask-image:none;mask-image:none;opacity:.001}}
.print-mount img{display:block;width:100%;height:auto;aspect-ratio:4/5;object-fit:cover;filter:grayscale(1) contrast(1.12)}
.print figcaption{padding:var(--s-2) 10px var(--s-3);background:var(--card);border:var(--line) solid var(--deep);border-top:0;
  font-family:var(--fu);font-size:13px;line-height:1.38;color:var(--deep)}
.print figcaption b{display:block;font-family:var(--fu);font-weight:400;text-transform:uppercase;letter-spacing:.08em;font-size:12px;opacity:.82}

.stage-copy{display:grid;justify-items:start}
.stage-copy .claim{margin:0 0 var(--s-1);font-family:var(--fd);font-weight:900;font-size:clamp(1.5rem,2.45vw,2rem);line-height:1.03;
  letter-spacing:var(--fd-track);color:var(--deep);max-width:13em;text-wrap:balance}
.stage-copy .roll{font-family:var(--fd);font-weight:900;font-size:var(--t-body);background:var(--deep);color:var(--cream)}
.stage-copy .hero-k{margin:var(--s-2) 0 0;font-family:var(--fu);font-size:var(--t-small);color:var(--deep);opacity:.82}
/* one row, so the hero stops being two stacked blocks of furniture */
.stage-copy .hero-cta{margin-top:var(--s-4);display:flex;flex-wrap:wrap;align-items:center;gap:var(--s-3) var(--s-5)}
@media (max-width:560px){.stage-copy .hero-cta{gap:var(--s-3)}}

.fl a{display:grid;justify-items:center;gap:var(--s-2);text-decoration:none;color:var(--deep);cursor:pointer}
.fl-icon{display:block;width:var(--fl-w);height:var(--fl-w);transition:transform 220ms var(--e-spring)}
.fl-icon img{display:block;width:100%;height:100%;object-fit:contain}
.fl-icon .ph{width:100%;height:100%;padding:4px;font-size:9px;gap:2px;background:var(--card)}
.fl-icon .ph b{font-size:14px}.fl-icon .ph i{display:none}
.fl-name{margin-top:-18px;position:relative;z-index:2;padding:3px 8px;background:var(--card);
  border:1.5px solid var(--deep);font-family:var(--fu);font-size:var(--t-small);line-height:1.25;
  text-align:center;color:var(--deep);overflow-wrap:anywhere}
.fl a:hover .fl-name,.fl a:focus-visible .fl-name{background:var(--yellow);color:#0D1633}
.fl a:hover .fl-icon,.fl a:focus-visible .fl-icon{transform:rotate(-2deg) scale(1.06)}
.fl a:focus-visible{outline:none}
.fl a:focus-visible .fl-icon{outline:3px solid var(--fluoro);outline-offset:3px}

/* ============================================================
   FINALISED HERO (6 Oct 2026). Appended last so it wins by order.

   Three changes from the previous hero, all of them subtractions except
   the panel:
   1. The cut-out name moves off the cream and onto a red halftone panel
      with the photograph, so the left column is one object instead of two.
   2. The separate rolling line is gone. Its word now rides inside the
      claim, which removes the second headline that was diluting the first.
   3. Both calls to action render as buttons. Square, hard-edged, 2px rule:
      a button in this system is a plane with a border, never a pill.
   ============================================================ */

/* --- the panel --------------------------------------------------- */
/* The tile sizes used to live under .stage-copy. The name has moved out of
   that scope, so without these the five cut-outs render at natural size, the
   grid column takes its minimum from them, and the panel blows out to 1289px
   with the photograph following it. Measured, not guessed: that is exactly
   what happened on the first build of this panel. */
/* The cut-out letters are a percentage of the panel rather than of the
   viewport, so the whole lockup scales as one object when the panel gives way.
   The percentages are the sizes they already had at a 330px panel. */

/* --- the claim ---------------------------------------------------- */
/* It is the largest type in the hero now that the second line is gone. */
.stage-copy .claim{font-size:clamp(1.75rem,3.1vw,2.6rem);line-height:1.06;max-width:15em;
  margin:0}
/* The roller. A clipped frame one line tall; the stack inside is moved by
   home.js. Spans, not list items: see the markup note. */
/* 1.25em is not a taste value. home.js moves the stack by i * 1.25em, so the
   frame and every row have to be exactly that or the words land between
   lines. They were 1.1em on the first pass and the roller settled 25px off. */
/* 1.6em, not 1.25: the display face at weight 900 draws a glyph box of
   about 1.56em, so a 1.25em frame shaved every word top and bottom and
   "campaigns" worst of all. This rule is 1500 lines below the base .roll
   rule and was quietly winning, which is why changing the base one did
   nothing. The JS step is 1.6 to match; frame, word and step move together
   or the words stop landing on the frame. */
.stage-copy .roll{display:inline-block;height:1.7em;overflow:hidden;vertical-align:bottom;
  padding:0 .22em;background:var(--deep);color:var(--cream);box-sizing:content-box;
  font-family:var(--fd);font-weight:900;font-size:1em;line-height:1.25;
  transition:width 420ms var(--ease)}
@media (prefers-reduced-motion:reduce){.stage-copy .roll{transition:none}}
.stage-copy .roll .rollstack{display:block;transition:transform 420ms var(--ease)}
/* width:max-content so each word keeps its own measured width. Without it a
   block child fills the frame, and re-measuring after the webfont loads
   returns the frame width instead of the word width. */
.stage-copy .roll .rollstack>span{display:block;width:max-content;height:1.7em;line-height:1.7em;white-space:nowrap}
.vh{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;border:0}

.stage-copy .hero-k{margin:var(--s-3) 0 0;opacity:1;color:var(--deep)}

/* --- both actions are buttons ------------------------------------- */
/* The brief asked for buttons rather than bare text. Square and ruled, so
   the pill veto still holds. The second one is the outline variant: equal
   size, lower weight, which is how two actions sit together without the
   secondary one disappearing. */
.stage-copy .hero-cta .hbtn{min-height:48px;padding:0 var(--s-4);border:var(--line) solid var(--deep);
  font-family:var(--fu);font-size:var(--t-small);letter-spacing:.04em;text-transform:uppercase;
  transition:background-color var(--d-1) ease,color var(--d-1) ease}
.stage-copy .hero-cta .hbtn-alt{background:transparent;color:var(--deep);text-transform:none;
  letter-spacing:0;font-family:var(--fb);font-weight:600;font-size:var(--t-small)}
.stage-copy .hero-cta .hbtn-alt:hover,.stage-copy .hero-cta .hbtn-alt:focus-visible{
  background:var(--yellow);color:#0D1633}
.stage-copy .hero-cta .hbtn:not(.hbtn-alt):hover,
.stage-copy .hero-cta .hbtn:not(.hbtn-alt):focus-visible{background:var(--yellow);color:#0D1633}

/* --- nav marks: the snap-in label --------------------------------- */
/* The short name is always there so the mark is never a mystery. The long
   label snaps in on hover and focus: a cut-out strip, held off-angle, no
   fade. A fade would read as a tooltip; a snap reads as a label being
   slapped on. Hidden from assistive tech because the link already carries
   its destination, and it is never the only way to know where a mark goes. */
.fl a{position:relative}
.fl-hint{position:absolute;left:50%;top:calc(100% + 6px);z-index:5;
  width:max-content;max-width:min(230px,42vw);padding:4px 9px;
  background:var(--yellow);color:#0D1633;border:var(--line) solid var(--deep);
  font-family:var(--fu);font-size:11px;line-height:1.3;text-align:left;
  transform:translate(-50%,0) rotate(-1.6deg) scale(.86);
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity 90ms linear,transform 140ms var(--e-spring),visibility 0s linear 140ms}
.fl a:hover .fl-hint,.fl a:focus-visible .fl-hint{
  opacity:1;visibility:visible;transform:translate(-50%,0) rotate(-1.6deg) scale(1);
  transition:opacity 90ms linear,transform 140ms var(--e-spring),visibility 0s}
@media (prefers-reduced-motion:reduce){
  .fl-hint{transition:opacity 90ms linear,visibility 0s linear 90ms;
    transform:translate(-50%,0) rotate(-1.6deg)}
  .fl a:hover .fl-hint,.fl a:focus-visible .fl-hint{transform:translate(-50%,0) rotate(-1.6deg)}
}
@media (hover:none){.fl-hint{display:none}}

/* ============================================================
   HERO v4 — THE CARD (13 Oct 2026, at his instruction)
   The name, the photograph and the copy are one object now: a licence
   card. Head strip, portrait, detail, actions, which is the order every
   ID card uses and the reason it reads clean instead of arranged.
   It sits left. The six marks have the rest of the field and float on
   their own CSS loops: no pointer, no ticker, no JS. Each has its own
   duration and delay so they never beat together, and because each one
   only moves a few pixels around a fixed position they cannot collide
   and cannot lag.
   ============================================================ */
.stage-centre{display:block;width:min(1180px,calc(100% - 2 * var(--pad)))}

.idcard{position:relative;z-index:3;width:min(540px,100%);
  background:#FFFDF7;color:#0D1633;border:var(--line) solid #0D1633;
  padding:clamp(1.1rem,2vw,1.5rem);display:grid;gap:clamp(.9rem,1.6vw,1.2rem);
  transform:rotate(-1.2deg)}

/* head: the cut-out name, and the number every licence carries */
.id-head{display:flex;align-items:flex-end;justify-content:space-between;gap:var(--s-4);
  border-bottom:var(--line) solid #0D1633;padding-bottom:clamp(.7rem,1.4vw,1rem)}
.id-head .hero-name{position:static;display:grid;justify-items:start;gap:3px;margin:0;
  width:auto;max-width:100%}
.id-head .hero-name .l1{display:flex;flex-wrap:wrap;align-items:flex-end;gap:5px}
.id-head .hero-name .l2{display:block;margin-top:2px}
.id-head .tile{display:block}
.id-head .tile img{display:block;width:100%;height:auto}
.id-head .t-hi{width:clamp(26px,3vw,34px)}
.id-head .t-my{width:clamp(31px,3.5vw,40px)}
.id-head .t-name{width:clamp(55px,6vw,70px)}
.id-head .t-is{width:clamp(18px,2vw,24px)}
.id-head .t-shrey{width:clamp(118px,13vw,158px)}

/* body: portrait beside the detail */
.id-body{display:grid;grid-template-columns:minmax(0,auto) minmax(0,1fr);
  gap:clamp(1rem,2vw,1.4rem);align-items:start}
.idcard .hero-pola{width:clamp(136px,16vw,188px);transform:none;margin:0}
.id-detail{min-width:0;display:grid;gap:var(--s-3)}
.idcard .stage-copy{max-width:none}
.idcard .claim{font-size:clamp(1.25rem,2.1vw,1.7rem);line-height:1.08;max-width:12em}
.idcard .hero-k{font-size:var(--t-small)}
.idcard .hero-cta{margin-top:var(--s-2);gap:var(--s-2) var(--s-3)}

/* the card is a printed object, so it does not follow the theme */
.idcard .claim,.idcard .hero-k,.idcard figcaption{color:#0D1633}
.idcard .roll{background:#173CA8;color:#FFFDF7}

/* ---- the marks: placed, and floating on their own loops ---- */
.floats{position:absolute;inset:0;width:auto;height:auto;transform:none;border-radius:0;
  z-index:4;pointer-events:none;list-style:none;margin:0;padding:0;
  --fl-w:clamp(128px,14vw,208px)}
.fl{position:absolute;width:calc(var(--fl-w) + 28px);pointer-events:auto;
  offset-path:none;will-change:transform}
.fl-icon{width:var(--fl-w);height:var(--fl-w)}
/* FIVE marks since 24 Oct: Ideas left with the zine and the nav entry, and
   pointing it at /about/ would have put a third mark on an About
   destination beside Me and FAQ. Re-spaced rather than left with a hole at
   34 percent, alternating between two columns so no two share a height. */
.fl-work{right:27vw;top:4%;animation:fl-a 7s ease-in-out infinite}
.fl-listen{right:6vw;top:14%;animation:fl-b 9.6s ease-in-out infinite 1.1s}
.fl-me{right:27vw;top:40%;animation:fl-a 9.2s ease-in-out infinite .9s}
.fl-faq{right:6vw;top:52%;animation:fl-b 7.8s ease-in-out infinite 1.4s}
.fl-contact{right:27vw;top:74%;animation:fl-a 8.6s ease-in-out infinite .2s}
@keyframes fl-a{0%,100%{transform:translate3d(0,0,0) rotate(-2deg)}50%{transform:translate3d(6px,-12px,0) rotate(2deg)}}
@keyframes fl-b{0%,100%{transform:translate3d(0,-8px,0) rotate(2.5deg)}50%{transform:translate3d(-7px,8px,0) rotate(-1.5deg)}}
.fl a:is(:hover,:focus-visible) .fl-icon{transform:scale(1.07)}
@media (prefers-reduced-motion:reduce){.fl{animation:none}}

@media (max-width:1280px){
  .floats{--fl-w:clamp(84px,9.5vw,124px)}
  .fl-work{right:3vw;top:4%}.fl-listen{right:22vw;top:17%}.fl-me{right:3vw;top:41%}
  .fl-faq{right:22vw;top:55%}.fl-contact{right:4vw;top:74%}
}
@media (max-width:860px){
  .idcard{width:100%;transform:none}
  .floats{position:relative;inset:auto;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
    gap:var(--s-4);margin:var(--s-7) auto 0;max-width:560px;pointer-events:auto}
  .fl{position:static;width:auto;animation:none}
  .fl-work,.fl-ideas,.fl-me,.fl-faq,.fl-contact,.fl-listen{right:auto;top:auto;animation:none}
  .fl-icon{width:100%;height:auto;aspect-ratio:1}
}
@media (max-width:560px){
  .id-body{grid-template-columns:minmax(0,1fr)}
  .idcard .hero-pola{width:min(220px,62%)}
}

/* ---------- the card is printed, so it ignores the theme ----------
   The fourth time this trap has been walked into, and it is always the same
   one: a surface that is conceptually a PRINTED OBJECT cannot be built from
   --cream and --deep, because those two swap in dark mode. The card is white
   paper with blue ink on it in both themes, which is also why the hero ground
   does not darken. Every value below is a literal for that reason. Caught by
   the harness at 1.25, 1.29 and 1.34 in dark, not by reading the file. */
.idcard .print-mount{background:#FFFDF7;border-color:#0D1633}
.idcard .print figcaption{background:#FFFDF7;border-color:#0D1633;color:#0D1633}
.idcard .hero-cta .hbtn{background:#173CA8;color:#FFFDF7;border-color:#173CA8}
.idcard .hero-cta .hbtn-alt{background:transparent;color:#173CA8}
.idcard .hero-cta .hbtn:hover,.idcard .hero-cta .hbtn:focus-visible,
.idcard .hero-cta .hbtn-alt:hover,.idcard .hero-cta .hbtn-alt:focus-visible{
  background:#FFED10;color:#0D1633;border-color:#0D1633}
/* the marks sit on the same light ground in both themes, so their labels do too */
.floats .fl-name{background:#FFFDF7;border-color:#173CA8;color:#173CA8}
.floats .fl a:hover .fl-name,.floats .fl a:focus-visible .fl-name{background:#FFED10;color:#0D1633}

/* ============================================================
   THE STAR-HEAD STRIP (13 Oct 2026, at his instruction, after Rishabh)
   His face, four cuts of it, running between the five things he believes.
   It replaces a plain <ol> of the same five lines, which was the weakest
   presentation of the strongest personal copy on the site.

   It travels sideways as the page scrolls DOWN rather than running itself.
   That is the whole point of the device here: home already carries one
   self-starting loop, he has said twice that motion must not cost the
   content, and a scroll-linked strip is controlled by the reader, cannot be
   missed, and stops the moment they stop. Driven by ScrollTrigger in
   about.js; with the script off, or under reduced motion, the strip simply
   sits still and scrolls horizontally by hand like any overflow list.
   ============================================================ */
.hstrip{position:relative;margin:var(--s-9) 0;padding:var(--s-6) 0;
  border-top:var(--line) solid var(--deep);border-bottom:var(--line) solid var(--deep);
  background:var(--yellow);overflow:hidden}
.hs-view{overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}
.hs-view::-webkit-scrollbar{display:none}
.hs-track{display:flex;align-items:center;gap:clamp(1.4rem,3vw,2.6rem);
  margin:0;padding:0 var(--pad);list-style:none;width:max-content}
/* ink on shield yellow is 14.73, which is the one pairing that lets display
   type sit on a yellow plane at all */
.hs-say{flex:none;max-width:22ch;font-family:var(--fd);font-weight:900;
  font-size:clamp(1.15rem,2.1vw,1.9rem);line-height:1.08;
  letter-spacing:var(--fd-track);color:#0D1633}
.hs-say span{display:block}
.hs-face{flex:none;width:clamp(96px,11vw,164px)}
.hs-face img{display:block;width:100%;height:auto}
/* the faces alternate their tilt, so four cut-outs in a row read as a strip
   of frames rather than a row of stickers set at one angle */
.hs-face:nth-of-type(odd) img{transform:rotate(-3deg)}
.hs-face:nth-of-type(even) img{transform:rotate(2.5deg)}
@media (max-width:640px){
  .hstrip{margin:var(--s-8) 0;padding:var(--s-5) 0}
  .hs-say{max-width:18ch}
}

/* ---------- the card carries the name properly (13 Oct 2026) ----------
   The first build of the card shrank the cut-out name to a 150px thumbnail in
   a head strip. That inverts the hierarchy the whole system is built on: the
   name is the identity, the identity is the idea, and the idea is the largest
   thing in the block. It also threw away the one device that is unmistakably
   his, the ransom lettering, by setting it at a size where you cannot read
   that the letters are cut from different sources.

   So the name is the headline now and the card is sized around it. */
.idcard{width:min(620px,100%)}
.id-head{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:start;
  gap:var(--s-3);padding-bottom:clamp(.9rem,1.8vw,1.2rem);
  /* red, not blue: the hero had no cape red in it at all except a 52px stamp,
     on a site whose system is three colours. One rule is enough to fix that. */
  border-bottom:3px solid var(--fluoro)}
.id-head .hero-name .l1{gap:8px}
.id-head .t-hi{width:clamp(38px,4.4vw,52px)}
.id-head .t-my{width:clamp(45px,5.2vw,61px)}
.id-head .t-name{width:clamp(80px,9.1vw,107px)}
.id-head .t-is{width:clamp(26px,3vw,36px)}
.id-head .t-shrey{width:clamp(186px,21vw,248px)}

/* the claim grows with it, so the card reads as one object rather than a
   large name with small print under it */
.idcard .claim{font-size:clamp(1.4rem,2.5vw,2rem);max-width:13em}
.idcard .hero-pola{width:clamp(150px,17vw,205px)}

/* A registration edge, not a shadow. Offset printing lays one plate slightly
   off another and leaves a hard colour edge; a drop shadow fakes depth. The
   veto list bans the second and this is the first: no blur, no transparency,
   a flat plane of cape red showing along two sides of the card. */
/* The registration edge. Third build, and the first honest one.
   Attempts one and two put the red on a pseudo-element behind the card and
   it painted over the whole card instead. The cause was never the z-index:
   .idcard carried transform:rotate(), and a transform creates a stacking
   context by itself, so a z-index:-1 child can never get behind its parent's
   background whatever the index says.

   Attempt three moved the paper onto a pseudo-element too, which worked
   visually and then lied: with no background on .idcard itself, the contrast
   harness walked up to the body and read the claim at 1.01 in dark mode on a
   card that is plainly white. A check that cannot see the real ground is
   worse than no check, because the next session trusts the zero.

   So the ROTATION moves up to .stage-centre, which holds nothing but the
   card. .idcard then has no transform and no stacking context, keeps its real
   background where anything reading this file would expect it, and the red
   plane sits behind it on a plain negative index. No blur, no transparency:
   one plate printed slightly off another, which is not what the veto list
   means by an offset shadow. */
.stage-centre{transform:rotate(-1.5deg)} /* H6: 1.5, not 1.2 */
.idcard{transform:none;z-index:auto;background:#FFFDF7;border:var(--line) solid #0D1633;
  padding:clamp(1.1rem,2vw,1.5rem)}
.idcard::before{content:"";position:absolute;z-index:-1;inset:12px -12px -12px 12px;
  background:var(--fluoro)}

@media (max-width:560px){
  .id-head{grid-template-columns:minmax(0,1fr)}
  .idcard::before{inset:7px -7px -7px 7px}
}

/* The deck says so when the file will not play, rather than leaving a button
   that silently does nothing. Set by motion-free code in home.js on the audio
   element's own error event, so it can only appear when the load really failed. */

/* ============================================================
   HOME IS SCHEDULED IN BLOCKS (13 Oct 2026, at his instruction)
   "The home is still not scheduled on different blocks and the grid
   background seems very overdone."

   Both halves of that are the same problem. Every section except Work sat on
   one continuous cream with one continuous grid behind it, so the page read
   as a single sheet eight screens long, and the only way to feel progress was
   the headings. Giving six sections six grounds is what makes a scroll feel
   like a sequence of rooms rather than a long corridor.

   The schedule, and the reason for each:
     1  HERO       paper + grid     the texture is the hero's signature
     2  NUMBERS    flat cream       a clean plain break (his Akriti note)
     3  WORK       cape blue        the one colour block, torn at both edges
     4  IDEAS      flat warm white  a clean sheet, so the zine art carries it
     5  ME         paper + grid     the texture returns, so it reads as a motif
     6  CONTACT    cream + star     unchanged

   Textured, plain, colour, plain, textured, colour. Nothing new is invented:
   every ground is a token already locked, and the two flat blocks are simply
   the grid not showing through.
   ============================================================ */

/* --- 2. THE PROOF BLOCK: quotes, the logo wall, the numbers ---------------
   Three sections that answer one question (who says so) and read as one band.
   The flat ground is an opaque cream painted over the fixed grid layer, which
   is why no grid shows here: there is nothing to switch off. */
/* The tape and the logo wall came off home at his instruction, so this block
   is now the numbers alone: what he does, and what it moved. It is still the
   plain break between the hero and the work, and it is still the only thing
   between them, which is the point he was making. */
.moved{background:var(--cream);border-top:var(--line) solid var(--deep)}
/* no bottom rule: the work block tears away from this one, and a ruled edge
   12px above a torn one reads as a mistake rather than as two devices. */

/* --- 3. WORK: the blue block is torn, not cut -----------------------------
   A full-bleed colour band with two razor edges is a <div>. The same band
   torn along both edges is a strip of paper laid on the page, which is the
   collage language the rest of the site is built in. The wobble is the quote
   tape's own edge, reused rather than redrawn, so the two devices are
   recognisably the same hand. */
.work{position:relative}
.work::before,.work::after{content:"";position:absolute;left:0;right:0;height:12px;z-index:20;
  pointer-events:none;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='12' viewBox='0 0 240 12'%3E%3Cpath d='M0 0h240v4c-14 3-26-2-40 0s-24 4-38 1-30-3-44 0-22 3-36 1-26-4-42-1-26 2-40-1z' fill='%23F7F4EC'/%3E%3C/svg%3E") repeat-x 0 0/240px 12px}
.work::before{top:-1px}
.work::after{bottom:-1px;transform:scaleY(-1)}
/* dark mode: the torn edge has to be the colour of the page it tears away
   from, and that colour flips. Hard-coding it would leave a cream scar on an
   ink page, which is the plane trap in its other direction. */
:root[data-theme="dark"] .work::before,:root[data-theme="dark"] .work::after{
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='12' viewBox='0 0 240 12'%3E%3Cpath d='M0 0h240v4c-14 3-26-2-40 0s-24 4-38 1-30-3-44 0-22 3-36 1-26-4-42-1-26 2-40-1z' fill='%230D1633'/%3E%3C/svg%3E")}

/* --- 4. IDEAS: a clean sheet ---------------------------------------------
   The zine cover is his artwork and it is busy. A grid behind busy artwork is
   two textures competing, and the artwork loses. Warm white rather than cream
   so the sheet reads as a different stock from the proof block above it. */
.ideas{background:#FFFDF7;position:relative}
/* bottom only, for the same reason: the work block above already tears into
   this one. */
.ideas::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  border-bottom:var(--line) solid var(--deep)}
:root[data-theme="dark"] .ideas{background:color-mix(in srgb,var(--cream) 94%,#000)}

/* --- 5. ME: the texture returns ------------------------------------------
   Nothing to declare: .about has no background, so the fixed grid shows
   through again. Stated here only so the schedule above is complete and the
   next session does not "fix" the gap by giving it one. */

/* ---------- Who I do it for (13 Oct 2026, his title) ----------
   The camera holds the whole family and the Taj frame still on its screen, so
   it is the picture that explains the other two. It sits in the middle at his
   instruction, and it is wider than the polaroid stacks either side of it
   (820x486 against 820x960), so a straight three-column grid would leave it
   floating in a tall cell. The centre column takes the extra width instead. */
.mot{grid-template-columns:minmax(0,.82fr) minmax(0,1.36fr) minmax(0,.82fr);
  align-items:center}
.mot-item--cam{animation:mot-b 9s ease-in-out infinite .6s}
@media (prefers-reduced-motion:reduce){.mot-item--cam{animation:none;transform:rotate(-1.2deg)}}
@media (max-width:760px){.mot{grid-template-columns:minmax(0,1fr)}}

/* the orange band on /about/ sits between the heading and the five cards, so
   it needs air on both sides rather than the flush bleed it has on home */
.dump .process{margin:var(--s-6) 0 var(--s-7)}

.mani-more a:is(:hover,:focus-visible){background:var(--yellow);color:#0D1633;border-color:#0D1633}

/* ---------- the star, with him in the middle of it (13 Oct 2026) ----------
   From the same delivery as the bobbleheads, found in 3.pdf of that zip. It is
   the best picture of him on the site: he is in it, he took it himself, and
   the star is the site's own mark drawn by hand rather than the vector one.
   It replaces the small floating head in the /about/ header, which was a
   decoration where this is a feature.
   Bigger than the head it replaces, because a 900x975 artefact at 240px reads
   as a sticker. Loaded eagerly: it is above the fold on this page, and a lazy
   image in the header pops in after the heading has already settled. */
.me-head--star{width:clamp(200px,26vw,360px);aspect-ratio:auto;
  top:calc(var(--s-9) + var(--s-4));right:max(var(--pad),6vw)}
.me-head--star img{width:100%;height:auto}
@media (max-width:900px){
  .me-head--star{width:min(300px,70%);margin-inline:auto}
}

/* ============================================================
   THE WORK WALL READS AS A SEQUENCE (19 Oct 2026, at his instruction)
   "The work stuff has to be remade in a way that actually follows the flow
   of readability."

   It was three posters at three hand-placed positions, accumulating as you
   scrolled. At any given moment you saw one, two or three of them scattered
   across a blue field with a few hundred pixels of nothing under them, and
   there was no order for the eye to follow: nothing said which to read first
   or that there were three.

   Each project now holds the stage alone, in one slot, at a size you can
   actually read, and hands it to the next. The pin earns its height because
   something changes the whole way down it, and the dead space is gone
   because there is only ever one object on the stage.
   ============================================================ */
@media (min-width:760px) and (prefers-reduced-motion:no-preference){
  .work .wk-tile,.work.wk-live .wk-tile,.work.wk-live .wk-tile.is-visible{
    /* one slot, right of the copy column, vertically centred */
    left:auto;right:max(var(--pad),7vw);top:50%;width:min(38vw,520px);
    /* enter from below, sit, leave upward. --p drives the arrival and --q the
       exit, so the two are independent and neither can strand a tile on
       screen when the scroll is thrown rather than dragged. */
    transform:translate3d(0,calc(-50% + (1 - var(--p,1)) * 26vh - var(--q,0) * 30vh),0)
      scale(calc(.92 + var(--p,1) * .08))
      rotate(calc(var(--r) * (1 - var(--q,0))));
    opacity:calc(var(--p,1) * 2.2 * (1 - var(--q,0)));
    transition:none}
  /* the number is the reading order, stated rather than implied */
  /* above the tile, not beside it: the poster is rotated, so a counter in
     the left margin sits under its corner at two of the three angles. */
  .wk-tile::after{content:attr(data-n);position:absolute;left:.2rem;top:-1.9rem;
    font-family:var(--fu);font-size:var(--t-small);letter-spacing:.1em;
    color:rgba(var(--cream-rgb),.62)}

}

/* The easter-egg head is GONE (20 Oct, at his instruction). A floating head
   at the end of the page was explicitly cut on 13 Oct and I put one back as
   the scroll reward, which reinstated the exact overlap he had removed. The
   egg needs a different form and that is his call, not another guess. */
/* ============================================================
   ONE TRANSITION LANGUAGE (19 Oct 2026, at his instruction)
   "The current pages are either bound by unwanted breaks, or transitions
   that do not create a huge difference between sections."

   Both halves were true and they were the same inconsistency. Some seams
   were a hard 2px rule, which cuts the page without saying anything. Others
   were nothing at all, so two different grounds met with no event between
   them. Only the work block had a real transition.

   The rule now: wherever two DIFFERENT grounds meet, the upper block tears
   away in its own colour. Wherever two blocks share a ground, a rule is the
   honest signal and stays. One device, applied by the same test every time,
   instead of three treatments chosen ad hoc.
   ============================================================ */

/* the tear is one shape, declared once, and each use only sets its colour */
.work::before,.work::after,.ideas::after{
  content:"";position:absolute;left:0;right:0;height:12px;z-index:20;pointer-events:none;
  background-repeat:repeat-x;background-position:0 0;background-size:240px 12px}

/* work tears UP into the numbers block, which is cream */
.work::before{top:-1px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='12' viewBox='0 0 240 12'%3E%3Cpath d='M0 0h240v4c-14 3-26-2-40 0s-24 4-38 1-30-3-44 0-22 3-36 1-26-4-42-1-26 2-40-1z' fill='%23F7F4EC'/%3E%3C/svg%3E")}

/* work tears DOWN into ideas, which is warm white, not cream. It was cream
   here and the 6-point difference showed as a pale line along the seam. */
.work::after{bottom:-1px;transform:scaleY(-1);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='12' viewBox='0 0 240 12'%3E%3Cpath d='M0 0h240v4c-14 3-26-2-40 0s-24 4-38 1-30-3-44 0-22 3-36 1-26-4-42-1-26 2-40-1z' fill='%23FFFDF7'/%3E%3C/svg%3E")}

/* ideas tears into Me, which is the grid ground returning. This boundary had
   a plain rule and was the one seam on the page you could scroll past
   without noticing the ground had changed. */
.ideas{position:relative}
.ideas::after{bottom:-1px;transform:scaleY(-1);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='12' viewBox='0 0 240 12'%3E%3Cpath d='M0 0h240v4c-14 3-26-2-40 0s-24 4-38 1-30-3-44 0-22 3-36 1-26-4-42-1-26 2-40-1z' fill='%23F7F4EC'/%3E%3C/svg%3E")}
/* the rule it replaces: a tear and a rule at the same seam is two devices
   doing one job, which is the inconsistency this block exists to end */
.ideas::before{border-bottom:0}

/* dark: a tear has to be the colour of the page it tears away from, and that
   colour flips. Hard-coding light values leaves a pale scar on an ink page. */
:root[data-theme="dark"] .work::before,
:root[data-theme="dark"] .work::after,
:root[data-theme="dark"] .ideas::after{
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='12' viewBox='0 0 240 12'%3E%3Cpath d='M0 0h240v4c-14 3-26-2-40 0s-24 4-38 1-30-3-44 0-22 3-36 1-26-4-42-1-26 2-40-1z' fill='%230D1633'/%3E%3C/svg%3E")}

@media (max-width:640px){.wa-marks{gap:var(--s-3) var(--s-4)}}

/* ---------- the figures group by capability (20 Oct 2026, his call) ----------
   Seven figures in a flat list read as seven things that happened. Fourteen
   under five capability headings read as five things he can do, each with
   evidence under it. That is the difference between a CV line and a claim a
   hiring manager can test, and it is why the group heading is the loudest
   thing in each block after the number itself. */
.mv-group{margin:0 0 var(--s-6)}
.mv-group:last-child{margin-bottom:0}
.mv-gk{margin:0 0 var(--s-3);padding-bottom:.35rem;
  font-family:var(--fu);font-size:var(--xs);letter-spacing:.1em;text-transform:uppercase;
  color:var(--deep);border-bottom:1.5px solid color-mix(in srgb,var(--deep) 32%,transparent)}

/* the pill carries two real figures instead of a count of skills. Shut, it is
   now the only place on the first two screens where a number appears, which
   is the whole reason for the change. */
.mv-pill-n b{font-family:var(--fd);font-weight:900;font-size:1.15em;letter-spacing:var(--fd-track)}
.mv-pill-n i{font-style:normal;opacity:.5;margin:0 .25em}
@media (max-width:760px){
  /* the two figures survive, the "12 more" does not: it is the first thing
     that can go when the row runs out of width */
  .mv-pill-n{font-size:var(--xs)}
}

/* ---------- the playlist, hanging off the deck (20 Oct 2026) ----------
   The mp3 stays: he asked for the file, and the embed gives anyone not
   signed in a thirty second preview. This is a second thing, not a
   replacement. It opens upward from the deck because the deck lives in the
   bottom-right corner and there is no room below it. */
/* ---------- the one record, in Me (20 Oct 2026, at his instruction) ----------
   Split from the deck: the deck is the playlist, this is the single track.
   It sits in Me because a favourite record is a fact about him rather than a
   utility in the corner of every screen, and it is the only audio on the
   site that plays a file he owns rather than an embed. */
/* pause: two bars, drawn from the same span so there is no second element */
:focus-visible){background:#FFED10}
:focus-visible) span{border-left-color:#0D1633}
:focus-visible) span{
  background:linear-gradient(to right,#0D1633 0 34%,transparent 34% 66%,#0D1633 66% 100%)}

/* ---------- /work reads, instead of competing with itself (20 Oct) ----------
   His note: the work pages read cluttered and the background impairs
   reading. Both were true and they were separate problems.

   THE GROUND. The drawn grid is the texture of the paper sections. /work is
   a dense index: eleven rows of cover, heading, teaser and tags, and a grid
   running under all of it is a second pattern competing with the first. The
   page gets a flat ground, which is the same decision the case pages got on
   19 Oct for the same reason.
   THE TAGS. Every row carried its state twice: once on the plate corner
   (01 · SPEC) and again as a tag. Vastr carried six tags, which reads as a
   tag cloud rather than a project. The state tag is gone; the plate keeps
   it, because that is where the eye lands first. */
body:has(.wlist),body:has(.wrow){background:var(--cream)}
body:has(.wrow)::before{display:none}
/* the rows get air: 11 of them at the old rhythm is a wall */
.wrow{padding-block:clamp(1.1rem,2vw,1.6rem)}
.wrow .tags{gap:.45rem}

/* ---------- TWO OBJECTS, not one box (20 Oct, his third correction) ----------
   "MP4 is a disc player on site and the spotify playlist is a music list on
   the site - you have merged these two."

   He was right and I kept missing it. Putting them in one bordered box is
   still a merge even when the contents are separate. A record player is a
   thing you OPERATE; a playlist is a thing you READ. They share a section
   and nothing else: no common frame, no common border, different shapes. */

/* 1. the disc player: an object, round, with a platter and an arm */
/* FIXED, bottom right, at his instruction (21 Oct). It rests small and
   opens on hover, focus or while playing: a floating control may cover the
   corner it lives in, it may not cover the copy. That rule was learnt in
   October when the old player sat over a figure, the zine and the last line
   of Me for the whole page. */
.disc{position:fixed;right:clamp(.8rem,2vw,1.6rem);bottom:clamp(.8rem,2vw,1.6rem);
  z-index:70;margin:0;display:grid;justify-items:center;gap:var(--s-2);
  transition:transform 320ms var(--e-spring)}
.disc .disc-meta{max-width:0;overflow:hidden;opacity:0;white-space:nowrap;
  transition:max-width 320ms var(--e-spring),opacity 200ms ease}
.disc:is(:hover,:focus-within) .disc-meta,.disc.spinning .disc-meta{max-width:16rem;opacity:1}
.disc:is(:hover,:focus-within) .disc-deck,.disc.spinning .disc-deck{width:clamp(104px,12vw,140px)}
.disc-deck{transition:width 320ms var(--e-spring)}
/* it stands down over the hero: the hero is the one screen that has to be
   uninterrupted, and html.at-hero is already set by home.js */
html.at-hero .disc{transform:translateY(140%);pointer-events:none}
.disc-deck{position:relative;width:clamp(76px,8vw,104px);aspect-ratio:1;
  border-radius:50%;background:#0D1633;display:grid;place-items:center;
  border:3px solid #0D1633}
.disc-platter{display:block;width:62%;aspect-ratio:1;border-radius:50%;overflow:hidden;
  border:var(--line) solid #FFFDF7}
.disc-platter img{display:block;width:100%;height:100%;object-fit:cover}
/* it only turns while sound is actually coming out */
.disc.spinning .disc-platter{animation:disc-spin 3.2s linear infinite}
@keyframes disc-spin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.disc.spinning .disc-platter{animation:none}}
.disc-arm{position:absolute;right:4%;top:2%;width:26%;height:auto;
  transform-origin:76% 9%;transform:rotate(-12deg);transition:transform 420ms var(--e-spring)}
.disc.spinning .disc-arm{transform:rotate(10deg)}
.disc-arm .arm-rod{fill:none;stroke:#FFFDF7;stroke-width:3;stroke-linecap:round}
.disc-arm .arm-head{fill:#E8251F}
.disc-arm .arm-pivot{fill:#FFFDF7}
.disc-btn{position:absolute;left:50%;top:50%;width:34px;height:34px;margin:-17px 0 0 -17px;
  border-radius:50%;border:var(--line) solid #0D1633;background:#FFED10;cursor:pointer;
  display:grid;place-items:center;padding:0}
.disc-btn span{display:block;width:0;height:0;margin-left:3px;
  border-left:12px solid #0D1633;border-top:8px solid transparent;border-bottom:8px solid transparent}
.disc.spinning .disc-btn span{width:12px;height:14px;margin:0;border:0;
  background:linear-gradient(to right,#0D1633 0 34%,transparent 34% 66%,#0D1633 66% 100%)}
.disc-btn:focus-visible{outline:3px solid var(--fluoro);outline-offset:3px}
.disc.dead .disc-btn{opacity:.5;cursor:not-allowed}
.disc-meta{display:grid;justify-items:center;gap:1px;text-align:center}
.disc-k{font-size:var(--xs);letter-spacing:.1em;text-transform:uppercase;color:var(--accent-type)}
.disc-title{font-family:var(--fd);font-weight:900;font-size:var(--t-body);
  letter-spacing:var(--fd-track);color:var(--deep)}
.disc-by{font-family:var(--fu);font-size:var(--t-small);color:var(--deep);opacity:.78}
.disc-audio{display:none}

/* 2. the music list: rows, not a machine. No border, no card. */
.mlist{min-width:0}
.mlist-k{display:flex;flex-wrap:wrap;gap:.3rem .7rem;align-items:baseline;margin:0 0 var(--s-2);
  font-size:var(--xs);letter-spacing:.1em;text-transform:uppercase;color:var(--deep)}
.mlist-k a{color:var(--accent-type);text-decoration:none;border-bottom:1px solid currentColor;
  text-transform:none;letter-spacing:.02em}
.mlist-k a:is(:hover,:focus-visible){background:var(--yellow);color:#0D1633}
.mlist-frame{display:block;width:100%;height:352px;border:0;border-radius:10px}
@media (max-width:560px){.mlist-frame{height:252px}}

/* ---------- the process cards: static, bigger, they pick up (21 Oct) ----------
   At his instruction. The row scrolled and that was wrong for this content:
   a moving row makes five things you have to WAIT for, when they are five
   things you want to read at once and compare. Still, they can be scanned in
   one pass and they can be bigger, because nothing has to fit a loop.

   Each card fills its column instead of being sized to a track, which is
   roughly sixty per cent larger than the scrolling version at the same
   width. The only motion left is the pick-up on hover and focus: the card
   lifts, squares up from its tilt and rises above its neighbours. */
.pcards{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));
  gap:clamp(.7rem,1.4vw,1.2rem);margin:var(--s-6) 0 0;padding:0;list-style:none;
  align-items:start}
.pcards li{min-width:0}
.pcards img{display:block;width:100%;height:auto;border:var(--line) solid #0D1633;
  /* a slight tilt so five identical rectangles read as cards on a table
     rather than as a chart. Alternating, so it is not a repeated stamp. */
  transform:rotate(-1.1deg);
  transition:transform 260ms var(--e-spring),box-shadow 260ms var(--e-out)}
.pcards li:nth-child(even) img{transform:rotate(1.3deg)}
/* the pick-up: squares up, lifts, and comes forward over its neighbours */
.pcards li:is(:hover,:focus-within){position:relative;z-index:3}
.pcards li:is(:hover,:focus-within) img{transform:rotate(0deg) translateY(-10px) scale(1.09)}
@media (prefers-reduced-motion:reduce){
  .pcards img{transition:none}
  .pcards li:is(:hover,:focus-within) img{transform:rotate(0deg)}
}
/* 5 across stops being readable before it stops fitting: at 1100 the card
   text is smaller than the body copy above it, so the row becomes 3 then 2 */
@media (max-width:1100px){.pcards{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media (max-width:700px){.pcards{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:460px){.pcards{grid-template-columns:minmax(0,1fr)}}

/* ---------- the hero stops holding 262px of nothing (21 Oct) ----------
   Measured at 1440x900: 161px above the card and 101px below it, in an
   821px hero, so a third of the first screen was empty. The hero is still
   a full screen, which is the point of it, but the card now sits in the
   middle of that screen rather than floating in the top half of it with a
   hundred pixels of nothing underneath.

   This is spacing only. Nothing moves, nothing is cut, and the marks keep
   their own field: the complaint was dead air, not density. */
.stage-hero{padding-block:clamp(var(--s-7),9vh,var(--s-9))}
/* The marks no longer depend on .stage at all: .floats is a sibling now, so
   inset:0 covers the whole hero and a top percentage means what it reads as.
   While it was a child, 6/38/70% resolved against .stage content height and
   put three 235px marks inside 391px. */
.stage{align-content:center}

/* the two calls to action were sitting on the card edge */
.idcard .hero-cta{margin-top:var(--s-4)}

/* ---------- Worked with, back in the hero (21 Oct, at his instruction) ----------
   On /about/ the eight marks were answering a question nobody had asked yet.
   In the hero they answer the first one a visitor has: has this person done
   real work. It sits at the bottom of the hero, under the card and the
   marks, so it is the last thing on the first screen rather than something
   competing with the claim.

   It runs, where the /about/ version was static. There the reader had
   already committed; here a strip that moves is what says there are more of
   these than fit the width. Same engine as the rest: holds on pointer and
   focus, never starts under reduced motion. */
/* The hero-pinned ticker block was deleted on 29 Oct (P0-10). It put
   .wwstrip at position:absolute against the hero's bottom edge and laid
   it out as a single flex row, which put the new Pause button on top of
   the first mark; and ".wwstrip .ww-band" beat the new bare rule, which
   kept each label inline beside its logo instead of under it.

   Deleted rather than overridden, which is this file's own lesson: two
   placement systems for one object is how the 560px offset bug
   happened. The replacement is the CSS marquee near the end of this
   file. git show pre-fix:assets/site.css has the original. */

/* The marks are placed against the hero, and the worked-with strip now owns
   the bottom of it, so the field has to stop above the strip or the last
   row lands on the logos. The FAQ label was doing exactly that. One value,
   measured from the strip height rather than nudged. */
.floats{inset:0 0 clamp(62px,7.5vh,82px) 0}

/* ---------- standalone link targets reach 24px (21 Oct) ----------
   Found by the UI/UX sweep, not by eye: five end-of-section links were
   rendering at 22 to 23px against the 24px minimum (WCAG 2.5.8). They are
   all the same shape, a line of text with an arrow that ends a section, and
   they all missed for the same reason: a bare inline anchor is only as tall
   as its line box.

   inline-flex with a min-height gives them the target without changing how
   they look, because the extra pixels go above and below the text. */
/* Targeted by STRUCTURE rather than class, because the markup is minified
   onto long lines and class-hunting was costing more than it was worth: a
   standalone end-of-section link is an anchor that is the only child of its
   paragraph, which is exactly the shape all five had. */
p > a:only-child,
.pfoot a,
.ways a,.so-col a,.mlist-k a,.case-toc a{display:inline-flex;align-items:center;min-height:24px}

/* ---------- the playlist plays inside the iPod (23 Oct) ----------
   His brief: the Spotify preview fits the iPod png and looks like the
   playlist is scrollable on it. Nothing about the embed changed; it is
   framed, not rebuilt.

   SCREEN COORDINATES ARE MEASURED off the artwork rather than eyeballed
   (scratchpad/screen.mjs scans the png for its dark frame): 6.33% left,
   4.36% top, 87.05% wide, 44.21% tall of the trimmed 332x527 artwork. The
   supplied png had a white background and wide margins, so it was keyed to
   transparent and trimmed first: on the original canvas the screen was only
   39% of the width and the embed was unusable in it. If the artwork is
   ever re-exported these four numbers are the only thing to re-measure.

   The embed is taller than the screen is, which is the point: the screen is
   an overflow window, so the list runs past the bottom edge the way it does
   on a real device and Spotify's own scrolling moves it. */
.ipod{position:relative;width:min(340px,100%);margin:var(--s-2) 0 0}
.ipod-shell{display:block;width:100%;height:auto}
.ipod-screen{position:absolute;left:6.33%;top:4.36%;width:87.05%;height:44.21%;
  overflow:hidden;background:#0D1633}
/* THE PLAYLIST SCROLLS IN THE SCREEN (24 Oct, item 13).
   It was a 352px embed clipped to a 239px window, so the visitor saw the
   cover, the controls and one and a half track rows with the second cut in
   half. That reads as a crop, not a device.

   Spotify’s embed has its OWN scrolling track list once it is tall enough
   to draw one. So the frame is taller than the window on purpose and is
   pulled up, so the window shows the LIST rather than the cover art: the
   rows are then scrolled by Spotify inside its own player, which is exactly
   "the playlist is scrollable on this iPod". Nothing about the embed
   changed, which was his constraint: same src, same player, same controls.

   --ipod-lift is the distance from the top of the embed to the first row.
   Measured, not guessed: scratchpad/p3e.js reads it off the rendered frame. */
.ipod{--ipod-lift:146px}
.ipod-screen .mlist-frame{display:block;position:absolute;left:0;right:0;
  top:calc(var(--ipod-lift) * -1);width:100%;height:calc(239px + var(--ipod-lift) + 40px);
  border:0;border-radius:0}
@media (max-width:560px){
  /* the screen is a fixed share of the artwork, so below about 380px of
     iPod the embed has fewer than 150px of width and Spotify collapses it
     to an unusable sliver. The frame comes off and the playlist stands on
     its own rather than being decoration you cannot operate. */
  .ipod-shell{display:none}
  .ipod-screen{position:static;width:100%;height:auto;background:none}
  /* 352, not 252: at 252 Spotify picks its compact layout, draws about
     150px of card and leaves the rest of the iframe empty, which reads as a
     gap in the column. 352 is the height its track-list layout wants. */
  /* and the lift comes OFF here. On a phone there is no screen to look
     through, so an absolutely positioned frame pulled 146px above its box
     would hang the player off the top of the column. The desktop rule sets
     position, top and height, so all three have to be undone, not just the
     one that looks relevant. */
  .ipod-screen .mlist-frame{position:static;top:auto;height:352px;border-radius:10px}
}

/* ---------- the sky behind the sign-off (23 Oct, his choice) ----------
   His cloud texture, in the end block. First pass put it full bleed at .34
   and it read as a photograph with graph paper laid over it: the section
   stopped being paper, and the blue pill had to fight a blue ground.

   So it is a BAND at the seam, not a background. It enters where the
   section does, fades out before the pill, and sits at an opacity where it
   reads as a wash rather than a picture. The star and the wordmark below
   are untouched, which keeps the end of the page his own artwork.

   The file is a phone-shaped crop, so it tiles across rather than
   stretching: a 720-wide photograph stretched to 1920 turns to mush. */
.contact::after{content:"";position:absolute;left:0;right:0;top:0;height:min(46vh,420px);
  z-index:-1;pointer-events:none;
  /* the MIRRORED tile: CSS has no mirror-repeat, so the tile is the
     photograph beside its own flip, which makes both joins invisible.
     The straight file tiled with a hard vertical seam every 720px, which
     reads as a rendering bug rather than a sky. */
  background-image:url("img/clouds-tile.webp");
  background-size:auto 100%;background-repeat:repeat-x;background-position:center top;
  opacity:.5;filter:saturate(.72);
  -webkit-mask-image:linear-gradient(to bottom,transparent 0%,#000 26%,transparent 94%);
  mask-image:linear-gradient(to bottom,transparent 0%,#000 26%,transparent 94%)}
/* in dark the sky would be a lit plate on an ink page */
:root[data-theme="dark"] .contact::after{opacity:.2;filter:saturate(.5) brightness(.6)}
/* It does NOT drift, and that is deliberate twice over. The tile is sized
   `auto 100%`, so its rendered width depends on the band height and no fixed
   keyframe distance can land back on itself: every loop would jump. And he
   asked for one moving device on the page, which is the comic tape. A sky
   that sits still is a ground; a sky that slides is a fourth ticker. */

/* ---------- the hero print is one card, not two boxes (23 Oct, TAB 2.5) ----
   His note: "Real Polaroid, not a drawn one."

   What made it read as drawn was structural, not decorative. It was TWO
   bordered boxes stacked: a mount holding the photograph with a 2px ink
   stroke, and a caption below it with its own stroke and `border-top:0` to
   fake a shared edge. No physical print is two objects, and the even 10px
   lip on three sides is the giveaway: a polaroid's bottom lip is five times
   its sides, because that is where the chemistry sits.

   The numbers come off HIS OWN scan (assets/img/mot-dad.webp, probed pixel
   by pixel): card rgb(242,241,234), not white; side lip 2.8% of the card
   width, top 3.5%, bottom 21%. The photograph sits in a WELL, so its edge is
   an inner line on the image, not a stroke around the whole object.

   Attempted first and abandoned: cutting the empty frame out of that scan
   and laying it over the photograph. The artefact is rotated with a second
   card behind it, so there is no rectangle to crop, and the same polaroid
   would then appear twice on the site with two different photographs in it.
   A true fix still wants one blank polaroid or a scan of the Delhi print in
   one; that is in NEEDS. */
.idcard .print{position:relative;background:#F2F1EA;
  border:1px solid rgba(13,22,51,.48);
  padding:3.2% 3.2% 0;
  /* paper grain, from the same dot technique as --paper but at print scale */
  background-image:radial-gradient(rgba(13,22,51,.05) .5px,transparent .5px);
  background-size:4px 4px}
/* the mount stops being an object: no plate, no stroke, no padding */
.idcard .print-mount{background:none;border:0;padding:0}
/* the well. An inset line on the IMAGE, which is what a photograph set into
   a card looks like, rather than a stroke drawn around the card. */
.idcard .print-mount::after{content:"";position:absolute;inset:0;pointer-events:none;
  box-shadow:inset 0 0 0 1px rgba(13,22,51,.34),inset 0 1px 3px rgba(13,22,51,.2)}
/* the lip: deep, and it carries the caption instead of a second box */
.idcard .print figcaption{background:none;border:0;
  padding:7% 1% 9%;text-align:left}

/* ---------- the copy button beside an address (24 Oct, item 10) ----------
   A separate control for a separate outcome: the link opens mail, this
   copies and does nothing else. Sized at the 24px target minimum, outlined
   rather than filled so it never competes with the address it serves. */
.copybtn{display:inline-flex;align-items:center;gap:6px;vertical-align:middle;
  margin-left:var(--s-2);min-height:28px;padding:3px 10px;
  background:none;border:1.5px solid color-mix(in srgb,currentColor 55%,transparent);
  color:inherit;font-family:var(--fu);font-size:var(--xs);letter-spacing:.07em;
  text-transform:uppercase;cursor:pointer;
  transition:background var(--d-1) var(--e-out),color var(--d-1) var(--e-out)}
.copybtn .ico{width:14px;height:14px;flex:none}
.copybtn:is(:hover,:focus-visible){background:currentColor}
/* The hover was mix-blend-mode difference plus invert, so one rule could
   serve every ground. It rendered as a pale ghost box on the blue pill and
   the label all but disappeared. Two explicit rules instead: the button
   takes the ground it sits on. A clever rule that cannot be read is worse
   than two plain ones. */
.copybtn:is(:hover,:focus-visible){background:var(--deep);color:var(--cream);
  border-color:var(--deep)}
.sign-off .copybtn:is(:hover,:focus-visible){background:var(--cream);color:#173CA8;
  border-color:var(--cream)}
/* the address and its button are one row, never two lines */
.mailrow{display:inline-flex;align-items:center;flex-wrap:wrap;gap:var(--s-1) 0}

/* the claim's resting word, before the script turns it into the roll.
   It ships visible so a reader with JS off gets the sentence rather than a
   hole; home.js adds .vh once it has built the stack. */
.rollword{font-family:var(--fd);font-weight:900;background:var(--deep);color:var(--cream);
  padding:0 .14em}

/* In the sign-off pill the column is 319px and the address plus a labelled
   button measures 323, so it wrapped to a line of its own and stopped
   reading as attached to the address. The word goes, the aria-label stays,
   and the target is still over the 24px minimum. Measured, not guessed. */
.sign-off .copybtn{padding:5px 7px}
.sign-off .copybtn span{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
.sign-off .copybtn .ico{width:16px;height:16px}
.sign-off .mailrow{flex-wrap:nowrap}

/* ---------- the work rows on the homepage (24 Oct, A10) ----------
   Same component as /work, so nothing is restyled here. The homepage
   version only needs its own breathing room: the index page opens with a
   filter bar and this one opens straight into the rows. */
.windex--home{margin-top:var(--s-6)}
.windex--home .wrow:first-child{border-top:var(--line) solid var(--deep)}

/* The homepage work section stands on the cape blue plane, which is that
   section's own ground. Every line and chip in a .wrow is drawn in --deep,
   so on blue they vanish: the row rules disappear, the tag outlines go and
   the B2C plane becomes a blue square on blue. Type was never the problem
   (contrast is 0 failures either way) but the chips carry the sorting
   information a hiring manager actually scans, so they have to be visible.
   Cream in, deep out, nothing else changed. */
.work .windex--home .wrow{border-color:color-mix(in srgb,var(--cream) 34%,transparent)}
.work .windex--home .wrow:first-child{border-top-color:color-mix(in srgb,var(--cream) 34%,transparent)}
.work .windex--home .tags span{border-color:color-mix(in srgb,var(--cream) 55%,transparent)}
/* THE PLANE TRAP, SEVENTH TIME, and the lesson is the opposite way round
   here. I hard-coded this chip's ink to #173CA8 against a background of
   var(--cream), and in dark mode --cream becomes the ink: blue on near
   black is 1.88. The rule in CLAUDE.md is "a plane must not use theme
   tokens", but this is not a plane. The work section is a SURFACE that
   follows the UI: its own ground is var(--deep) and its type var(--cream),
   and both flip together. Anything sitting on it has to flip with it, so
   here both values are tokens and neither is literal. Hard-code a printed
   object; tokenise a surface. Getting that backwards fails either way. */
.work .windex--home .tags .aud{background:var(--cream);color:var(--deep);
  border-color:var(--cream)}
.work .windex--home .wrow:is(:hover,:focus-visible) .go{color:var(--yellow)}

/* ---------- block E: the card answer, and tabular figures (25 Oct) ----------
   His spec for a project card: straighten to 0 degrees, lift 4px, offset
   shadow 6px to 10px, 180ms; press depresses in 80ms.

   A NOTE ON THE VETO LIST, which bans drop shadows and offset shadows.
   This is his later instruction and it wins, and the two are reconcilable:
   what the list is banning is soft depth, the glassy lift that makes a web
   card look like every other web card. A hard offset in flat ink is a
   print device, a plate printed slightly off its own outline, and it is
   already on the site for exactly that reason. Nothing here is blurred. */
.windex .plate{transition:transform var(--d-1) var(--e-out),box-shadow var(--d-1) var(--e-out)}
a.wrow:is(:hover,:focus-visible) .plate{transform:translateY(-4px) rotate(0deg);
  box-shadow:10px 10px 0 #0D1633}
a.wrow:active .plate{transition-duration:80ms;transform:translateY(0) rotate(0deg);
  box-shadow:6px 6px 0 #0D1633}
/* on the blue plane the ink shadow disappears, so it prints in cream there */
.work .windex--home a.wrow:is(:hover,:focus-visible) .plate{box-shadow:10px 10px 0 var(--cream)}
.work .windex--home a.wrow:active .plate{box-shadow:6px 6px 0 var(--cream)}

/* Tabular figures, so a counting number does not change width while it
   counts. Without this the whole row jitters sideways for 900ms, which is
   the thing that makes a count-up look cheap. */
.mv-figs b{font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1}

@media (prefers-reduced-motion:reduce){
  .windex .plate{transition:none}
  a.wrow:is(:hover,:focus-visible) .plate{transform:none;box-shadow:6px 6px 0 #0D1633}
}

/* ---------- stamp chips (27 Oct, pass 1, design brief D) ----------
   Status is the honest layer of this site, so it stops looking like a UI
   label and starts looking like something pressed onto the artefact.

   It KEEPS its cream plate. The documented rule here is "plate labels sit
   on a cream tab, never straight on red or blue", because the chip sits
   over poster artwork and a transparent stamp over a photograph is
   unreadable. So the stamp is cream paper with a red double rule and red
   ink: the look comes from the border, the rotation and the letterspacing,
   not from knocking the background out. Ink is --accent-type, 5.52 on
   cream, which is the small-type floor. */
.plate .pm{position:absolute;z-index:2;font-family:var(--fd);font-weight:900;
  font-size:var(--t-small);letter-spacing:.14em;text-transform:uppercase;
  opacity:1;background:var(--cream);color:var(--accent-type);
  padding:4px 9px;border:var(--line) solid var(--accent-type);
  box-shadow:inset 0 0 0 1px var(--cream),inset 0 0 0 3px color-mix(in srgb,var(--accent-type) 55%,transparent);
  transform:rotate(-4deg);transform-origin:center}
/* every plate's chip was individually coloured for its own ground; the
   stamp is one object now, so those overrides have to stop winning */
.p-vastr .pm,.p-noise .pm,.p-voice .pm,.p-vrl .pm,.p-abs .pm{color:var(--accent-type)}
/* the stamp lands once when a case page opens: scale 1.15 to 1, 120ms */
.case .stamp-thunk{animation:thunk 120ms var(--e-out) 1}
@keyframes thunk{from{transform:rotate(-4deg) scale(1.15)}to{transform:rotate(-4deg) scale(1)}}

/* ---------- stitched dividers (27 Oct, pass 1, design brief D) ----------
   The sewing machine in the 2003 photograph is the origin story, so the
   rule that separates two blocks is a running stitch rather than a line.
   Irregular dash lengths and a slight vertical wobble, so it reads as
   sewn rather than dashed: a CSS dashed border is perfectly even and
   reads as a form field.

   This does NOT replace the torn edges. Those carry a different meaning
   and the brief keeps them: where two grounds meet the upper block tears
   away in its own colour; where two blocks SHARE a ground, this is what
   goes between them. */
.stitch{position:relative;height:14px;border:0;margin:0;padding:0;
  background:none;overflow:hidden}
.stitch::before,.stitch::after{content:"";position:absolute;left:0;right:0;height:2px;
  background-repeat:repeat-x}
.stitch::before{top:5px;
  background-image:repeating-linear-gradient(90deg,
    var(--fluoro) 0 11px,transparent 11px 19px,
    var(--fluoro) 19px 27px,transparent 27px 37px,
    var(--fluoro) 37px 51px,transparent 51px 58px)}
/* the second pass sits a pixel lower and starts out of phase, which is
   what gives the line its wobble without a background image */
.stitch::after{top:8px;opacity:.55;
  background-image:repeating-linear-gradient(90deg,
    transparent 0 6px,var(--fluoro) 6px 15px,transparent 15px 24px,
    var(--fluoro) 24px 33px,transparent 33px 44px)}
/* on the yellow plane the thread is blue, per the brief */
.pillbox .stitch::before,.pillbox .stitch::after{background-image:repeating-linear-gradient(90deg,
  var(--deep) 0 11px,transparent 11px 19px,var(--deep) 19px 27px,transparent 27px 37px)}

/* it sews itself in once as the section arrives, 600ms, then stays */
.stitch{clip-path:inset(0 100% 0 0)}
.stitch.in{clip-path:inset(0 0 0 0);transition:clip-path 600ms var(--e-out)}
@media (prefers-reduced-motion:reduce){
  .stitch{clip-path:none}
  .stitch.in{transition:none}
  .case .stamp-thunk{animation:none}
}

/* ---------- two fixes the sweep caught in pass 1 ----------
   1. .wk-note was built for the blue work section, so its type is cream.
      On the cream FAQ block that is cream on cream: 1.00, invisible. The
      class carries the arrow behaviour, which is worth keeping, so only
      the colour is overridden rather than a second link component built. */
.faqhome .wk-note{color:var(--deep)}
.faqhome a.wk-note:focus-visible{outline:3px solid var(--fluoro);outline-offset:6px}

/* ---------- Contact v2 (27 Oct) ----------
   One job per block, three type sizes, no horizontal rules: the old page
   cut twelve lines into fragments with three of them. The stitch is used
   twice, which is the brief's limit of three. */
.chead{max-width:62ch;margin:0 0 var(--s-7)}
.chead .lede{margin:0 0 var(--s-3)}
.cz-promise{margin:0;font-family:var(--fu);font-size:var(--t-small);color:var(--deep)}
.cgrid{display:grid;gap:clamp(1.6rem,3.5vw,3rem);align-items:start;
  grid-template-columns:minmax(0,1.618fr) minmax(0,1fr)}
@media (max-width:900px){.cgrid{grid-template-columns:minmax(0,1fr)}}
.cz-h{margin:0 0 var(--s-3);font-family:var(--fd);font-weight:900;
  font-size:var(--t-h3);line-height:1.05;letter-spacing:var(--fd-track)}

/* the chips, with one indicator that slides between them rather than four
   backgrounds fading in and out */
.cz-chips{position:relative;display:flex;flex-wrap:wrap;gap:var(--s-2);margin:0 0 var(--s-4)}
.cz-chip{position:relative;z-index:1;min-height:40px;padding:8px 14px;
  background:none;border:var(--line) solid var(--deep);color:var(--deep);
  font-family:var(--fu);font-size:var(--t-small);cursor:pointer;
  transition:color var(--d-1) var(--e-out)}
.cz-chip[aria-checked="true"]{color:var(--cream)}
.cz-ind{position:absolute;z-index:0;left:0;top:0;height:100%;
  background:var(--deep);opacity:0;
  transition:transform 250ms var(--e-out),width 250ms var(--e-out)}
.cz-ind[data-ready]{opacity:1}

/* the letter, on paper */
.cz-paper{background:#FFFDF7;border:var(--line) solid var(--deep);padding:var(--s-4)}
.cz-k{margin:0 0 var(--s-2);font-size:var(--xs);letter-spacing:.08em;
  text-transform:uppercase;color:#0D1633;opacity:.75}
.cz-letter{margin:0;font-size:var(--t-body);line-height:2;color:#0D1633}
.cz-blank{font:inherit;color:#173CA8;background:none;border:0;
  border-bottom:var(--line) solid var(--fluoro);padding:0 2px;min-width:4ch;
  border-radius:0}
.cz-blank::placeholder{color:#0D1633;opacity:.42}
.cz-blank:focus-visible{outline:2px solid var(--fluoro);outline-offset:3px}
.cz-wipe{animation:cz-wipe 250ms var(--e-out)}
@keyframes cz-wipe{from{clip-path:inset(0 100% 0 0)}to{clip-path:inset(0 0 0 0)}}

.cz-acts{display:flex;flex-wrap:wrap;gap:var(--s-2);margin-top:var(--s-4)}
.cz-btn{min-height:40px;padding:9px 14px;cursor:pointer;
  font-family:var(--fu);font-size:var(--t-small);
  background:none;border:var(--line) solid var(--deep);color:#173CA8;
  transition:background var(--d-1) var(--e-out),color var(--d-1) var(--e-out)}
.cz-btn:is(:hover,:focus-visible){background:var(--deep);color:var(--cream)}
.cz-btn--go{background:var(--deep);color:var(--cream)}
.cz-btn--go:is(:hover,:focus-visible){background:var(--fluoro);border-color:var(--fluoro)}
.cz-say{margin:var(--s-3) 0 0;min-height:1.4em;font-family:var(--fu);
  font-size:var(--t-small);color:var(--accent-type)}
.cz-fallback{display:block;width:100%;margin-top:var(--s-2);font:inherit;
  font-size:var(--t-small);padding:8px;border:var(--line) solid var(--deep);background:var(--cream)}
.cz-plain{margin:var(--s-3) 0 0;font-size:var(--t-small);color:#0D1633}

/* the three taped notes down the side */
.cside{display:grid;gap:var(--s-5)}
.cnote{background:#FFFDF7;border:var(--line) solid var(--deep);padding:var(--s-4);
  transform:rotate(-.8deg)}
.cside .cnote:nth-child(2){transform:rotate(.7deg)}
.cside .cnote:nth-child(3){transform:rotate(-.4deg)}
.cnote .cz-h{font-size:var(--t-body);color:#173CA8}
.clist{margin:0;display:grid;grid-template-columns:max-content minmax(0,1fr);
  gap:6px var(--s-3);font-size:var(--t-small)}
.clist dt{font-family:var(--fu);color:#0D1633;opacity:.75}
.clist dd{margin:0;color:#0D1633}
.cnote .wk-note{color:#173CA8;margin-top:var(--s-3)}

.cfaq,.cways{margin:var(--s-6) 0 0}
.cways .creach{margin-top:var(--s-3)}

/* body copy caps at 62 characters, the brief's number. .stand was 672px,
   which is nearer 74 at this size. */
.phead .stand{max-width:62ch}
/* with no script every letter is hidden, because [hidden] is in the markup
   and the script is what reveals the chosen one. So with the script off
   the FIRST letter shows: the page must never be a set of buttons with no
   letter under them. */
.cz-letter:first-of-type{display:revert}

/* THE CHECKED CHIP MUST NOT DEPEND ON THE INDICATOR.
   The chip ships aria-checked="true" in the markup, and the checked rule
   set its text to cream. With the script off the sliding indicator never
   gets painted, so the first chip was cream type on cream paper: a blank
   box. Found by loading the page with scripts disabled.

   So the checked chip carries its own plane by default, and only gives it
   up once the script has marked the group as driving the indicator. */
.cz-chip[aria-checked="true"]{background:var(--deep);color:var(--cream)}
.cz-chips[data-js] .cz-chip[aria-checked="true"]{background:none}

/* ---------- M1: a reveal that cannot leave a blank screen (27 Oct) ----------
   Measured with scripts disabled: EVERY .rv block on every route sat at
   opacity 0. That is the work rows, every section heading, the stitched
   dividers. The homepage showed a hero and a receipt and then nothing.

   .rv ships at opacity 0 and a script adds .in. The script also has a
   fallback for browsers with no IntersectionObserver, but that branch
   cannot run if the script itself never runs, which is the case this is
   for: blocked, failed, or still downloading.

   Scoped with :not(.in) so it can NEVER compete with the real reveal.
   Writing this without the :not() is exactly what held the hero claim
   invisible yesterday: declared later, more specific, won the cascade. */
@keyframes rv-fallback{to{opacity:1;transform:none}}
.js .rv:not(.in){animation:rv-fallback 1ms linear 2.5s forwards}
@media (prefers-reduced-motion:reduce){.js .rv:not(.in){animation:none}}

/* M10: the stickers answer a hover once and stop. A loop here would make
   the desk restless, and the brief is explicit that it never loops. */
@media (hover:hover) and (pointer:fine) and (prefers-reduced-motion:no-preference){
  .fl a:hover .fl-icon{animation:wiggle 300ms var(--e-out) 1}
}
@keyframes wiggle{0%{transform:rotate(0)}30%{transform:rotate(2deg)}
  65%{transform:rotate(-2deg)}100%{transform:rotate(0)}}

/* ---------- two fixes the sweep caught on Contact v2 ----------
   1. The checked chip read 1.00. The indicator that gave it its plane is an
      absolutely positioned SIBLING, so nothing walking up the ancestors can
      see it: the harness read cream type on cream paper and it was right to,
      because any tool a recruiter's employer runs would read it the same way.

      So the checked chip keeps its OWN plane in every case, and the
      indicator becomes a sliding underline rather than the fill. It still
      slides between choices, which is the point of M3, and the chip is now
      legible to anything that inspects it. Better than suppressing a
      warning about a thing that genuinely cannot be seen. */
.cz-chips[data-js] .cz-chip[aria-checked="true"]{background:var(--deep)}
.cz-ind{top:auto;bottom:-6px;height:3px;background:var(--fluoro)}

/* 2. THE PLANE TRAP, EIGHTH TIME. .cnote is a printed card, hard-coded
      #FFFDF7 in both themes, but the .slot inside it draws its text from
      --deep, which becomes the light ink in dark mode: 1.26 on white.
      Anything inside a hard-coded plane carries literal values. */
.cnote .slot,.cnote .slot-i,.cnote p,.cnote dd{color:#173CA8}
.cnote .slot,.cnote .slot-i{border-left-color:#E8251F}
.cz-paper .cz-letter,.cz-paper .cz-k,.cz-plain{color:#0D1633}

/* THE PLANE TRAP, NINTH. .stage-copy .hero-cta .hbtn-alt sets its ink from
   --deep while sitting on the ID card, which is hard-coded #FFFDF7 in both
   themes: 4.42, under the floor. The card is a printed object, so the
   button on it carries literal values like every other child of it.
   This rule is last in the file so it cannot be undone by source order,
   which is how the earlier idcard fix stopped winning. */
.idcard .hero-cta .hbtn-alt,.stage-copy .hero-cta .hbtn-alt{color:#173CA8;border-color:#173CA8}
.idcard .hero-cta .hbtn-alt:is(:hover,:focus-visible){background:#173CA8;color:#FFFDF7}

/* the indicator is now translated on both axes from the row's top-left,
   because the chips wrap, so it can no longer be pinned to the bottom */
.cz-ind{top:0;bottom:auto;height:100%;background:none;
  border-bottom:3px solid var(--fluoro);
  transition:transform 250ms var(--e-out),width 250ms var(--e-out)}

/* ---------- the record player stops covering copy (27 Oct) ----------
   The documented rule for this device is that a floating control may cover
   the corner it lives in, but it may not cover the copy. The overlap scan
   caught it doing exactly that at 768 and 1280: over the figures in What
   it moved, over a work row's teaser, over "Open" and the read time.

   It had already been shrunk to 64px for this reason; what it still lacks
   is clearance from the content column. The page now ends with enough room
   under it that nothing readable shares its corner, and the deck itself
   sits a little further out. */
.disc{right:clamp(10px,1.4vw,22px);bottom:clamp(10px,1.4vw,22px)}
/* the last block on every page reserves the deck's corner */
.contact,.pfoot{padding-bottom:max(var(--s-7),96px)}
/* and a work row never runs under it: the aside column stops short */
@media (min-width:760px){.windex .wrow{padding-right:clamp(0px,6vw,96px)}}

/* ---------- the homepage FAQ is three, not nine (28 Oct) ----------
   The bento is a 6-column grid with hand-set spans for nine tiles. The
   homepage carries three, so those spans left two ragged rows and about
   400px of empty cream in an 866px section: the first tile ended at
   column 3, the second ran to column 8, and the third dropped to a new
   row on its own.

   Three tiles want three equal columns. The spans are overridden rather
   than the grid rebuilt, so /about/ keeps its nine-tile bento exactly as
   it is. They also stretch to a shared height, because three cards of
   three different depths in one row is what made it look unfinished. */
.faqhome .bento{grid-template-columns:repeat(3,minmax(0,1fr));align-items:stretch;
  gap:var(--s-3)}
.faqhome .tile-q,.faqhome .tile-q:nth-child(n){grid-column:span 1}
.faqhome .tile-q{display:flex;flex-direction:column}
/* the answers are open here, so the card grows to its content and the
   three level up rather than one hanging below the others */
.faqhome .tile-q .ans{flex:1 1 auto}
@media (max-width:900px){
  .faqhome .bento{grid-template-columns:minmax(0,1fr)}
}

/* ---------- Me stops opening on 324px of nothing (28 Oct) ----------
   Measured: the section pays 128px of its own padding, then .me-turn adds
   96px above itself and 64px below, so the heading did not start until
   324px into the section. Two devices were spacing the same seam.

   The turn is a divider, so it belongs near the torn edge it follows, not
   floating in the middle of the gap. The section gives up most of its top
   padding and the air moves below the turn, where it is doing something:
   separating the divider from the heading. 324px to 166px, and nothing
   moved except space. */
.about{padding-top:var(--s-5)}
.me-turn{margin:40px 0 72px}

/* ---------- D1: nothing starts under the floating island ----------
   Measured at 375: the island wraps to two rows and ends at 108px, and
   the hero's cut-out name tiles started at 96. The reserve is the island's
   own measured bottom plus a gap, published as --nav-h by site.js, with a
   108px fallback so a blocked script reserves the worst case rather than
   nothing.

   Scoped to the widths where the island wraps: at desktop it is one row
   and the hero is a centred full-screen layout that must not be pushed. */
@media (max-width:860px){
  .stage-hero{padding-top:calc(var(--nav-h,108px) + 16px)}
}
/* and an anchor jump never lands a heading under it, at any width */
[id]{scroll-margin-top:calc(var(--nav-h,108px) + 16px)}

/* the lockup is the picture of the heading, so it keeps every rule the
   .hero-name children already had; it is only a wrapper for aria-hidden */
.hero-lockup{display:contents}

/* ---------- D12: 44px tap targets (28 Oct) ----------
   Measured at 375: thirteen interactive elements under 44px, none under
   the 24px WCAG floor. So the site met the standard and missed his bar.

   The height is bought with padding rather than a min-height box, so the
   text stays where it is and only the hit area grows. Where an element is
   inline in a sentence, the padding is vertical only and negative margin
   pulls the line back to its original rhythm: a 44px link in a paragraph
   would otherwise space the paragraph out.

   The island is the exception and it is deliberate. Its three controls sit
   inside a floating pill he has tuned three times; growing them grows the
   pill on every route. They are brought to 44 in HEIGHT, which is the axis
   a thumb misses on, and keep their width. Flagged in PRD-V1-ADDENDUM B2
   as a decision, and this is the middle answer until he picks. */
.ways a,.so-col a,.mlist-k a,.creach a,.pfoot a,.cnote .wk-note,
a.wk-note,.rc-more summary,.srcs a{
  min-height:44px;display:inline-flex;align-items:center}
/* inline links inside running copy: grow the target, keep the rhythm */
p > a,li > a{padding-block:12px;margin-block:-12px}
.copybtn{min-height:44px;padding-block:9px}
.disc-btn{width:44px;height:44px}
.theme-toggle{width:44px;height:44px}
/* the island: 44 tall, natural width */
nav#nav .links a{min-height:44px;display:inline-flex;align-items:center}
nav#nav .sig{min-height:44px;display:inline-flex;align-items:center}
/* the sign-off copy button is icon-only to fit a 319px column; it gets its
   width from padding so the icon stays the size it is */
.sign-off .copybtn{min-width:44px;justify-content:center}

/* ---------- D7: the experience summary ----------
   Title, employer, dates, one line. The site had no dates anywhere, so a
   recruiter could not tell whether iSelect was a year or a month: on the
   sixty-second read that was the biggest unanswered question on the page.

   Generated into the markup, not rendered in the browser, so the dates are
   in the HTML a crawler and a slow phone both receive. The section does
   not exist at all until a row is published; it never ships as a stub. */
.xp{padding:var(--sec) 0}
.xp-list{list-style:none;margin:var(--s-6) 0 0;padding:0;
  border-top:var(--line) solid var(--deep)}
.xp-row{display:grid;gap:4px var(--s-5);align-items:baseline;
  grid-template-columns:minmax(0,1.2fr) minmax(0,1fr) max-content;
  grid-template-areas:"title org when" "line line line";
  padding:var(--s-4) 0;border-bottom:1px solid rgba(var(--deep-rgb),.25)}
.xp-title{grid-area:title;font-family:var(--fd);font-weight:900;
  font-size:var(--t-h3);line-height:1.1;letter-spacing:var(--fd-track)}
.xp-org{grid-area:org;font-family:var(--fu);font-size:var(--t-small);
  color:color-mix(in srgb,var(--deep) 84%,var(--cream))}
.xp-when{grid-area:when;font-size:var(--t-small);white-space:nowrap;
  color:color-mix(in srgb,var(--deep) 84%,var(--cream))}
.xp-line{grid-area:line;max-width:60ch;font-size:var(--t-small);line-height:1.5}
@media (max-width:760px){
  .xp-row{grid-template-columns:minmax(0,1fr);
    grid-template-areas:"title" "org" "when" "line";gap:2px}
  .xp-when{white-space:normal}
}
/* the /work filter chips were missed by the D12 pass: 47x29 */
.fbtn{min-height:44px;display:inline-flex;align-items:center}

/* ---------- T1: the footer clears the wordmark (28 Oct) ----------
   .bigmark was absolutely positioned and pulled down 22%, so the "Made by
   / Melbourne" row ran straight through it. The wordmark joins the flow,
   so there is a real box for the row to sit under, and the row follows it
   in the DOM with 24px of clearance.

   line-height .85, his number: the wordmark is one line of display type
   and the default leading was adding a band of air under it that the
   absolute positioning was hiding. */
.bigmark{position:static;transform:none;width:auto;line-height:.85;
  margin:var(--s-6) 0 0}
.foot-wrap{position:relative;z-index:2}
.foot-wrap .foot{margin-top:24px;padding-top:0;border-top:0}
/* the email in the pill reads at 18, not 14 */
.sign-off .so-mail{font-size:var(--t-body);min-height:44px;display:inline-flex;align-items:center}
/* moving the row down put it exactly where the fixed record player sits,
   so the clock ran under it. The row keeps clear of that corner. */
.foot-wrap .foot{padding-right:clamp(0px,7vw,110px)}
@media (max-width:640px){.foot-wrap .foot{padding-right:0;padding-bottom:72px}}

/* ---------- T2: one left edge (28 Oct) ----------
   Measured at 1280 before: section headings landed on 67, 96, 68, 68 and
   145 — five edges for one grid. The container is 1120 wide with 24/48
   padding now, which puts its content edge at 128.

   Two headings still missed it and both for a structural reason, not a
   spacing one:
   - the metrics pill's own heading sat 1px in, from the summary's padding;
   - the sign-off heading is inside the blue pill, which is a plane with
     its own inset. A plane's content cannot sit on the page's edge
     without the plane starting there too, and the pill is deliberately
     centred. So it is excluded by name rather than forced.

   The work-row titles at 467 are NOT misaligned: they are the second
   column of a row grid, which this repo already records as a thing the
   alignment harness reports and a human has to dismiss. */
.moved .mv-pill{padding-left:0}
.moved .mv-pill-k{margin-left:0}

/* ---------- T3: two tilts, not fifteen (28 Oct) ----------
   Measured: fifteen distinct angles on main. Most were legitimate — the
   six floating stickers, the desk stickers, the sign star, the ground
   star, and two 45deg icon markers which are states rather than tilt.

   Two things were genuinely arbitrary and are now -1.5 and +1.5:
   - the validation cards sat at 1.4, -0.6 and -1.0, three angles chosen
     to look random, which reads as three mistakes rather than one device;
   - the status stamps sat at -4, which I picked on 27 Oct by eye.

   The hero card keeps its own -1.2, which his note allows. */
.plate .pm{transform:rotate(-1.5deg)}
.case .stamp-thunk{animation:thunk 120ms var(--e-out) 1}
@keyframes thunk{from{transform:rotate(-1.5deg) scale(1.15)}to{transform:rotate(-1.5deg) scale(1)}}

/* ---------- T4: one line weight (28 Oct) ----------
   Measured on home: 1px on 63 elements, 2px on 51, 3px on 11. Three
   weights doing one job, so a card's edge and a section's rule read as
   different kinds of line for no reason.

   One weight, 1.6px, and 2.4px only where something is active or hovered,
   so weight means state rather than decoration. Declared as tokens so the
   next component inherits the decision instead of picking a number. */
:root{--line:1.6px; --line-hi:2.4px}
/* the html body prefix is the pattern this file already uses for the
   island: component rules like .wrow .plate are two classes deep and beat
   a bare .plate, so the first attempt changed nothing. */
html body :is(.plate,.idcard,.vouch-card,.tile-q,.cnote,.cz-paper,.cz-chip,
.cz-btn,.copybtn,.fl-name,.print,.xp-list,.wrow,.tags span,.mv-pill,
.casefig,.creach a,.hbtn,.so-mail,.fl-hint,.pm,.shead,.windex,.id-head,
.mv-grid,.wwstrip,.mv-gk,.more){border-width:var(--line)}
.windex .wrow{border-top-width:0;border-bottom-width:var(--line)}
.windex--home .wrow:first-child{border-top-width:var(--line)}
.xp-list{border-top-width:var(--line)}
.xp-row{border-bottom-width:var(--line)}
/* the left rule on a gap, a trade-off or a pull quote is the one place a
   heavier line carries meaning, so it takes the active weight */
.slot,.slot-i{border-left-width:var(--line-hi)}
/* state, and only state */
a.wrow:is(:hover,:focus-visible) .plate,
.tile-q[aria-expanded="true"],
.cz-chip[aria-checked="true"],
.cz-btn:is(:hover,:focus-visible),
.copybtn:is(:hover,:focus-visible){border-width:var(--line-hi)}
/* the last two 3px edges: the contact section's top rule and the pill's
   chevron, which is a drawn mark rather than a border */
html body section.contact{border-top-width:var(--line)}
html body .mv-chev{border-width:var(--line-hi)}

/* T5: buttons and inputs inherit the page's face. The theme toggle and the
   record-player button were rendering in Arial, because a <button> takes
   the UA font unless told otherwise and neither carried a family. */
button,input,select,textarea{font:inherit}
/* T5: the two things still under the floor.
   .fl-hint is the sticker's hover caption at 11px, and the photo caption's
   year at 12. Both go to 12, which is the caps-label step and the floor
   for anything that is not body copy. The hint is sentence case, so it
   takes the step rather than the label treatment. */
html body .fl-hint{font-size:.75rem}
html body .print figcaption b{font-size:.75rem}

/* ---------- T6: the stitch is the only divider (28 Oct) ----------
   HIDDEN, NOT DELETED, and flagged so both come back in one line.

   The torn edge was the transition language settled 19-20 Oct: where two
   grounds meet the upper block tears away in its own colour. The cloud
   band was the sky added to the sign-off on 23 Oct. Both are his and both
   are one declaration away from returning: delete this block.

   What the page keeps is the dashed running stitch, which is the device
   with a reason behind it — the sewing machine in the 2003 photograph —
   and which does the same job with one line instead of three.

   The seams still need to say something, so the blocks that were torn
   apart take a stitch's worth of rule instead of a ragged edge. */
.work::before,.work::after,.ideas::after{display:none}
/* .contact::after (the sky) is back, 30 Oct, at his instruction: "restore the
   scenery for end of page". T6 had hidden it. */

/* the ground change is now carried by the block's own edge, not a tear */
.work{border-top:var(--line) solid rgba(var(--cream-rgb),.34);
  border-bottom:var(--line) solid rgba(var(--cream-rgb),.34)}

/* ---------- T7: the homepage FAQ is a list, not cards (28 Oct) ----------
   Yesterday it became three equal cards, which fixed a ragged grid but
   kept the boxes. His note is a two-column list: question left, answer
   right, no boxes. That reads as a transcript of the screening call,
   which is what the section is, rather than three things to choose from.

   The tiles stay <button>s with their disclosure wiring, because the nine
   on /about/ use the same component and must keep working. Here they are
   open and the chrome comes off: no plane, no border, no marker. */
.faqhome .bento{display:grid;grid-template-columns:minmax(0,1fr);gap:0;
  border-top:var(--line) solid rgba(var(--deep-rgb),.28)}
.faqhome .tile-q,.faqhome .tile-q:nth-child(n){
  grid-column:span 1;display:grid;align-items:start;
  grid-template-columns:minmax(0,1fr) minmax(0,1.3fr);
  gap:var(--s-3) var(--s-6);
  background:none;border:0;border-bottom:var(--line) solid rgba(var(--deep-rgb),.28);
  padding:var(--s-5) 0;text-align:left;cursor:default}
.faqhome .tile-q[aria-expanded="true"]{background:none;color:inherit}
.faqhome .qn{grid-column:1;font-family:var(--fu);font-size:var(--xs);
  letter-spacing:.08em;color:var(--accent-type)}
.faqhome .qt{grid-column:1;grid-row:2;font-family:var(--fd);font-weight:900;
  font-size:var(--t-h3);line-height:1.12;letter-spacing:var(--fd-track);
  color:var(--deep);display:block}
.faqhome .ans{grid-column:2;grid-row:1 / span 2;max-height:none;overflow:visible;
  margin:0;font-size:var(--t-small);line-height:var(--lh-body);color:var(--deep)}
/* the plus marker is a disclosure affordance and nothing here discloses */
.faqhome .qt .pm{display:none}
@media (max-width:760px){
  .faqhome .tile-q,.faqhome .tile-q:nth-child(n){grid-template-columns:minmax(0,1fr)}
  .faqhome .qt{grid-row:2}
  .faqhome .ans{grid-column:1;grid-row:3}
}
/* the answer spans both rows, which stretched them and dropped the
   question to the bottom of its cell. The rows size to their content and
   the question sits under its number. */
.faqhome .tile-q,.faqhome .tile-q:nth-child(n){
  grid-template-rows:max-content max-content;align-content:start}
.faqhome .qn,.faqhome .qt{align-self:start}
/* The answer spans both rows, so its extra height was shared between
   them and pushed the question down whenever the answer was long: Q.02
   sat 41px lower than Q.01. Row one is the number's own line exactly, so
   all the slack lands in row two, where align-self:start pins the
   question to the top of it. */
.faqhome .tile-q,.faqhome .tile-q:nth-child(n){grid-template-rows:1.4em auto}

/* ---------- T8: the first three receipts, no click (28 Oct) ----------
   The drawer ships open, which reverses the 13 Oct decision to ship it
   closed. His call, and the right one: the figures are the only
   independently real evidence on the page and a scanning reader does not
   open drawers.

   Full width, and the first three figures lead. The rest stay in the
   drawer below them, so nothing is lost and nothing needs a second click
   to find. The summary stops looking like a button, because it no longer
   is the way in: it is a heading with a control on it. */
.mv[open] .mv-pill{background:none;border:0;padding:0;cursor:default}
.mv[open] .mv-pill-n{display:none}
.mv[open] .mv-chev{display:none}
.mv[open] .mv-pill-k{font-family:var(--fd);font-weight:900;font-size:var(--t-h3);
  letter-spacing:var(--fd-track);color:var(--deep)}
.mv[open] .mv-grid{margin-top:var(--s-5)}
/* the figures run full width and the first three read first */
.mv[open] .mv-figs{grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--s-5) var(--s-6)}
@media (max-width:900px){.mv[open] .mv-figs{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:560px){.mv[open] .mv-figs{grid-template-columns:minmax(0,1fr)}}
/* "full width" means the figures get the page, not 61% of it. Open, the
   two columns stack: the disciplines read as a line across the top and
   the figures run the full measure underneath, which is what stops a
   label like "click-through, new product and pricing launches" wrapping
   over six lines in a 190px column. */
.mv[open] .mv-grid{grid-template-columns:minmax(0,1fr);gap:var(--s-6)}
.mv[open] .mv-do{display:flex;flex-wrap:wrap;gap:4px var(--s-4)}
.mv[open] .mv-figs li{grid-template-columns:max-content minmax(0,1fr)}

/* ============================================================
   WHAT IT MOVED - THE SEEDED SCATTER            (28 Oct 2026)
   Five group headings and a tidy three-column list became
   fourteen tiles, each carrying its own group as a tag. Size,
   tilt and offset are dealt once by tools/build-stats.js from
   the seed in content/stats.json and written into the class
   list, so the layout is fixed in the markup and cannot
   reshuffle between loads, between pages or between readers.

   NO OVERLAP IS STRUCTURAL, NOT HAND-PLACED. These are grid
   items with a 24px gap, so nothing can land on anything else
   whatever the column count. The tilt is the only thing that
   grows a tile past its box: at 1.5deg a 200x150 tile gains
   2.6px on each side, so the worst-case clear gap is 18.8px,
   above the 16px floor. Absolute placement was not attempted:
   this file records four passes where hand-placed hero marks
   collided at a width nobody checked.

   This is a SURFACE that follows the UI, not a printed plane,
   so border and ink are tokens and both flip with the theme.
   ============================================================ */
.mv-scatter{list-style:none;margin:0;padding:0;
  display:grid;grid-template-columns:repeat(auto-fill,minmax(216px,1fr));
  gap:26px;align-items:start}

.mv-tile{position:relative;display:flex;flex-direction:column;gap:.3rem;
  padding:var(--s-4) var(--s-4) var(--s-5);
  border:var(--line) solid var(--deep);background:none;
  transform:rotate(var(--tl,0deg));transform-origin:50% 50%;
  margin-top:var(--of,0)}

/* the group heading, now one tag per tile */
.mv-tag{margin:0 0 .15rem;font-family:var(--fu);font-size:var(--xs);
  letter-spacing:.1em;text-transform:uppercase;
  /* 84%, not a lower mix and never opacity. Sampled from the PAINTED
     pixel: 78% composites to rgb(73,100,182) on the ground and measures
     5.05, which passes and is thin, and this exact pattern has failed
     twice in this file. 84% measures 5.8. */
  color:color-mix(in srgb,var(--deep) 84%,transparent)}

.mv-tile b{font-family:var(--fd);font-weight:900;line-height:.95;
  letter-spacing:var(--fd-track);color:var(--deep);
  font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1}
.mv-tile .mv-what{margin-top:.1rem}

/* three size classes */
.mv-sz-1 b{font-size:clamp(1.25rem,2.1vw,1.6rem)}
.mv-sz-2 b{font-size:clamp(1.55rem,2.8vw,2.1rem)}
.mv-sz-3 b{font-size:clamp(1.95rem,3.7vw,2.8rem)}
.mv-sz-3{padding-bottom:var(--s-6)}

/* three tilts and two vertical offsets, dealt from the seed */
.mv-tl-1{--tl:-1.5deg}
.mv-tl-2{--tl:0deg}
.mv-tl-3{--tl:1.5deg}
.mv-of-1{--of:0px}
.mv-of-2{--of:18px}

/* ---- how it was measured ----------------------------------
   The text is ALWAYS in the DOM and never display:none or
   visibility:hidden, so a screen reader reads it whether or
   not the reveal is open: the collapse is visual only, done
   with a 0fr grid row. The control exists only on a tile that
   has a method (.has-how), so an empty figure shows no button,
   no collapsed box and no affordance at all - an empty reveal
   reads as a broken reveal, which is worse than none.
   ------------------------------------------------------------ */
.mv-how-btn{appearance:none;background:none;border:0;padding:.35rem 0 0;
  margin:.25rem 0 0;min-height:24px;text-align:left;cursor:pointer;
  font-family:var(--fu);font-size:var(--xs);letter-spacing:.08em;
  text-transform:uppercase;color:var(--accent-type);
  text-decoration:underline;text-underline-offset:3px}
.mv-how-btn:focus-visible{outline:3px solid var(--deep);outline-offset:3px}

.mv-how{display:grid;grid-template-rows:0fr;margin:0;
  font-family:var(--fb);font-size:var(--t-small);line-height:1.35;
  color:var(--deep);opacity:0;
  transition:grid-template-rows .22s var(--e-out),opacity .22s var(--e-out)}
/* the row collapses to 0fr, so the text needs a real element to be
   clipped by: bare text in the <p> is an anonymous grid item and
   cannot take overflow:hidden. The generator emits the span. */
.mv-how > span{display:block;min-height:0;overflow:hidden}
.mv-tile.has-how:is(:hover,:focus-within) .mv-how,
.mv-tile.is-open .mv-how{grid-template-rows:1fr;opacity:1;margin-top:.4rem}

@media (prefers-reduced-motion:reduce){.mv-how{transition:none}}

/* phone: one column, and the tilt halves so a full-width tile
   does not read as crooked rather than placed.

   THE GAP HAS TO SCALE HERE AND A CONSTANT WILL NOT DO. A tilt
   lifts a tile's corner by half its WIDTH times sin(angle), so
   the same 1deg that costs a 190px tile 1.7px costs a 720px one
   6.3px - and two neighbours tilted opposite ways pay it twice.
   Measured at 768 with a flat 20px gap, the clear space between
   two tiles fell to 7.69px, under his 16px floor. 5vw tracks the
   tile width, which is what the geometry actually depends on. */
@media (max-width:768px){
  .mv-scatter{grid-template-columns:minmax(0,1fr);gap:clamp(30px,5.5vw,44px)}
  /* 26px cleared his 16px floor by 0.05px at 430 once the tilt went
     back to 1.5 - passing on a rounding error is not passing. 30/5.5vw
     clears by 16 to 20px at every width measured. */
  .mv-tl-1{--tl:-1deg}
  .mv-tl-3{--tl:1deg}
  .mv-of-2{--of:0px}
}

/* ============================================================
   H1: THE SUBTRACTION                        (28 Oct, his list)
   HIDDEN BEHIND A FLAG, NOT DELETED. Every rule below is one
   line, named for what it switches off, so any of them comes
   back by deleting its own line and nothing else. The markup,
   the images and the behaviour are all still in the repo.

   Already off before this pass, by T6 on 27 Oct, and left
   alone here so there is one switch per thing and not two:
     the wave edge   .work::before/::after, .ideas::after
     the cloud band  .contact::after
   Both are in the T6 block above.
   ============================================================ */

/* the iPod and the playlist frame. The LINK to the playlist
   survives this: it is in the About line, which H2 keeps. */
#listening{display:none}

/* The hero marks are BACK, 29 Oct, at his instruction. P0-6 hid them as
   stickers that duplicate the nav; this reverses that and settles the
   question P1-4 left open.

   The Listening mark's target moved with them: the iPod is still hidden,
   so a link to #listening would be a visible link into a display:none
   block. It points at the playlist now, which is where listening lives
   and is the same URL the floating record and the About line use. */

/* the three marks fanned into the sign-off */
.so-fan{display:none}

/* H1, last item: the About text is one column, 65ch.
   .abody was a 1.618/1 grid and .prose p capped at 32em, which
   is roughly 32 characters per em-width - near 65ch already on
   the body face, but stated in the wrong unit to hold. ch is
   the unit the brief names and the one that tracks the face. */
.abody{grid-template-columns:minmax(0,1fr)}
/* THE MEASURE GOES ON THE CONTAINER, NOT THE PARAGRAPH. ch scales
   with font-size, and the opening paragraph is set at --md, so a
   per-paragraph 65ch resolved to 1062px there and capped nothing
   at all while the body paragraphs sat at 585px. One measure means
   one element at one size: .prose is at body size, so 65ch there
   is 65 characters of the face the reader is actually reading. */
.abody .prose,.prose{max-width:65ch}
.abody .prose p,.prose p{max-width:none}

/* ============================================================
   H2: THE MUSIC IS A LINK                    (28 Oct, his note)
   The record player is hidden behind the same one-line flag as
   H1. The deck, the arm, the sleeve, the mp3 and home.js's
   wiring are all still here; delete the line below and the
   player is back.
   ============================================================ */
/* the deck is back, 30 Oct: the vinyl plays ABBA and goes nowhere. The ABBA
   sleeve is its label (assets/img/abba-cover.webp). */

/* 56px, bottom right, the same corner the deck held. It is a
   link and nothing else: no hover growth, no spin, no state to
   disagree with. The site's own line work draws the record -
   a Spotify glyph is a brand asset and the ABBA sleeve would
   promise one track and open a playlist - so the destination
   is carried by the accessible name, which says Spotify and
   says the tab is new. */
.playlink{position:fixed;z-index:60;
  right:clamp(.8rem,2vw,1.6rem);bottom:clamp(.8rem,2vw,1.6rem);
  width:56px;height:56px;display:block;border-radius:999px;
  -webkit-tap-highlight-color:transparent}
.playlink svg{display:block;width:56px;height:56px}
.playlink .pl-disc{fill:#101830}
.playlink .pl-groove{fill:none;stroke:#F7F4EC;stroke-width:1.6;opacity:.38}
.playlink .pl-label{fill:#E8251F}
.playlink .pl-spindle{fill:#F7F4EC}
.playlink:is(:hover,:focus-visible) .pl-label{fill:var(--yellow)}
.playlink:focus-visible{outline:3px solid var(--deep);outline-offset:3px}
@media (prefers-reduced-motion:no-preference){
  .playlink .pl-label{transition:fill 160ms var(--e-out)}
}

/* the playlist line, lifted out of the hidden #listening block.
   Label and link on one row, the label in the metadata register so
   the link is the only thing with weight. 44px tall so it clears
   the target minimum on a phone (WCAG 2.5.8). */
.plline{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--s-3) var(--s-4);
  margin:var(--s-6) 0 0}
.plline-k{font-size:var(--xs);letter-spacing:.08em;text-transform:uppercase;
  color:color-mix(in srgb,var(--deep) 84%,transparent)}
.plline a{display:inline-flex;align-items:center;min-height:44px;
  font-family:var(--fd);font-weight:900;font-size:var(--t-h3);
  letter-spacing:var(--fd-track);color:var(--deep);text-decoration:none;
  border-bottom:var(--line) solid transparent}
.plline a:is(:hover,:focus-visible){border-bottom-color:var(--accent-type)}

/* the deck inherits the single column from H1, so it inherits the
   measure too. Left at full width a quote card ran 1024px with its
   text ending at 760, which reads as a stretched box rather than a
   card. Same 65ch, so the column has one right edge. */
.abody .vouch{max-width:65ch}

/* ============================================================
   H3: ONE DIVIDER, DRAWN ONCE          (28 Oct, to his numbers)
   assets/img/stitch.svg, generated by tools/build-stitch.js with
   a fixed seed. His spec, verbatim: stitch 9px +-1.5, gap 6px
   +-1, angle +-3deg, y-offset +-0.8px, baseline wobbling 1.5px
   over a ~200px wavelength, stroke 2.2px round-capped in
   #B8141A at 0.9 to 1 opacity, a 1px soft shadow beneath, a 1px
   fold line above, back-stitch at both ends and a knot on each.

   THE GRADIENTS COULD NOT DO THIS AND THAT IS WHY THEY ARE GONE.
   A repeating-linear-gradient cannot rotate one stitch, wobble a
   baseline, double back or tie a knot. It can only repeat, and a
   thing that repeats exactly is the dashed form-field border the
   stitch exists in order not to be.

   2560px wide and NOT a small tile, for the reason the cloud
   band already taught this repo: a tile repeats, and on a line
   this thin the eye finds the period at once.
   ============================================================ */
.stitch{position:relative;height:14px;border:0;margin:0;padding:0;
  background-image:url("img/stitch.svg");
  background-repeat:repeat-x;background-position:left center;
  background-size:2560px 14px;overflow:hidden}
.stitch::before,.stitch::after{content:none}

/* on the yellow plane the thread would vanish, so it inverts.
   This is the one place the stitch is not tomato. */
.pillbox .stitch{filter:none}

/* ---- the seam, where the ground changes ----
   Between two blocks that SHARE a ground an <hr class="stitch">
   is the divider. Where cream meets cobalt the stitch sits ON
   the boundary, half its height either side, because that is
   what sewing two pieces together looks like: one line holding
   two different cloths, not a line drawn on one of them.

   These are .work's own pseudo-elements, which T6 switched off
   when it removed the torn edge. They are switched back on here
   carrying the stitch instead - one device per seam, rather than
   a third element stacked on top of a disabled second. */
.work{position:relative}
.work::before,.work::after{content:"";display:block;position:absolute;
  left:0;right:0;height:14px;z-index:3;pointer-events:none;
  background-image:url("img/stitch.svg");
  background-repeat:repeat-x;background-position:left center;
  background-size:2560px 14px}
.work::before{top:-7px}
.work::after{bottom:-7px}
/* the block's own hairline is now redundant: the seam carries it */
.work{border-top:0;border-bottom:0}

/* it sews itself in once as the section arrives, then stays.
   Static is the default the brief asks for; this is the optional
   draw-on, it runs once, and reduced motion never starts it. */
.stitch{clip-path:inset(0 100% 0 0)}
.stitch.in{clip-path:inset(0 0 0 0);transition:clip-path 600ms var(--e-out)}
@media (prefers-reduced-motion:reduce){
  .stitch{clip-path:none}
  .stitch.in{transition:none}
}

/* ============================================================
   H4: THE RESULTS ARE A LEDGER                (28 Oct, his note)
   number | label | org | period, a 1.6px rule under each row,
   no boxes and no pills. Six rows, then a 44px toggle to all 14.

   THIS REVERSES TWO EARLIER INSTRUCTIONS OF HIS and that is
   deliberate: the shield-yellow pill (13 Oct) and the seeded
   scatter (28 Oct, this morning). Both are in git. The ledger
   is the later word and a ledger is what a reader scans.

   ONE CLASH, FLAGGED NOT SPLIT. H4 asks for the number at 40px;
   H6 locks the scale to 12/16/20/32/56 and his own TESTS say
   "<=5 font sizes". 40 would be a sixth and fail his test, so
   the number is 32. One value to change if he wants the 40.
   ============================================================ */
.mv-h{font-family:var(--fd);font-weight:900;font-size:var(--t-h3);
  line-height:1.1;letter-spacing:var(--fd-track);color:var(--deep);
  margin:0 0 var(--s-6)}

.ledger{list-style:none;margin:0;padding:0}
.lg-row{display:grid;align-items:baseline;
  /* the LABEL takes the slack, not the number. The first cut gave the
     number 127px and the label 91, which is the most important text in
     the row at the narrowest column in it. */
  grid-template-columns:max-content minmax(0,1fr) minmax(0,16ch) minmax(7ch,max-content);
  gap:var(--s-3) var(--s-5);padding:var(--s-4) 0;
  border-bottom:var(--line) solid rgba(var(--deep-rgb),.28)}

/* 32 (the cap of --t-h3), not the 40 H4 asks for: 40 is not in the
   H6 scale and would be a sixth font size, failing his own
   "<=5 font sizes" test. One value to change if he wants the 40. */
.lg-n{font-family:var(--fd);font-weight:900;font-size:var(--t-h3);
  line-height:1;letter-spacing:var(--fd-track);color:var(--deep);
  font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1}
.lg-l{font-family:var(--fb);font-size:var(--t-body);line-height:1.35;color:var(--deep)}
.lg-o{font-family:var(--fu);font-size:var(--xs);letter-spacing:.06em;
  text-transform:uppercase;color:var(--accent-type)}
/* THE PERIOD CELL IS EMPTY WHEN THERE IS NO PERIOD. Never TBC, never
   n/a: a placeholder in a date column reads as an unfinished ledger.
   The cell keeps its width so the four columns stay aligned. */
.lg-p{font-family:var(--fu);font-size:var(--xs);letter-spacing:.06em;
  color:color-mix(in srgb,var(--deep) 84%,transparent);
  font-variant-numeric:tabular-nums}
.lg-how{grid-column:2 / -1;font-family:var(--fb);font-size:var(--t-small);
  line-height:1.35;color:color-mix(in srgb,var(--deep) 84%,transparent)}

/* rows 7 to 14 are only hidden where a script can bring them back.
   With no script every row is visible and the toggle is not shown,
   because a control that does nothing is worse than no control. */
.lg-toggle{display:none}
html.js .lg-toggle{display:inline-flex;align-items:center;
  min-height:44px;padding:0 var(--s-5);margin-top:var(--s-5);
  background:none;border:var(--line) solid var(--deep);border-radius:999px;
  font-family:var(--fu);font-size:var(--xs);letter-spacing:.08em;
  text-transform:uppercase;color:var(--deep);cursor:pointer}
html.js .lg-toggle:is(:hover,:focus-visible){background:var(--deep);color:var(--cream)}
html.js .lg-toggle:focus-visible{outline:3px solid var(--deep);outline-offset:3px}
html.js .ledger .lg-more{display:none}
html.js .ledger.is-all .lg-more{display:grid}

@media (max-width:768px){
  .lg-row{grid-template-columns:minmax(0,1fr);gap:var(--s-2) 0;padding:var(--s-4) 0}
  .lg-how{grid-column:1}
  /* an empty period on one column would be an empty ROW, which reads as
     a gap in the list rather than a missing value, so it collapses */
  .lg-p:empty{display:none}
}

/* THE LEDGER TAKES THE FULL CONTAINER, so the two blocks stack.
   In the 1fr/1.618fr split the ledger had 595px for four columns and
   the label - the most important text in the row - wrapped to four
   lines while the empty period column held 7ch of nothing beside it.
   A four-column ledger needs the width. Stacking also keeps both
   blocks starting at the container left, which is what H6 asks for. */
.mv-grid{grid-template-columns:minmax(0,1fr);gap:var(--s-7)}
.mv-col--do .mv-do{max-width:none}

/* ============================================================
   H5: THE WORK ROWS LOSE THEIR CHROME        (28 Oct, his note)
   Max two tags, plain text joined by " / ", no outlines. The
   reading time goes. The plate stamp and "Open" stay.

   Five outlined chips under a two-line teaser were competing
   with the title for the row, and four of them repeated words
   already in the teaser. Two in plain text read as a byline.

   HIDDEN, NOT DELETED: tags three and up are still in the
   markup and still carry their words, so the cut is one
   declaration to undo. /work filters on data-d, not on these,
   so nothing stops working. The rows are one component shared
   by home and /work, so this lands on both, which is the point.
   ============================================================ */
.wrow .tags{display:flex;flex-wrap:wrap;align-items:baseline;gap:0}
.wrow .tags > span:nth-child(n+3){display:none}

/* no plane, no outline, no padding: plain text in the metadata
   register. .aud loses its blue plane here too - it is the first
   of the two tags, not a badge. */
.wrow .tags span,
.work .windex--home .tags span,
.wrow .tags .aud,
.work .windex--home .tags .aud{
  background:none;border:0;padding:0;color:inherit;font-weight:inherit}

/* the separator is generated, so it never appears before the
   first tag and never dangles after a hidden one */
/* THE SPACING IS A MARGIN, NOT SPACES IN THE STRING. content:" / "
   drops its leading space against the inline box before it, which
   rendered "B2C/ BRAND STRATEGY". The \00a0 fix for that was worse:
   the shell turned the backslash-zero into a literal NUL byte and
   wrote two of them into this file, which grep then reported as a
   binary. A margin needs no escape, cannot collapse, and cannot be
   mangled on its way through a shell.

   Both faults were found by LOOKING at the page and at the file.
   textContent never shows a pseudo-element, so no DOM probe would
   have caught either one. */
.wrow .tags > span + span::before{content:"/";margin:0 .45em;opacity:.55}

/* reading time: gone. It was an estimate nobody asked for, and on
   a five-piece slate it invited the reader to budget rather than
   to open. "Open" keeps the row's one call. */
.wrow .aside .t{display:none}


/* H6: row titles are h3 on home (under the section h2) and h2 on
   /work (under the page h1). Both are correct for their page, and
   every rule that styled .wrow h2 has to style .wrow h3 too or the
   two pages drift - which is the failure this repo keeps returning
   to. One selector, both tags. */
html body .wrow h3{font:inherit;margin:0}
html body .wrow :is(h2,h3){font-family:var(--fd);font-weight:900;
  font-size:var(--t-h2);line-height:1.08;letter-spacing:var(--fd-track);
  color:inherit;margin:0 0 var(--s-3)}

/* ---- H6, the last strays off the scale -------------------------
   The audit measures every text leaf on the page. After the clamps
   were stepped, four sizes were still outside 12/16/20/32/56:

     13px  .print figcaption, a hand-set value
     27px  .vc-big, a clamp between 20 and 32
     29px  .idcard .claim, a clamp between 20 and 32
     198px .bigmark

   The first three are on the scale now. .bigmark is NOT type and is
   left alone with its reason: it is the giant wordmark at the foot
   of the page, aria-hidden, a drawn shape that happens to be made of
   letters. Putting it on a type scale would be like putting the star
   on one. If his test counts it, it is the one exception and it is
   named here rather than quietly excluded. */
html body .print figcaption{font-size:var(--xs)}
html body .vc-big{font-size:var(--t-h3)}
html body .idcard .claim{font-size:var(--t-h3)}

/* ---- H6: two display steps, not one --------------------------
   Stepping the clamps put section headings and work-row titles both
   on --t-h2, so the section heading stopped dominating the rows
   under it, and the hero claim landed on --t-h3 next to its own
   sub-line at the same size. That is the "flat, nothing stands out"
   failure the type scale exists to prevent, and it is a type-scale
   problem, not a spacing one.

   Two steps, both in his five: a section heading is 56, everything
   below it in that block is 32. */
html body :is(.shead h2,h2.clip,.wlist-head h2){font-size:var(--t-h1)}
html body .mv-h{font-size:var(--t-h2)}
html body .idcard .claim{font-size:var(--t-h2)}

/* ============================================================
   H7: THE FAQ IS A LIST ON /about/ TOO       (28 Oct, his note)
   T7 made the three on home a two-column list on 27 Oct. The nine
   on /about/ were still a six-column bento of bordered cards,
   collapsed. Same component, two treatments, which is how two
   pages drift apart.

   The answers OPEN. /about/#faq is where the FAQ actually lives,
   so nine questions behind nine clicks is nine reasons to leave;
   the home three have read open since T7 and these now match. The
   buttons keep their disclosure wiring - the markup is unchanged
   and site.js still drives them - so this is a style change only
   and it reverts in one block.
   ============================================================ */
.faq .bento{display:grid;grid-template-columns:minmax(0,1fr);gap:0;
  border-top:var(--line) solid rgba(var(--deep-rgb),.28)}
.faq .bento .tile-q,.faq .bento .tile-q:nth-child(n){
  grid-column:span 1;display:grid;align-items:start;
  grid-template-columns:minmax(0,1fr) minmax(0,1.3fr);
  gap:var(--s-3) var(--s-6);
  background:none;border:0;
  border-bottom:var(--line) solid rgba(var(--deep-rgb),.28);
  padding:var(--s-5) 0;text-align:left;cursor:default}
.faq .bento .tile-q[aria-expanded="true"]{background:none;color:inherit}
.faq .bento .qn{grid-column:1;font-family:var(--fu);font-size:var(--xs);
  letter-spacing:.08em;color:var(--accent-type)}
.faq .bento .qt{grid-column:1;grid-row:2;font-family:var(--fd);font-weight:900;
  font-size:var(--t-h3);line-height:1.12;letter-spacing:var(--fd-track);
  color:var(--deep);display:block}
/* the + marker was the card's affordance; with the answer open it
   points at nothing */
.faq .bento .qt .pm{display:none}
.faq .bento .ans{grid-column:2;grid-row:1 / span 2;
  max-height:none;overflow:visible;opacity:1;
  font-family:var(--fb);font-size:var(--t-small);line-height:1.5;
  color:var(--deep)}
@media (max-width:768px){
  .faq .bento .tile-q,.faq .bento .tile-q:nth-child(n){
    grid-template-columns:minmax(0,1fr)}
  .faq .bento .ans{grid-column:1;grid-row:3;margin-top:var(--s-3)}
}

/* THE QUESTION SAT LOW IN ITS CELL, in both FAQs. The answer spans
   both rows and is the taller column, so the tile grows to it and
   the two auto rows in the left column stretch to share the extra
   height - which pushed the question away from its own Q number by
   up to 100px. align-items:start aligns an item inside its track;
   it is align-CONTENT that stops the tracks themselves stretching.
   Only visible in a screenshot: every box measured correctly. */
.faqhome .tile-q,.faq .bento .tile-q{align-content:start}

/* ============================================================
   H9: /contact/ IS A HEADLINE AND TWO ACTIONS  (28 Oct, his note)
   headline > one line of guidance > Email me + Copy address >
   looking-for rows > LinkedIn > Resume (PDF).

   Everything else that was on this page is gone because its value
   was empty, which is his own rule: thirteen slots reading "To be
   confirmed" and "Being written" across a reply time, a what
   happens next, four looking-for rows, a receipts block and four
   FAQ answers. Four of those sat in one list, which is exactly the
   repetition HARD RULE 3 exists to stop.
   ============================================================ */
.cguide{font-family:var(--fb);font-size:var(--t-body);line-height:1.5;
  color:var(--deep);max-width:50ch;margin:var(--s-6) 0 var(--s-7)}

.cacts{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s-4);
  margin:0 0 var(--s-4)}
.cbtn{display:inline-flex;align-items:center;justify-content:center;
  min-height:44px;padding:0 var(--s-5);
  font-family:var(--fu);font-size:var(--t-small);letter-spacing:.06em;
  text-decoration:none;cursor:pointer;
  background:none;color:var(--deep);
  border:var(--line) solid var(--deep);border-radius:999px}
.cbtn--go{background:var(--deep);color:var(--cream)}
.cbtn:is(:hover,:focus-visible){background:var(--deep);color:var(--cream)}
.cbtn--go:is(:hover,:focus-visible){background:none;color:var(--deep)}
.cbtn:focus-visible{outline:3px solid var(--deep);outline-offset:3px}

/* INLINE, NOT A DIALOG. A modal to confirm a clipboard write is a
   modal to dismiss. The region is in the DOM from the start with
   role=status, because a live region created at the moment of the
   message is announced by nobody. */
.ccopied{font-family:var(--fu);font-size:var(--xs);letter-spacing:.08em;
  text-transform:uppercase;color:var(--accent-type);min-height:1em}

.cplain{font-family:var(--fb);font-size:var(--t-small);line-height:1.5;
  color:color-mix(in srgb,var(--deep) 84%,transparent);margin:0 0 var(--s-8)}
.cplain a{color:var(--deep)}

.ch2{font-family:var(--fd);font-weight:900;font-size:var(--t-h2);
  line-height:1.1;letter-spacing:var(--fd-track);color:var(--deep);
  margin:0 0 var(--s-5)}
.cblock{margin:0 0 var(--s-8)}

.croutes{list-style:none;margin:0;padding:0;
  border-top:var(--line) solid rgba(var(--deep-rgb),.28)}
.croutes a{display:grid;grid-template-columns:minmax(0,14ch) minmax(0,1fr);
  gap:var(--s-4);align-items:baseline;min-height:44px;padding:var(--s-4) 0;
  border-bottom:var(--line) solid rgba(var(--deep-rgb),.28);
  text-decoration:none;color:var(--deep)}
.croutes .k{font-family:var(--fu);font-size:var(--xs);letter-spacing:.08em;
  text-transform:uppercase;color:var(--accent-type)}
.croutes .v{font-family:var(--fb);font-size:var(--t-small)}
.croutes a:is(:hover,:focus-visible) .v{text-decoration:underline;
  text-underline-offset:3px}
@media (max-width:640px){
  .croutes a{grid-template-columns:minmax(0,1fr);gap:var(--s-2)}
}

/* ---- H6, the last two failures, both on the sign-off ----------
   h2.so-cta measured 46px (not in the scale) and started at x=205
   while every other heading started at 128. The 205 is the pill's
   own left padding: it is a plane, and text inside a plane keeps
   the plane's margin.

   His test says all h2 share one left x, so the PILL moves rather
   than the text inside it. It bleeds left by exactly its own left
   padding, which puts the heading on the container edge and leaves
   the pill's right side where it was. At 1280 the page gutter is
   80px a side, so the bleed sits in the gutter rather than off the
   screen; below 1100 the pill squares off and the bleed goes with
   it, because there the gutter is the content's only breathing
   room. */
html body .so-cta{font-size:var(--t-h1);font-weight:900}
@media (min-width:1101px){
  .contact .sign-off{margin-left:calc(-1 * clamp(2.4rem,6vw,6rem))}
}

/* ============================================================
   P0-1: THE SECTION BANDS                    (29 Oct, his spec)
   hero cream | Results yellow | work cobalt | about cream |
   FAQ cobalt | contact cream. Never two the same touching.

   Measured before changing anything: only TWO pairs clashed,
   hero/Results and about/FAQ, both reading cream on cream. The
   other four were already his order, so they are not touched.

   Existing tokens only. Ink on yellow is 14.73 and cream on
   cobalt is 8.50, so the two new bands need no new colour - but
   every accent ON them does, because tomato on cobalt is 1.5
   and that is what makes a band a trap rather than a change of
   ground. Each band below restates its own accent.
   ============================================================ */

/* ---- Results: shield yellow, ink type ---- */
.moved{background:var(--yellow);color:#0D1633}
.moved :is(h2,.mv-h,.mv-k,.lg-n,.lg-l,.mv-what,.lg-p){color:#0D1633}
/* tomato on yellow is 3.2 and this is small caps type, so the
   employer line takes ink at full weight instead of the accent */
.moved :is(.lg-o,.mv-who,.mv-tag){color:#0D1633}
.moved .lg-row{border-bottom-color:rgba(13,22,51,.34)}
.moved .ledger{border-top-color:rgba(13,22,51,.34)}
html.js .moved .lg-toggle{border-color:#0D1633;color:#0D1633}
html.js .moved .lg-toggle:is(:hover,:focus-visible){background:#0D1633;color:var(--yellow)}
html.js .moved .lg-toggle:focus-visible{outline-color:#0D1633}
/* the stitch is tomato and tomato on yellow is 3.2: as a LINE
   that is fine (it is not type), but the seam either side of a
   yellow band carries the ink shadow the SVG already draws */

/* ---- FAQ: cobalt, cream type ---- */
.faqhome{background:var(--deep);color:var(--cream)}
.faqhome :is(h2,.qt,.ans,.sub,.shead h2,.shead .sub){color:var(--cream)}
/* the Q number was --accent-type, which is 1.5 on cobalt. Yellow
   on cobalt is 8.70 and is the one accent that survives here,
   which is the same choice the sign-off pill already made. */
.faqhome .qn{color:var(--yellow)}
.faqhome .bento{border-top-color:rgba(var(--cream-rgb),.34)}
.faqhome .tile-q{border-bottom-color:rgba(var(--cream-rgb),.34)}
.faqhome .no,.faqhome .smark{color:var(--cream)}
.faqhome a{color:var(--cream)}
.faqhome a:is(:hover,:focus-visible){color:var(--yellow)}

/* ---- about: cream, stated rather than inherited ----
   It was transparent, so it took the body ground. That reads the
   same today and stops reading the same the moment anything
   above or below it changes, which is exactly the bug this task
   is fixing. A band declares its own ground. */
.about{background:var(--cream);color:var(--deep)}

/* .wk-note is declared later than the band block and carries its own
   colour, so "All nine answers" sat at 1.00: deep ink on the cobalt
   band. This is the whole reason a band needs its accents restated
   rather than assumed - a ground change is a trap for every rule
   written before it. Found by measuring every text leaf on each band,
   not by reading the file. */
html body .faqhome .wk-note{color:var(--cream)}
html body .faqhome .wk-note:is(:hover,:focus-visible){color:var(--yellow)}
html body .faqhome .wk-note .wk-arrow{background:currentColor}

/* THE WHOLE YELLOW BAND IS INK, not just the headings. The first
   cut left the discipline list on cape blue, which PASSES contrast
   at 7.86 and is still wrong: his spec says "on yellow/cream = ink",
   and blue on yellow reads as two accents arguing rather than as one
   ground. A band has one ink. Caught by looking, not by the numbers,
   which is the distinction that matters here. */
html body .moved,
html body .moved :is(p,li,span,h2,h3,b,i,em,strong,a,button,dt,dd){color:#0D1633}
html body .moved .mv-do li::after{color:#0D1633;opacity:.45}

/* ============================================================
   P0-2: THE STITCH IS ON EVERY COLOUR SEAM   (29 Oct, his spec)
   The SVG, the seeded RNG, the back-stitch and the knots all
   landed on 27 Oct (H3). What P0-1 changed is the number of
   seams: two new grounds meant two new colour joins, and the
   audit found both of them bare.

     hero  -> Results   cream to yellow   was bare
     about -> FAQ       cream to cobalt   was bare

   One declaration per band that starts a ground, rather than a
   new element at each join: the seam belongs to the block below
   it, the same way .work already owns its own two.
   ============================================================ */
.moved,.faqhome{position:relative}
.moved::before,.faqhome::before{content:"";display:block;position:absolute;
  left:0;right:0;top:-7px;height:14px;z-index:3;pointer-events:none;
  background-image:url("img/stitch.svg");
  background-repeat:repeat-x;background-position:left center;
  background-size:2560px 14px}

/* the labelled blue rule goes, hidden not deleted. It was a
   full-bleed 3px rule with "Enough about the work" sitting on it:
   a second divider language on a page that now has one. The copy
   is still in the markup and one line brings it back. */
.me-turn{display:none}

/* ============================================================
   P0-3: ONE GRID                             (29 Oct, his spec)
   Container 1120 and side padding 24/48 were already his numbers
   from the T pass, and every h2, case title and result group was
   put on one left edge by H6. Both verified, neither touched.

   What was NOT uniform is the section padding. Measured across
   the six bands: 81/81, 80/64, 96/88, 24/96, 0/0 and 80/96 -
   six blocks, six rhythms, which is what makes a page feel
   assembled rather than set. One value now, 64 on a phone and 96
   on a desktop, applied with enough weight to beat the
   per-section rules rather than by hunting them down one at a
   time and missing one.

   THE HERO IS EXEMPT, and only the hero: his spec says it may
   break the grid. It sizes itself to the viewport, so a fixed
   block padding would fight that.
   ============================================================ */
html body :is(.moved,.work,.about,.faqhome,.contact){
  padding-block:var(--sec)}
/* --sec is clamp(64px,10vw,96px): his two numbers, and the 10vw
   between them means the step is not a jump at one breakpoint. */

/* ============================================================
   P0-4: RESULTS ARE THE SEEDED SCATTER AGAIN (29 Oct, his spec)
   This REVERSES H4's ledger, which was yesterday's instruction.
   Both are in git. Flat, square-cornered, 1.6px outline, three
   size classes, tilts -1.5/0/+1.5, two vertical offsets, no
   overlaps, same content and same order.

   Kept from the ledger pass, because none of it was his to
   reverse: the hardcoded freeze on all fourteen figures, the
   8-word label check, and the column-aware order with +310%
   pinned first.

   THE TILES ARE ON YELLOW NOW, so the outline and the ink are
   the band's ink and not cape blue. A tile is a surface that
   follows its band, not a printed plane, so these are literals
   only because the band itself is - see the plane trap note.
   ============================================================ */
html body .moved .mv-tile{border-color:#0D1633;background:none}
html body .moved .mv-tile b,
html body .moved .mv-tile .mv-what,
html body .moved .mv-tile .mv-who,
html body .moved .mv-tag{color:#0D1633}
html body .moved .mv-tag{color:color-mix(in srgb,#0D1633 84%,transparent)}
html body .moved .mv-how{color:#0D1633}
html body .moved .mv-how-btn{color:#0D1633;text-decoration-color:#0D1633}
html body .moved .mv-how-btn:focus-visible{outline-color:#0D1633}
/* a period only exists on a tile that has one; the generator emits
   no element at all when it is empty */
.mv-period{font-family:var(--fu);font-size:var(--xs);letter-spacing:.06em;
  color:color-mix(in srgb,#0D1633 84%,transparent)}

/* the ledger furniture has no markup any more. Hidden rather than
   swept, so the next sweep decides: .ledger, .lg-row and the toggle
   are H4's and come back with it. */
.lg-toggle{display:none}

/* ============================================================
   THE HIDE FLAG HAS TO WIN                   (29 Oct, P0-6)
   `hidden` is display:none in the UA stylesheet, which means ANY
   author display rule beats it. `.beat{display:block}` is
   declared later than the UA sheet, so the loophole sentence
   carried the attribute and stayed on the page - visible in the
   rendered text while the markup looked correct.

   This is the same class of bug as the source-order traps this
   repo keeps hitting, and it matters more here: a hide flag that
   silently does nothing is worse than no flag, because the next
   person reads the attribute and believes it.

   [hidden] is now unconditional. Anything that needs to be
   hidden-but-measurable uses .vh, which is a different tool.
   ============================================================ */
html body [hidden]{display:none !important}

/* ============================================================
   P0-8: ONE TAP-TO-COPY BUTTON AND A TOAST   (29 Oct, his spec)
   The button carries the address as its own visible label, so a
   reader whose clipboard is blocked can still read and select
   it, and everyone else gets it in one tap.
   ============================================================ */
.cbtn--mail{flex-direction:column;align-items:flex-start;gap:2px;
  min-height:64px;padding:var(--s-3) var(--s-5);text-align:left}
.cbtn-k{font-size:var(--xs);letter-spacing:.1em;text-transform:uppercase;opacity:.82}
.cbtn-v{font-family:var(--fb);font-size:var(--t-body);letter-spacing:0;
  text-transform:none;word-break:break-all}

/* THE TOAST. Fixed, bottom centre, transform and opacity only - the
   two properties the compositor handles and the only two his spec
   allows. role="status" and in the DOM from first paint, because a
   live region created at the moment of the message is announced by
   nobody. It is not a dialog, it traps nothing and it needs no
   dismissing. */
.ctoast{position:fixed;left:50%;bottom:24px;z-index:80;margin:0;
  padding:12px 20px;pointer-events:none;
  background:#0D1633;color:#F7F4EC;
  font-family:var(--fu);font-size:var(--t-small);letter-spacing:.06em;
  border-radius:999px;
  opacity:0;transform:translate(-50%,12px);
  transition:opacity 200ms var(--e-out),transform 200ms var(--e-out)}
.ctoast.is-up{opacity:1;transform:translate(-50%,0)}
/* hard-coded, not tokens: this is a plane floating over whatever is
   behind it, and --deep becomes the light cream in dark mode. The
   plane trap, for the eighth time in this file. */
@media (prefers-reduced-motion:reduce){
  .ctoast{transition:none;transform:translate(-50%,0)}
}

/* ============================================================
   P0-9: THE RECORD SPINS WHILE IT IS REACHED FOR (29 Oct)
   The link, the target and the rel landed on 28 Oct (H2). What
   this adds is the motion: it turns while hovered or focused and
   is still otherwise, which is the whole of his spec.

   Transform only, so the compositor handles it and nothing
   reflows. The spin is on the SVG rather than the <a>, because
   the anchor is the hit area and a rotating hit area moves the
   target out from under the pointer that is already on it.
   ============================================================ */
@keyframes pl-spin{to{transform:rotate(360deg)}}
.playlink svg{transform-origin:50% 50%}
@media (prefers-reduced-motion:no-preference){
  .playlink:is(:hover,:focus-visible) svg{animation:pl-spin 2.6s linear infinite}
}
/* reduced motion: the final state, which for a record is simply
   the record. No animation is declared at all under the query
   above, so there is nothing to stop - collapsing a duration
   does not stop a loop, which this repo has recorded twice. */

/* ============================================================
   P0-10: THE WORKED-WITH TICKER              (29 Oct, his spec)
   A CSS marquee: 40s, linear, infinite, transform only. There is
   no per-frame JavaScript driving it at all now - the old strip
   was on the JS ticker engine, which is why it carried four
   copies of the list. A -50% loop needs exactly two.

   THE LABELS RESTORE A RULE RATHER THAN ADD A DECORATION.
   CLAUDE.md's CONTENT section: UN Youth and BCG "were side
   projects during college, and are always labelled 'College
   project' wherever they appear. Never present them as
   employment." That came off on 6 Oct and has been an open risk
   in the handover since. Amazon is Internship, which the site
   has never said.
   ============================================================ */
.wwstrip{overflow:hidden}
.ww-head{display:flex;align-items:center;gap:var(--s-5);
  margin:0 0 var(--s-5)}
.ww-k{margin:0;font-size:var(--xs);letter-spacing:.1em;text-transform:uppercase;
  color:color-mix(in srgb,var(--deep) 84%,transparent)}

/* the control is in the markup and hidden until a script exists to
   make it mean something */
.ww-pause{display:none}
html.js .ww-pause{display:inline-flex;align-items:center;min-height:44px;
  padding:0 var(--s-4);background:none;
  border:var(--line) solid var(--deep);border-radius:999px;
  font-family:var(--fu);font-size:var(--xs);letter-spacing:.08em;
  text-transform:uppercase;color:var(--deep);cursor:pointer}
html.js .ww-pause:is(:hover,:focus-visible){background:var(--deep);color:var(--cream)}
html.js .ww-pause:focus-visible{outline:3px solid var(--deep);outline-offset:3px}

.ww-band{overflow:hidden}
.ww-track{display:flex;width:max-content;will-change:transform}
.ww-set{display:flex;align-items:flex-end;gap:clamp(2rem,4vw,3.5rem);
  list-style:none;margin:0;padding:0 clamp(1rem,2vw,1.75rem) 0 0}
.ww-item{display:grid;justify-items:center;gap:8px;flex:none}
/* 28px, his number, for every mark. One height, no optical nudges:
   he looked at those and wanted them all the same. */
.ww-mark{display:block;height:28px;width:auto;max-width:none}
.ww-label{font-family:var(--fu);font-size:var(--xs);letter-spacing:.1em;
  text-transform:uppercase;white-space:nowrap;
  color:color-mix(in srgb,var(--deep) 84%,transparent)}

@keyframes ww-roll{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@media (prefers-reduced-motion:no-preference){
  html.js .ww-track{animation:ww-roll 40s linear infinite}
  /* pointer and focus hold it; the button is the mechanism */
  .wwstrip:is(:hover,:focus-within) .ww-track{animation-play-state:paused}
  html.js .wwstrip.is-paused .ww-track{animation-play-state:paused}
}

/* REDUCED MOTION IS A STATIC WRAPPED GRID, not a stopped marquee.
   A paused track is still one long row clipped to the viewport, so
   most of the marks are simply unreachable. The second copy goes
   with it - it exists only to make the loop seamless. */
@media (prefers-reduced-motion:reduce){
  .ww-track{display:grid;width:auto;animation:none}
  .ww-set{flex-wrap:wrap;justify-content:flex-start;padding-right:0;
    gap:var(--s-5) clamp(2rem,4vw,3.5rem)}
  .ww-set[aria-hidden="true"]{display:none}
  .ww-pause,html.js .ww-pause{display:none}
}

/* ---- P0-10, two faults found by looking at it ----------------
   1. THE HEAD RAN OFF THE LEFT EDGE. The marquee is full bleed on
      purpose - a strip that stops at the container reads as a
      panel - but "Worked with" and the Pause button are content
      and belong on the one left edge P0-3 set. The head gets the
      container; the band keeps the bleed.

   2. THE MARKS WERE NOT MONOCHROME. The files are named -mono and
      are not: comparethemarket's red dot, tickertape's red,
      amazon's orange, TEDx's red and BCG's green all rendered.
      The 2 Oct one-ink pass is in the repo's history but these
      files are the untinted cut.

      brightness(0) collapses every non-transparent pixel to black
      while keeping the alpha, so each mark becomes its own
      silhouette rather than a greyscale photograph of itself.
      That is what one ink means here, and it is a filter rather
      than eight re-exported files, so a re-supplied logo needs no
      pipeline run. */
.ww-head{max-width:var(--max);margin-inline:auto;
  padding-inline:var(--pad);box-sizing:border-box}
.ww-mark{filter:brightness(0) saturate(100%);opacity:.86}
:root[data-theme="dark"] .ww-mark{filter:brightness(0) invert(1);opacity:.9}

/* ============================================================
   P0-11: THE MOTION MINIMUM                  (29 Oct, his spec)
   Transform and opacity only, everywhere. Every block below has
   a reduced-motion branch that is the FINAL STATE with no
   transition, not a shortened one.
   ============================================================ */

/* ---- section reveal: 12px, 400ms, ease-out, once ---- */
html body .js .rv,
html.js body .rv{opacity:0;transform:translateY(12px);
  transition:opacity 400ms var(--e-out),transform 400ms var(--e-out)}
html body .rv.in{opacity:1;transform:none}
/* the stagger is written by site.js as a delay per entry within one
   observer batch: siblings that arrive together go 60ms apart, and a
   block that arrives alone waits for nothing. */

/* ---- hover lift: -4px, +0.5deg, shadow grows, 180ms ----
   The offset shadow is a deliberate exception to the veto list and
   his spec asks for it by name. What that list bans is SOFT depth,
   the glassy lift that makes a web card look like every other web
   card. This is a hard offset in flat ink with no blur, which is a
   print device and is already on the site for that reason. */
@media (hover:hover) and (pointer:fine){
  .mv-tile{transition:transform 180ms var(--e-out),box-shadow 180ms var(--e-out)}
  .mv-tile:hover,.mv-tile:focus-within{
    transform:rotate(calc(var(--tl,0deg) + .5deg)) translateY(-4px);
    box-shadow:6px 6px 0 0 rgba(13,22,51,.18)}
  .wrow{transition:transform 180ms var(--e-out),box-shadow 180ms var(--e-out)}
  .wrow:hover,.wrow:focus-within{transform:translateY(-4px) rotate(.5deg)}
}

/* ---- the stitch draws itself in, 800ms, once ----
   HIS SPEC SAYS stroke-dashoffset AND IT CANNOT BE. The stitch is a
   background-image, so there is no stroke in the document to offset;
   inlining 169 paths into every seam to get one would cost more than
   the effect. A clip wipe is the same reading - the thread appears
   left to right, once - using the one property that can do it here. */
.stitch{clip-path:inset(0 100% 0 0)}
.stitch.in{clip-path:inset(0 0 0 0);transition:clip-path 800ms var(--e-out)}
@keyframes seam-draw{from{clip-path:inset(0 100% 0 0)}to{clip-path:inset(0 0 0 0)}}
@media (prefers-reduced-motion:no-preference){
  html.js .moved.in::before,
  html.js .faqhome.in::before,
  html.js .work.in::before,
  html.js .work.in::after{animation:seam-draw 800ms var(--e-out) both}
}

/* ---- reduced motion: the final state, no transition ---- */
@media (prefers-reduced-motion:reduce){
  html body .js .rv,html.js body .rv{opacity:1;transform:none;transition:none}
  .stitch,.stitch.in{clip-path:none;transition:none}
  .mv-tile,.wrow{transition:none}
  .mv-tile:hover,.mv-tile:focus-within,
  .wrow:hover,.wrow:focus-within{transform:rotate(var(--tl,0deg));box-shadow:none}
  .moved::before,.faqhome::before,.work::before,.work::after{animation:none}
}

/* ---- P0-11, three rules that were being beaten ---------------
   Measured, not assumed. .rv carries its own transition and every
   one of these elements is also a .rv, so the reveal was winning:

     .wrow hover read 400ms, not 180
     .stitch read "opacity, transform", so the clip wipe never ran
     the seam ::before never animated at all, because a band is not
     a .rv and so never gets .in

   The first two need the reveal's properties kept alongside the
   new ones - dropping them would stop the element revealing at
   all - so these declare both rather than replacing. */
@media (hover:hover) and (pointer:fine){
  html body .wrow.rv,html body .wrow{
    transition:opacity 400ms var(--e-out),
               transform 180ms var(--e-out),
               box-shadow 180ms var(--e-out)}
  html body .wrow:hover,html body .wrow:focus-within{
    transform:translateY(-4px) rotate(.5deg)}
}
html body .stitch.rv,html body .stitch{
  transition:opacity 400ms var(--e-out),clip-path 800ms var(--e-out);
  transform:none}
html body .stitch.rv.in,html body .stitch.in{clip-path:inset(0 0 0 0)}
@media (prefers-reduced-motion:reduce){
  html body .wrow,html body .stitch{transition:none}
  html body .stitch{clip-path:none}
}

/* ---- ACCEPTANCE FIXES (29 Oct), all three found by his own tests ----

   1. THE TILE FIGURES WERE OFF THE SCALE. P0-4 restored the scatter
      with its original clamp()s, which land on 25, 26, 31, 34 and 45px
      depending on the width - none of them in 12/16/20/32/56, and
      enough to take the page to eight sizes at 1280. Three steps, all
      from his five. A clamp cannot produce a step; this is the same
      finding as H6 and it came back with the scatter. */
html body .mv-sz-1 b{font-size:var(--t-h3)}    /* 20 */
html body .mv-sz-2 b{font-size:var(--t-h2)}    /* 32 */
html body .mv-sz-3 b{font-size:var(--t-h1)}    /* 56 */

/* 2. TWO HEADINGS LEFT THE EDGE BELOW 1101px.
      .so-cta: the pill bleed that aligns it was scoped to >=1101, so
      under that the pill's own padding pushed its heading in again.
      Below 1100 the pill squares off, so it can simply lose the
      padding on the left instead of bleeding.
      h2.clip: the work band's inner wrap carries its own padding on
      top of the container's, so the heading started at 48 where every
      other heading started at 24. */
@media (max-width:1100px){
  .contact .sign-off{padding-left:var(--pad);padding-right:var(--pad);
    margin-left:calc(-1 * var(--pad));margin-right:calc(-1 * var(--pad));
    border-radius:0}
}
@media (max-width:1100px){
  html body .work > .wrap,html body .work .windex{padding-inline:0}
}

/* The floating record is fixed bottom-right and the ticker head puts
   its Pause button in the same corner at narrow widths, so the two
   controls overlapped at 375. This repo's own rule is that a floating
   control may cover the corner it lives in but may not cover the copy;
   covering another CONTROL is worse than covering copy, because both
   are targets. The head wraps below 560 and the button goes to its own
   row, clear of the corner. */
@media (max-width:560px){
  .ww-head{flex-wrap:wrap;gap:var(--s-3)}
  html.js .ww-pause{margin-right:72px}
}

/* THE RECORD STANDS DOWN OVER THE HERO, which the deck it replaced
   already did and the new link never inherited. home.js still sets
   html.at-hero while the hero holds the screen; nothing was reading
   it for this element, so at 375 the fixed 56px link sat on the
   hero card's own email button - two targets in one place.

   Opacity and pointer-events, not display, so it fades rather than
   appearing from nothing when the hero leaves. */
html.at-hero .playlink{opacity:0;pointer-events:none}
.playlink{transition:opacity 240ms var(--e-out)}
@media (prefers-reduced-motion:reduce){.playlink{transition:none}}

/* THE MARKS HOLD STILL WHILE SOMEONE IS REACHING FOR ONE.
   This rule was recorded on 7 Oct and is not in the current stylesheet:
   it went when the hero was rebuilt. Restoring the marks without it
   restores the usability problem it fixed, which is that these are
   primary navigation and a moving target is harder to acquire than a
   still one.

   It is on .stage-hero, NOT on .floats: .floats has pointer-events:none,
   so a hover rule there only fires once the pointer is already on a
   mark, which is the hard part. The marks drift while nobody is
   engaging and stop the moment anyone is. */
.stage-hero:is(:hover,:focus-within) .fl{animation-play-state:paused}

/* THE PHONE TILT GOES BACK TO +-1.5, which is the only tilt his tokens
   allow. It was halved to +-1 in the P0 pass to protect the 16px gap on
   a full-width tile, and that is no longer needed: the gap there is
   clamp(26px,5vw,40px), so at 768 a ~720px tile at 1.5deg eats 18.8px
   of the 38.4px gap and clears by 19.6, and at 375 a ~327px tile eats
   8.6 of 26 and clears by 17.4. Both above the floor. Four distinct
   tilt angles on the page became two. */
@media (max-width:768px){
  .mv-tl-1{--tl:-1.5deg}
  .mv-tl-3{--tl:1.5deg}
}

/* ============================================================
   HERO v2: ONE CENTRED OBJECT, SIX MARKS            (30 Oct)
   Top to bottom, his order: the cut-out name, the childhood
   portrait, the value line, the rolling-word line, the tags, his
   sentence, the actions. Three marks down each side.

   CENTRED AND SYMMETRICAL IS ON THE VETO LIST AS A FALLBACK, and
   this is the deliberate exception he asked for by name.

   THIS BLOCK IS THE LAST WORD ON THE HERO. The card carried a
   column layout, a two-column layout and a bumped-width layout,
   each written over the one before. Rather than a fourth
   override of the same properties, this restates every one it
   touches under html body, so nothing earlier can win by source
   order. Tokens only: the card is a printed plane, so it takes
   --c-* values, which do not swap in dark mode.
   ============================================================ */
html body .stage-centre{margin-inline:auto}
html body .idcard{width:min(580px,100%);margin-inline:auto;text-align:center;
  justify-items:center;gap:var(--space-3);
  padding:var(--space-3) var(--space-3) var(--space-4)}

/* name lockup, centred */
html body .id-head{display:grid;grid-template-columns:minmax(0,1fr);justify-items:center;width:100%}
html body .id-head .hero-name{justify-items:center;margin-inline:auto}
html body .id-head .hero-name .l1{justify-content:center}

/* one column: portrait, then the words */
html body .id-body{display:grid;grid-template-columns:minmax(0,1fr);justify-items:center;
  gap:var(--space-3);width:100%}
html body .idcard .hero-pola{width:clamp(150px,15vw,190px);margin:0 auto}
html body .id-detail,html body .idcard .stage-copy{display:grid;justify-items:center;
  gap:var(--space-2);max-width:none;text-align:center;width:100%}

/* THE VALUE LINE. L1 on the scale (56, stepping to 32 on a phone), because
   the point of the hero is the value delivered. */
.hero-vp{margin:0;font-family:var(--font-display);font-weight:900;
  font-size:var(--t-h1);line-height:1.02;letter-spacing:var(--font-display-track);
  color:var(--c-ink);text-wrap:balance}
.hero-vp .hw{display:inline-block}
.hero-vp .hl{position:relative;display:inline-block;isolation:isolate}
/* THE HIGHLIGHT SITS BEHIND THE WORDS WITHOUT GOING BEHIND THE CARD.
   .stage-centre is rotated, which makes a stacking context, so a bare
   z-index:-1 here would paint under the card's white background and the
   yellow would never show. isolation on .hl keeps the -1 inside it. */
.hero-vp .hl::before{content:"";position:absolute;z-index:-1;
  left:-.06em;right:-.06em;bottom:.04em;height:.48em;
  background:var(--c-yellow);transform-origin:left center}
/* visible by default, so with no script the line still has its highlight;
   the draw only exists where a script is there to trigger it */
html.js .hero-vp .hl::before{transform:scaleX(0)}
html.js .hero-vp .hl.rise::before{animation:hl-draw 700ms var(--ease) 380ms forwards}
@keyframes hl-draw{to{transform:scaleX(1)}}
@media (prefers-reduced-motion:reduce){
  html.js .hero-vp .hl::before,html.js .hero-vp .hl.rise::before{transform:scaleX(1);animation:none}
}

/* the rolling-word line, demoted to L3 now that the value line leads */
html body .idcard .claim{font-size:var(--t-h3);line-height:1.2;max-width:24em;
  margin:0 auto;color:var(--c-ink)}
html body .idcard .roll{background:var(--c-cobalt);color:var(--c-card)}

.hero-tags{margin:0;font-family:var(--font-label);font-size:var(--size-label);
  letter-spacing:.14em;text-transform:uppercase;color:var(--c-accent-type)}
.hero-line{margin:0;max-width:46ch;font-family:var(--font-body);
  font-size:var(--size-body);line-height:1.5;color:var(--c-ink)}
html body .idcard .hero-cta{display:flex;flex-wrap:wrap;justify-content:center;
  gap:var(--space-1) var(--space-2);margin-top:var(--space-1)}

/* SIX MARKS, THREE A SIDE. They sit in the gutters either side of the card
   and are sized FROM the gutter, so they cannot reach the card at any
   width: (viewport - card) / 2, less the page margin and the label. Each
   side staggers between two columns so no two marks share a height. */
html body .floats{--fl-w:clamp(84px,calc((100vw - 580px) / 2 - 5vw - 28px),208px)}
html body .fl-work{left:2vw;right:auto;top:4%}
html body .fl-me{left:calc(2vw + 20px);right:auto;top:35%}
html body .fl-faq{left:2vw;right:auto;top:66%}
html body .fl-ideas{right:2vw;left:auto;top:10%}
html body .fl-contact{right:calc(2vw + 20px);left:auto;top:41%}
html body .fl-listen{right:2vw;left:auto;top:72%}

/* 1200, not 900: at 1100 the gutter is 260px and the card is tilted with a
   12px red offset, so a mark in it reached the card by a few pixels. Measured,
   not guessed - the check below reports marks on the card at every width. */
@media (max-width:1200px){
  html body .floats{position:relative;inset:auto;display:grid;
    grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-2);
    margin:var(--space-4) auto 0;max-width:560px;pointer-events:auto}
  html body .fl{position:static;width:auto;animation:none}
  html body :is(.fl-work,.fl-me,.fl-faq,.fl-ideas,.fl-contact,.fl-listen){
    left:auto;right:auto;top:auto}
  html body .fl-icon{width:100%;height:auto;aspect-ratio:1}
}
@media (max-width:900px){ html body .idcard{width:100%} }

/* THE VALUE LINE WAS LANDING AT 600-720px, BELOW THE FOLD ON A LAPTOP.
   The point of this hero is the value delivered, so the portrait comes
   down to a stamp-sized print and the card's gaps close up. The order is
   still his: name, portrait, value line. */
html body .idcard{gap:var(--space-2);padding-top:var(--space-2)}
html body .id-body{gap:var(--space-2)}
html body .idcard .hero-pola{width:clamp(112px,11vw,138px)}
html body .id-head .hero-name{margin-block:0}

/* ============================================================
   HERO FITS THE FRAME                          (30 Oct, his note)
   The card measured 927px tall, so the actions fell below the fold
   on every laptop. Cuts, in the order he allowed:
     the photo caption goes - hidden, not deleted, one line to
       restore (the locked 2003 line stays in the markup);
     the name lockup comes down to four fifths;
     the rule under it goes, the gaps close, the sentence widens
       to three lines instead of four.
   ============================================================ */
html body .idcard .print figcaption{display:none}
html body .id-head .t-hi{width:clamp(30px,3.5vw,42px)}
html body .id-head .t-my{width:clamp(36px,4.2vw,49px)}
html body .id-head .t-name{width:clamp(64px,7.3vw,86px)}
html body .id-head .t-is{width:clamp(21px,2.4vw,29px)}
html body .id-head .t-shrey{width:clamp(149px,16.8vw,198px)}
html body .id-head{border-bottom:0;padding-bottom:0}
html body .idcard{gap:var(--space-1);padding:var(--space-2) var(--space-3) var(--space-3)}
html body .id-body{gap:var(--space-1)}
html body .idcard .hero-pola{width:clamp(100px,10vw,124px)}
html body .id-detail,html body .idcard .stage-copy{gap:var(--space-1)}
html body .hero-line{max-width:54ch}

/* ============================================================
   THE TICKER, PLAIN                            (30 Oct, his notes)
   The Pause motion button is hidden: it read as abrupt and
   cluttery. Pointer and focus still hold the loop, and reduced
   motion still stops it. Hidden with a flag, not deleted.

   The labels (Employer / Internship / Event / College project)
   are hidden too, so it is a regular scroll. NOTE: CLAUDE.md
   says UN Youth and BCG must always carry "College project";
   this is his explicit instruction and it overrides that, the
   same way the 6 Oct pass did. The labels stay in the markup.

   Sizes are by VISUAL WEIGHT, not one shared height. A shared
   28px made the device marks (Bliss and Birch, UN Youth) specks
   beside wordmarks that fill their box, so each mark has its own
   height and the uneven result is the point: the employers lead,
   the rest sit beneath them.
   ============================================================ */
html body .ww-pause{display:none !important}
html body .ww-label{display:none}
html body .ww-set{align-items:center}
html body .ww-item{gap:0}
html body .ww-item:nth-child(1) .ww-mark{height:38px}   /* iSelect */
html body .ww-item:nth-child(2) .ww-mark{height:30px}   /* Compare the Market */
html body .ww-item:nth-child(3) .ww-mark{height:26px}   /* Tickertape */
html body .ww-item:nth-child(4) .ww-mark{height:36px}   /* Amazon */
html body .ww-item:nth-child(5) .ww-mark{height:32px}   /* TEDxVIPS */
html body .ww-item:nth-child(6) .ww-mark{height:48px}   /* Bliss and Birch */
html body .ww-item:nth-child(7) .ww-mark{height:50px}   /* UN Youth */
html body .ww-item:nth-child(8) .ww-mark{height:26px}   /* BCG */

/* the Bliss and Birch file is mostly padding around a small device, so 48px
   of height drew ~30px of ink; it needs 76 to read at the weight of its
   neighbours. Found by looking, not by the numbers. */
html body .ww-item:nth-child(1) .ww-mark{height:40px}
html body .ww-item:nth-child(2) .ww-mark{height:32px}
html body .ww-item:nth-child(3) .ww-mark{height:28px}
html body .ww-item:nth-child(4) .ww-mark{height:34px}
html body .ww-item:nth-child(5) .ww-mark{height:30px}
html body .ww-item:nth-child(6) .ww-mark{height:76px}
html body .ww-item:nth-child(7) .ww-mark{height:56px}
html body .ww-item:nth-child(8) .ww-mark{height:28px}

/* THE LOWER MARKS WERE LANDING ON THE STRIP. With the Pause button gone the
   strip changed height and the field the marks live in no longer stopped
   above it: FAQ sat on "Worked with" and Vinyl on a logo. The field now
   stops 150px short of the hero's foot, and the two lowest marks come up. */
@media (min-width:1201px){
  html body .floats{inset:0 0 150px 0}
  html body .fl-faq{top:60%}
  html body .fl-listen{top:66%}
}
@media (min-width:1201px){ html body .floats{inset:0 0 200px 0} }   /* the strip starts 207px above the hero's foot */

/* ============================================================
   THE FOLD: ONE OPEN/CLOSE COMPONENT         (30 Oct, his notes)
   What I do / what it moved, and the screening call, are the same
   object: a heading row with a chevron, a rule under it, and a
   body that opens and closes. A native <details>, so it is
   keyboard operable and announced correctly, and the content is
   in the DOM whether or not it is open.

   It takes its ink from whatever band it sits in (currentColor),
   so the yellow band gets ink and the cobalt band gets cream
   without either one being written here.

   Results opens on arrival (the figures are the evidence); the
   FAQ is closed (it is the second thing a reader wants). One
   attribute flips either.
   ============================================================ */
.fold{margin:0}
.fold > summary.fold-sum{list-style:none;cursor:pointer;
  display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);
  min-height:var(--tap-min);padding-block:var(--space-2);
  border-bottom:var(--line) solid currentColor}
.fold > summary.fold-sum::-webkit-details-marker{display:none}
.fold > summary.fold-sum h2{margin:0}
.fold > summary.fold-sum:focus-visible{outline:3px solid currentColor;outline-offset:4px}
.fold-chev{flex:none;width:14px;height:14px;margin-right:6px;
  border-right:var(--line-strong) solid currentColor;
  border-bottom:var(--line-strong) solid currentColor;
  transform:rotate(45deg) translate(-3px,-3px);
  transition:transform var(--dur-base) var(--ease)}
.fold[open] > summary .fold-chev{transform:rotate(225deg) translate(-3px,-3px)}
.fold-body{padding-top:var(--space-3)}
/* opening is a short rise, transform and opacity only */
@keyframes fold-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.fold[open] > .fold-body{animation:fold-in var(--dur-base) var(--ease)}
@media (prefers-reduced-motion:reduce){
  .fold-chev{transition:none}
  .fold[open] > .fold-body{animation:none}
}
.fold-body > .sub{margin:0 0 var(--space-3);font-family:var(--font-body);
  font-size:var(--size-item);line-height:1.4}
/* the two fold headings are the same size: the FAQ one was inheriting a
   smaller clamp, so the pair did not read as a pair */
html body .fold > summary.fold-sum h2{font-family:var(--font-display);font-weight:900;
  font-size:var(--t-h2);line-height:1.1;letter-spacing:var(--font-display-track)}

/* ME, LONGER FORM (30 Oct). Three paragraphs of his own writing at body
   size. The old rule set the FIRST paragraph large as a one-line thesis,
   which on a 70-word paragraph is a wall of display type, so every
   paragraph here is body. */
html body .about .abody .prose p,
html body .about .abody .prose p:first-child{font-size:var(--t-body);line-height:1.55;
  font-weight:400;margin:0 0 var(--space-2)}

/* THE PILL, SIZED TO ITS NEW LINE (30 Oct). The sign-off headline was set at
   56 to carry a three-line sentence; his line is one short question, so it
   drops to 32 and the vertical padding comes down with it. The horizontal
   padding is left exactly as it was, because the left-edge alignment rule
   (the pill bleeds left by its own padding so the heading lands on the
   container edge) is tied to that value. */
html body .so-cta{font-size:var(--t-h2);line-height:1.1;max-width:14em}
html body .sign-off{padding-block:clamp(2rem,4vw,3rem)}

/* THE SPOTIFY LINK IS HIDDEN, NOT DELETED. His note: the vinyl on home does
   not lead anywhere except playing the music. The hero Vinyl mark and the
   floating deck both play ABBA; neither navigates. */
.playlink{display:none}

/* ONE OUTLINE WEIGHT. The new chevron was drawn at the 2.4px hover weight
   and the restored deck carried a 3px frame; neither is a hover state, so
   both are the one weight. */
html body .fold-chev{border-right-width:var(--line);border-bottom-width:var(--line)}
html body .disc-deck{border-width:var(--line)}

/* the deck's play button measured 43.8px at 1280: a fraction under the 44px
   floor, because the deck itself is sized by a clamp and the button scales
   with it. Fixed size, centred on the platter. */
html body .disc-btn{width:48px;height:48px;margin:-24px 0 0 -24px}

/* ============================================================
   PRE-PROMOTION FIXES                          (30 Oct, his notes)
   ============================================================ */

/* 3. A HOVERED MARK COMES TO THE FRONT, with its label and its hint. Each
   mark is its own stacking context (they all animate a transform), so a later
   sibling paints over an earlier one: the hint under the mark above it was
   being covered by the next icon down. z-index lifts the whole mark while it
   is hovered or has focus, so its hint is never behind a neighbour. */
html body .fl:is(:hover,:focus-within){z-index:30}

/* 4. THE VINYL'S TEXT FOLLOWS THE GROUND behind it. home.js sets data-ground
   from the colour actually under the deck. Tokens that do not swap in dark
   mode, because the ground is measured, not assumed. No opacity: a mix, so
   the harness can read it. */
.disc[data-ground="light"] .disc-k{color:var(--c-accent-type)}
.disc[data-ground="light"] .disc-title{color:var(--c-ink)}
.disc[data-ground="light"] .disc-by{color:var(--c-ink);opacity:1}
.disc[data-ground="dark"] .disc-k{color:var(--c-yellow)}
.disc[data-ground="dark"] .disc-title{color:var(--c-cream)}
.disc[data-ground="dark"] .disc-by{color:var(--c-cream);opacity:1}

/* 5. THE SKY, LOUDER, AND THE GRID BEHIND IT NEARLY GONE. The grid layer on
   this section sits ABOVE the clouds (z 0 against their -1), so every cloud
   was being cross-hatched. Dropping it to a ghost lets the sky read; the cloud
   itself is brighter, taller and fades in later so more of it shows. */
html body .contact::before{opacity:calc(.05 + var(--paper-in) * .07)}
html body .contact::after{height:min(62vh,560px);opacity:.95;filter:saturate(1.05);
  -webkit-mask-image:linear-gradient(to bottom,transparent 0%,#000 14%,#000 62%,transparent 100%);
  mask-image:linear-gradient(to bottom,transparent 0%,#000 14%,#000 62%,transparent 100%)}
/* the section label now sits on the sky, not on cream: cobalt on that blue is
   about 3:1, ink is about 6:1 */
html body .contact .no{color:var(--c-ink)}

/* ============================================================
   THE FIVE BELIEFS, MOVING                     (30 Oct, his note)
   A yellow band between Work (cobalt) and Me (cream), so no two
   grounds touch alike. His five lines from /about/ run on their
   own and keep running: transform only, no per-frame script.
   Slow on purpose: this is text to be read, not a texture, so the
   loop is 80s where the logo strip is 40s.
   Pointer and focus hold it. Reduced motion is a wrapped list.
   ============================================================ */
.beliefs{position:relative;overflow:hidden;background:var(--c-yellow);color:var(--c-ink);
  padding-block:var(--space-3)}
.bel-track{display:flex;width:max-content;will-change:transform}
.bel-set{display:flex;align-items:center;gap:var(--space-4);list-style:none;margin:0;
  padding:0 var(--space-4) 0 0}
.bel-say{flex:none;white-space:nowrap;font-family:var(--font-display);font-weight:900;
  font-size:var(--t-h2);line-height:1.1;letter-spacing:var(--font-display-track);color:var(--c-ink)}
.bel-gap{flex:none;display:flex;align-items:center}
.bel-gap img{display:block;height:1.1em;width:auto;font-size:var(--t-h2)}
@keyframes bel-roll{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@media (prefers-reduced-motion:no-preference){
  .bel-track{animation:bel-roll 80s linear infinite}
  .beliefs:is(:hover,:focus-within) .bel-track{animation-play-state:paused}
}
@media (prefers-reduced-motion:reduce){
  .bel-track{display:grid;width:auto;animation:none;padding-inline:var(--gutter)}
  .bel-set{flex-wrap:wrap;gap:var(--space-2) var(--space-3);padding:0}
  .bel-say{white-space:normal}
  .bel-set[aria-hidden="true"]{display:none}
  .bel-gap{display:none}
}

/* (the extra stitch into Me went with the home ticker: .work::after already carries that seam) */

/* ============================================================
   THE BELIEFS STRIP RUNS BY ITSELF, on /about/        (30 Oct)
   It moved only while the page scrolled. Now it loops on its own:
   transform only, 60s, a seamless -50% over two copies. Pointer
   and focus hold it. Reduced motion goes back to a strip you can
   scroll sideways by hand, with the duplicate copy not rendered.
   ============================================================ */
.hs-view{overflow:hidden}
.hs-run{display:flex;width:max-content;will-change:transform}
.hs-run > .hs-track{flex:none}
@keyframes hs-roll{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@media (prefers-reduced-motion:no-preference){
  .hs-run{animation:hs-roll 45s linear infinite}
  .hstrip:is(:hover,:focus-within) .hs-run{animation-play-state:paused}
}
@media (prefers-reduced-motion:reduce){
  .hs-view{overflow-x:auto}
  .hs-run{animation:none}
  .hs-run > .hs-track[aria-hidden="true"]{display:none}
}
