/* ============================================================
   Ink & Intuition - Who I work with
   Desktop wears the Hero credential pill's CSS glass (see .who-card__surface),
   with the coloured .who-card__ring supplying the outline. Mobile keeps its
   own lighter CSS/SVG glass treatment. Neither uses WebGL.
   ============================================================ */

/* Asymmetric on purpose. The arch grew 192 stage units taller downward, and
   the stage grew with it, so a symmetric bottom pad would push the seam that
   much further away and leave a slab of dead paper under the doorway. The
   short bottom lets the doorway stand on the ground it just claimed.

   Retuned in the 2026-07-26 cohesion pass to the chapter's spacing target,
   132 / 108 at desktop (measured 168 / 84 before). Who is a quieter chapter
   than Approach and Ways and no longer wants the full --section-pad above it,
   and the extra 24px below buys the teal threshold glow room to reach zero
   alpha inside the stage instead of running at the seam lip. At 1440 the gap
   from the arch's base to the seam goes 117px -> 141px; the doorway still
   stands on its own ground. Mobile is retuned separately below, to the 80-96
   band, because there the rail sits underneath the stage rather than beside
   it and the section is already very tall. */
.who {
  position: relative;
  overflow: clip;
  padding-block: clamp(96px, 9.2vw, 132px) clamp(76px, 7.5vw, 108px);
}

/* ---------- Tonal ramp: the cool end ----------
   Who, About and the CTA sat on the same untouched cream, so the last third of
   the page read as one undifferentiated surface. The three now carry a ramp of
   light across the SAME sheet: cool here, warm rose in About, amber deepening
   into the footer. Washes only. There is no second paper-fibre layer anywhere in
   this pass, so the one-continuous-sheet rule in base.css still holds, and no
   mix-blend-mode is used, so the transparent-backdrop trap that produced the
   grey corner wedge cannot happen here.

   Two constraints shaped the numbers more than taste did.

   1. LUMINANCE, NOT PIGMENT. The rail headings have almost no contrast headroom:
      measured on the bare sheet, .who-rail__group--pink h3 (--pink-link) runs
      4.69:1 against a 4.5:1 requirement, and the coral and violet headings are
      within a tenth of that. Relative luminance is 71.5% green, so ANY saturated
      violet laid over cream costs contrast fast: plain --violet at only 5% alpha
      already drops that heading to 4.50. So the broad wash below is a tint whose
      red and green sit AT the paper's own levels and whose blue sits far above
      them. It adds blue rather than subtracting yellow, which is what a cooler
      light actually does to cream paper, and it measures luminance-neutral to
      slightly positive. The violet pigment proper is in the second layer, kept
      to the left of the copy rail where it can only fall on the balloons and the
      doorway -- which already own violet, pink and coral, so it supports them
      instead of competing.

   2. THE SEAM LIP IS BARE PAPER. Since the tears were straightened, each chapter
      join is a full-width lip of untinted sheet (--seam-lip, 16px at 390 rising
      to 26px at 2552). A wash that ran at strength into a join would put that lip
      between two colours as a hard bright stripe, with nothing ragged left to
      hide it. So every envelope here starts and ends at ZERO alpha at its own
      section edges, over a ramp more than ten times the lip height. At each join
      the paper is genuinely bare for a couple of hundred pixels either side, the
      lip is a continuation of that, and the colour changes hue by crossing
      through the sheet's own tone rather than by stepping. It also keeps the
      argument honest: the true stock is visible at every chapter change.

   Measured with tools/ramp-contrast.mjs and tools/ramp-joins.mjs (both take
   BEFORE=1 to reproduce the pre-ramp page). The three chapters used to sit at
   R-B 22.5 / 22.8 / 22.9 at their own mid-heights, i.e. the same surface three
   times, which was the reported complaint; they now read 9.6 / 32.8 / 39.2 at
   1440 while CIE L* moves by less than 1.5 anywhere. Different light, same
   paper. Re-run both after any change here.

   Zero-alpha end stops are spelled out rather than written `transparent`, the
   same as --sheet-contact-0 in base.css: `transparent` is rgba(0,0,0,0) and an
   engine that interpolates gradients unpremultiplied darkens the whole fade. */
.who {
  /* Blue only. R and G are the sheet's own measured levels (229, 223), so the
     layer is nearly invisible in the two channels luminance is made of. */
  --who-wash-cool: rgba(229, 223, 252, 0.21);
  --who-wash-cool-0: rgba(229, 223, 252, 0);
  /* The pigment. --violet lightened toward the sheet; this one does cost
     luminance, hence the low alpha and the left-of-the-rail placement. */
  --who-wash-lilac: rgba(154, 140, 222, 0.075);
  --who-wash-lilac-0: rgba(154, 140, 222, 0);

  /* Top layer, new in the cohesion pass: the LUMINOUS FIELD. The complaint it
     answers is that the balloons read as paper stickers mounted on a flat
     wall. Glass needs a lit room behind it, so this is one broad radial of
     --atmos-cream over the cluster: warm white, the field's own colour, which
     can only ADD light. That makes it the one layer here with no contrast cost
     at all -- it raises the backdrop luminance under every glyph it touches,
     so the rail headings gain headroom rather than spending it. --atmos-cream-3
     is used as the radial CORE only, per the token block's rule.
     Zero alpha at 82% of the radius puts the layer's edge at 1.8% and 64.2% of
     the section height, so it is spent long before either seam lip. */
  background-image:
    radial-gradient(56% 38% at 31% 33%,
      var(--atmos-cream-3),
      var(--atmos-cream-2) 34%,
      var(--atmos-cream-1) 60%,
      var(--atmos-cream-0) 82%),
    radial-gradient(76% 48% at 27% 50%, var(--who-wash-lilac), var(--who-wash-lilac-0) 72%),
    linear-gradient(180deg,
      var(--who-wash-cool-0) 0%,
      var(--who-wash-cool) 30%,
      var(--who-wash-cool) 70%,
      var(--who-wash-cool-0) 100%);
}

/* ---------- The ambient: volumetric light ----------

   Transplanted from tmp/anim-lab-v2/07-volumetric-light/ at Daniel's own
   settings; the shader, the constants and the reasoning are in who.js. These
   three layers are children of <section class="who">, NOT of .who-stage,
   because the hole they fill is the empty right two-thirds below the copy
   panel at 2200 to 2552, which is outside the stage's own box.

   z-index 0, and that number is load-bearing. .who is position:relative with
   z-index auto, so it is NOT a stacking context: every z-index inside this
   chapter (ink canvas 1, studio objects 2, tethers 5, arch 6, balloons 8,
   tether note 12, grain 15, copy panel 20) resolves against .content-sheet's
   context, and 0 is the only value that puts the light above the section's own
   painted background washes and below all of them. Do not give this element or
   .who-stage a stacking context of their own to "fix" anything: an isolated
   context with nothing opaque painted in it is what turned the grain overlay
   into a flat grey veil twice already.

   OPACITY IS THE PRESENCE KNOB. The lab study is a full-bleed hero where the
   light IS the page. Here it is one chapter of a paper document that has to go
   on reading as paper, on top of the chapter's tonal-ramp washes and under the
   sitewide grain, so the field runs at a fraction of the study's own strength.
   The shader's internal alpha ceiling (.46) is untouched at the lab's value;
   this is the only attenuation, and it is one number rather than a re-tune. */
.who-lightfield,
.who-lightmotes,
.who-lightfield-fallback {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  /* THE SEAM RULE, and it is not optional. Since the chapter tears were
     straightened, every join between chapters is a full-width lip of UNTINTED
     sheet (--seam-lip, 16px at 390 rising to 26px at 2552). Any layer that runs
     at strength into a join puts that bare lip between two different colours as
     a hard bright stripe, with nothing ragged left to hide it, which is why
     every wash in the block above starts and ends at zero alpha at this
     section's own edges. The shader has no idea where the section ends, so the
     fade is applied here: it reaches zero over roughly 12% of the section
     height, more than five times the lip at every width, and the same argument
     tools/ramp-joins.mjs exists to protect. Horizontal edges do not need it,
     the section is full-bleed and its neighbours join top and bottom only. */
  -webkit-mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, #000 12%, #000 88%, rgba(0, 0, 0, 0) 100%);
  mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, #000 12%, #000 88%, rgba(0, 0, 0, 0) 100%);
}

.who-lightfield { opacity: .40; }
.who-lightmotes { opacity: .5; }

/* WebGL missing, refused or lost. Three broad warm shafts on the same diagonal
   as the shader's, deliberately with NO mix-blend-mode: a blended layer against
   a region its stacking context has not painted falls through to its own source
   colour and reads as a flat grey veil, which has cost this project two
   separate bugs. Plain alpha over cream cannot do that. */
.who-lightfield-fallback {
  display: none;
  overflow: hidden;
}

.who-lightfield-fallback span {
  position: absolute;
  left: -14%;
  width: 128%;
  height: 30%;
  transform-origin: 0 50%;
  transform: rotate(-17deg);
  background: linear-gradient(96deg,
    rgba(247, 178, 95, 0),
    rgba(247, 178, 95, .16) 26%,
    rgba(236, 95, 156, .07) 72%,
    rgba(143, 131, 224, 0));
  filter: blur(26px);
}

.who-lightfield-fallback span:nth-child(1) { top: -2%; }
.who-lightfield-fallback span:nth-child(2) { top: 24%; opacity: .6; }
.who-lightfield-fallback span:nth-child(3) { top: 52%; opacity: .4; }

.who-container { position: relative; }

.who-layout {
  position: relative;
  display: grid;
  /* rework-2 brought the rail's fr share down from .43 to .36 (floor 292px
     to 272px) so the stage got more of the row. Round 4 goes further: floor
     272px -> 252px, fr .36 -> .30. Daniel again, on his own 2560 display:
     "space out the balloons section too, the animation sits too close to
     the words."

     tools/who-spacing-probe.mjs (new this round) measures the REAL rendered
     gap between the rightmost card and the panel directly, rather than
     reading it off tools/who-geometry.mjs's stage-percentage numbers, and it
     found something the previous pass's reasoning missed: .who-stage spans
     grid-column 1/-1 (the whole row), and the content-sheet caps this row's
     own total width around 1132px regardless of viewport (identical at 1440
     and 2552) -- so growing column-gap does NOT move anything once column 2
     is floor-bound, as it is here. The row's total width is fixed, column 2
     sits at its own explicit floor rather than its .fr share, so column 2's
     rendered width is the floor value exactly (verified: panelRect.width was
     precisely 272px, then 252px after this change) and its LEFT edge is
     simply (row width - column-2 width), independent of column-gap entirely.
     A wasted pass proved this: raising column-gap's ceiling from 132px to
     230px moved the panel by 0.0px. The floor is the only lever that
     actually works here, confirmed by shrinking it and re-measuring: 272 ->
     252 moved the panel right by exactly 20px, real gap 74px -> ~94px.
     252px is the tightest this goes without "Trauma, burnout & psychosis"
     wrapping to two lines (tested at 246px, 258px and 252px); 264px is the
     768-980 tablet band's own floor for comparison, a different font-size
     bracket so not directly transferable.

     column-gap is left as the pre-round-4 clamp(56px, 9vw, 132px): it is a
     genuine no-op at 1440/2552 for the reason above, but the 768-980 tablet
     band still uses its OWN column-gap value below and a wider ultra-ultra-
     wide desktop where the row's width has not yet saturated would still
     read it, so there is no reason to touch it. Re-run
     tools/who-spacing-probe.mjs after any further change to either the floor
     or the fr share. */
  /* 2026-07-27: the floor goes 252px -> 300px. The whole point of the 252px
     value above was that it was the tightest width that kept "Trauma, burnout
     & psychosis" on one line at a 17px h3, and this pass raises that h3, so the
     floor had to move with it. Widening column 2 only ever moves its own LEFT
     edge; the block's right edge and therefore its centre do not move. */
  grid-template-columns: minmax(0, 1fr) minmax(300px, .30fr);
  grid-template-rows: auto;
  align-items: start;
  column-gap: clamp(56px, 9vw, 132px);
}

/* ---------- Wide-viewport stage breakout ----------

   >> SUPERSEDED 2026-07-29. Everything below describes the FULL-BLEED anchor
   >> that used to live in the @media block at the end of this comment, where
   >> .who-container dropped .container's centring and redistributed the freed
   >> width as --who-wide-pad. Daniel reversed it that day on the same display:
   >> "who i work with is way too wide". The rule is now a plain max-width cap,
   >> --who-wide-max, and the reasoning that still applies is restated on it.
   >> KEPT BECAUSE THE HISTORY IS LOAD-BEARING: the round-4 diagnosis below
   >> (the constraint is the box, not the object positioning), and the reason
   >> the COPY column must never leave the shared .container rail even when the
   >> stage does, are both still true and still constrain anything done here.
   >> Do not restore the full-bleed rule from this text.

   Daniel, twice now, reviewing on his 2560 display: Who "doesn't fit right,
   too centred and small," and asked for the balloon stage and the studio
   objects to sit further left and be bigger. Round 4 answered the SIZE half
   (paper scrap 17% -> 24% of stage width, paint tube 13% -> 19%, both pushed
   to 0% of the STAGE's own left edge) but not the POSITION half, because
   .who-layout above still lives inside the shared ~1240px .container: on a
   2552-2560px screen that leaves roughly 1300-1400px of empty gutter split
   either side, so pushing an object to the stage's own left edge only gets
   it to the CONTAINER's edge, nowhere near the screen edge. Round 4's own
   comment ("the honest limit of the current box") had it right: the
   constraint was the box, not the object positioning.

   Above 1600px the STAGE breaks out of .container; the COPY does not. Every
   section on this site aligns its text to the same .container rail (hero,
   Approach and Ways all start their copy at that same x), so dragging the
   panel out of it would break the page's vertical rhythm site-wide -- a
   much worse problem than the one being fixed here. Below 1600px nothing in
   this block applies and the section is byte-for-byte the round-4 layout.

   THE MECHANISM. .who-stage spans grid-column 1/-1 (the whole row, gap
   included -- see the long comment on this rule above), and who.js reads
   the STAGE's OWN rendered width every layout()/rebuild() via a
   ResizeObserver (this.scale = rect.width / config.width). So widening the
   row in CSS is the entire fix: who.js re-measures and re-scales the whole
   1300x1152 design grid uniformly on the next frame, no JS change needed,
   and nothing stretches on one axis only because both the stage's width
   AND height (config.height * scale) move by the same factor.

   THE ANCHOR. .who-container drops .container's max-width/centring and
   becomes a full-width box whose OWN padding stands in for the centring
   maths:
     - padding-right reproduces exactly where .container's own right inner
       edge would fall at this viewport width -- (100% - 1240px)/2 +
       gutter, where 100% is .who-container's own containing block width, not
       100vw (Chromium's vw unit includes the scrollbar band; the percentage
       basis does not, and the two disagreed by 7.5px at 2552 in testing) --
       so the copy column's right edge never moves relative to where every
       other section's rail sits, at any width above 1600px.
     - padding-left is a plain, much smaller edge gutter, not tied to the
       container maths at all, so nearly all of the freed width goes to
       column 1 (the stage) rather than being split evenly the way
       centring would split it.

   THE COLUMN. grid-template-columns' second track changes from
   minmax(252px, .30fr) to a hard 252px. The .30fr already renders at
   exactly its 252px floor at every width this file has shipped at (see the
   long comment on this rule above), but only because the row was never
   wide enough for the fr share to overtake the floor. This block makes the
   row much wider on purpose, and an fr track under a 1600px+ row DOES
   overtake a 252px floor (the naive fr share at the 2552 width this block
   produces is roughly 400px). Fixing the track at 252px is what keeps the
   panel's width -- and therefore the drag/hold gap to its right that round
   4 won -- structurally unreachable by the extra row width: 1fr vs a fixed
   length can only ever hand overflow to the 1fr track.

   THE STAGE'S OWN CLIP STAYS. .who-stage keeps overflow:clip; nothing here
   removes it. It now clips a bigger box, which is the fix -- the balloons
   and studio objects still cannot escape into the page gutter, there is
   just far more stage for them to live in before they would need to.

   Verified with tools/who-geometry.mjs (uniform scale, minGap, safeRight
   clearance) and tools/who-spacing-probe.mjs (the real rendered rail gap).
   Below 1600px this block is inert; both probes are unchanged at 1440.

   2026-07-27: Daniel again, this time on the CENTRING rather than the size:
   "the distance between the left edge padding and the right needs to be
   centred so the arch doesn't sit so far to the left but the text is in the
   middle." The asymmetric padding this block used to carry (a small
   padding-left, and a padding-right reproducing exactly where the shared
   .container's own right inner edge falls) does what its own comment always
   said it would -- keep the copy rail aligned with every other section --
   but the side effect is that the whole block's visual centre sits well
   left of the viewport's own centre: at 2552 that was 307px left of centre,
   exactly HALF of (padding-right - padding-left), and the arch (centred in
   the stage) drifted left along with everything else.

   THE FIX KEEPS THE STAGE'S FULL WIDTH. --who-wide-pad below is exactly
   (the old padding-left + the old padding-right) / 2, as one calc() so it
   tracks both source formulas continuously rather than a number pinned to
   one viewport width. For a full-width box, content-left = P and
   content-right = 100% - P sit symmetric about the midpoint for ANY equal P,
   so redistributing the SAME total padding evenly centres the block WITHOUT
   giving back any of the row width this pass already won: the stage still
   measures 68.4% of the viewport at 2552, unchanged, and so does the real
   rendered card-to-panel gap (tools/who-spacing-probe.mjs). A naive fix that
   instead raised padding-left to MATCH the old padding-right (to keep the
   copy rail pinned to the shared rail AND go symmetric) would collapse the
   whole block back to roughly the original 1240px container and undo the
   wide-stage win entirely -- not done here.

   THE COST, PAID IN FULL, NOT HIDDEN. The panel's right edge is no longer
   pinned to the shared .container rail hero/Approach/Ways/About all align
   their copy to: measured with tools/who-center-probe.mjs, it now sits
   85px right of that rail at 1600, 154px at 1900, 223px at 2200 and 307px
   at 2552. That is a real, visible difference from every other section's
   copy column, and it is the direct, unavoidable price of honouring
   "centred" over "aligned to the shared rail" now that Daniel has asked
   for centred twice on his own display. */
@media (min-width: 1600px) {
  .who-container {
    /* 2026-07-29, Daniel on his own ultrawide: "who i work with is way too
       wide". THIS REPLACES THE FULL-BLEED RULE THE COMMENT ABOVE DESCRIBES,
       and that comment is now history rather than instruction. Measured before
       the change at 2552: the row ran the entire viewport, 1746px of stage
       with 403px of paper either side, against About's 1240. It was the widest
       thing on the page by 500px.

       A HARD max-width, not a rebalanced padding. The old --who-wide-pad blended
       a small left gutter with the container-aligned right edge and halved the
       result, which is why the row kept growing with the viewport - at 3440 it
       would have reached 2190px and earned exactly this complaint again. A cap
       stops that at every width, and it keeps the centring Daniel asked for
       twice, because this is .container's own idiom: cap, then auto margins.

       --who-wide-max IS THE KNOB. It is the only number to touch if this wants
       to be tighter or looser; nothing else in this file or in who.js reads it.
       who.js re-measures via ResizeObserver (this.scale = rect.width /
       config.width) and rescales the whole 1300x1152 design grid uniformly on
       the next frame, so the balloons shrink with the row and nothing stretches
       on one axis. At 1560 the content box is 1452px and the stage scale goes
       1.343 -> 1.117, i.e. the cluster is about 17% smaller as well as tighter. */
    --who-wide-max: 1560px;
    max-width: var(--who-wide-max);
    margin-inline: auto;
    padding-inline: var(--gutter);
  }

  .who-layout {
    /* 2026-07-27: 252px -> 330px. The rail's type went up (Daniel: "text on
       this section is too small for the size") and 252px was already the
       tightest width that kept "Trauma, burnout & psychosis" on one line at
       17px, so the heading would have wrapped immediately. Widening column 2
       moves its LEFT edge left and leaves its RIGHT edge exactly where it was,
       so the block's centre does not move and tools/who-center-probe.mjs still
       reads 0.0px offset. The gap it costs the stage is bought back many times
       over by the composition shrink above. */
    /* 2026-07-27, second pass: a hard 330px becomes clamp(330px, 22vw, 470px).
       The independent composition review found a 384px dead band between the
       cluster's right edge and the copy at 2560 -- 45% of the section's width
       is now empty vertical bands, up from 38% -- and named the rail, not the
       cluster, as the cheapest lever. It is right, and widening this track is
       the one move that closes the gap without touching the composition:
       widening column 2 moves only its own LEFT edge, so the block's right
       edge and therefore its centre do not move, and tools/who-center-probe.mjs
       still reads 0.0px at every width (re-run and confirmed).

       vw-scaled with a floor and a ceiling rather than a single number,
       because the room to spend only exists at the top of the range. At 1600
       the floor still binds (330px) and the band is byte-identical to before,
       which matters: the review called 1440 to 1700 the best this section has
       ever looked and the fix must not touch it. The ceiling is set by the
       cluster, not by taste -- the balloons' chamber wall is design x 910, and
       470px is the widest track that still leaves a clear gap between that
       wall and the copy at every width from 1600 up.

       It also fixes a third thing the review flagged: a 28px lead in a 330px
       column is a 24-character measure. See .who-lead below. */
    /* 2026-07-27, the 1600 step: the floor goes 330px -> 386px, and the 386 is
       arithmetic rather than taste. Everything else about this track is
       unchanged -- 22vw overtakes the floor at 1755, so 1755 and up (including
       1900 at 418px and 2552 at the 470px ceiling) render byte-identically to
       before.

       WHY. This block moves the copy column's RIGHT edge 86px right at 1600,
       because .who-container stops using the shared .container's centring and
       adopts the evenly redistributed --who-wide-pad instead. That is the
       documented, deliberate price of "centred" over "aligned to the shared
       rail" recorded at the end of the comment above, and it is not being
       undone here. But the copy's LEFT edge is what the reader actually sees
       move, and it is (right edge - this track), so widening the track by
       exactly the 86px... in practice 52px, measured: panelLeft was 1065 at
       1598 and 1099 at 1600, and 386px lands it back on 1065. The column's own
       right edge still moves; the block of text no longer jumps sideways.

       This costs the stage NOTHING. .who-stage spans grid-column 1/-1, the
       whole row, so its width is not a function of either column's width (see
       the long comment on .who-layout above, and the wasted pass recorded
       there that proved the same thing about column-gap). Widening column 2
       only moves where the copy starts. Measured with
       tools/who-breakpoint-step.mjs. */
    grid-template-columns: minmax(0, 1fr) clamp(386px, 22vw, 470px);
  }

  /* The lead's 21ch cap was sized for the old 330px column and is what was
     actually producing the four-line wrap, not the column width; widening the
     track alone would have left the measure exactly where it was. */
  .who-lead { max-width: 24ch; }

  /* Deliberately .who-layout .who-stage and not a bare .who-stage: this whole
     wide-viewport block sits ABOVE the .who-stage rule that declares the
     default, so at equal specificity source order would hand the later
     declaration the win and this override would silently never apply. It did
     exactly that for one round of this pass and the measured content scale at
     2560 came back as the 1440 value. */
  /* 2026-07-27: .70 -> .730 AT THE BREAKOUT ONLY, then a short ladder back down
     to .70 by 1900 (below). This is the fix for the visible snap when a window
     is dragged across 1600.

     THE SNAP, MEASURED (tools/who-breakpoint-step.mjs, 1598 -> 1600):
       section height  1147.6 -> 1084.6   (-63px, the loudest symptom)
       content scale     .7315 ->   .7011 (-4.2%, the whole composition)
       stage width        1132 ->   1302  (the breakout itself)
       compose             .84 ->    .70

     The stage's height is config.height * (stageWidth / 1300) * compose, so the
     row widening by 15% and compose dropping by 17% do not cancel: they leave a
     4.2% rescale of every balloon, the arch and the stage box in the space of
     two pixels of viewport. .730 is the compose value that makes the product
     continuous at 1600 exactly (.7315 / (1302/1300) = .7304), so nothing about
     the composition's SIZE changes as the boundary is crossed.

     WHY A LADDER AND NOT A clamp(). --who-compose is a UNITLESS custom property
     -- who.js multiplies the design grid by it and .who-paper-scrap /
     .who-paint-tube multiply percentages by it -- and CSS calc() cannot divide
     a length by a length to produce a number, so there is no legal way to ramp
     a unitless value against 100vw. (Giving it a unit would parse fine in
     who.js, which uses parseFloat, and would immediately break the two
     percentage multiplications.) Six declarations at 60px intervals hold every
     remaining step to about 0.8% of the content scale, roughly 9px of section
     height, against the 63px it replaces. 1900 and above are unchanged at .70,
     and nothing below 1600 is touched at all -- the review's "1440 to 1700 is
     the best this section has ever looked" band only moves in the direction of
     the 1440 sizing it was praising. */
  .who-layout .who-stage { --who-compose: .730; }
}

/* The ladder back down to .70. See the comment on --who-compose just above for
   why this is six media queries and not one clamp(). */
@media (min-width: 1660px) { .who-layout .who-stage { --who-compose: .724; } }
@media (min-width: 1720px) { .who-layout .who-stage { --who-compose: .718; } }
@media (min-width: 1780px) { .who-layout .who-stage { --who-compose: .712; } }
@media (min-width: 1840px) { .who-layout .who-stage { --who-compose: .706; } }
@media (min-width: 1900px) { .who-layout .who-stage { --who-compose: .70; } }

/* ---------- Letterboxing at 2000 and above ----------

   The independent composition review's second finding: the section went from
   1792px tall to 1326px at 2560 when the composition shrank, so its aspect
   went 1.43 to 1.93 and a 233px arch now sits in a wide thin band at 9.1% of
   the section's width, down from 13.0%. Its conclusion, which matches what the
   screenshots show: "the letterboxing hurts about as much as the shrink".

   Height is given back as PAPER, not by regrowing the composition. Undoing the
   shrink is explicitly off the table -- at 1440 it is exactly right and the
   review called that width the best this section has ever looked -- and the
   whole point of the arch cap in who.js is that the doorway must not grow with
   the viewport. What was actually lost is the quiet margin the chapter used to
   stand in, so that is what comes back: +72 top and +64 bottom, 136px in all,
   inside the 120-180px the review asked for.

   Scoped to 2000px and up so that nothing between 1440 and 1700 moves, and
   written as its own block above the base .who rule's padding rather than
   folded into that clamp, so the base value stays readable as one number.
   Both edges still land on bare paper well before either seam lip -- the
   chapter's washes and the light-field mask are proportions of the section's
   own height, so a taller section makes those envelopes longer, never shorter,
   and tools/ramp-joins.mjs is the check. */
/* 2026-07-27: ramped rather than switched, and moved from 2000 to 1900.

   Sweeping the whole 1500-2000 range with tools/who-breakpoint-step.mjs while
   fixing the 1600 snap turned up a SECOND and larger one here that had not been
   reported: the section's height went 1133.2 -> 1297.8 across 2000, a 164.6px
   jump, against the 63px one at 1600. Same defect, same cause -- a hard step in
   a value that everything else scales continuously against.

   Both edges are plain lengths, so unlike --who-compose above these CAN be a
   clamp(). Each one starts at exactly the value the base rule's own clamp is
   already pinned to by 1435px (132px top, 108px bottom), so 1900 renders
   byte-identically to before and nothing below it is touched at all; each
   reaches its authored ceiling a little past 2100, so 2200 and 2552 are also
   unchanged. Only 1900-2100 moves, and there it now grows into the letterboxing
   allowance instead of arriving at it in one frame.

   The vw coefficients are just the two-point line through (1900, floor) and
   (ceiling): 32vw - 476px passes through 132 at 1900 and 204 at 2125; 26.7vw -
   399px through 108 at 1900 and 172 at 2139. The clamp ends do the rest.
   tools/ramp-joins.mjs still applies unchanged -- the chapter's washes and the
   light-field mask are proportions of the section's own height, so a taller
   section makes those envelopes longer, never shorter. */
@media (min-width: 1900px) {
  .who { padding-block: clamp(132px, calc(32vw - 476px), 204px) clamp(108px, calc(26.7vw - 399px), 172px); }
}

/* ---------- The composition shrink ----------

   Daniel, 2026-07-27: "Arch is and balloon animation is way too large shrink
   about 30% but keep centred. Weight it correctly against the text."

   --who-compose is the single knob. who.js reads it every layout() and
   multiplies the whole design grid by it: card boxes, label type, the arch, the
   rope geometry, both canvases and the stage's own height, all by the same
   factor, then offsets the result so the tether knot and the arch's centre land
   on exactly the pixel they did before. Nothing about the section's own box
   moves, so tools/who-center-probe.mjs still measures 0.0px block offset at
   every width and --who-wide-pad is untouched.

   Two values rather than one, because the bug was width-dependent. Above the
   1600px stage breakout the row keeps widening and the balloons grew with it
   (304 to 371px wide on his 2560 display against a 252px copy rail), so that
   band takes the full 30%. Below it the section was already close to balanced
   and 1440 was never one of the widths complained about, so it takes a lighter
   16% and the copy rail's own size increase does the rest of the rebalancing.
   Tried .78 there first and reverted it: at 1440 the copy column then measured
   908px tall against a 782px stage and the whole bottom-left quadrant went
   empty, which trades one weighting complaint for its mirror image. Mobile has
   its own value further down. */
.who-stage {
  --who-compose: .84;
  grid-column: 1 / -1;
  grid-row: 1;
  position: relative;
  width: 100%;
  height: auto;
  min-height: 1px;
  overflow: clip;
  /* No explicit z-index and no isolation: either one alone is enough to give
     this element its own stacking context, and a stacking context with
     nothing opaque painted inside it gives the grain overlay a transparent
     backdrop to blend with, turning soft-light into a flat grey veil.
     position:relative (with z-index left at auto) still anchors the
     absolutely-positioned cards and arch without creating that context, and
     the panel column still paints above this stage because it carries its
     own explicit z-index. .content-sheet already forms a stacking context
     above this section, so blending stays contained to the narrative sheet
     regardless. This is an installation on the shared paper, not a second
     paper card. */
  background: transparent;
}

/* who.js measures the STAGE's own rendered width and paints this canvas at
   that size (sizeCanvas()), so no width/height/attribute box mismatch trap
   applies here the way it once did for the (now removed) WebGL colour field
   -- see the retirement note in tools/who-field.mjs for that history. */
.who-ink-field,
.who-tethers,
.who-mobile-filter {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.who-ink-field { z-index: 1; }
.who-tethers { z-index: 5; }

/* Full SECTION box, not the stage: it is a child of <section class="who">,
   which is position:relative, and the last child so it paints over the
   chapter's own art and under .who-panel (z-index 20), exactly where the
   stage-scoped version sat relative to the balloons.

   It runs the sitewide contract now -- --grain-size / --grain-blend /
   --grain-opacity -- instead of the local 180px / .2 it carried while it was
   a stage overlay. Measured with tools/grain-consistency.mjs, that .2 was the
   only light-section consumer disagreeing with the rest of the page, and the
   old box covered about 63% of the section's height, so the finish visibly
   changed where the stage ended. The url() is spelled out rather than
   tokenised: a relative url() inside a custom property resolves against the
   stylesheet that USES it, so a --grain-url token would 404 from here.
   No isolation and no background here: .who is not a stacking context, so
   this soft-light tile blends against .content-sheet's opaque background,
   which is what tools/blend-safety.mjs verifies. */
.who-grain {
  position: absolute;
  inset: 0;
  z-index: 15;
  pointer-events: none;
  background: url('../../assets/img/grain.png') center / var(--grain-size) repeat;
  mix-blend-mode: var(--grain-blend);
  opacity: var(--grain-opacity);
}

.who-mobile-filter {
  width: 0;
  height: 0;
  overflow: hidden;
}

/* ---------- Anchor arch ----------

   2026-07-27 rework: the CSS shadow/glass/ink/pin stack this comment used to
   describe is gone, replaced by the real render at site/assets/who/arch.webp
   (405x523). The image already carries its own contact shadow and glass
   bevel baked in, so no separate shadow/glass layer is needed on top of it.

   The container (.who-arch) is still sized in px by who.js from
   config.arch.x/y/width/height, but the WIDTH changed from 200 to ~333 design
   units (height held at 430) so the box's aspect ratio (333/430 = 0.774)
   matches the source image's true 405/523 = 0.774 instead of squashing a
   405-wide photograph into a 200-wide slot. x moved from 360 to 293.5 to keep
   the box CENTRED on the same anchor.x (460) the knot uses, so nothing about
   the rope geometry moved: the knot, floor, dragFloor and rescueFloor are all
   defined off anchor.y and the arch's TOP edge, neither of which changed,
   only its width did. See the comment above DESKTOP/MOBILE in who.js.

   Resolution and the size cap: who.js writes the container's left/top/width/
   height every layout() from config.arch.x/y/width/height * this.scale,
   where this.scale is the STAGE's own rendered width (the whole row, gap and
   panel included -- see the wide-viewport comment on .who-layout above)
   divided by the 1300-unit design grid. Below the 1600px stage breakout that
   stayed close to 1:1 and the image rendered a little below its native
   405x523, a genuine downscale. Above 1600px the row keeps widening with the
   viewport and the arch scaled right along with it -- Daniel's "the arch is
   too big" at 1900px, and a real regression: the balloons are meant to grow
   with the wider stage, the arch is not. who.js caps the SCALE used for the
   arch's own width/height at ARCH_SCALE_CAP (see the comment on that constant
   and on layout() there), while its left/top still track the FULL uncapped
   scale, so the box stays centred on the same anchor.x and its top edge never
   moves; only the growth below the cap is what stops.

   THE CAP IS 0.70, NOT 1. This paragraph said 1 and "never renders past its
   own authored 333x430" until 2026-07-29, which was true only of the cap's
   ORIGINAL value. Daniel changed it the same day the composition shrank --
   "arch is and balloon animation is way too large shrink about 30% but keep
   centred" -- so above the breakout the arch renders at 233x301, i.e. 0.70 of
   333x430. Read ARCH_SCALE_CAP in who.js as the single source of truth; do not
   trust a number quoted here. See tools/who-geometry.mjs for the measured
   rendered size at each width. */

.who-arch {
  position: absolute;
  z-index: 6;
  pointer-events: none;
  transform-origin: top center;
}

.who-arch__render {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  /* Safety net only: the container is sized to the image's own aspect ratio
     above, so contain should never need to letterbox in normal operation. */
  object-fit: contain;
  pointer-events: none;
}

/* ---------- Glass balloons ---------- */

/* Entrance start scale. Read once by who.js; see playCards(). */
.who-stage { --who-enter-from: 0.28; }

.who-card {
  --ring: 236, 95, 156;
  --ring-deep: 201, 73, 127;
  position: absolute;
  z-index: 8;
  display: block;
  margin: 0;
  padding: 0;
  border: 0;
  /* The transplanted glass material's geometry: a fixed-px rounded
     rectangle, not the old percentage-based organic blob (see the blob-N
     rule block below for why px, not %, this time). */
  border-radius: var(--who-blob-r, 27px);
  color: var(--ink-900);
  background: transparent;
  cursor: grab;
  /* 2026-07-30, mobile remediation pass (Agent WHO2). Was `pan-y`, which was
     the bug: on Chrome for Android the browser decides whether a touch
     begins a native scroll FROM touch-action, before who.js's own
     pointerdown/pointermove/preventDefault ever gets a say, and pan-y tells
     it a vertical drag on this element is exactly that -- a scroll to hand
     off. Measured with tools/who-real-touch-drag.mjs, which drives real
     touch input through CDP Input.dispatchTouchEvent (a JS-dispatched
     PointerEvent, what tools/who-touch-drag.mjs uses, never reaches the
     compositor's gesture arbitration at all and reported a false pass
     against this exact bug -- see that file's own header): a touch on a
     card's centre with a vertical component scrolled the page (scrollY
     4508 -> 4433) while the balloon only partially followed (30 design
     units, not a settled drag), which is precisely Daniel's "sometimes
     works" -- the small pre-recognition moves reach who.js, then the
     compositor claims the gesture and the rest goes to scrolling instead.
     `none` stops the browser claiming ANY native gesture starting on a
     card, so who.js's own handler owns the whole sequence. Scoped to the
     card itself, not the stage or the section, so scrolling everywhere
     else in Who is untouched -- verified by the same probe's "touch away
     from any card" case. */
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  /* `transform` only, and dropping `opacity` from this list is load-bearing,
     not tidying. `will-change: opacity` makes the element a BACKDROP ROOT in
     Chrome exactly as a real opacity < 1 would, permanently -- and a backdrop
     root is opaque to every backdrop-filter inside it. That is what was
     silently eating the frost: with `opacity` in this list, .who-card::before
     rendered its 14% milk and nothing else however large its blur, and a sweep
     from blur(0) to blur(96px) moved the measured transmission by 0.0000
     (tools/who-frost.mjs, and the sweep is reproducible from its own header).
     Removing it took the same card from 0.455 to 0.036.

     The same fault was also holding .who-card__surface::before's own
     saturate/contrast inert for the whole life of the V6 material, for the
     same reason and one level further in. So this line restores the rim band
     as well as enabling the frost.

     `transform` is NOT a backdrop root, so the per-frame transform who.js
     writes still gets its compositor hint. The entrance's opacity tween loses
     its hint, which is the trade: an opacity animation on seven small
     elements is not what costs frames here, and while it runs the card is
     genuinely below opacity 1 anyway, so it is a backdrop root for those few
     hundred milliseconds regardless of what this property says. */
  will-change: transform;
}

.who-card--coral { --ring: 244, 136, 92; --ring-deep: 217, 102, 60; }
.who-card--violet { --ring: 123, 111, 208; --ring-deep: 106, 91, 201; }

/* ---------- THE FROST, and why it is on .who-card and not on the surface ----

   Daniel, 2026-07-27, twice: "Balloons need more frost on the main section of
   it so add more blur to it", then "it should be all the balloons are blurred
   more than are too see through all of them". The face of every card, not its
   rim.

   THE TRAP THIS AVOIDS. `backdrop-filter: none` on .who-card__surface is
   load-bearing (see the V6 material block below): any non-none value there
   makes that element a backdrop ROOT, and a backdrop root is opaque to its own
   descendants -- the nested ::before could then only ever sample the surface's
   own 3%-alpha background instead of the page, and the material collapses.
   Nothing about that changes here. The frost is not a descendant of the
   surface; it is a SIBLING of it, .who-card's own ::before, painted at z-index
   0 against the surface's 1. So:

     - it samples the true page backdrop, because .who-card is not a backdrop
       root (it has no filter and no non-1 opacity at rest);
     - it cannot make the surface a backdrop root, because it is not an
       ancestor of anything;
     - the paint order that makes ::after the crisp wall is untouched. The
       order is now frost(0) -> surface(1) + its ::before band + its ::after
       wall -> ring(3) -> label(4). ::after still paints last inside the
       surface and still owns the outer edge, so the wall stays crisp: it is
       four tiers of zero-blur inset box-shadow, drawn ON TOP of the frost,
       and no amount of backdrop blur underneath can soften a box-shadow.

   inset 0 with `border-radius: inherit` on purpose, rather than holding the
   frost off the rim: an unblurred perimeter window is exactly the "clear
   edges" reading Daniel already asked to shrink in round 4. Frosting the whole
   body and letting ::after's hairlines draw the edge is what makes it read as
   a thick slab with a polished lip rather than a clear pane with a fat border.

   THE BLUR NUMBER IS MEASURED, NOT NOMINAL, and this matters. Chrome's
   backdrop-filter blur is not a Gaussian of the declared standard deviation:
   probed against a 40px stripe pattern in real headed Chrome
   (the measurement and its sweep live in tools/who-frost.mjs), a
   declared blur(10px) behaved like a sigma of roughly 5.8px. So the useful
   figure is transmission -- how much of the backdrop's own contrast survives
   through the card's face -- and 34px is the value that took the seven desktop
   balloons from 0.75 (a clear window) to the frosted reading below. Tune this
   with tools/who-frost.mjs, not by eye.

   RELATED, AND WORTH KNOWING BEFORE ANYONE "FIXES" THE RIM: `url()` filter
   references DO NOT WORK inside backdrop-filter in Chrome. CSS.supports()
   answers true and getComputedStyle echoes the declaration back, but the
   filter is not applied -- measured directly: `url(#f) blur(10px)` and
   `blur(10px)` attenuate a stripe pattern identically (0.5516 vs 0.5408), and
   `url(#f) saturate(1.18) contrast(1.03)` matches `saturate(1.18)
   contrast(1.03)` exactly (0.8446 vs 0.8466). So the ::before band's
   feDisplacementMap refraction has never rendered in Chrome; what that band
   actually contributes is its own warm 12% fill plus saturate/contrast. It is
   left exactly as authored -- it is doing real work and this pass is not the
   place to re-cut the V6 material -- but do not spend a session tuning
   #who-refract-desktop expecting the screen to change. This is the same class
   of fault as .ways-card's blur(23px) computing to none, one level deeper:
   here the computed value is honest and the PAINT is the lie.

   The milk is as important as the blur. Real frost scatters light forward, so
   it lightens as well as softens; a pure blur just smears. The warm 14% fill
   is also what buys the label its contrast back, since the type is dark ink on
   this surface -- measured, the labels GAIN headroom rather than spend it.

   SCALED WITH THE CARD, NOT FIXED. A single fixed blur reads as thick glass on a
   250px balloon at 2560 and as an opaque pebble on a 120px one at 390, so the
   blur tracks --who-content-scale, the REAL composed content scale who.js
   already publishes on .who-stage for the probes (stage width / design width,
   times --who-compose). Custom properties inherit, so the cards read it for
   free. That holds blur at a constant ~13.5% of each card's own rendered width
   at every desktop width and in the 768-980 tablet band; mobile has its own
   base number because its design grid is 390 units wide rather than 1300, so
   the same scale figure does not mean the same thing there. */
.who-card {
  --who-frost-blur: calc(14px * var(--who-content-scale, 1));
  --who-frost-milk: rgba(255, 251, 244, .14);
}

.who-card::before {
  content: '';
  position: absolute;
  z-index: 0;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: var(--who-frost-milk);
  -webkit-backdrop-filter: blur(var(--who-frost-blur)) saturate(1.12);
  backdrop-filter: blur(var(--who-frost-blur)) saturate(1.12);
}

/* Every desktop balloon gets its own corner radius, same as before, but the
   VALUES changed in the 2026-07-27 rework, transplanted from
   tmp/glass-lab/glass-lab.html's round-2 V6 (Codex-signed-off): a fixed px
   ROUNDED RECTANGLE, not the old organic-blob percentages. Percentages that
   stretch per axis cannot guarantee an actual rounded rect at this scale, and
   the transplanted material's ::before refraction ring depends on a true
   concentric corner (see --who-blob-r consumed there). Sized to roughly a
   fifth of each card's own height, the same ratio the lab used (27px on a
   150-tall card), with a few px of manual variation per card so eight
   balloons of different heights do not all read as one stamped shape.
   who.js writes no radius at all; see the note in layout(). */
.who-card--blob-1 { --who-blob-r: 26px; }
.who-card--blob-2 { --who-blob-r: 25px; }
.who-card--blob-3 { --who-blob-r: 30px; }
.who-card--blob-4 { --who-blob-r: 23px; }
.who-card--blob-5 { --who-blob-r: 29px; }
.who-card--blob-6 { --who-blob-r: 24px; }
.who-card--blob-7 { --who-blob-r: 27px; }
.who-card--blob-8 { --who-blob-r: 31px; }

.who-card__surface,
.who-card__ring {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
}

/* ---------- The transplanted glass material ----------

   2026-07-27: replaces the Hero credential pill's borrowed material wholesale
   with tmp/glass-lab/glass-lab.html's round-2 V6, the winner Codex signed off
   on after four review rounds ("Lip thickness: Pass... Glass vs plastic:
   Pass... Specular warmth: Pass... Rounded-rectangle geometry: Pass"). Values
   below are V6's verbatim, mapped onto this section's real class names.
   Three things carried over from the lab are load-bearing, not stylistic:

   1. backdrop-filter: none on .who-card__surface ITSELF is deliberate, not a
      leftover. Codex round 3: "any non-none backdrop-filter creates a
      backdrop root in Chrome. The nested ::before filter cannot reliably
      reach [the backdrop] through that parent." The clarity of the card's
      face comes from having no blur device of its own between it and the
      true backdrop, not from a near-zero blur.
   2. Paint order. The crisp "wall" (the tiered inset box-shadows that used to
      live on .who-card__surface's own box-shadow) moved to a NEW ::after,
      which paints AFTER ::before by normal DOM order. .who-card__surface's
      OWN box-shadow is now only the two external contact shadows. Lab round
      3 traced a real bug to the old order: ::before's feDisplacementMap was
      sampling (and warping) the already-painted bright hairline when it was
      still part of the parent's own box-shadow.
   3. The lip is sized for THIS section's card scale (roughly 218-268 wide,
      122-150 high) -- a 9px well and a 2.5px inset (round-4: halved from an
      18px well; see the round-4 note on ::before below -- Daniel's own
      reference has a thin crisp edge, and the rework-2 wash-out fix had
      left this section's band wider than the lab's). Do not reuse these
      numbers on a materially smaller card without scaling them down further
      (the lab's own note: about 0.55x off the ORIGINAL 18px figure). */
.who-card__surface {
  z-index: 1;
  overflow: hidden;
  background: rgba(255, 253, 248, .03);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  /* External contact shadows only; the wall lives on ::after now. */
  box-shadow:
    2px 3px 7px -2px rgba(108, 78, 66, .28),
    13px 24px 38px -18px rgba(138, 96, 88, .22);
}

/* THE REFRACTION BAND. A padding-ring pseudo, masked so only the ring itself
   (not the card's interior) carries the genuine feDisplacementMap distortion
   -- #who-refract-desktop in 06-who.html, the same technique
   #who-refract-mobile already uses for the mobile cards, just confined to a
   perimeter band here instead of applied to the whole face. inset is 2.5px so
   the true outer edge (painted by ::after, below) stays outside the filter's
   reach; border-radius is the card's own radius minus that inset, so the
   ring's corner arc is genuinely CONCENTRIC with the card's true corner
   rather than sharing a centre only by coincidence.

   rework-2: background alpha .06 -> .09. tools/who-edge-contrast.mjs measured
   the rim reading crisp in the lab (painted paper) but washing to a soft
   frosted rectangle wherever the live WebGL colour field runs bright behind
   a card (ratio as low as 0.32x, bright-field edge step vs calm-field). The
   lab's own busy-backdrop test never ran against colours this pale: the
   field is deliberately subtle so the paper stays visible through it, which
   means the swing between "bright" and "calm" patches under a real card is
   smaller in absolute RGB terms than the lab's more saturated test swatch,
   so the band needed more of its OWN presence to stay legible either way.
   This is still the band, not the interior: the mask below excludes the
   card's own content-box, so nothing here touches how clear the middle of
   the glass reads.

   round-4: padding 18px -> 9px. Daniel, reviewing on his own 2560 display:
   "I also think you didn't get the glass right, the clear edges are too
   big." Comparing tmp/glass-lab/screenshots/FINAL-r2-rowH.png's actual V6
   card against the live section confirmed it: the lab's rim reads as a thin
   double-line on a ~280px-wide card (roughly 10-12px), where rework-2's own
   fix for the wash-out problem (widening the band's PRESENCE) had also left
   the band wider than the lab's reference at this card's own ~230-270px
   scale. Halving the well, per Daniel's own suggested starting point, brings
   it back toward the lab's proportion; the background alpha goes up slightly
   (.09 -> .12) so the now-thinner band keeps the presence rework-2 bought
   without spending it on width. */
.who-card__surface::before {
  content: '';
  position: absolute;
  inset: 2.5px;
  border-radius: calc(var(--who-blob-r, 27px) - 2.5px);
  padding: 9px;
  background: rgba(255, 248, 238, .12);
  -webkit-backdrop-filter: url('#who-refract-desktop') saturate(1.18) contrast(1.03);
  backdrop-filter: url('#who-refract-desktop') saturate(1.18) contrast(1.03);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
}

/* THE WALL. Four tiers of zero-blur inset box-shadow (a wide faint contour,
   then a warm/cool tier, then a bright/dark hairline pair) instead of one
   flat ring: zero blur keeps every tier a hard-edged crescent rather than a
   soft wash, and layering several at different offsets is what builds a
   perceived bevelled WALL rather than a single stroke. Painted on ::after,
   not folded into the parent's own box-shadow -- see point 2 above. This is
   what replaces the old diagonal sheen highlight; that device is gone.

   rework-2 pushed every tier's alpha up (roughly 1.4-1.6x) so the wall would
   survive a bright field rather than just calm cream. round-4 keeps that
   alpha (this IS the "buy the edge back from the edge treatment, not the
   interior" instruction, and it is still working -- see
   tools/who-edge-contrast.mjs) but HALVES every tier's spread, matching the
   ::before change above and Daniel's "the clear edges are too big": the wide
   20px contour that did most of the visual "fattening" comes down to 9px,
   and the hairline pair tightens from 2-3px to 1.5-2px with its own alpha
   nudged up so the line stays crisp rather than just fainter. */
.who-card__surface::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow:
    inset 0 0 0 9px rgba(112, 94, 82, .11),
    inset 4px 4px 0 0 rgba(255, 246, 232, .17),
    inset -4px -4px 0 0 rgba(100, 80, 70, .13),
    inset 1.5px 1.5px 0 0 rgba(255, 244, 226, .52),
    inset -2px -2px 0 0 rgba(90, 70, 60, .27);
}

/* THE ACCENT RING. Round-5, Daniel: "can the balloons have a nice small
   outline with glow that corresponds to the group colour it comes from?" The
   lab-era crescent above (a single directional inset shadow, no border, no
   all-around glow -- kept the ring from reading as a UI/neon halo) is still
   here as the inset line, but it alone was too quiet to answer this ask: it
   only showed on the bottom-right edge and needed hover to read at all. This
   adds the two things Daniel actually asked for, ALWAYS on rather than
   hover-gated, using the same --ring/--ring-deep tokens the tether ropes
   already draw from (no new palette):
     1. a thin permanent border in the group colour -- the "outline"
     2. a small soft OUTER box-shadow -- the "glow"
   Both are box-shadow/border, never filter: drop-shadow(). A filter on this
   element or any ancestor of .who-card__surface would make Chrome treat that
   ancestor as its own backdrop root and the frost stops filtering anything
   behind it -- see the will-change note on .who-card below, which is the same
   trap. Kept deliberately small (1px border, single-digit blur/spread) so it
   reads as colour embedded in the glass rather than a glow overlay sitting on
   top of it; hover keeps its existing "slightly stronger", not a new effect. */
.who-card__ring {
  z-index: 3;
  border: 1px solid rgba(var(--ring), .34);
  box-shadow:
    0 0 8px 0 rgba(var(--ring), .22),
    inset -5px -5px 0 -1px rgba(var(--ring), .09);
}

/* ---------- The label face ----------

   Daniel: "there was another font we used in the balloons I think it was the
   bottom font codex created from a font selector looked hand written."

   It is Caveat, and the memory is exact. The canonical prototype
   (Final Handoff/Ink & Intuition Who v2.dc.html) carries a `labelFont` enum
   whose ten options end with Caveat -- the LAST entry in the selector, and the
   only handwritten face in it. The prototype's own spec for it, verbatim from
   that file, is `{f:"'Caveat',cursive", s:29, w:600, i:0, t:'0'}`: weight 600,
   not italic, and zero tracking, all three of which differ from the Instrument
   Serif italic 400 at -.01em this rule used to carry. Hence the explicit
   `font-style: normal` and `letter-spacing: 0` below rather than just swapping
   the family: both would otherwise have been inherited straight from the rule
   this replaces.

   Declared as a section-scoped custom property rather than a sitewide token
   because Caveat is used here and nowhere else, and css/tokens.css is shared.
   The stack falls back to the site's own serif rather than to a generic
   `cursive`, which resolves to Comic Sans on Windows: a half-second of
   Instrument Serif during the swap is a far better failure than that. Loaded
   from the single Google Fonts link in index.template.html at wght 600 only,
   the one weight this uses. */
.who { --who-label-font: 'Caveat', var(--font-serif); }

.who-card__label {
  position: relative;
  z-index: 4;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  padding: .45em .6em;
  color: var(--ink-900);
  font-family: var(--who-label-font);
  font-style: normal;
  font-weight: 600;
  line-height: 1.02;
  letter-spacing: 0;
  text-align: center;
  text-wrap: balance;
}

.who-card:hover .who-card__ring {
  border-color: rgba(var(--ring), .5);
  box-shadow:
    0 0 13px 1px rgba(var(--ring), .32),
    inset -5px -5px 0 -1px rgba(var(--ring), .17);
}
.who-card.is-dragging { cursor: grabbing; }

.who-card--mobile { display: none; }

/* ---------- Studio objects ----------

   The tactile counterweight to the glass: a torn paper scrap and a paint
   tube, sitting on the surface behind the balloons and above the ink canvas
   (z-index 2, between .who-ink-field at 1 and
   .who-tethers/who-arch/who-card at 5/6/8). Decoration only -- aria-hidden,
   pointer-events:none and empty alt are on the <img> itself in 06-who.html.
   Sized with aspect-ratio rather than a height percentage: the stage box is
   not square (1300 x 1152), so a height% would distort a square source image
   differently than a width% would, the same elliptical-radius trap the arch
   note elsewhere in this file warns about. Both source images already carry
   a baked-in soft drop shadow, which is what makes them read as sitting ON
   the paper instead of floating -- no CSS drop-shadow filter is added here,
   which would only double it and cost a compositor layer for nothing. */
.who-paper-scrap,
.who-paint-tube {
  position: absolute;
  z-index: 2;
  display: block;
  pointer-events: none;
  object-fit: contain;
}

/* Mid-left, partly behind the leftmost (pink) balloon, per Daniel's
   reference. round-4: width 17% -> 24%. Daniel, on his own 2560 display:
   "make the paint tube and paper bigger... too centred and small." Pushed
   as far left as the stage's own left edge (0%, was 1%): the stage clips its
   own overflow, so this is the honest limit -- any further negative would
   just crop the art rather than buy more room. */
/* THE STUDIO OBJECTS SHRINK WITH THE COMPOSITION TOO, and this is the only
   place that has to be said in CSS rather than JS. Everything else on the stage
   is positioned and sized by who.js from the design grid, so --who-compose
   reaches it automatically; these two are authored as percentages of the
   STAGE's own box, which did not change size, so without this they would have
   stayed at their old rendered width and gone from reading as a counterweight
   to the balloons to being bigger than any of them.

   `left` reproduces the same mapping who.js applies to every design x:
   x * compose + originX, with --who-origin-x published by layout(). `top` needs
   no correction at all, because the composition contracts toward the stage's
   own top edge and the stage's height is scaled by exactly the same factor, so
   a percentage of it is already right. */
/* 2026-07-27, Daniel: "Double the size of the paper and paint tube push more
   to the left."

   Scaled ON TOP of the --who-compose relationship rather than instead of it,
   so both objects still contract with the composition exactly as the block
   above describes: 24% -> 48% and 19% -> 38%, both still multiplied by
   --who-compose, so at the desktop 0.70 they render at 33.6% and 26.6% of the
   stage where they used to render at 16.8% and 13.3%. Exactly double, measured
   with output/cohesion/who-review/objects.mjs.

   HOW FAR LEFT IS SAFE, MEASURED RATHER THAN GUESSED. Both sources are 1000px
   squares with the art floated inside a transparent margin: paper.webp's ink
   starts at x=70 (7.0% of its own width) and paint.webp's at x=48 (4.8%). The
   stage clips its own overflow, so a negative `left` eats that margin first
   and only starts cropping ART once it exceeds it. At the new 48% width, 7% of
   the image is 3.4% of the stage, so -3% keeps the paper's own drawn edge
   inside the stage with a little to spare, and the same -3% against the tube's
   38% width and 4.8% margin leaves an even wider margin. Nothing is cropped at
   any width; tools/who-frost.mjs reports the alpha-box left edge in stage
   coordinates as the regression check.

   The tube keeps OVERLAPPING the paper rather than sitting beside it (an
   explicit earlier instruction): both scale about the same origin-tracking
   left edge and the tube's top stays inside the paper's vertical span, so the
   overlap grows with them instead of being a coincidence of two fixed boxes.
   Its `top` comes down 39% -> 34% because doubling the height alone would have
   walked the tube off the bottom of the composition. */
/* 2026-07-27, later the same day, Daniel again: "in the balloon section need
   to move paint tube and paper to the left more it's clashing with the arch.
   Move it about 150px to the left and rotate the paper a little." At the
   doubled size above the tube's tail now reaches into the arch and the
   doorway, so both objects move further left again -- the SAME -12.2
   percentage-point delta on each, since a shared delta (not a shared percent)
   is what keeps a constant px gap between them and therefore keeps the tube
   overlapping the paper exactly as it did before this change. -12.2% of the
   stage's own width times --who-compose is 150px at his 2560 display
   specifically (stage 1750px wide there, compose .70: 1750 * .70 * .122 =
   149.5px); it reads as a smaller absolute move at narrower widths because
   the stage itself is narrower there, which is the same "read of the right
   look at 2560, not a literal value everywhere" allowance round 4 and the
   doubling pass both already used. Measured with
   output/cohesion/who-aim/objects-check.mjs: the paper's own drawn ink (not
   its transparent box) still clears the stage's left clip edge by 39px at
   2560, 35px at 1900, 22px at 1440, comfortably inside the "a little to
   spare" margin the doubling pass already established, and the paint tube by
   even more. Nothing crops; tools/who-collide.mjs's 768 arch/tether-note
   check (see the tablet band below) still passes at -18. */
.who-paper-scrap {
  left: calc(var(--who-origin-x, 0px) + -15.2% * var(--who-compose, 1));
  top: 12%;
  width: calc(48% * var(--who-compose, 1));
  aspect-ratio: 1 / 1;
  /* -7deg -> -13deg: Daniel, same message, "rotate the paper a little" -- a
     steeper lean so the scrap reads as a sheet that fell and settled at an
     angle, not a rectangle placed square-ish on the surface. */
  transform: rotate(-13deg);
}

/* Bottom-left, overlapping the paper's lower edge, pointing up and to the
   right, per Daniel's reference. round-4: width 13% -> 19%, and the rotation
   comes down from 30deg to 2deg. The SOURCE image (site/assets/who/
   paint.webp) already draws the tube on its own ~30 degree diagonal, tail
   at lower-left and nozzle at upper-right -- exactly "pointing up and to
   the right" with no rotation needed. The old rotate(30deg) was stacking a
   second 30 degrees on top of that, so the tube rendered at roughly 60
   degrees, far steeper than intended; a small +2deg keeps the natural
   variation without the compounding. Repositioned (left 3% -> 1%, top 45%
   -> 39%) so the tail end actually sits UNDER the paper scrap's lower-right
   corner rather than below it with a gap -- confirmed with a cropped
   screenshot, not just the bounding-box math, since both source images
   carry a lot of transparent margin around the drawn object and a
   bounding-box overlap alone does not mean the ART overlaps. */
/* 2026-07-27: 19% -> 38%, and both offsets rescaled with it rather than left
   where they were. The tube's position was authored as a small offset from the
   paper (left +1% of stage width, top +15% of stage height), and those numbers
   only mean "just inside the paper's lower-left corner" AT THE OLD SIZE. Held
   fixed while both objects doubled, the tube would have slid up into the
   paper's middle and the overlap Daniel asked for would have become a
   near-total eclipse. So the offsets are re-derived as fractions of the
   PAPER's own box, which is the relationship that actually has to hold: the
   old left offset was 4.2% of the paper's width and the old top offset was 55%
   of the paper's height, and at the new 48% paper width those come out as +2%
   of stage width and +30% of stage height. Hence left -1% (paper's -3% plus
   2%) and top 42% (paper's 12% plus 30%). The tube's tail still sits UNDER the
   paper's lower-left region and its nozzle still clears the paper's edge to
   the lower right, which is the arrangement round 4 confirmed from a cropped
   screenshot rather than from bounding boxes. */
/* 2026-07-27, later the same day: -1% -> -13.2%, the same -12.2 point delta
   the paper scrap gets just above, so the two objects move left BY THE SAME
   PIXEL AMOUNT and the tube's offset from the paper (the relationship that
   actually produces the overlap) is unchanged. `top` is untouched: the
   overlap is a left/width relationship, and this move does not touch either
   object's height. */
.who-paint-tube {
  left: calc(var(--who-origin-x, 0px) + -13.2% * var(--who-compose, 1));
  top: 42%;
  width: calc(38% * var(--who-compose, 1));
  aspect-ratio: 1 / 1;
  transform: rotate(2deg);
}

/* 981-1599px: a real crop, found by fixing tools/who-frost.mjs's own object
   check to be rotation-aware (see that file's comment) rather than by eye.
   The -12.2 point delta above is safe at 2560/1900 because the wide-viewport
   breakout drops --who-compose to .70, pulling the objects back in; below
   1600px --who-compose is .84 (less contraction) against a narrower stage, so
   the SAME delta lands the paper's own worst pixel (measured off the source
   PNG's alpha channel, not the corner) 13px past the stage's clip edge at
   1440, and the tube's 44px past it. Both were invisible to eye at this
   crop's small size and only surfaced once artLeft was computed correctly.
   The correction is +6.7 points on both -- the tube's own larger shortfall,
   not the paper's smaller one, since a SHARED delta is what keeps the two
   objects' relative offset (and therefore the tube-over-paper overlap) the
   same one it is everywhere else. Verified with tools/who-frost.mjs: paper
   clears by 47px and the tube by 23px at 1440 after this. Not scoped to the
   768-980 tablet band below, which already has its own answer (a smaller
   WIDTH rather than a smaller offset) for a different collision (the tether
   note). */
@media (min-width: 981px) and (max-width: 1599px) {
  .who-paper-scrap { left: calc(var(--who-origin-x, 0px) + -8.5% * var(--who-compose, 1)); }
  .who-paint-tube { left: calc(var(--who-origin-x, 0px) + -6.5% * var(--who-compose, 1)); }
}

/* ---------- Copy and colour rail ---------- */

.who-panel {
  grid-column: 2;
  grid-row: 1;
  z-index: 20;
  align-self: start;
  position: relative;
  padding-top: clamp(24px, 6vw, 84px);
}

/* ---------- Type scale ----------
   Daniel, 2026-07-27: "Text on this section is too small for the size." It was,
   and the reason was structural rather than a bad choice: above the 1600px
   breakout the stage keeps growing with the viewport while this column was
   pinned at 252px with a 17px heading and 16px lists, so at 2200 and above the
   copy was a sliver beside a composition three times its width.

   Sized against the neighbours at the same viewport rather than by feel. The
   h2 cap lands at 60px, the same as .ways__heading's cap and above
   .approach-header-paper h2's 58px and .about-heading's 50px, which is the
   right order for a chapter of this weight; it does not shout past Ways.
   The rail keeps its own smaller scale, because it is a reference list and not
   body copy, but it is no longer smaller than the site's own body size. */
.who-heading {
  font-size: clamp(2.7rem, 4.2vw, 3.75rem);
  line-height: 1.05;
}

.who-lead {
  max-width: 21ch;
  /* 24px, inside the chapter's 20-28px heading-to-supporting-copy target. */
  margin-top: 24px;
  color: var(--ink-900);
  font-family: var(--font-serif);
  font-size: clamp(1.35rem, 2vw, 1.75rem);
  line-height: 1.38;
}

.who-lead em {
  /* This lead only reaches the 24px large-text threshold above about 1333px,
     so below that it is normal text and needs the deeper gradient. */
  background-image: var(--grad-text-deep);
  display: inline;
  padding-bottom: .06em;
  font-style: italic;
  line-height: 1.15;
}

.who-rail-panel { margin-top: clamp(34px, 4vw, 48px); }

.who-rail { position: relative; }

.who-rail__segment {
  position: absolute;
  z-index: 0;
  left: 3.5px;
  width: 3px;
  height: 0;
  border-radius: 4px;
  transform-origin: 50% 0;
}

.who-rail__segment--one { background: linear-gradient(180deg, var(--pink), var(--coral)); }
.who-rail__segment--two { background: linear-gradient(180deg, var(--coral), var(--violet-deep)); }

.who-rail__group {
  position: relative;
  z-index: 1;
  padding-left: 32px;
}

.who-rail__group + .who-rail__group { margin-top: 34px; }

.who-rail__dot {
  position: absolute;
  top: 4px;
  left: 0;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  box-shadow: 0 0 0 3px rgba(244,234,217,.9);
}

.who-rail__dot--pink { background: var(--pink); }
.who-rail__dot--coral { background: var(--coral); }
.who-rail__dot--violet { background: var(--violet-deep); }

.who-rail h3 {
  margin: 0;
  font-family: var(--font-sans);
  /* 17px -> 18px. Still below the 24px WCAG large-text threshold, so the three
     heading colours below are held to the same 4.5:1 they were measured
     against; nothing about their contrast headroom changes. */
  font-size: 1.125rem;
  font-weight: 600;
  line-height: 1.22;
}

.who-rail__group--pink h3 { color: var(--pink-link); }
.who-rail__group--coral h3 { color: #a33f28; }
.who-rail__group--violet h3 { color: #5d4fc0; }

.who-rail ul {
  display: grid;
  gap: 9px;
  margin: 11px 0 0;
  padding: 0;
  color: var(--ink-700);
  /* 16px -> 17px. */
  font-size: 1.0625rem;
  line-height: 1.42;
  list-style: none;
}

/* .who-replay is gone (2026-07-27). Daniel, asked directly, chose "drop the
   Replay button only", so the button, its wiring in who.js and its rules here
   have all been removed while the one-shot balloon entrance it re-triggered
   stays. Nothing is left parked at the entrance's start scale by the removal;
   tools/who-replay.mjs is the regression check and now asserts exactly that. */

.who-tether-note {
  position: absolute;
  z-index: 12;
  bottom: 4.2%;
  left: 5.2%;
  max-width: 28ch;
  margin: 0;
  color: var(--mood-line);
  font-family: var(--font-serif);
  font-size: clamp(.78rem, 1.1vw, .94rem);
  font-style: italic;
  line-height: 1.4;
  pointer-events: none;
}

.who-screen-reader-list {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.who-screen-reader-list--mobile { display: none; }

@media (max-width: 980px) and (min-width: 768px) {
  .who-layout { grid-template-columns: minmax(0, 1fr) minmax(264px, .4fr); column-gap: clamp(28px, 4vw, 48px); }
  .who-heading { font-size: clamp(2.35rem, 4vw, 3.125rem); }
  .who-lead { font-size: clamp(1.24rem, 2.4vw, 1.4rem); }
  .who-rail h3 { font-size: 1.0625rem; }
  .who-rail ul { font-size: 1rem; }
  /* ---- The 768-980 collision, and it is a real bug ----

     The independent composition review called 768x1024 the worst width on the
     site: the cluster's right edge measured 490 against a copy column starting
     at 444, a 46px OVERLAP, with Burnout and Psychosis printing over the h2
     and the lead. Reproduced with tools/who-collide.mjs at 768 (47px) and
     at 900 (5px, still touching). It predates the composition shrink and the
     shrink made it less bad, not worse.

     WHY IT HAPPENS. .who-stage spans grid-column 1/-1, the whole row, so the
     balloons' chamber -- design x 0..safeRight(910) out of a 1300 grid, i.e.
     70% of the row -- is mapped onto the FULL row width, while the copy column
     begins at its own 286px floor, about 58% of the row at 768. Seventy per
     cent cannot fit left of fifty-eight per cent. The desktop bands get away
     with it because --who-compose contracts the composition and originX pulls
     it back toward the arch; here compose was .88 and the contraction was not
     enough to clear the copy.

     THE FIX IS TWO NUMBERS, both of them already levers this file owns.
     Compose goes .88 -> .70, the same value the wide desktop band uses, and
     the copy column's floor goes 286px -> 264px, which moves the copy's LEFT
     edge RIGHT (a narrower column 2 in a fixed-width row starts further along)
     and buys back most of what compose alone could not. 264px is not a fresh
     guess: it is the figure this band's own earlier comment already recorded
     as its floor for keeping "Trauma, burnout & psychosis" on one line at its
     17px h3, which is a different font-size bracket from the desktop rail's.

     A THIRD LEVER WAS TRIED AND REVERTED, and it is worth recording so nobody
     spends the afternoon on it again. Compose alone would have needed roughly
     .60 to clear the copy, because the composition contracts toward design x
     460 (the arch's centre and the tether knot), so shrinking it pulls the
     left side right almost as fast as it pulls the right side left and the
     chamber's right wall barely moves. Contracting toward design x 0 instead
     spends the whole shrink on the colliding side, and a --who-compose-origin
     property was added to who.js to do exactly that. It worked -- 31px clear
     at 768 -- and it was still wrong: moving the origin drags the ARCH about
     29px left as well, straight under .who-tether-note's bottom-left corner,
     so the instruction line printed across the doorway's black interior.
     Trading a card/heading collision for a copy/arch collision is not a fix.
     Both the property and its JS reader are gone again. */
  .who-stage { --who-compose: .70; }

  /* The studio objects take a smaller multiple in this band than the 2x
     Daniel asked for, and the reason is room rather than taste. This is the
     narrowest stage that still shows them at all -- 692px at 768 -- and
     .who-tether-note lives in its bottom-left corner (bottom 4.2%, left 5.2%),
     which is the same corner the paint tube's tail occupies. At the full 48%/38% the tube's tail
     printed straight through that line at 768 -- 12.48px italic serif over a
     photographed foil tube, which is not readable. 34%/27% is about 1.4x the
     pre-pass size rather than 2x, and it keeps the bottom-left corner clear.
     The brief's own allowance applies: "double" is Daniel's read of the right
     look on his 2560 display, not a number to hit at a width where it does not
     fit. The full doubling is intact at every desktop width above this band
     and the objects are still dropped entirely below it. */
  .who-paper-scrap { top: 14%; width: calc(40% * var(--who-compose, 1)); }
  .who-paint-tube { top: 38%; width: calc(32% * var(--who-compose, 1)); }
}

@media (max-width: 767px) {
  /* Was the 80-96px band (21vw = 82px at 390). Daniel, on the 390 screenshot:
     "also too much spacing". Brought down to 64-84 (17vw = 66px at 390): this
     section stacks its copy above AND below the stage, so it is by far the
     tallest chapter on a phone, and it is the one place where the chapter's
     shared spacing target was buying nothing but scroll. */
  .who { padding-block: clamp(64px, 17vw, 84px); }

  /* The columns become a stack, so the copy rail moves from beside the balloons
     to underneath them and the desktop radial's left lobe would sit right on it.
     Re-centred and made shallow so the pigment stays inside the stage's band and
     has run out well before the rail begins. The vertical envelope on the cool
     layer is unchanged: it is keyed to the section, which is what the seam lips
     care about. The cream field is re-centred the same way and for the same
     reason, though it is the one layer that could safely reach the rail: it
     only adds light. */
  .who {
    background-image:
      radial-gradient(88% 30% at 50% 42%,
        var(--atmos-cream-3),
        var(--atmos-cream-2) 34%,
        var(--atmos-cream-1) 62%,
        var(--atmos-cream-0) 84%),
      radial-gradient(96% 32% at 50% 43%, var(--who-wash-lilac), var(--who-wash-lilac-0) 72%),
      linear-gradient(180deg,
        var(--who-wash-cool-0) 0%,
        var(--who-wash-cool) 30%,
        var(--who-wash-cool) 70%,
        var(--who-wash-cool-0) 100%);
  }

  .who-layout {
    display: flex;
    flex-direction: column;
    gap: 0;
    /* align-items:start is inherited from the desktop grid rule, and in a
       COLUMN flex container that is align-items:flex-start, which shrink-wraps
       every item to its own content width instead of stretching it. That is why
       the copy read as adrift on a phone: .who-title-group measured 236px and
       .who-rail-panel 255px inside a 350px column, both hard against the left
       gutter, so no amount of text-align or margin-inline:auto below could have
       centred anything. Measured, not guessed. */
    align-items: stretch;
  }

  .who-panel { display: contents; }

  /* ---------- Mobile weighting ----------
     Daniel, on the 390 screenshot: "also too much spacing and text should be
     centred i would say or better weighted everything is too large shrink the
     arch on mobile a touch too."

     CENTRED, BUT NOT ALL OF IT. The title group is genuinely centred: it is two
     short blocks of display type under a centred composition, and left-aligning
     them against a centred arch was the thing that read as adrift.
     The rail is NOT centre-aligned, because centring a bulleted reference list
     breaks the one thing it is for, which is scanning down a left edge; every
     item also hangs off a coloured dot and a connecting segment that only make
     sense against a straight rail. Instead the rail is centred as a BLOCK, so
     the whole group sits under the middle of the phone while its own text stays
     ranged left. That is the "better weighted" half of the instruction, and it
     is the half I think he actually wants; it is easy to flip to full centring
     by adding text-align:center here if not. */
  /* 2026-07-30, mobile remediation pass. Daniel, item 10, "across the board":
     "the card sizes could be smaller to give space to see more of the
     background animation on mobile. Especially the width, we need to see the
     hard work we put in the background animations." Applied here to the
     COPY, not the balloons: this section has no bordered "card" in its copy
     panel, but .who-title-group and .who-rail-panel below both stretched to
     the full column width (align-items:stretch on .who-layout's mobile flex
     column), leaving only a 6px inline pad before the ambient light field
     and paper texture. The balloon LABEL cards (Autism, ADHD, etc.) are
     explicitly excluded per the brief: they are physics bodies sized to the
     composition and to being a finger target, and shrinking them would
     change the cluster's balance and its collision behaviour, not just its
     footprint.

     84vw lands both panels at roughly 84% of the 390 viewport (measured with
     tools/who-mobile-width.mjs: the .container gutter and this vw width
     compound to a symmetric margin, since the panel is centred inside an
     already-centred column), inside Daniel's 82-86% band. align-self:center
     overrides the column's own stretch for these two items only -- the stage
     between them is untouched and still spans the full column, since it is
     the background this pass is trying to reveal more of, not narrow. */
  /* z-index 20 is the chapter's documented value for copy (see the stacking
     list at the top of this file: ink canvas 1, studio objects 2, tethers 5,
     arch 6, balloons 8, tether note 12, grain 15, copy panel 20). The title
     group never had it, and with no position it is not a positioned element at
     all, so the balloons at z-index 8 painted straight over it.
     That only became visible on a phone. The negative margin on .who-stage
     below is deliberate and its reasoning is sound, but its premise - that the
     stage's own first ~100px are always blank - does not hold here: measured on
     a real iPhone 17 Pro at 402x714 the top balloons settle within about 6px of
     the stage top, so a -34px pull put them roughly 27px into the lede and
     covered "identification is enough". Reproduced in local Chrome AND WebKit
     at 402x714 and 390x844, so it was never an iOS bug.
     Lifting the copy is the fix that keeps the spacing pass intact: the stage
     still tucks under the title exactly as intended, the balloons now pass
     BEHIND the words instead of over them, and no margin moves. Safe as a
     stacking context because this element contains only text - the section's
     one blended layer, .who-grain, is not inside it (CLAUDE.md gotcha 7). */
  .who-title-group { order: 1; align-self: center; width: 84vw; max-width: 84vw; padding-inline: 0; text-align: center; position: relative; z-index: 20; }

  /* ---- The spacing was inverted, not fixed ----
     Daniel asked for less spacing on mobile and got it in aggregate, but the
     review measured where the remaining slack actually sits: 85px between the
     lead and the first card and 18px between the stage's bottom and the rail.
     All of the air is above the composition and none is below it, so the rail
     reads as jammed against the doorway while the stage floats.

     The 85px is not margin, which is why turning margin down did not fix it:
     it is the empty top band INSIDE the stage. The mobile grid's cards rest at
     design y 20..35 of a 652-unit box and the physics settles them lower
     still, so the stage's own first ~100px are always blank. A negative
     margin-top pulls that dead band back up under the copy, where it does no
     harm, and the space it frees is handed to the rail below. Net section
     height is unchanged; only the distribution moves. */
  .who-stage { order: 2; margin-top: -34px; --who-compose: .90; }
  /* Narrowed the same way and for the same reason as .who-title-group above
     -- see that comment. The stage itself (the balloons and the background
     animation Daniel is asking to see more of) is deliberately NOT touched
     by this rule: it keeps spanning the column at its own --who-compose. */
  .who-rail-panel {
    order: 3;
    align-self: center;
    width: 84vw;
    max-width: 84vw;
    /* 18px -> 52px, funded entirely by the negative stage margin above. */
    margin-top: 52px;
    padding-inline: 0;
  }

  .who-rail {
    width: max-content;
    max-width: 100%;
    margin-inline: auto;
  }

  /* 9.5vw -> 11vw, i.e. 37.05px -> 42.9px at 390. The review found this
     heading had become the SMALLEST on the page after the desktop type pass:
     Who 37.05 against Ways 43.29 and About 39.00, which inverts the order
     those three chapters are meant to read in. 11vw puts it level with Ways
     and above About, matching the desktop ranking. The rem floor and ceiling
     move with it so the 320-360 band and the 430+ band stay in proportion. */
  .who-heading { font-size: clamp(2.5rem, 11vw, 2.85rem); }
  .who-lead { max-width: 26ch; margin-inline: auto; margin-top: 12px; font-size: clamp(1.1rem, 4.8vw, 1.18rem); }

  .who-card--desktop { display: none; }
  .who-card--mobile { display: block; }

  /* Rounded rect, same --who-blob-r mechanism the desktop blob-N classes use
     (2026-07-27), reduced to five: mobile's own cull, Autism/ADHD/Family &amp;
     care/Burnout/Psychosis. Sized smaller than desktop's, in proportion to
     these cards' own ~74-92px heights. */
  .who-card--shape-1 { --who-blob-r: 14px; }
  .who-card--shape-2 { --who-blob-r: 13px; }
  .who-card--shape-3 { --who-blob-r: 17px; }
  .who-card--shape-4 { --who-blob-r: 15px; }
  .who-card--shape-5 { --who-blob-r: 16px; }

  /* The frost's own base number for this mode. Mobile's design grid is 390
     units wide against desktop's 1300, so --who-content-scale means something
     different here and the desktop 14px base would leave a 120px card LESS frosted than a 250px
     one in absolute terms, which is the reading Daniel objected to. 12px puts
     mobile in the same absolute 9-13px blur band the desktop widths land in,
     which is what the eye actually judges, at the cost of a proportionally
     heavier frost on a smaller card. Measured transmission moves from 0.84
     before this pass to about 0.30. Mobile is in scope for this change: Daniel asked
     for "all the balloons", and tools/who-frost.mjs measured mobile as the
     LEAST frosted of the lot beforehand (transmission 0.84 against desktop's
     0.75) despite its surface declaring blur(10px) -- see the note below on
     why that declaration never painted. */
  .who-card { --who-frost-blur: calc(12px * var(--who-content-scale, 1)); }

  /* The blur(10px) in the chain below has never reached the screen, and this
     is worth knowing before anyone tunes it. Two separate faults stack here:
     Chrome does not apply url() filter references inside backdrop-filter at
     all (measured, see the frost block above), and this element also carries
     its own `filter`, which is what actually suppresses its backdrop-filter.
     Left as authored -- the gradient, the ring tint and the box-shadow stack
     ARE this material and they do paint -- but the real frost on mobile now
     comes from .who-card::before, which has neither problem. */
  .who-card--mobile .who-card__surface {
    background: linear-gradient(138deg, rgba(255,255,255,.56), rgba(var(--ring),.15) 54%, rgba(255,255,255,.22));
    -webkit-backdrop-filter: url('#who-refract-mobile') blur(10px) saturate(1.18) brightness(1.05) contrast(1.02);
    backdrop-filter: url('#who-refract-mobile') blur(10px) saturate(1.18) brightness(1.05) contrast(1.02);
    filter: url('#who-refract-mobile');
    box-shadow: 0 18px 40px -18px rgba(var(--ring-deep),.32), 0 5px 14px -8px rgba(42,34,48,.14), inset 0 1.5px 1px rgba(255,255,255,.92), inset 0 -1px 1px rgba(255,255,255,.35), inset 0 -4px 9px -4px rgba(var(--ring),.22), inset 0 0 22px rgba(255,255,255,.2);
  }

  /* Round-5 outline+glow (see the desktop .who-card__ring note above) reaches
     mobile too -- Daniel asked for "the balloons", no mobile exception, and a
     white-only rim would not "correspond to the group colour" here. Kept to
     the ring element alone: the crisp white border and the surface recipe
     above it are still exactly the untouched mobile material the note below
     is about. The outer glow is scaled down (4px vs desktop's 8px) for these
     ~70-95px-tall cards, same reasoning as the shape radii just above. */
  .who-card--mobile .who-card__ring {
    border-width: 1px;
    border-color: rgba(255,255,255,.72);
    box-shadow:
      0 0 4px 0 rgba(var(--ring),.24),
      inset 0 0 0 1px rgba(var(--ring),.17);
  }
  /* Mobile keeps its OWN lighter CSS/SVG glass treatment above, unchanged.
     The transplanted V6 material's ::before (perimeter refraction band) and
     ::after (the tiered wall) are new base-level rules calibrated for
     desktop's much bigger ~245-305px cards; at mobile's ~110-160px scale an
     18px well would read as oversized (the lab's own note: scale edge widths
     down about 0.55x if you ever need to reuse them smaller). Simpler and
     lower-risk to leave mobile's own recipe alone entirely and switch the new
     pseudo-elements off here, rather than retune a second copy of the well. */
  .who-card--mobile .who-card__surface::before,
  .who-card--mobile .who-card__surface::after { display: none; }

  /* The watercolour pools this mask was written for are gone. It still has a
     job: the only thing left on this canvas is the tether aura, and on a
     390-wide stage two of its three strokes are drawn from points outside the
     canvas (anchor.x - 278 and anchor.x + 202 on a stage 390 units across),
     so without the mask a 22-unit blurred stroke ends in a hard vertical cut
     at the canvas edge. Tethers use their own canvas and stay crisp. */
  .who-ink-field {
    -webkit-mask-image: radial-gradient(ellipse 72% 66% at 50% 48%, #000 34%, rgba(0,0,0,.76) 55%, rgba(0,0,0,.22) 76%, transparent 100%);
    mask-image: radial-gradient(ellipse 72% 66% at 50% 48%, #000 34%, rgba(0,0,0,.76) 55%, rgba(0,0,0,.22) 76%, transparent 100%);
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-size: 100% 100%;
    mask-size: 100% 100%;
  }

  /* The note used to live in the bottom-left corner of the stage, moved to the
     head of the stage when the arch was widened because its second line ran
     straight across the arch's ink, and comes back to the floor now that the
     2026-07-27 composition shrink has given that floor real room again: the
     arch's base sits 47px above the stage's bottom edge at 390 where it used to
     sit 23px above it, and the arch is 129px wide where it was 165px.

     Centred, unlike desktop's, because the whole mobile arrangement is now
     centred and because the head of the stage is no longer available: the first
     balloon's own top edge is only 19px below the stage's, so a centred note up
     there would print across the ADHD card. Ranged left at the top it merely
     missed it by luck. Measured with the mobile block of
     tools/who-rebalance-shots.mjs, which reports the clearance. */
  .who-tether-note {
    top: auto;
    bottom: 0;
    left: 0;
    right: 0;
    max-width: none;
    padding-inline: 12px;
    font-size: .78rem;
    text-align: center;
  }

  .who-paper-scrap,
  .who-paint-tube { display: none; }

  .who-rail__group { padding-left: 30px; }
  .who-rail__group + .who-rail__group { margin-top: 26px; }
  .who-rail h3 { font-size: 1rem; }
  .who-rail ul { gap: 6px; font-size: .9375rem; line-height: 1.45; }

  .who-screen-reader-list--desktop { display: none; }
  .who-screen-reader-list--mobile { display: block; }
}

/* ---------- Backdrop-filter reduction for constrained tiers ----------

   2026-07-30, mobile remediation pass. This file carries the most
   backdrop-filter declarations on the page (tools/canvas-backing-dpr.mjs's
   sibling count), and every one of them recomputes every physics frame,
   because the card underneath it never stops moving while the balloon
   simulation is active. `html[data-tier]` is written by site/js/device-tier.js
   before this stylesheet's own layout runs, so this needs no JS here.

   ONE cut, chosen for being provably free of legibility cost rather than for
   being the biggest number. `.who-card__surface::before` is the RING -- the
   perimeter refraction band -- and its own mask
   (mask-composite: exclude against a content-box layer, a few rules above)
   guarantees it paints ONLY the perimeter and never the card's interior,
   which is where `.who-card__label` lives. Removing its backdrop-filter
   therefore cannot touch label contrast: the two do not share a pixel. What
   it actually removes in Chrome is `saturate(1.18) contrast(1.03)` -- the
   docs/ENGINEERING-NOTES.md entry on this exact rule records that the url()
   feDisplacementMap reference it also carries has never rendered in Chrome
   at any tier, so no blur was ever part of what this cut takes away. The
   background fill (`rgba(255,248,238,.12)`) stays, so the rim keeps its
   warm tint; only the compositor's backdrop sample for the saturate/contrast
   pass goes.

   THE FROST ITSELF (.who-card::before) IS DELIBERATELY NOT TOUCHED HERE.
   It is the thing Daniel asked to be MORE present, twice, and the milk fill
   it carries is what buys the label its contrast headroom (see the note on
   .who-card above) -- reducing its blur radius without a full label-contrast
   regression sweep (tools/who-frost.mjs) is not a trade this pass makes
   blind. It is the single biggest remaining backdrop-filter cost in this
   file and is called out here so it is not forgotten, not because it was
   judged safe to cut.

   Gated at BOTH reduced and minimal, not minimal alone: 'reduced' is what
   every phone gets from device-tier.js (isMobile alone is enough), and
   Daniel's own report was a phone. 'minimal' inherits it for free since both
   selectors are listed. */
html[data-tier="reduced"] .who-card__surface::before,
html[data-tier="minimal"] .who-card__surface::before {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

@media (prefers-reduced-motion: reduce) {
  .who-card { cursor: default; }
}

@media (prefers-reduced-transparency: reduce) {
  /* The light field is not a transparency device (nothing shows THROUGH it, it
     adds warm light to the paper) so it is not switched off here, only brought
     down: a user asking for less transparency is asking for less see-through
     chrome, not for a darker room. */
  .who-lightfield { opacity: .34; }
  .who-lightmotes { opacity: .3; }

  .who-card__surface {
    background: #fffaf6;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    filter: none;
  }

  /* The frost is a transparency device by definition -- it exists so the page
     shows through the card at all -- so unlike the light field above it does
     go away entirely here, and the opaque #fffaf6 surface below stands in. */
  .who-card::before { display: none; }

  .who-card__surface::after { display: none; }
  /* backdrop-filter: none on the PARENT does not disable the pseudo-element's
     OWN filter -- ::before carries its own url(#who-refract-desktop) that is
     otherwise untouched by the rule above, so without this line
     reduced-transparency users would still get the refraction band. The
     arch is a plain <img> now (site/assets/who/arch.webp), so it needs no
     entry here; it never had a backdrop-filter to disable. */
  .who-card__surface::before { display: none; }
}

/* The forced palette is the whole point of this mode, so the ramp gets out of
   its way. It is decoration only: nothing in this section reads its colour.
   The grain goes with it for the same reason -- forced colours do not
   repaint a soft-light tile, they just leave a dirty film over a palette the
   user chose. */
@media (forced-colors: active) {
  .who { background-image: none; }
  .who-grain { display: none; }
  /* A backdrop blur under a forced palette just smears the colours the user
     chose; the card's own outline is what carries meaning in this mode. */
  .who-card::before { display: none; }
  /* Same reasoning as the grain: the forced palette is the point of this mode,
     and a warm light field is decoration nothing here reads meaning from. */
  .who-lightfield,
  .who-lightmotes,
  .who-lightfield-fallback { display: none; }
}
