/* ==========================================================================
   Moda Media — the operating system
   --------------------------------------------------------------------------
   Direction is fixed in DESIGN.md. This file implements it and nothing else.
   The whole page is drawn from six elements: node, connector, module,
   control, body, field. A seventh shape is a regression, so there is no
   place in here to define one.

   The previous dark stylesheet is retired (git history holds it). What is
   carried across is craft, not code: one documented law per decision, and a
   comment wherever a choice would otherwise look arbitrary.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. FACES
   Display is Bricolage Grotesque — a variable grotesque/geometric hybrid
   that carries idiosyncrasy at size and calms down small. opsz is
   deliberately never locked: font-optical-sizing defaults to auto and that
   axis doing its own work is the reason this face was chosen.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Bricolage Grotesque";
  src: url("fonts/bricolage-grotesque.woff2") format("woff2-variations");
  font-weight: 200 800;
  font-stretch: 75% 100%;
  font-display: swap;
}

@font-face {
  font-family: "Geist";
  src: url("fonts/geist-v5-latin-regular.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}

@font-face {
  font-family: "Geist";
  src: url("fonts/geist-v5-latin-500.woff2") format("woff2");
  font-weight: 500;
  font-display: swap;
}

@font-face {
  font-family: "Geist Mono";
  src: url("fonts/geist-mono-v1-latin-regular.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}

/* --------------------------------------------------------------------------
   2. TOKENS
   -------------------------------------------------------------------------- */

:root {
  /* --- palette. Measured pairings live in DESIGN.md; the one trap is
         cobalt on periwinkle (4.21:1, fails). Text on periwinkle is always
         --cobalt-deep. --- */
  --ground:      #EDE9FB;
  --ground-soft: #F7F6FC;
  --cobalt:      #3B44D6;
  --cobalt-deep: #2C34B4;
  --periwinkle:  #B9C7F6;
  --lime:        #E2EFC0;
  --blush:       #FCE4EA;
  --white:       #FFFFFF;

  /* Body text: a desaturated cobalt, never near-black. Recomputed properly:
     6.56:1 on lavender, 6.45:1 on lime, 4.67:1 on periwinkle. All three
     figures here were previously wrong — the periwinkle one was stated as
     4.42:1 and read as a failure, which it is not. It clears AA by 0.17,
     which is not enough room to spend, so text on periwinkle still uses
     --cobalt-deep at 5.57:1. */
  --ink-soft:    #4A4F7A;

  /* Shadow tints. Not palette additions: each is its own module's fill
     driven darker, so a module's shadow is always its own hue. */
  --shade-lime:  #C8DC9E;
  --shade-peri:  #9FB2EE;
  --shade-white: #D6D1EC;

  /* --- the drawing. Every scene reads these, so a ground can repaint the
         whole vocabulary by overriding three values. --- */
  --stroke:      var(--cobalt);      /* every outline, connector, node ring */
  --text:        var(--cobalt);      /* claims and headings */
  --text-body:   var(--ink-soft);    /* running text */
  --core:        var(--white);       /* node cores */
  --focus:       var(--cobalt);

  /* Line weight is 2px at every scale. Shapes carry non-scaling-stroke so a
     viewBox transform can never thicken or thin the system. */
  --w: 2;

  /* --- type. Six sizes, each at least 1.25x the step below, which is the
         hierarchy the previous version failed. Nothing else may be used. --- */
  --t-micro: 12px;                              /* mono, caps only */
  --t-body:  17px;
  --t-lede:  22px;
  --t-sub:   28px;
  --t-h2:    clamp(36px, 3.9vw, 54px);
  --t-claim: clamp(46px, 6.4vw, 92px);

  --f-display: "Bricolage Grotesque", "Geist", system-ui, sans-serif;
  --f-body:    "Geist", system-ui, sans-serif;
  --f-mono:    "Geist Mono", ui-monospace, monospace;

  /* --- space --- */
  --wrap:      1240px;
  /* Stated as a token because two rules have to agree about it: .wrap-wide's
     own width, and the hero's correction back onto the .wrap spine (section
     11). Two copies of 1440 is exactly the shape of duplication the --len law
     has already cost this page three times. */
  --wrap-wide: 1440px;
  --gutter:  clamp(20px, 5vw, 64px);

  /* --- the seam between two scenes. Three widths, not one.
         Until this round every scene carried the same 158px head and the same
         158px tail at 1440, which produced nine identical joints of 309 to
         319px and 2,810px of a 10,952px document: 25.7% of the page was the
         gap between its scenes. A constant applied without regard to what
         sits on either side is why the page read slow rather than calm, and
         it is also why the document grew with the viewport (9,524 at 1024,
         10,952 at 1440, 11,344 at 1600) — 11vw kept climbing long after the
         scenes themselves had stopped.

         A seam is now a property of the two scenes it joins. The measured
         assignment, scene by scene, is the table in section 5. Three values
         are enough to carry it, and each is flatter than the 11vw it
         replaces, so the page stops stretching as the window widens.

         At 1440: 74.88 / 100.8 / 123.84. At 1024 and below all three sit on
         their floors, which is deliberate: a narrow screen has no air to
         spend and the grounds are doing the room-change work anyway. --- */
  --seam-tight: clamp(56px, 5.2vw, 78px);
  --seam-base:  clamp(76px, 7.0vw, 104px);
  --seam-open:  clamp(96px, 8.6vw, 130px);
  /* The operating line's two horizontal measures, and they are two rather
     than one because they answer to different things. --op-gap is the grid
     gap between a station and its explanation, so it belongs to the viewport.
     --op-dot is the air between the branch's terminal node and the first
     glyph of the text, so it belongs to the type, which is 17px at every
     width — hence a fixed figure. The floor on --op-gap is 34 rather than 20
     because the branch is what is left after the dot and its air: at 3vw the
     gap is 23px at 768, which after a 10px node and 12px of air would leave
     the line shorter than the node sitting on it. */
  --op-gap:  clamp(34px, 3vw, 60px);   /* station to explanation */
  --op-dot:  12px;                     /* terminal node to first glyph */

  /* --- motion. Nothing over 400ms except a connector drawing its own
         path, which is the one duration the eye is meant to follow. --- */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --dur-fast: 140ms;
  --dur-base: 220ms;
  --dur-draw: 900ms;
}

/* --------------------------------------------------------------------------
   3. RESET — only what is actually needed
   -------------------------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--text-body);
  font-family: var(--f-body);
  font-size: var(--t-body);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
}

/* The same clip on html, 2026-09-01. This page runs content past the right
   edge on purpose (the hero ring's box reaches 1464 in a 1440 view), and
   body's clip above was meant to keep all of it off the scroller, but with
   the clip on body alone Chromium still leaves the ROOT scroller scrollable:
   measured in a hover-capable 390x844 window, scrollWidth read 400 against
   innerWidth 390 (the operating line's hover wash, .op-row::before at
   inset -30px, stands 30px past its row's right edge at 370), and a real
   scrollTo(10,0) stuck at scrollX 10 with the whole layout shifted. Touch
   devices never build that wash, which is why phones did not show it and a
   narrowed desktop window did. With the clip here too the root is not a
   scroll container at all, and measured after: scrollX pins at 0 at 390 and
   360, scrollWidth equals innerWidth, and the bleeds still paint to the
   edge and are cut there. clip and not hidden, because hidden is a scroller
   without scrollbars, which is the exact fault this repairs. The sticky
   masthead is safe since clip makes no scroll container: measured pinned at
   top 0 through a 2000px scroll before and after. */
html { overflow-x: clip; }

h1, h2, h3, p, dl, dd, dt, ol, ul, figure { margin: 0; }
ol, ul { padding: 0; list-style: none; }
img, svg { display: block; max-width: 100%; }
button, input, textarea { font: inherit; color: inherit; }

/* Every interactive element gets a visible ring. Non-negotiable. */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
  border-radius: 2px;
}
:focus:not(:focus-visible) { outline: none; }

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 99;
  padding: 12px 18px;
  background: var(--cobalt);
  color: var(--white);
  text-decoration: none;
}
.skip:focus { left: 12px; top: 12px; }

.vh {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   4. TYPE
   Huge expressive claim, tiny technical label. That contrast carries every
   scene, so both ends of it are defined once here.
   -------------------------------------------------------------------------- */

.claim, .h2, h1, h2 {
  font-family: var(--f-display);
  font-weight: 700;
  color: var(--text);
  line-height: 0.98;
  text-wrap: balance;
}

h1, .claim {
  font-size: var(--t-claim);
  letter-spacing: -0.04em;
  font-weight: 700;
}

h2, .h2 {
  font-size: var(--t-h2);
  letter-spacing: -0.03em;
}

h3, .sub {
  font-family: var(--f-display);
  font-size: var(--t-sub);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.14;
  color: var(--text);
}

.lede {
  font-size: var(--t-lede);
  line-height: 1.4;
  letter-spacing: -0.01em;
  color: var(--text-body);
  text-wrap: pretty;
}

.body { font-size: var(--t-body); text-wrap: pretty; }

/* The measuring voice. Caps and digits only — it never sets a sentence. */
.label {
  font-family: var(--f-mono);
  font-size: var(--t-micro);
  line-height: 1.35;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--stroke);
}

.label-quiet { color: var(--text-body); }

/* A placeholder is labelled in the UI itself, never dressed as real content.
   That requirement is absolute and it has not moved. What has moved is the
   volume.

   The blush badge was doing two jobs and only succeeding at one. It was
   unmistakable, and it was also the loudest thing in three separate scenes: a
   cold buyer read the pink as either sloppiness or a private joke, and seven
   of them rendered at rest on a page whose colour law allows blush three
   appearances anywhere. Restyling the atom is what fixes the breach, because
   the breach was never a colour choice — it was blush sitting inside the one
   element that repeats.

   So the tag speaks the page's own grammar instead. A dashed outline means
   "potential, not yet active" everywhere else on this drawing, and the marker
   is an empty node: the ring this system uses for an entity, with nothing in
   its core. Both say "something is coming here" without raising their voice,
   and the words in the tag still say it in English.

   Colour is the ground's own running-text ink, so one rule is correct on
   lavender (6.56:1), ground-soft, lime (6.45:1), periwinkle (cobalt-deep at
   5.57:1) and cobalt (lavender ink at 5.91:1) without a variant for each. */
.slot-tag {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 12px 4px 10px;
  border: 2px dashed currentColor;
  border-radius: 999px;
  background: transparent;
  color: var(--text-body);
  font-family: var(--f-mono);
  font-size: var(--t-micro);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
}
.slot-tag::before {
  content: "";
  width: 8px; height: 8px;
  border-radius: 50%;
  border: 2px solid currentColor;
}

/* --------------------------------------------------------------------------
   5. GROUNDS — the field element
   A ground repaints the vocabulary rather than the vocabulary being
   respecified per scene. Scrolling between them should feel like moving
   through rooms, so they alternate deliberately down the page.

   The rotation is a law rather than a taste. No two adjacent scenes share a
   ground, and --ground-soft, the weakest of the five, is spent at most
   twice. There are five grounds and there may never be a sixth: a seventh
   colour is on the banned list. Re-derived for eight scenes on 2026-08-31,
   the round that cut the Pierce scene (the selection argument on the
   roster's disc), one round after it was built, by the owner. Measured
   relative luminance, in document order:

     0  hero        lavender     0.8325     4  channels    lime         0.8171
     1  roster      periwinkle   0.5781     5  people      periwinkle   0.5781
     2  claim       cobalt       0.0992     6  network     ground-soft  0.9271
     3  method      periwinkle   0.5781     7  close       lime         0.8171

   Seven boundaries, each the difference of its two luminances. Differenced
   before rounding this time, which is why hero|roster reads 0.2543 where
   the old table's pre-rounded values gave 0.2544: the luminances above are
   the four-decimal displays of longer figures, and the boundary is the
   subtraction of the long ones.

     hero      | roster      0.2543      channels  | people      0.2389
     roster    | claim       0.4789      people    | network     0.3490
     claim     | method      0.4789      network   | close       0.1100
     method    | channels    0.2389

   --ground-soft lands exactly once, at network, one appearance against a
   cap of two. Periwinkle carries three scenes and sits beside itself
   nowhere. No boundary is zero.

   THE TIGHTEST BOUNDARY STAYS 0.1100, NETWORK INTO CLOSE. The cut closes
   the two seams the Pierce scene owned, 0.8279 above it and 0.3490 below,
   and opens claim into method at 0.4789, so the claim keeps deep silence
   on both of its sides and the page's floor does not move. The tightest
   seam has now run 0.0154 at nine scenes, 0.0946 at twelve, 0.2389 at ten,
   0.0947 at eleven, 0.1100 at nine again, and 0.1100 here.

   The network scene's own ground is forced rather than chosen: it has to
   differ from the periwinkle people above and the lime close below,
   lavender against that lime close would measure 0.0154, the tightest seam
   this page would ever have had, and cobalt is spent exactly once, on the
   claim. Ground-soft is what is left, and at 0.3490 above and 0.1100 below
   it earns the slot rather than merely filling it.
   -------------------------------------------------------------------------- */

/* THE SEAM TABLE. A scene declares the air it wants above it and the air it
   wants below it, and the joint a reader actually sees is the sum of the two.
   Written this way rather than as nine joint values because a seam has two
   owners: insert a scene, or merge two, and every joint either side of the
   change stays correct without being recomputed by hand. That is the same
   bookkeeping failure the rotation law above exists to prevent.

   The rule the assignment follows: tight after a dense scene, generous
   around a claim. Re-assigned 2026-08-30 for the nine-scene order, and
   three declarations moved with their reasons rather than surviving them:

   - selection dropped its tight head. It was tight because the scene
     followed 1,000px of roster; it follows the claim now, and the claim
     earns silence on both sides.
   - method dropped its open head, the same reason read the other way: that
     open head WAS the claim's silence, and the claim is two scenes up.
   - the numbers scene's tight tail died with the scene; the merged network
     scene declares nothing and takes base at both ends.

   Re-checked 2026-08-31, the round that cut the Pierce scene: that scene
   declared nothing, so no declaration dies with it; the claim's open tail
   now meets method's base head directly, and every other joint keeps both
   of its owners unchanged.

   What remains is unchanged and so are its reasons: method, channels and
   people are the three heaviest scenes on the page and run consecutively,
   so the joints inside that run stay the tightest on it; the claim keeps
   the two widest, one sentence on the only cobalt ground earning silence
   on both sides; the close keeps its open head, because the ask is what
   the whole page has been walking toward.

   The token values themselves are untouched from the round that fitted
   them, and that round's measurement stands as its record: seam sum over
   document height fell from 21.0%..26.3% to 13.9%..18.2% across five
   widths, measured on the eleven-scene page. The per-joint pixel table
   that stood here was taken against that page's order and died with it;
   what governs now is the declarations below, summed pairwise. */
.scene {
  position: relative;
  padding-block: var(--seam-head, var(--seam-base)) var(--seam-tail, var(--seam-base));
  background: var(--ground);
  isolation: isolate;
}

.claim-scene  { --seam-head: var(--seam-open);  --seam-tail: var(--seam-open); }
.what-you-get {                                 --seam-tail: var(--seam-tight); }
.channels     { --seam-head: var(--seam-tight); --seam-tail: var(--seam-tight); }
.people       { --seam-head: var(--seam-tight); }
.close        { --seam-head: var(--seam-open); }

.g-soft { background: var(--ground-soft); }

.g-cobalt {
  background: var(--cobalt);
  --stroke: var(--white);
  --text: var(--white);
  --text-body: var(--ground);   /* 5.91:1 on cobalt */
  --core: var(--cobalt-deep);
  --focus: var(--white);
}

.g-lime {
  background: var(--lime);
  --text: var(--cobalt);
  --text-body: var(--ink-soft); /* 6.10:1 on lime */
}

.g-peri {
  background: var(--periwinkle);
  --text: var(--cobalt-deep);
  --text-body: var(--cobalt-deep);  /* 5.57:1 — plain cobalt would fail */
}

.wrap {
  width: min(var(--wrap), 100% - var(--gutter) * 2);
  margin-inline: auto;
}

.wrap-wide {
  width: min(var(--wrap-wide), 100% - var(--gutter) * 2);
  margin-inline: auto;
}

/* --------------------------------------------------------------------------
   6. THE VOCABULARY, IN SVG
   Six elements. Strokes never scale, so the system reads as one drawing at
   any viewport. Classes are terse because they appear hundreds of times in
   the markup: n = node, c = connector, m = module, k = control, b = body.
   -------------------------------------------------------------------------- */

.sys {
  width: 100%;
  height: auto;
  overflow: visible;
}

.sys [class] { vector-effect: non-scaling-stroke; }

/* -- connector. 2px, rounded corners, solid = active, dashed = potential.
      Every connector starts and ends on a node; none float. -- */
.c {
  fill: none;
  stroke: var(--stroke);
  stroke-width: var(--w);
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* .c-soft (opacity 0.55, the depth device for a connector) died 2026-08-31:
   its last named user was the old selection drawing's outer rings, nothing
   in the markup wears it, and dead CSS dies with its markup. The measured
   finding it carried survives where the channels scene cites it: cobalt at
   0.55 reads as cobalt seen from further away, and anything under that
   drifts into pale lilac. */
.c-dash { stroke-dasharray: 3 7; }

/* -- node. A ring holding a core; means an entity. -- */
.n {
  fill: var(--core);
  stroke: var(--stroke);
  stroke-width: var(--w);
}
.n-fill  { fill: var(--stroke); }
.n-lime  { fill: var(--lime); }
.n-peri  { fill: var(--periwinkle); }
.n-core  { fill: var(--core); stroke: none; }

/* A node whose core is the stroke colour rather than the ground: the
   densest state in the system, used where an entity is fully committed. */
.n-fill-c {
  fill: var(--stroke);
  stroke: var(--stroke);
  stroke-width: var(--w);
}
.n-plus     { stroke: var(--stroke); stroke-width: var(--w); stroke-linecap: round; fill: none; }
.n-plus-inv { stroke: var(--core);   stroke-width: var(--w); stroke-linecap: round; fill: none; }
.g-cobalt .n-lime, .g-cobalt .n-peri { stroke: var(--white); }

/* -- module. A capsule or rounded rect doing work, with a soft same-hue
      shadow ~4px down. The shadow is drawn as a sibling shape rather than a
      filter so it stays crisp at any scale and costs nothing to composite. -- */
.m {
  stroke: var(--stroke);
  stroke-width: var(--w);
}
.m-lime  { fill: var(--lime); }
.m-peri  { fill: var(--periwinkle); }
.m-white { fill: var(--white); }
.m-fill  { fill: var(--stroke); }

/* A module's shadow is its own fill driven darker, so it has to be darker
   than whatever it lands on. --shade-white measures 0.6595 and periwinkle
   0.5781, so on the periwinkle ground a white shape's own shade would sit
   above its ground and read as a highlight. There, the shadow is the ground
   driven darker instead — which is what --shade-peri already is. */
.sh { stroke: none; }
.sh-lime  { fill: var(--shade-lime); }
.sh-peri  { fill: var(--shade-peri); }
/* White's own shade, for a white shape seated on something lighter than
   periwinkle. It measures 0.6595 against lime's 0.8171, so on a lime body it
   is a shadow; on periwinkle it would be a highlight, which is the rule two
   comments up and the reason this is the only ground it appears on. */
.sh-white { fill: var(--shade-white); }
.g-cobalt .sh-lime,
.g-cobalt .sh-peri { fill: var(--cobalt-deep); }

/* -- body. Large circles and discs; means scale, or a whole domain. -- */
.b-lime { fill: var(--lime); }
.b-peri { fill: var(--periwinkle); }
.b-cobalt { fill: var(--cobalt); }
.b-open { fill: none; stroke: var(--stroke); stroke-width: var(--w); }

/* Two body states the page had no name for until the close scene grew leaves.
   Every filled body so far has been a flat field carrying no outline, and
   every outlined body has been empty; a leaf is both at once. On the lime
   ground it has to be, because a lime fill on lime is not a shape at all —
   which is why the leaves take the three states the bodies already standing on
   that ground take: outlined and empty, periwinkle, or solid cobalt.

   Named after .n-fill-c, which is the same idea one shape down: a form
   carrying the stroke colour as its fill. They deliberately do not reuse
   .b-peri or .b-cobalt — the collision gate reads those two as background
   surfaces text is allowed to sit on, and an outlined leaf is not one. */
.b-fill-peri { fill: var(--periwinkle); stroke: var(--stroke); stroke-width: var(--w); }
.b-fill-c    { fill: var(--stroke);     stroke: var(--stroke); stroke-width: var(--w); }

/* -- field. A wash of ground colour a scene sits in, flat and at full
      strength. Faded fields were what cost this page its confidence: lime at
      0.62 composited to 0.8225 over lavender while periwinkle at 0.28
      composited to 0.8200 over ground-soft, so two different colours resolved
      to within 0.0027 of one another and the deck's flat saturated punch
      turned to mud. A field either earns its ground at full strength or it is
      removed. It is never faded until it agrees with its neighbour. The 0.28
      periwinkle was deleted on that finding rather than corrected. -- */
.f-lime  { fill: var(--lime); }
.f-peri  { fill: var(--periwinkle); }

/* -- text inside a drawing. There is none left, so the three classes that
      set it — .t, .t-on-peri and .t-lg — have gone with the hero legend, the
      last thing on the page using any of them; .t-on-peri and .t-lg were
      already unused before it went. Every readable word on this page is HTML
      pinned over a drawing, which is what section 13 exists for, and .t was
      also the one 11px figure on a page whose type scale starts at 12. If a
      diagram ever needs a word inside it again, that is the finding to
      revisit rather than this rule to restore. -- */

/* --------------------------------------------------------------------------
   7. THE VOCABULARY, IN HTML
   The same six elements where the content has to be real text: modules that
   hold copy, controls that can be operated, nodes that anchor a connector.
   -------------------------------------------------------------------------- */

.mod {
  position: relative;
  border: 2px solid var(--stroke);
  border-radius: 18px;
  padding: clamp(20px, 2.4vw, 32px);
  background: var(--white);
  box-shadow: 0 4px 0 var(--shade-white);
}
.mod-lime  { background: var(--lime);  box-shadow: 0 4px 0 var(--shade-lime); }
.mod-peri  { background: var(--periwinkle); box-shadow: 0 4px 0 var(--shade-peri); color: var(--cobalt-deep); }
.mod-pill  { border-radius: 999px; padding: 12px 26px; }

.g-cobalt .mod {
  background: transparent;
  box-shadow: 0 4px 0 var(--cobalt-deep);
}

/* The same law as the SVG shades: a white module keeps its white face on the
   periwinkle ground, but --shade-white sits 0.08 above periwinkle in
   luminance and would lift the module rather than seat it. The shadow
   becomes the ground driven darker. */
.g-peri .mod { box-shadow: 0 4px 0 var(--shade-peri); }

/* A module with its content still missing. Dashed for the same reason a
   connector is dashed — potential rather than active — and flat on the
   ground, because a shadow is what lifts a surface forward and there is
   nothing on this one yet to lift. Two class levels so it beats the grounds
   above, which set a face and a shade on every .mod they hold. */
.mod.mod-empty {
  border-style: dashed;
  box-shadow: none;
  background: transparent;
}

/* -- slot. An empty module that holds space for content somebody else has to
      supply. The two instances it was sized for, the campaign figures under
      the claim and the testimonial before the ask, have both been removed
      now that the placeholders they stood in for are gone from the live
      site, so nothing on the page currently wears this class. It stays as
      the shared primitive rather than going with them: the next real gap
      should reach for the same shape instead of a new one invented to fill
      it. -- */
.slot { max-width: 620px; }
.slot .label { display: block; }
.slot .slot-hold { display: block; margin: 14px 0; }

/* -- control. Toggles and plus/minus pills: the system reads as operable
      rather than decorative, so they respond on hover as if they could be. -- */
.ctrl {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 7px 12px;
  border: 2px solid var(--stroke);
  border-radius: 999px;
  background: var(--lime);
  box-shadow: 0 4px 0 var(--shade-lime);
}
.ctrl-track {
  width: 34px; height: 16px;
  border: 2px solid var(--stroke);
  border-radius: 999px;
  background: var(--white);
  position: relative;
}
.ctrl-track::after {
  content: "";
  position: absolute;
  inset: 1px auto 1px 1px;
  width: 10px;
  border-radius: 50%;
  background: var(--stroke);
  transition: translate var(--dur-base) var(--ease-out);
}
.ctrl:hover .ctrl-track::after { translate: 14px 0; }

/* --------------------------------------------------------------------------
   8. ACTIONS
   -------------------------------------------------------------------------- */

.cta {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 17px 30px;
  border: 2px solid var(--cobalt);
  border-radius: 999px;
  background: var(--cobalt);
  color: var(--white);
  font-family: var(--f-body);
  font-weight: 500;
  font-size: var(--t-body);
  text-decoration: none;
  cursor: pointer;
  box-shadow: 0 4px 0 var(--cobalt-deep);
  transition: translate var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
.cta::after {
  content: "";
  width: 9px; height: 9px;
  border-right: 2px solid currentColor;
  border-top: 2px solid currentColor;
  rotate: 45deg;
  translate: 0 1px;
}
.cta:hover {
  background: var(--cobalt-deep);
  translate: 0 2px;
  box-shadow: 0 2px 0 var(--cobalt-deep);
}
.cta:active { translate: 0 4px; box-shadow: 0 0 0 var(--cobalt-deep); }

/* On a cobalt ground the button inverts: the ground is already the ink. */
.g-cobalt .cta {
  background: var(--white);
  border-color: var(--white);
  color: var(--cobalt);
  box-shadow: 0 4px 0 var(--cobalt-deep);
}
.g-cobalt .cta:hover { background: var(--ground); }

.cta-quiet {
  background: transparent;
  color: var(--cobalt);
  box-shadow: 0 4px 0 var(--shade-peri);
}
.cta-quiet:hover { background: var(--cobalt); color: var(--white); }

.link {
  color: var(--text);
  text-decoration: none;
  border-bottom: 2px solid var(--stroke);
  padding-bottom: 2px;
  transition: opacity var(--dur-fast) var(--ease-out);
}
/* Fading to 0.62 dropped this to 2.82:1 on lavender — a hover state is still
   text and still has to be legible. Every .link on the page sits on a light
   ground, so cobalt-deep is safe and reads as pressed rather than faded. */
.link:hover { color: var(--cobalt-deep); border-bottom-color: var(--cobalt-deep); }

/* -- 44px on a phone, without the underline moving -----------------------  --
   Measured at 390: the text links were 26px and 32px boxes. They clear the
   24px minimum the harness checks and miss the 44px platform guideline, and
   the fix everywhere else on this page is padding plus an equal negative
   margin, which costs the layout nothing.

   It cannot be applied straight here, because the rule under a .link is a
   border-bottom and a border sits below the padding: symmetric padding would
   have dropped the underline by however much was added. So on a narrow screen
   the border is handed to a pseudo-element positioned from the box's own
   bottom edge, which the padding then cannot move. bottom: 10px is
   padding-bottom (12) less the 2px of clearance the border had, so the rule
   lands on the pixel it always did.

   10 above and 12 below rather than 11 and 11: the extra pixel goes on the
   side a thumb overshoots toward. 22px of growth takes the 17px links to 44
   and the one 22px link in the method scene's ask to 50, and both negative
   margins give every pixel of it back, so no line on the page moves.

   1024, NOT 760, AND THE 760 WAS THE MISTAKE. This was written against "a
   phone" and 760 is where this page stops drawing phone compositions, which
   is a fact about the drawings and not about thumbs. A tablet is touched the
   same way a phone is. Measured before this line moved: 390 had zero
   interactive elements under 44px, which was deliberate work, while 768 and
   1024 each had four, three of them .link and the fourth the footer's mail
   address. The same treatment, applied at the same widths the page already
   treats as touch-first, takes all four to 44 and above. The technique costs
   the layout nothing at any width, which is why the breakpoint can move
   without anything else being re-measured: every pixel of padding is handed
   straight back by an equal negative margin. */
@media (max-width: 1024px) {
  .link {
    display: inline-block;
    position: relative;
    border-bottom: 0;
    padding-block: 10px 12px;
    margin-block: -10px -12px;
  }
  .link::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 10px;
    height: 2px;
    background: var(--stroke);
  }
  .link:hover::after { background: var(--cobalt-deep); }
}

/* --------------------------------------------------------------------------
   9. MASTHEAD
   -------------------------------------------------------------------------- */

.masthead {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--ground);
  border-bottom: 2px solid var(--cobalt);
}

.masthead-in {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding-block: 14px;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  color: var(--cobalt);
  text-decoration: none;
}
/* The masthead mark was the one interactive element on the page with a
   focus ring but no hover. Both are required, everywhere. */
.brand { transition: color var(--dur-fast) var(--ease-out); }
.brand:hover { color: var(--cobalt-deep); }
/* The mark is 30px tall and the name's line box is 28px, so the whole target
   measured 142x28 at 390: clear of the 24px the harness checks and 16px short
   of the 44px platform guideline. Padding buys the height and the negative
   margin hands it straight back, which is the technique the footer link has
   used since it had the same problem; the masthead's content box is 54px
   here, so a 44px child fits inside it and the bar does not move by a pixel. */
.brand { padding-block: 8px; margin-block: -8px; }
.brand-mark { width: 30px; height: auto; color: inherit; }
.brand-name {
  font-family: var(--f-display);
  font-weight: 700;
  font-size: var(--t-body);
  letter-spacing: -0.01em;
}

.nav-cta {
  padding: 11px 22px;
  border: 2px solid var(--cobalt);
  border-radius: 999px;
  color: var(--cobalt);
  font-size: var(--t-body);
  font-weight: 500;
  text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.nav-cta:hover { background: var(--cobalt); color: var(--white); }

/* THE PILL MAY NOT SHRINK, AND THIS IS NOT A PREFERENCE.
   .masthead-in is a flex row with no flex-wrap, so an over-wide row does not
   wrap, it shrinks its items down toward min-content. With the short label
   that never bit. With "Get the Creator Activation Map" it would: the pill
   would compress toward the width of its longest word, the label would break
   to two or three lines inside it, and the masthead would grow from 82px to
   something over 110px.
   That is not a cosmetic failure. #brief, #method, #creators
   and the scrolled targets beside them all carry a hard-coded
   scroll-margin-top of 120px,
   and the .js-start handler's landing arithmetic is measured against an 82px
   bar. A masthead that grows puts every jump target back underneath it, which
   is the exact fault that margin was written to repair. And nothing catches
   it: flex items do not overlap, so the collision gate reports CLEAN
   throughout.
   nowrap plus shrink:0 is the fix, and it is stated on the mark too so the
   two ends of the row are pinned and only the nav in the middle can give. */
.nav-cta { white-space: nowrap; flex-shrink: 0; }
.brand { flex-shrink: 0; }

/* THE LABEL GIVES BEFORE THE PILL DOES. Measured 2026-09-01: the long label
   sets the pill at 294.2px, and with the brand ending at x=162.4 and the
   row's 20px gap floor the pill's right edge sat at 476.6 in a 390 viewport,
   86.6px past the edge; at 360 it was 116.6 past. body's own overflow-x: clip
   swallowed the excess, so the page's only always-visible CTA rendered as
   half a button on every phone, with nothing scrollable to reveal the rest.
   It had been so since round 12 gave the pill the long label.
   nowrap and flex-shrink: 0 above stay exactly as they are: a pill allowed to
   shrink wraps its label and grows the masthead past 82px, which strands
   every scroll-margin-top: 120px target back under the bar, and that is the
   arithmetic the block above carries. So the label gives instead. The row
   needs gutter + 142.4 (brand) + 20 (gap floor) + 294.2 (pill) + gutter,
   which against the 5vw gutters clears the wrap only from 507.3 up. Add 15
   for a classic scrollbar, which counts in the media query's width and not
   in the row's, and 12 of air, this page's floor, and the switch lands at
   534.3, stated as 540. At 540 and below the pill reads "Get the Map", 98px
   of text against the long label's 246.2; above it the full label, and
   nothing else, changes.
   The inline padding drops with the label because the short form alone is
   not enough at the floor: at 320 the row would set
   20 + 142.4 + 20 + 146.2 = 328.6 with the 22px padding, 8.6 past the
   viewport, and 12px of air needs the right edge at 308 or less, which caps
   the padding at 11.8 a side. 10 clears the floor by whole pixels rather
   than by a hair: right edge 304.6 at 320, one line, padding-block untouched
   so the pill keeps its 52.3px height. Both labels live in the markup and
   one is display: none at a time, so the accessible name is always exactly
   the visible text and there is no aria-label to fall out of sync. */
.nav-cta .cta-short { display: none; }
@media (max-width: 540px) {
  .nav-cta { padding-inline: 10px; }
  .nav-cta .cta-long { display: none; }
  .nav-cta .cta-short { display: inline; }
}

/* THE NAV DIED 2026-09-01, markup and rules together (.mast-nav, .nav-link
   and their 900px cut), by the owner in the round-15 pass: brand plus pill
   is the whole masthead now, on a page of eight scenes and one scroll. The
   row's justify-content: space-between takes over the job the group's
   margin-left: auto was doing, pinning the brand left and the pill right
   with nothing between them to place. What the nav taught stays where it
   still binds: every fragment target keeps id + tabindex="-1" +
   scroll-margin (section 9), and the pill keeps nowrap/flex-shrink:0,
   because the row can still only give in the middle. */

/* -- the scroll progress line -------------------------------------------- --
   The page's one piece of chrome that is not a scene, and the reason it is
   drawn here rather than anywhere below: it belongs to the masthead the same
   way the masthead's own rule does.

   Positioned absolutely inside the sticky masthead rather than fixed to the
   viewport, and that is deliberate rather than a shortcut. `position: sticky`
   establishes a containing block, and the masthead is pinned at top: 0 from
   the first pixel of the document, so absolute-inside-sticky and fixed
   resolve to the same screen position at every scroll offset — with one
   difference that matters: this one reads the masthead's own height instead
   of restating it as a hard-coded `top`. That height is 82.34px at 1440 and
   changes with the type scale; a `top: 82px` would be a second copy of a
   number the stylesheet already owns, and DESIGN.md has now paid three times
   over for exactly that shape of duplication (see the --len law).

   bottom: 0 puts it on the inside edge of the masthead's 2px cobalt rule, so
   progress reads as that rule thickening from the left. Two 2px lines, never
   one 4px line: the line-weight law is about a stroke that changes width with
   scale, and neither of these two ever does.

   It is aria-hidden and pointer-events: none because it carries nothing a
   reader needs and nothing anyone can operate. Scroll position is already
   conveyed by the scrollbar, natively and better. */
.prog {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  pointer-events: none;
}
.prog[hidden] { display: none; }

.prog-fill {
  display: block;
  height: 100%;
  background: var(--cobalt);
  transform-origin: 0 50%;
  scale: 0 1;
}

/* The masthead is sticky and stays pinned above everything below it, so any
   in-page scroll target needs its own scroll-margin-top, or a hash jump and
   scrollIntoView both land it flush against the viewport top, right under
   the masthead's own 82px, with the field the reader actually wants hidden
   behind it. No target on this page carried one until now. #brief, the
   casting-map form the three js-start CTAs all jump to, is the one target
   that exists, so the margin lives on it rather than as a blanket rule
   waiting for other targets to justify it. 82px of masthead plus a little
   air clears it with room to spare.

   Every later target takes the same 120px rather than a figure of its own,
   because the thing being cleared is the same masthead in every case and
   two numbers for one obstacle is how they drift. (#selection carried this
   margin from the round that proved the fault on it, a jump landing the
   heading under the bar, until 2026-08-31, when it left the list with the
   scene it named.)

   The margin is what makes the plain fragment link correct with no
   JavaScript at all; the focus half of the fix is a listener at the foot of
   index.html and adds nothing this rule needs. */
#brief, #method, #creators { scroll-margin-top: 120px; }
/* The two scrolled headings carry it too, because each click handler scrolls
   a heading rather than the form it focuses, so the landing has something to
   read at the top of it: #close-h for the .js-start route, #roster-h for the
   .js-call route that replaced the dialog on 2026-08-30. #cs-email is the
   call route's own no-JS fragment target and needs the margin for the plain
   jump. Same number, same masthead; stated rather than computed so the
   targets cannot drift apart. */
#close-h, #roster-h, #cs-email { scroll-margin-top: 120px; }

/* The landing point for that jump, and the reason it can be focused at all.
   A section is not focusable, so before this the hash jump left focus on
   BODY and the reader's next Tab went back to the top of the document, past
   everything they had just asked to be taken to. tabindex="-1" makes it a
   programmatic focus target only: it never enters the tab order, so the
   sequence a keyboard reader walks is unchanged, and the next Tab after the
   jump continues from the scene instead of from the masthead.

   No ring on it. The global rule above is for elements a reader can operate,
   and this one cannot be operated: it is a landing mark that holds focus for
   the length of one keystroke. Outlining a full-bleed section would also
   draw a 2px rectangle around a quarter of the page, which is not a focus
   indicator, it is a border nobody asked for. Every real control inside the
   scene keeps its own ring untouched. */
#method:focus, #method:focus-visible,
#creators:focus, #creators:focus-visible { outline: none; }

/* The list above was one id when there was one target. The masthead nav made
   it four, and the argument the comment above gives against a blanket rule
   ("#brief is the one target that exists") stopped being true the moment a
   reader could aim at three more. It is still stated as a list rather than as
   a rule on every section, because a scene that nothing points at should not
   silently acquire a scroll margin: an id here means somebody can arrive, and
   arriving is what the 120px is for. Every section in the list carries the
   same tabindex="-1", for the same reason in each case: a section is not
   focusable, so without it the jump leaves focus on BODY and the reader's
   next Tab goes back to the masthead. #why left the list on 2026-08-30 with
   the scene it named, and #selection followed on 2026-08-31 the same way.
   The nav itself died 2026-09-01; #method and #creators stay in the list
   because their ids stay in the markup, and an id is an address whether or
   not the page still links to it: a shared or bookmarked fragment arrives
   under the same masthead the 120px has always cleared. */

/* --------------------------------------------------------------------------
   9b. WHY NOW, CUT
   The scene stood here for one round: two blocks of type on the second
   ground-soft, between the hero and the roster. Cut 2026-08-30, the same
   round that re-derived the rotation for nine scenes: its claim moved into
   the hero's own offer line, the ground went back on the shelf, and the
   0.0947 boundary the rotation table had recorded as a debt is repaid.
   Both of the scene's type rules died with its markup.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   10. MOTION
   The settled composition is the default. Connectors are drawn, nodes are
   activated, orbital bodies are parked. Arrival is added only when the
   document is marked .motion, which happens exclusively in a browser that
   has not asked for reduced motion.

   Doing it this way means no-JS, reduced-motion and headless capture all
   land on the same finished picture, and none of the three is a degraded
   version of the others.
   -------------------------------------------------------------------------- */

/* THE UNIT LAW FOR --len, which cost this page every connector on it.

   Under vector-effect: non-scaling-stroke the renderer lays a dash pattern
   out in screen pixels, not in the viewBox's user units, and pathLength does
   not move it back. So --len has to be the path's length *as drawn*, and a
   --len taken from getTotalLength() is only right at the one viewport where
   the drawing happens to render at 1:1. Every other width is wrong twice
   over: too short leaves a stretch of the connector painted at t=0 that then
   never draws, and too long finishes the stroke early and leaves it sitting
   idle for the rest of its own duration.

   Hence the shape every rule below takes: --len is the path's user length
   over its viewBox width, times --map-w, the width that drawing actually
   renders at. The ratio is pure geometry and never changes; --map-w carries
   the whole of the viewport.

   --map-w is read off the box the drawing is in rather than restated as
   layout algebra, because the algebra is what drifts: a grid, a gap, a
   negative margin and a breakpoint all have to be kept in step by hand, and
   each of the seven scenes below would have needed its own copy of them.
   container-type: inline-size makes the container measurable from inside it,
   so 100cqw *is* the rendered width, exactly, at every width and with no
   second statement of the layout to fall out of date. The two scenes fixed
   before this pass state theirs the long way and carry 1.5% of overshoot to
   cover the scrollbar 100vw counts and .wrap does not; a container-relative
   width has no such gap, so these carry none. */
.motion .draw {
  stroke-dasharray: var(--len, 1);
  stroke-dashoffset: var(--len, 1);
}
.motion .is-in .draw {
  animation: draw var(--dur-draw) var(--ease-out) forwards;
  animation-delay: var(--d, 0ms);
}
@keyframes draw { to { stroke-dashoffset: 0; } }

/* A dashed connector has to draw in like any other and still be dashed when
   it lands. It was not: .motion .draw sets stroke-dasharray from --len and
   beats .c-dash on specificity, so in any browser that had not asked for
   less motion the hero's ring closure was a solid line for the whole life of
   the page — the one place on the drawing that says "potential, not yet
   active", saying the opposite. The pattern comes back the instant the
   stroke is complete, which is also the only moment it could. */
.motion .is-in .c-dash.draw {
  animation: draw var(--dur-draw) var(--ease-out) forwards,
             dash-back 1ms linear forwards;
  animation-delay: var(--d, 0ms), calc(var(--d, 0ms) + var(--dur-draw));
}
@keyframes dash-back { to { stroke-dasharray: 3 7; } }

.motion .rise { opacity: 0; translate: 0 14px; }
.motion .is-in .rise {
  animation: rise 380ms var(--ease-out) forwards;
  animation-delay: var(--d, 0ms);
}
@keyframes rise { to { opacity: 1; translate: 0 0; } }

/* A node activates once and then holds completely still. */
.motion .pop { opacity: 0; scale: 0.6; transform-box: fill-box; transform-origin: center; }
.motion .is-in .pop {
  animation: pop 320ms var(--ease-out) forwards;
  animation-delay: var(--d, 0ms);
}
@keyframes pop { to { opacity: 1; scale: 1; } }

/* Orbital drift — the one thing on this page allowed to keep moving after its
   scene has settled, and only for a body. Nodes activate once and hold still.

   Rotation, never translation or scale. Every stroke on the page is 2px held
   there by non-scaling-stroke, and a scale would make a line look heavier
   while it ran; a rotation cannot change a stroke's width, so this is the only
   transform the line-weight law leaves open.

   Applied to a group whose box is dominated by one symmetrical shape, it reads
   as travel rather than spin: the big shape turns about its own centre and so
   appears not to move, and anything sitting off that centre rides around it.
   The period is set per element and is prime-ish against its siblings, so no
   two drifting groups ever line back up. Under reduced motion the group is
   parked at 0deg, which is the position it is authored at — the settled
   picture and the screenshot are the same picture. */
.motion .drift {
  transform-box: fill-box;
  transform-origin: center;
  animation: drift var(--period, 47s) linear infinite;
}
@keyframes drift {
  from { rotate: 0deg; }
  to   { rotate: 360deg; }
}

/* Arrival for a group that has no path to draw and no single centre to pop
   from. The signal scene's noise is several hundred marks, and each of them
   arriving on its own would be several hundred delays for an effect nobody
   can resolve; the field arrives in bands instead, and staggering the bands
   across the drawing is what makes it read as noise gathering rather than
   noise appearing. */
.motion .settle { opacity: 0; }
.motion .is-in .settle {
  animation: settle 640ms var(--ease-out) forwards;
  animation-delay: var(--d, 0ms);
}
@keyframes settle { to { opacity: 1; } }

/* The noise once it has gathered. Opacity only: a mark that moved would be a
   mark that meant something, and these mean nothing individually — that is
   the point of them. It rides on a group inside .settle rather than beside
   it, because two animations writing the same property on one element leaves
   only the second, and the arrival has to finish before the breathing starts.
   Periods are prime-ish against each other, the rule --period already follows
   on .drift, so the bands never breathe in unison. */
.motion .is-in .shimmer {
  animation: shimmer var(--period, 47s) ease-in-out infinite;
}
@keyframes shimmer {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.74; }
}

/* The one ambient motion on this page that moves a shape rather than turning
   it. .drift is a rotation because a body has a centre to turn about; a trace
   has none, and a waveform that rotates stops being a waveform. So the two
   secondary traces sway instead, carrying their own end nodes with them so a
   connector never leaves the node it started on.

   The line-weight law the comment above .drift is protecting is intact here:
   what that law forbids is a transform that changes how heavy a 2px stroke
   looks, and only a scale can do that. A translation moves a 2px stroke to a
   new place at 2px. Eight units end to end over a minute and a half is under
   a fifth of a pixel a second — measurable, never watchable, and it cannot
   pull at the eye while somebody is reading the copy beside it. */
.motion .is-in .sway {
  animation: sway var(--period, 89s) ease-in-out infinite;
}
@keyframes sway {
  0%, 100% { translate: 0 -4px; }
  50%      { translate: 0 4px; }
}

/* The safety net, and the one rule that governs it: every arrival primitive
   above holds its element at a *start* state — a connector offset by its own
   length, a block at zero opacity, a node at zero and 0.6 — and the animation
   is the only thing that ever writes the settled value. So `animation: none`
   on its own does not stop the motion, it freezes the element mid-absence.

   This block only ever matches when the preference is switched on after the
   page has loaded, because .motion is granted in the head exclusively to a
   reader who has not asked for less motion. That is precisely the case that
   has to hand the composition back rather than blank it: three of the four
   primitives carry the whole drawing, and parking them at their start state
   turns the net into the failure it exists to prevent — every connector
   unpainted, every node and every rising block gone.

   So each one is parked at the value its own keyframe lands on. Read each
   pair against the @keyframes it mirrors; they have to stay in step. */
@media (prefers-reduced-motion: reduce) {
  .motion .draw   { stroke-dashoffset: 0; animation: none; }
  .motion .rise   { opacity: 1; translate: 0 0; animation: none; }
  .motion .pop    { opacity: 1; scale: 1; animation: none; }
  .motion .settle { opacity: 1; animation: none; }
  /* A dashed connector's pattern is written by dash-back, at the end of the
     draw and nowhere else. With no animation left to write it, the dasharray
     .motion .draw fills from --len is what remains and it beats .c-dash on
     specificity — the same solid ring closure the rule above dash-back was
     added to fix, arrived at by the other road. */
  .motion .c-dash.draw { stroke-dasharray: 3 7; }
  /* The ambient three are the exception: each is authored at a position its
     own loop passes through, so stopping the loop is the whole of the work. */
  .motion .drift, .motion .shimmer, .motion .sway { animation: none; }
}

/* --------------------------------------------------------------------------
   11. SCENE 1 — HERO
   Two columns that read as one field: the system bleeds past the right
   gutter to the viewport edge, so the ring is cropped rather than framed.
   Cropping is what makes it read as a machine that continues off-screen
   instead of an illustration sitting in a box.
   -------------------------------------------------------------------------- */

/* THE TOP PADDING ANSWERS TO THE HEIGHT OF THE WINDOW AS WELL AS ITS WIDTH.
   Everything above the hero's CTA is stacked, so every pixel of padding at
   the top is a pixel the button loses at the bottom, and the padding was
   derived from width alone: 6vw is 86px at 1440 and 96px at 1680 whether the
   window is 1080 tall or 720. A wide, short window is exactly the shape a
   laptop makes, and it was the shape being charged the most.

   min(6vw, 8vh) keeps the width term as the ceiling on a tall screen and
   lets the height take over on a short one: 8vh is 86px at 1080, 72px at
   900, 62px at 780 and 58px at 720. Continuous, so there is no breakpoint
   where the hero jumps, and it only ever gives room back. The floor stays
   56px so a very short window cannot close the gap under the masthead.

   Together with the headline rule below this is what moves the CTA from
   "visible at 900" to visible with 59px to spare at 780, at 1440. The bottom
   padding is untouched: it is below the fold at every height in question and
   it is what separates this scene from the next. */
.hero {
  padding-block: clamp(56px, min(6vw, 8vh), 96px) clamp(72px, 8vw, 128px);
  overflow: clip;
  /* The one gradient the direction permits: a ground wash soft enough to
     read as light rather than as a shape. Anything with a visible edge
     belongs in the drawing, not in the background. */
  background:
    radial-gradient(105% 85% at 82% 92%, rgba(185, 199, 246, 0.5), transparent 62%),
    var(--ground);
}

/* THE HERO SITS ON THE PAGE'S OWN LEFT MARGIN, AND UNTIL THIS ROUND IT DID NOT.
   Measured at 1440: the masthead logo's left edge was 100px, and so were the
   roster, the method scene, the stat row, the close and the footer, because all
   of them sit in a .wrap capped at 1240. The hero sits in a .wrap-wide capped
   at 1440, which at that viewport puts its left edge at 64. So the two largest
   elements above the fold were 36px out of line with each other and with
   everything below them.

   THE 36px WAS A BOX OFFSET, NOT SIDE-BEARING, AND THAT WAS CHECKED RATHER
   THAN ASSUMED. Bricolage Grotesque's left side-bearing on the H1's first
   glyph measures 1.31px at 92px, which is 0.014em: the ink starts at 65.31
   while the box starts at 64. The masthead mark's own ink starts 0.03px
   inside its box (the wordmark path's bbox begins at x=0.44 of a 456-unit
   viewBox, rendered 30px wide). So 1.31px of the gap is optical and 34.69px
   of it was the two wraps disagreeing. The box is what is corrected here. The
   1.31px is left alone on purpose: it is below the threshold of sight at any
   size on this page, and a hanging correction tuned to a capital T silently
   becomes wrong the day the headline's first letter changes.

   THE CORRECTION IS THE DIFFERENCE BETWEEN THE TWO WRAPS, NOT A NUDGE.
   .wrap-wide's left edge is (100% - min(--wrap-wide, 100% - 2 * gutter)) / 2
   and .wrap's is the same expression with --wrap; the gap between them is
   half the difference of the two widths, which is 0 until the viewport is
   wide enough for --wrap to cap, then grows, then stops at half the
   difference of the two caps. Written as exactly that, so it can never drift
   from either wrap:

     100%   gutter   padding-left   .wrap-wide left   sum    .wrap left
     1200      60      0                60             60        60   equal already
     1368      64      0                64             64        64   .wrap caps here
     1440      64     36                64            100       100
     1500      64     66                64            130       130
     1600      64    100 (capped)       80            180       180
     1920      64    100 (capped)      240            340       340

   Percentage padding resolves against the containing block, which for
   .hero-in is the section's content box: the viewport width less the
   scrollbar, which is the same width .wrap itself divides. No vw anywhere, so
   the scrollbar gap that forced the channels scene into 1.5% of overshoot
   cannot open here.

   The stage keeps its own margin-right untouched, so the ring bleeds off the
   right edge by exactly as much as it always did; the drawing simply starts
   36px further in and gets that much narrower.

   THE COPY COLUMN IS WIDER THAN IT WAS, AND THAT IS PART OF THE SAME FIX.
   The padding takes its 36px out of the grid's content box, so at 0.86fr the
   copy column fell from 674 to 655 at 1440 — and 92px display type breaks on
   the pixel: measured, "The creators your" sets in 657, so the headline went
   from five lines to six and the hero grew 90px for nothing. The ratio moves
   to 0.80fr, which returns the column to 677 at 1440 and holds the five-line
   set. The ring gives up 39px of width for it, which is the right trade in a
   scene whose two largest elements are the headline and the drawing and whose
   first job is the headline.

   2026-09-02: THE RATIO IS 1fr TO 1fr, AND THE REASON THE OLD ONE GAVE HAS
   EXPIRED TWICE OVER. 0.80 was set to hold a 92px headline at five lines
   inside a 677px column; that headline was replaced on 2026-09-01 by two
   short sentences, and the size is read off the column itself as 11.2cqw, so
   the line count no longer depends on the ratio at all. Swept from 0.80 to
   1.12 at 1100, 1280, 1440 and 1512, the headline sets three lines in every
   cell of the table.

   What the ratio now decides is only how the scene is split, and the bleed's
   removal is what forces the question: the drawing used to occupy its track
   plus 88px of margin plus its own 12.89% of overflow, and now it occupies
   its track exactly. Measured at 1440, the drawing painted 710.6px wide and
   605.5 of that was on screen. An even split gives the painted drawing 608.9,
   which is the same picture, 3.4px wider, with none of it past the window.
   So the reversal changes what the reader sees of the drawing and not how
   much of the window it gets, which is the narrowest honest reading of an
   instruction to stop cutting it.

   The headline pays 67.7px of column for that, 676.9 down to 609.2 at 1440,
   and 7.6px of type, 75.8 down to 68.2. It is still the largest thing in the
   scene by a factor of five and it still sets three lines. The CTA gains
   rather than loses: at 1440x750 its lower edge measures 642.8 against a
   750 fold, 107.2px clear, where the pre-reversal hero left 61.1. */
.hero-in {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  gap: clamp(32px, 4vw, 72px);
  padding-left: clamp(
    0px,
    calc((100% - var(--gutter) * 2 - var(--wrap)) / 2),
    calc((var(--wrap-wide) - var(--wrap)) / 2)
  );
}

/* The drawing sits under the copy, never over it. .hero-stage is positioned
   and .hero-say is not, so the stage used to win the paint order; while the
   fields were faded that only washed the button's corner, but an opaque field
   swallows it outright — measured at 500px, the lime body ate the lower-right
   corner of the CTA. Nothing on this page may run over text, and a control is
   text with a box around it. */
/* container-type so the headline below can be sized from the column it
   actually gets rather than from the viewport, which is not the same number
   above 1368 where .wrap-wide caps and the column stops growing. It changes
   no layout: the copy column is a pure fr track against a zero minimum, so
   containing its inline size cannot feed back into the grid. Measured at
   1440 with and without: 609.2px both ways, re-measured 2026-09-02 with the
   even split below; it read 677 under the 0.80fr ratio. Same technique, same
   check, as
   .hero-stage below. */
.hero-say { position: relative; z-index: 1; container-type: inline-size; }

/* 12a. THE HEADLINE IS SIZED SO THE BUTTON UNDER IT IS ON SCREEN.
   This was the most expensive defect on the page and it was invisible at the
   one viewport the previous round checked. Measured across a height sweep at
   three widths, before this rule:

     1280   CTA 776..840   first fully visible at a viewport height of 850
     1440   CTA 835..899   first fully visible at 900
     1512   CTA 839..903   not visible at any height up to 900

   A 13" MacBook Air maximised leaves roughly 780 to 790px of layout viewport.
   So on the machine the buyer is most likely to be holding, the primary
   action this whole scene exists to offer could not be seen without
   scrolling, at every width tested.

   THE CAUSE WAS THE MEASURE, NOT THE SIZE. max-width: 12ch held the headline
   to 660px at 1440 and the type was 92px, which sets five lines: two for the
   first sentence and three for the second, the last of them the single word
   "numbers." Five lines of 92px is 451px of headline. And the cap could not
   be escaped by shrinking the type, because a ch scales with the font: at
   every size from 92 down to 56, measured one pixel at a time, 12ch never
   produced fewer than five lines. The cap was the thing holding the line
   count up.

   FOUR LINES, DERIVED FROM THE COLUMN. With the cap off, the column is the
   measure, and the size at which the second sentence closes up into two lines
   was measured at six widths from 1280 to 1920:

     1280  column 612   four lines at 69px and under   11.275% of the column
     1368  column 658   ................. 75px .....   11.398%
     1440  column 677   ................. 77px .....   11.374%
     1512  column 695   ................. 80px .....   11.511%
     1680  column 707   ................. 81px .....   11.457%
     1920  column 704   ................. 81px .....   11.506%

   The ratio is the same number at every width because it is a property of the
   longest line in the sentence, not of the viewport, so it is written as that
   ratio. 11.2cqw is under the tightest of the six with room to spare, and cqw
   is the column rather than the viewport, which is what keeps it true above
   1368 where the two stop tracking each other.

   min(), not a replacement: --t-claim still governs everywhere it is the
   smaller of the two, which is every width up to 1024 where the hero is a
   single column and the headline already sets three or four clean lines. The
   narrow rule in 12b below still overrides both, and still wins on source
   order.

   THE THIRD TERM IS THE WINDOW'S HEIGHT, WHICH IS WHAT THE DEFECT WAS
   ACTUALLY ABOUT. Four lines fixed the line count, and the line count is a
   width problem; whether four lines plus the three blocks under them clear
   the fold is a height problem, and nothing in the hero was answering to
   height at all. With only the first two terms the CTA cleared 780 at every
   width but by as little as 12px at 1512, which is not a margin, it is a
   coincidence waiting for a slightly taller masthead.

   9vh is 70px at a 780px viewport, 81px at 900 and 97px at 1080, so it is the
   binding term only on a window short enough for it to matter and is inert on
   anything tall: at 900 and above the cqw term is smaller at every width and
   the headline is the size it would have been without this. Continuous, like
   the other two, so nothing jumps as a window is dragged.

   MEASURED AFTER, sweeping viewport heights at six widths. The figure is the
   gap between the CTA's lower edge and the fold, and every cell of it sets
   four clean lines:

              700    720    750    780    800    850    900
     1280     +2     +17    +39    +65    +83   +129   +175
     1366    -17     -2     +21    +43    +58   +101   +147
     1440    -25     -10    +13    +35    +50    +88   +134
     1512    -32     -17     +5    +27    +42    +79   +123
     1680    -37     -22     +0    +23    +37    +75   +115
     1920    -36     -21     +1    +24    +39    +76   +117

   So the button is fully on screen from 750px of viewport height at every
   width, and at 780, which is roughly what a maximised 13" MacBook Air
   leaves, it clears the fold by 23 to 65px. Before this round it needed 850
   at 1280, 900 at 1440, and more than 900 at 1512.

   2026-08-30: the pill this arithmetic protected died with the round that
   cut the page to nine scenes. The fold subject is the offer line now, the
   hero's last block, and the same three terms keep it on screen with more
   room than the pill ever had, the pill's own height back in hand.

   2026-09-01: the pill is back as the stack's fourth block (the markup
   comment carries the owner's reversal), and it is again what the fold is
   measured to. The table above was swept against the retired five-line
   headline; the current two-line one is far shorter, so the three terms
   hold with room to spare: measured at restore time, pill lower edge 688.9
   against the 750 fold at 1440 (61.1px clear) and 515.2 against 667 at
   390 (151.8px clear), with 87.9 and 77.2 of clearance at 1280x750 and
   1512x780. The max-height 740 block below still bites where it always
   did: at 1440x720 the pill clears by 104.5. */
.hero h1 { font-size: min(var(--t-claim), 11.2cqw, 9.0vh); }

/* Short viewports: 9vh still leaves the primary CTA past the fold on the
   shortest real devices. Measured, gap from the pill's lower edge to the
   fold: 375x667 was -5px and 360x640 was -26px, both phones people still
   carry, and 1440x720 was -10px. 8.2vh plus the tighter hero head recovers
   it: the same six measurements read +14, +7 and +11 after. Bounded to
   short viewports so nothing at normal height moves, and the ratio stays
   under the four-line ceiling measured above. */
/* Short viewports. On a phone the vh term does nothing, because 11.2cqw wins
   there already: at 375 wide it resolves to 42px against 8.2vh of 54.7px, so
   lowering the vh cap moves nothing at all. Measured at 375x667 the overflow
   is in the gaps and not the type: 56 above the head, 26 above the lede and
   18 above the offer, putting the pill's lower edge at 672 against a 667
   fold. Those three gaps are what come in, and the vh cap stays for the wide
   short window where it does bite. */
@media (max-height: 740px) {
  .hero h1 { font-size: min(var(--t-claim), 11.2cqw, 8.2vh); }
  .hero { padding-top: clamp(32px, 5vh, 56px); }
  .hero .lede { margin-top: 16px; }
}
h1 .turn { display: block; color: var(--ink-soft); }
/* 34ch held the lede to 496px, which set it in four lines and pushed the
   offer line and the buttons below the fold at 1440x900. At 50ch it takes the
   column instead and sets in three, which is 31px the hero no longer spends
   on a wrap it never needed. Still narrower than .get-lede's 62ch, so it is
   inside the family of measures this page already uses. */
.hero-sub { margin-top: 26px; max-width: 50ch; }

/* .hero-offer died 2026-09-08: the new .hero-sub took over its content, and
   the echo between "the voices your buyers already trust" and "people they
   already trust" read as sloppy rather than deliberate. Two other rules
   existed only to size this element and went with it: the short-viewport
   override in section 12's max-height: 740px block above, and the 54ch
   step it used to share with .hero-sub in the 1024 stack below. */

/* The round-14 spread died 2026-09-01, one round after it landed. It
   stepped the offer line down and 48-96px to the right so the three blocks
   read as a composition across the column; the owner's round-15 mock asks
   for the opposite, one tight left stack with the pill CTA back as its
   fourth block, and an offer line indented under a left-aligned actions
   row would orphan the one block the stack now ends on. The margin-left
   step and its margin-top drop go together: both existed to spread a
   stack that is now deliberately tight. */

/* The actions row, back 2026-09-01 with the pill (the markup comment
   carries the reversal). The row itself is the round-12 rule unchanged:
   flex so a second action could stand beside the pill without new CSS,
   wrapping so a narrow screen stacks rather than clips. 26 rather than a
   round 30 was measured against the old five-line headline (pair's lower
   edge 899 against a 900 fold); the two-line headline above it now leaves
   176px of slack at 1440x750, so the figure is kept for rhythm, not
   re-derived for the fold it no longer presses. */
.hero-acts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 20px 30px;
  margin-top: 26px;
}

/* 12b. THE HEADLINE ON A NARROW SCREEN
   Measured at 390, where --t-claim is already clamped to its 46px floor and
   the measure is 350px:

     "The creators"           250.5      "Not the ones"           253.3
     "The creators your"      351.5  X   "Not the ones with"      349.6
     "your buyers trust."     355.8  X   "the biggest numbers."   420.0  X
     "buyers trust."          254.8      "biggest numbers."       346.1

   .turn is display: block, so each sentence wraps on its own. Both two-line
   splits of the first sentence overrun 350px, by 1.5px and by 5.8px, which
   forces a third line and leaves one word alone on it whichever way the
   break falls. text-wrap: balance did not cause that and could not fix it:
   there is no two-line solution for it to find. Measured before this rule,
   the six lines were "The creators / your / buyers trust. / Not the ones /
   with the / biggest numbers."

   The binding line is "the biggest numbers." at 420px against 350px of
   measure, so four clean lines need 46 x 350 / 420 = 38.3px or less, and the
   ratio holds at every width because the measure is 100vw minus the gutter
   pair. Written as a fluid size rather than a step, so it stays true from
   320px up instead of at one tested width:

     10.6vw - 4px  ->  390: 37.3   (needs <= 38.3)
                       430: 41.6   (needs <= 42.4)
                       320: 29.9   (needs <= 30.7)
                       470: 45.8   (needs <= 47.1, and the 46px cap takes it)

   It meets the 46px cap just under the breakpoint, so nothing jumps at 470
   where the stated breaks switch off and balance takes the headline back. */
/* Inert everywhere else, and stated first: a media query does not raise
   specificity, so the unconditional rule has to lose on source order rather
   than on weight. display: none is what makes a <br> stop breaking; the
   space authored before each one survives, so above 470px the sentence wraps
   normally with no seam where the break used to be. */
.brk-xs { display: none; }

@media (max-width: 470px) {
  .hero h1 { font-size: clamp(28px, calc(10.6vw - 4px), 46px); }
  .brk-xs { display: inline; }
}

.hero-stage {
  position: relative;
  /* THE BLEED IS OVER, 2026-09-02, BY THE OWNER, and this reverses the
     decision the comment here used to state: "the bleed: the ring runs off
     the right edge of the viewport", carried by a margin-right of
     calc(var(--gutter) * -1 - 24px).

     It was deliberate and it is still reversed. A partner reported the
     clipping twice and sent a screenshot of what the crop actually took: the
     lime body and one periwinkle node, both sliced flat at the window edge.
     Measured before the reversal, the stage's right edge stood at 1464 on a
     1440 window, 1304 on 1280, 1047.98 on 1024 and 791.98 on 768, which is
     24px past the viewport at every width the ring is drawn at, or 3.8% of a
     629.5px drawing at 1440. What the composition gained from that crop is
     real and is what the old comment was defending: a machine that carries on
     past the frame reads as bigger than the frame. What it cost is a reader
     who cannot tell a designed crop from a layout fault, and the owner has
     ruled that the second outweighs the first. The drawing now sits inside
     the gutter with the same breathing room every other scene has.

     The dash lengths need no attention: --map-w is 100cqw off this box, so
     every --len below re-derives from the narrower box on its own.

     REMOVING THE MARGIN IS NOT THE WHOLE FIX, AND THE BOX IS NOT THE DRAWING.
     .sys is overflow: visible, and the desktop composition uses that: two
     shapes are deliberately drawn outside the 760-unit viewBox, the corner
     field circle at cx 700 r 150, which reaches 850, and the cobalt disc of
     the signature pair at cx 790 r 68, which reaches 858. So the drawing
     paints 858 units wide in a box that measures 760, and its painted right
     edge stands at 112.89% of whatever this box is. With the margin gone and
     nothing else changed, measured: 1445.8 against a 1440 window, 818.7
     against 768, 1279.1 against 1280. The box was inside and the disc the
     partner photographed was still being cut.

     So the box is set to the fraction of its track that puts the PAINTED
     drawing on the track's right edge: 760 over 858, written as that division
     rather than as 88.58%, so the two numbers stay readable as the viewBox
     and the drawing. An explicit width defeats the grid's default stretch and
     start-aligns the item, which is where the drawing already began, so no
     alignment property is needed here.

     The rule this states, and the mobile composition is measured against the
     same one below: no node, module, connector or body may be cut by the
     window; a field may run off it, because a field is ground and this page's
     grounds are full bleed by law. On the desktop drawing the binding shape
     is a body, so bringing it in brings the field in with it.

     THE 760/858 WIDTH IS GONE, 2026-09-08, AND SO IS THE REASON FOR IT.
     Everything above is why it was here and all of it was true. What it never
     said, because nobody had looked, is that it fixed one of two drawings.
     The SVG it narrowed was the fallback at the time; about a second after
     load hero.js added .gl-on and a WebGL scene took over, and that scene
     framed itself by frustum rather than by viewBox. Its canvas was inset:0
     on this box, so narrowing this box narrowed the canvas and the frustum
     together and the GL composition kept exactly the same fraction of itself
     outside. The partner reported the same crop again after round 18 shipped,
     which is how it was found.

     Written in the past tense on 2026-09-08, one day later, because that
     second layer no longer exists: hero.js and the canvas were removed and
     the drawing below is now the only hero. The paragraph is kept rather than
     deleted because the lesson outlives the layer, and it is the reason this
     block is worth reading at all: a fix verified in one of two renderings is
     not verified. Section 23 carries why the layer went.

     This half is simpler than the hack it replaces. The desktop viewBox now
     reads 0 -98 858 878 instead of 0 0 760 780, which moves no shape at all:
     it widens the coordinate window until it holds the drawing that was
     always being painted, the lime field reaching -98 above and the cobalt
     body reaching 858 across. With nothing painting outside its own box any
     more, the box needs no compensation and this stage takes its whole track
     back. The drawing does not change size: the viewBox grew by 858/760 and
     the box grew by the reciprocal, and the two cancel.

     A second cut this fixes, which round 18 did not name because it was
     looking at the right edge. .hero carries overflow: clip, and .hero's top
     edge sits 82.3px down the page under the masthead, so the lime field was
     not running off the window at all: it was being sliced at a section
     boundary in the middle of the page, which is the one thing that cannot
     read as bleed. Measured before the fix: nothing at 1440x900, 9.6px at
     1440x750, 26.9px at 1280x720. The shorter the window, the smaller the 8vh
     term in this scene's padding-block and the deeper the slice, so it was
     invisible on a tall desktop and always there on a laptop.

     The mobile drawing is deliberately left alone. Its lime field runs 51.8px
     off the right at 390 and that cut lands on the window, where a field is
     allowed to bleed by the rule stated above. */

  /* Both hero drawings are width:100% of this box, so this box is --map-w.
     The stage is a grid item in tracks that are already pure fr against a
     zero minimum, so containing its inline size changes no layout — measured
     539.61px at 1440 with and without, re-measured 2026-09-02 against the
     even split and the 760/858 width; it read 668 before either, and reads
     that again now the width is gone. */
  container-type: inline-size;
  --map-w: 100cqw;
}

/* The drawn lengths. See the unit law above section 10's .draw: these are
   user length over viewBox width, and --map-w carries the viewport. The two
   compositions never render together, so each states its own ratios against
   its own box and no media query is needed to swap them. */
.hr-d1 { --len: calc(0.1399 * var(--map-w)); }   /* 120.0 over 858 */
.hr-d2 { --len: calc(0.2062 * var(--map-w)); }   /* 176.9 over 858 */
.hr-d3 { --len: calc(0.2901 * var(--map-w)); }   /* 248.9 over 858 */
.hr-d4 { --len: calc(0.1352 * var(--map-w)); }   /* 116.0 over 858 */
.hr-d5 { --len: calc(0.2003 * var(--map-w)); }   /* 171.9 over 858 */
.hr-d6 { --len: calc(0.1922 * var(--map-w)); }   /* 164.9 over 858 */
.hr-d7 { --len: calc(0.2797 * var(--map-w)); }   /* 240.0 over 858 */
.hr-d8 { --len: calc(0.3192 * var(--map-w)); }   /* 273.9 over 858 */

.hr-m1 { --len: calc(1.3754 * var(--map-w)); }   /* 536.4 over 390 */
.hr-m2 { --len: calc(0.3089 * var(--map-w)); }   /* 120.5 over 390 */
.hr-m3 { --len: calc(0.3141 * var(--map-w)); }   /* 122.5 over 390 */

.hero-mark { color: var(--cobalt); overflow: visible; }
.hero-sys-m { display: none; }

/* The last 56px before the hero stacks, where the copy is given a nudge back.
   It was 0.78 against the wide layout's 0.80, a 2.5% shift toward the
   headline; the pair is 0.97 against 1fr now, the same 2.5% against the ratio
   set above on 2026-09-02, so nothing steps at the boundary. */
@media (max-width: 1080px) {
  .hero-in { grid-template-columns: minmax(0, 1fr) minmax(0, 0.97fr); }
}

/* 12c. THE HEADLINE BETWEEN 768 AND 1024, WHERE IT BROKE WORST
   iPad portrait is a real buyer device and the H1 set six lines on it, one of
   which was a single word. Measured at 768 before this rule:

     The creators / your / buyers trust. / Not the ones / with the biggest /
     numbers.

   The cause was not the wrap algorithm. At 768 the H1 is held to a 370px
   measure, which is the 1fr side of a two-column grid whose other side is the
   ring; the type is 49.15px there, and "The creators your" alone sets 375px.
   No two-line split of either sentence exists inside 370px, so text-wrap:
   balance had nothing to find and left a word on its own. Meanwhile the art
   the 370px was being spent on is cropped at that width anyway: its box runs
   to x=792 on a 768px screen.

   So the hero stacks from 1024 down instead of from 760 down, and the
   headline takes the full wrap. It is the same single-column arrangement the
   narrow breakpoint already used, moved up to where the two-column one stops
   working, and the art falls below the copy rather than competing with it for
   a measure neither can use. The composition swap stays at 760: 761 to 1024
   is the ring, stacked; below 760 it is the spine, which is a different
   drawing and not this one reflowed.

   Measured after: 768 sets three clean lines, 1024 sets three, 390 still sets
   the four the fluid size below was derived for, and 1440 is untouched
   because it is above every breakpoint here. */
@media (max-width: 1024px) {
  .hero-in {
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(36px, 5vw, 52px);
  }
  /* The wrap is the measure now. A ch cap on top of it would re-impose the
     column that caused the orphan in the first place. */
  .hero h1 { max-width: none; }
  .hero-sub { max-width: 54ch; }

  /* The drawing must not get bigger as the screen gets smaller, which is what
     a stacked full-width stage does: re-measured 2026-09-02 against the
     760/858 width above, it renders 816.3px wide at 1024 against 539.6px at
     1440, so every capsule and node in it would be half as large again on the
     smaller device. The pair read 997 and 630 before that width landed and
     the ratio underneath it moved; the gap between the two is what this rule
     is about and it has widened rather than closed. 760 is the ring's own
     viewBox width,
     so this caps it at 1:1 and never above. At 768 the cap is not even
     reached.

     THE SECOND HALF OF THIS COMMENT DIED 2026-09-02, WITH THE BLEED IT
     DEFENDED. It read that the width "keeps the right bleed and gives up the
     left edge instead, because the crop is the whole point of the composition
     and the machine is meant to run off the screen on that side", and it
     stated the width as the grid area plus the gutter plus the same 24px
     overshoot the wide layout used. The owner reversed the bleed; see the
     full record on .hero-stage above. So the width is the grid area and
     nothing added, still capped at the viewBox's own 760.

     justify-self survives the reversal and changes value with it. It was end
     because the composition was being cropped on the right and the slack had
     to be spent on the left; measured, an auto margin against the grid's
     default stretch resolves the box to 0px wide, so the alignment property
     is still what has to carry this rather than a margin. With nothing
     cropped there is no side to spend slack on, and centre is what a finished
     drawing under a full-width column wants. It is start now instead, and the
     reason is the 858 above: centring a box whose paint runs 12.89% past its
     own right edge spends half the slack on the side that does not need it
     and hands the other half back to the overflow. Measured at 1024 with the
     760 cap in force: start puts the box at 51.2 and the last painted unit at
     909.2, 63.6 clear of the track and 114.8 clear of the window; centre put
     the same paint at 990. Start also lines the drawing up with the copy
     above it, which is what a stacked column wants. */
  .hero-stage {
    justify-self: start;
    width: min(100%, 858px);
  }
}

/* 390px is a different composition, not the ring reflowed: the mark stacks
   over a vertical spine and the system routes around it. */
@media (max-width: 760px) {
  .hero-in {
    grid-template-columns: 1fr;
    gap: 44px;
  }
  /* No measure at 390px: the wrap is already narrower than the measure
     would be, and the two together fight text-wrap: balance into orphans. */
  .hero h1 { max-width: none; }
  /* The spine ran full bleed here, margin-right and margin-left both
     calc(var(--gutter) * -1), which put its right edge exactly on the window
     at 390: right 390.0 against a client width of 390.0, touching rather than
     clipped. That is the same decision the owner reversed above and it goes
     with it, so the pair of margins is gone and the drawing keeps the gutter
     the copy above it keeps. Measured at 390 after: the stage is 350.0 wide,
     its right edge at 370.5, 19.5px of ground on each side. The drawing loses
     10% of its scale and no geometry at all.

     The 760/858 correction above belongs to the desktop composition and is
     put back to the plain track here, because this is a different drawing
     with a different overflow and the rule is measured per drawing. The spine
     paints from x=34 to x=316 of its 390-unit viewBox, which is 81% of the
     box: no node, module, connector or body comes near the window at any
     width. The one shape outside the viewBox is the corner field circle at
     cx 358 r 112, reaching 470, and a field is ground rather than drawing.
     Grounds on this page are full bleed by law, so it keeps running off the
     corner as it always has, now by 51.8px at 390 rather than the 80.8 the
     double negative margin gave it. */
  .hero-stage { width: min(100%, 760px); }
  .hero-sys-d { display: none; }
  .hero-sys-m { display: block; }
}

/* --------------------------------------------------------------------------
   12. FOOTER
   -------------------------------------------------------------------------- */

.footer {
  border-top: 2px solid var(--cobalt);
  background: var(--ground);
  padding-block: 28px;
}
.foot-in {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 32px;
  align-items: baseline;
}
/* The one link in the footer is a 12px mono label, so its line box is 16px and
   its target failed the 24px minimum. It is not inline in a sentence, it is a
   flex item of its own, so the inline exception does not cover it.

   Padding rather than a taller line: the negative margin gives the box back to
   the layout, so the target grows to 28px and the footer does not move by a
   pixel. Measured before and after, the baseline row is identical. */
.footer a {
  text-decoration: none;
  display: inline-block;
  padding-block: 6px;
  margin-block: -6px;
}
.footer a:hover { text-decoration: underline; text-underline-offset: 3px; }

/* The footer's call. Since 2026-08-30 the call is an anchor rather than a
   button: it stopped opening a dialog and became a .js-call fragment link
   to the roster form's email field, so the .link grammar it always wore
   arrives natively now, and so does .link's own 44px touch treatment at
   1024 and down (section 8). The button reset that stood here, and the
   restated touch-target block under it, died with the element they were
   correcting. Only the size is still stated: .op-call sits inside .op-ask,
   which sets the lede scale, and the call reads at body scale in both
   places, as it always has.

   .foot-nav (the under-900px copy of the masthead's two links, and its
   900px reveal) died 2026-09-01 with the masthead nav itself: with no nav
   above, a footer-only copy would be section links no width ever pairs
   with their original. The markup tombstone in index.html carries the
   cut. */
.foot-ask { margin-bottom: clamp(28px, 3vw, 44px); }
/* .foot-line, 2026-09-08: new markup, "Trusted voices. Measurable
   momentum." carrying the H1's own phrase down into the footer, set as
   its own wrapped line above .foot-in. .foot-ask above it sits at --t-lede
   (22px, the size .op-ask sets below) because it is the row that has to
   keep the reader's attention, so this stays a full step down at --t-body
   (17px) and --text-body rather than --text, a line under the call rather
   than a second one competing with it. font-family is --f-display, the
   same allowance .bg-fact takes to read as a set phrase rather than
   running prose. margin-bottom reuses .foot-ask's own
   clamp(28px, 3vw, 44px) instead of a new figure, so one rhythm carries
   both gaps, ask to line and line to nav. */
.foot-line {
  margin-bottom: clamp(28px, 3vw, 44px);
  font-family: var(--f-display);
  font-size: var(--t-body);
  color: var(--text-body);
}
.foot-call, .op-call { font-size: var(--t-body); }
.op-ask { display: flex; flex-wrap: wrap; gap: 14px 32px; align-items: baseline; }
/* 28px was the answer to the 24px minimum. On a touch screen the target the
   reader is actually aiming a thumb at wants 44, and the same padding-and-
   give-it-back trick scales to it without the footer row moving.

   1024 rather than 760, for the reason recorded on the .link block in
   section 8: 760 is where this page swaps to its narrow *drawings*, and a
   thumb does not know that. Measured at 768 and 1024 before this moved: this
   link was the fourth and last element on the page still under 44px there,
   at 153x26 and 141x28. */
@media (max-width: 1024px) {
  .footer a { padding-block: 14px; margin-block: -14px; }
}

/* --------------------------------------------------------------------------
   13. MAP — drawing and real text in one composition
   A scene's geometry lives in SVG; anything that has to be readable text
   lives in HTML pinned over it. Pins are positioned in percentages taken
   straight from the viewBox, and because the SVG is width:100%/height:auto
   its box *is* the pin container, so the two coordinate systems cannot
   drift apart. This is how every diagram on the page carries copy.
   -------------------------------------------------------------------------- */

.map { position: relative; }
.map .sys { display: block; width: 100%; height: auto; }

.map-pin {
  position: absolute;
  left: var(--x);
  top: var(--y);
  translate: 0 -50%;
  white-space: nowrap;
}

/* Descendant, not child: the channels scene wraps its two SVGs in .ch-stage,
   and a child combinator stopped matching the moment that wrapper appeared —
   which put the mobile composition on screen at 1440 underneath the desktop
   one. Specificity still has to beat `.map .sys`, hence the two class levels. */
.map .map-m { display: none; }
@media (max-width: 760px) {
  .map .map-d { display: none; }
  .map .map-m { display: block; }
}

/* --------------------------------------------------------------------------
   14. ASKS — the form, wherever it appears
   Two fields is the maximum anywhere on this page, which the casting-map
   form already uses in full, so the styling deliberately makes a third
   field look wrong.
   -------------------------------------------------------------------------- */

.ask { display: grid; gap: 18px; max-width: 460px; }
.ask-wide { max-width: 560px; }

.field { display: grid; gap: 7px; }

.field label {
  font-family: var(--f-mono);
  font-size: var(--t-micro);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--stroke);
}

.field input {
  width: 100%;
  padding: 15px 18px;
  border: 2px solid var(--stroke);
  border-radius: 12px;
  background: var(--white);
  color: var(--cobalt-deep);
  font-size: var(--t-body);
  transition: box-shadow var(--dur-fast) var(--ease-out);
}
/* 0.62 put this at 3.06:1 on white and it is text, so it needs 4.5. At 0.85
   the same ink measures 5.27:1 and still reads as a hint rather than a value. */
.field input::placeholder { color: var(--ink-soft); opacity: 0.85; }
.field input:hover { box-shadow: 0 4px 0 var(--shade-white); }
.field input:focus-visible { outline-offset: 2px; }

.g-cobalt .field input { background: var(--white); border-color: var(--white); }

/* The invalid state was announced but never drawn: the border stayed
   identical, so a sighted user got the message text and no field to look at.
   Blush is the page's "not right yet" colour and this is the same job. */
.field input[aria-invalid="true"] {
  border-color: var(--cobalt-deep);
  box-shadow: 0 0 0 3px var(--blush);
}
.g-lime .field input:hover { box-shadow: 0 4px 0 var(--shade-lime); }

.ask .cta { justify-self: start; }

/* `[hidden]` lives in the UA stylesheet, so it loses to the author-level
   `display` values above. Without these two rules a successful submit left the
   filled form and a live-looking button on screen underneath the "Done."
   message, and people submitted a second time. Author-level hiding is the only
   thing that actually wins. */
.field[hidden],
.ask .cta[hidden] { display: none; }

/* And while the request is in flight the button has to look spent. */
.cta:disabled {
  background: var(--periwinkle);
  border-color: var(--periwinkle);
  color: var(--cobalt-deep);          /* 5.57:1 — plain cobalt would fail here */
  box-shadow: 0 4px 0 var(--shade-peri);
  translate: 0 2px;
  cursor: default;
}
.cta:disabled::after { opacity: 0.45; }

.ask-note {
  font-size: var(--t-body);
  color: var(--text-body);
}

/* Weight, not colour. The invalid field already carries a 3px --blush ring,
   which is the page's "not right yet" signal and does render (measured
   rgb(252,228,234) at 3px). What did not read was this line: at weight 400
   in --cobalt-deep it is the body text colour at the body weight, so it
   arrived looking like helper copy under a field rather than a correction.
   Red would be a sixth colour and is out, so the distinction is carried by
   weight, which the palette does not have to pay for. */
.err { color: var(--cobalt-deep); font-size: var(--t-body); font-weight: 600; }
.g-cobalt .err, .g-cobalt .ask-note { color: var(--ground); }
.hp { position: absolute; left: -9999px; }


/* -- 14b. THE ONE DIALOG, RETIRED ----------------------------------------- --
   The case-studies ask opened in a modal for one round and stands inline in
   the roster scene again since 2026-08-30. The dialog cost a hidden opener
   per door, a runtime form move, a measured scroll lock and two entrances,
   and its whole content was one short form: a door in front of a doormat.
   The two doors are .js-call fragment links to #cs-email now, the focus
   handling went back to the browser, and every selector of the dialog's
   own died with its markup. -- */

/* --------------------------------------------------------------------------
   15. SCENE 2 — THE ROSTER INVITATION (p02)
   The single highest-value change in the partner feedback: the roster line
   leaves the hero and becomes the invitation. The deck's thick cobalt disc
   carries it, because a disc is what "a whole domain" means in this system
   and the roster is exactly that. Naming the three is the diagram; inviting
   is the heading. One proof element, said once.

   The scene sits on periwinkle, which decides what may be drawn on it: a
   periwinkle module has no face left, so the three name modules take the
   white face and the rack's second capsule goes with them. That is the same
   rule the people scene's name cards already run on — white is what is
   available above this ground, not a decorative preference.

   2026-08-30: the disc and both of its compositions moved to the selection
   scene; 2026-08-31, that scene was cut and the drawing left the page, so
   the rules that dressed it here (.roster-map, the .rs-* lengths, the
   .roster-tag pin) died with their markup, and .roster-lead died separately
   the same day when the owner promoted his sentence from the 28px sub to
   the scene's own h2.

   2026-09-01, THE WIRING COMES BACK WITHOUT THE DISC. Measured, the row the
   two rounds above left behind was 882px of scene carrying three unwired
   pills: 289, 287 and 209px of module spread across the whole 1240 wrap with
   228 and 227px of ground between them, which is far enough apart that the
   three read as three separate facts instead of one body of work. Nothing
   was drawn between them because the only thing that ever had been was the
   disc, and the disc is dead.

   So the relationship is rebuilt and the body is not: three name modules
   hang off drawn connectors from a single origin node, at three different
   heights and three different horizontal positions, and .roster-map is the
   container that measures them (the name is reused from the retired rule
   above deliberately, because it is doing the same job for the same scene).
   Below it the scene is two zones facing each other, .roster-zone against
   .mod.roster-ask, which is what killed both of the rules tombstoned lower
   down: the full-width .roster-names row, and the centred .roster-second.
   -------------------------------------------------------------------------- */

/* THE OWNER'S SENTENCE AND ITS REGISTER NOTE BOTH DIED HERE, 2026-09-01, BY
   HIS OWN HAND. "Architecting high-impact creator campaigns for global
   pioneers." stood one round as the scene's 28px sub and one as its h2, used
   verbatim, louder than a page that had been deliberately plained down, and
   protected by a DESIGN.md exemption from the plain-voice pass on the
   grounds that the contrast was his decision rather than drift. Three
   independent cold readers then read it back as generic agency filler, and
   he replaced it himself with "The work behind these names". The exemption
   dies with the sentence it protected (DESIGN.md, amended 2026-09-01) and
   nothing in this scene is exempt from the plain voice now. No full stop, so
   it matches every other heading on the page since round 14.

   No ch cap is still right and for a smaller reason than before: the heading
   is 27 characters and sets one line at every width this page draws, so a
   cap would be a rule with nothing to do. It no longer has to take the full
   measure to hold a composition up, because the composition below it is two
   zones now and neither of them depends on the heading's width. */

/* The three names are the whole proof of this scene and they have to carry
   it without logos, so they are set at heading size, level with the heading
   that now stands over them. Line-height is stated because a module
   inherits the 1.55 of running text, which at 54px would put 30px of air
   inside a capsule holding one word. */
.roster-name {
  font-family: var(--f-display);
  font-size: var(--t-h2);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--cobalt-deep);
  padding: 22px 26px;
}

/* .roster-names DIED 2026-09-01, WITH THE ROW IT LAID OUT. It was a wrapping
   flex row that spread the three modules to the wrap's ends from 900px up,
   and the spread is exactly what killed it: measured at 1440 it put 228 and
   227px of empty ground between three pills of 289, 287 and 209px, with no
   connector anywhere, so the only evidence in the scene read as three
   unrelated facts laid out in a line. The three are pinned to nodes on a
   drawn constellation now (.roster-n1 to .roster-n3 below), which is where
   their positions come from, so a row rule has nothing left to lay out.
   The 900px space-between override and its measured floor died with it. */

/* THE CONSTELLATION'S CONTAINER, 2026-09-01. Both drawings are width:100% of
   this box, so 100cqw is the width either one actually renders at, and every
   --len below is measured against it. See the unit law above section 10's
   .draw: --len is the path's user length over its viewBox width, times the
   width the drawing renders at, and container-type is what makes the second
   half of that a measurement rather than a restatement of the layout.

   The box takes its width from the zone it sits in at three tiers, and each
   tier states the width on THIS element rather than on the svg inside it,
   because the moment the cap sits on the svg, 100cqw stops being the render
   width and every dash on the drawing is laid out against a number the
   renderer is not using. */
.roster-map {
  container-type: inline-size;
  --map-w: 100cqw;
}

/* The drawn lengths. 660 and 350 are the two viewBox widths; the figure in
   the comment is the path's own length in user units, summed by hand from
   its segments, and each 20x20 rounded corner is a quadratic with its
   control point on the corner, which measures 32.4639 rather than the 31.42
   of the quarter circle it looks like (the same arithmetic the retired
   .rs-d1 was checked against: 10 + 32.46 + 9 = 51.5). */
.rn-d1 { --len: calc(0.4802 * var(--map-w)); }   /* 316.9 over 660 */
.rn-d2 { --len: calc(0.4621 * var(--map-w)); }   /* 305.0 over 660 */
.rn-d3 { --len: calc(0.5969 * var(--map-w)); }   /* 393.9 over 660 */

/* One class for all three of the mobile chain's links: they are the same
   100-unit drop three times over, and three identically-valued rules would
   be three chances for one of them to drift. */
.rn-m  { --len: calc(0.2857 * var(--map-w)); }   /* 100.0 over 350 */

/* The three pinned names, in percentages taken straight from the viewBox, so
   the pin box and the drawing cannot drift apart. Desktop, over 660x430:
   each name's left edge is 30 units right of its node, which clears the
   node's own 18-unit ring by 12 and keeps every module off every connector.
   x = (node x + 30) / 660, y = node y / 430.

   These live here and never in an inline style on the markup: an inline
   custom property beats a media query outright, and the last time this
   scene's pins carried one the mobile block three rules down was dead from
   the day it was written. */
.roster-n1 { --x: 37.12%; --y: 14.42%; }   /* node 215,62  */
.roster-n2 { --x: 53.94%; --y: 46.51%; }   /* node 326,200 */
.roster-n3 { --x: 44.24%; --y: 85.58%; }   /* node 262,368 */

/* AND THE ONE THING THAT BROKE WHEN THE TWO WERE COMBINED, MEASURED
   2026-09-01. .pop sets `transform-box: fill-box`, which is an SVG idea and
   the right one for every previous user of it, all of which were SVG shapes.
   On an HTML element there is no fill box, so it resolves to the CONTENT box,
   and that is the reference box .map-pin's own `translate: 0 -50%` is then a
   percentage of. A pinned module that also pops therefore centres itself on
   half its content height instead of half its whole height, and lands exactly
   its own padding-plus-border below the node it is pinned to: 24px low at
   1440, 14px low at 390, both measured before this rule existed.

   Put back to the border box, which is what .map-pin has always assumed and
   what the initial value already resolves to for an HTML element, so the
   no-script picture was never wrong and only the .motion one was. Two class
   levels plus the ancestor, because it has to beat `.motion .pop`. The pop
   still scales about the same centre; only the reference box moves. */
.motion .roster-name.pop { transform-box: border-box; }

/* The two zones, 2026-09-01, replacing .roster-invite. The old rule was a
   margin and nothing else: it had held a lede-left/form-right grid until
   the owner centred the form on the page axis, and once the form was
   centred the wrapper had no columns left to declare.

   1.4fr against 1fr, not halves: the left zone has to hold a 660-wide
   drawing with a 286.6px module pinned at 53.94% of it, and an even split
   at 1440 gives that zone 591.2px, which the widest pin overruns by 14.3.
   At 1.4fr the drawing renders 689.7px wide and that pin lands 31.1px
   inside the edge. The whole range was swept rather than spot-checked: 53
   widths from 320 to 1920, worst clearance anywhere between a pinned module
   and the box edge or its neighbour is 8px, at 320, which is below the
   narrowest width the collision gate tests. The tightest inside the
   two-column tier is 1200, at 19.1px; past 1368 the wrap caps at 1240 and
   the geometry stops growing.

   align-items: center is the composition: the booking module is 323px tall
   against the left zone's 543, and centring it makes the two zones face
   each other across the wrap instead of both hanging from one top edge.

   The split only runs from 1200px up. At 1180 the wrap is 1062 and the two
   columns would be 643 and 461, which the drawing survives and the form
   does not: the module's inner measure drops under the submit button's own
   width and the label wraps. Below that the two zones stack, which is the
   same picture read top to bottom. */
.roster-body {
  margin-top: clamp(32px, 4vw, 56px);
  display: grid;
  gap: clamp(32px, 4vw, 64px);
}
@media (min-width: 1200px) {
  .roster-body {
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
    align-items: center;
  }
}
/* Stacked, 761 to 1199: the drawing is capped at its own viewBox width so it
   never renders larger than it was fitted at, and the booking module is
   capped too, because a white slab the full 1062px of the wrap would be the
   loudest object in a scene whose subject is three names. */
@media (max-width: 1199px) {
  .roster-map  { width: min(660px, 100%); }
  .roster-ask  { max-width: 520px; }
}
/* The scene's lede since 2026-08-31: with the sub and the old heading both
   gone this is the one running sentence left, so it takes the lede scale
   the other scenes' ledes already use rather than standing as a caption in
   a column built for a heading. 40ch keeps it to two to three lines.
   It sits under the constellation and inside the same zone since
   2026-09-01, which is where it belongs: it is what the drawing says in
   words, so it travels with the drawing rather than with the ask. */
/* The campaign line under the roster heading, 2026-09-08. It is the first
   named client outcome anywhere on this site, so it takes the lede measure
   rather than the heading's full width: 1240px of 22px type is nobody's
   reading line, and four figures in one sentence need a column a reader can
   actually track across. 20px off the heading, the same step .ch-lede takes
   off its own. */
.roster-proof { margin-top: 20px; max-width: 54ch; }

.roster-invite-say {
  margin: 32px 0 0;
  max-width: 40ch;
  font-size: var(--t-lede);
  line-height: 1.4;
  letter-spacing: -0.01em;
  color: var(--text-body);
}
/* .roster-second DIED 2026-09-01, WITH THE CENTRING IT EXISTED TO DO. It was
   `max-width: 440px; margin-inline: auto`, the owner's round-15 call to put
   the booking block on the page's centre axis, and it was recorded as his
   decision rather than drift because it stood against this page's own left
   spine. It has nowhere to centre to now: the block is .mod.roster-ask, the
   right-hand half of a two-zone composition, and a block that is one side of
   a facing pair cannot also be on the middle. Its 440px measure survives in
   spirit rather than in figures, since .ask already caps itself at 460 and
   the module's padding takes the rest.

   The white face is the ground's own rule and not a preference: a periwinkle
   module has no face left on a periwinkle ground, which is why the three
   name modules take white here too. .g-peri .mod already drives the shadow
   to --shade-peri so the module seats rather than lifts. */
.roster-ask {
  align-self: center;
}

/* The supporting line under the booking button: what the twenty minutes
   buy. Body voice, quiet ink, the same treatment the close card's micro
   line wears. The h3 that used to head this column died 2026-08-30; its
   booking half is the submit button's own label now. */
.roster-micro {
  margin-top: 16px;
  max-width: 40ch;
  font-size: var(--t-body);
  color: var(--text-body);
}

/* The disc's shadowed faces. Same hue driven darker, exactly like a module's
   offset shadow — the disc is just a module with real thickness. */
.b-deep { fill: var(--cobalt-deep); }

/* --------------------------------------------------------------------------
   15b. THE DISC AND ITS ARRIVAL, CUT 2026-08-31
   The whole block died with the Pierce scene: the disc drawing, the switch
   it threw once on arrival (--rs-live, rs-throw, rs-close, the 0.85 lift),
   and the .selection selectors that keyed it. Dead CSS dies with its
   markup. Two of its findings outlive it and are recorded where they still
   bind: a switch is thrown after the line that feeds it has landed, never
   before (the derived-delay pattern, which any future operated control
   should copy), and a brighten is opacity and nothing else on a page with
   no soft edges. Git history holds the drawing.
   -------------------------------------------------------------------------- */

@media (max-width: 760px) {
  /* Shallower: heading-size type in a deep capsule is most of a phone's
     width, so the pinned modules give back padding they cannot afford at
     36px type. Measured at 390: the widest of the three is 200px and all
     three are 64px tall against a 350px wrap, which is what the chain's
     100-unit pitch was fitted to, 36px of air between neighbours. */
  .roster-name { padding: 12px 18px; }

  /* The chain's container. Same rule as the two tiers above, one width
     narrower: the cap sits on the box so 100cqw stays the render width.
     350 is the wrap at 390, so the drawing is 1:1 there and scales down
     with the box at 360 and up with nothing, since it is capped. */
  .roster-map { width: min(350px, 100%); }

  /* Over 350x380: the spine runs at x=40 and every name's left edge is at
     x=74, which is 34 units clear of the spine and 18 clear of a node's own
     16-unit ring. x = 74/350, y = node y / 380. The pitch is even here
     where the desktop fan's is not, because a chain that staggered would
     be a chain that looked broken. */
  .roster-n1 { --x: 21.14%; --y: 34.21%; }   /* node 40,130 */
  .roster-n2 { --x: 21.14%; --y: 60.53%; }   /* node 40,230 */
  .roster-n3 { --x: 21.14%; --y: 86.84%; }   /* node 40,330 */
}


/* --------------------------------------------------------------------------
   16. THE PIERCE SCENE, CUT 2026-08-31
   The scene stood one round: the selection argument's copy over the
   roster's disc, on the page's second ground-soft, with a three-depth
   pointer parallax (.plx-a/b/c, fed by discParallax in motion-features.js).
   Cut by the owner in the round-14 structure pass, one round after it was
   built; the copy rules (.sel-say, .sel-lede), both parallax media blocks
   and the feeding script died with the markup, and the rotation was
   re-derived for eight scenes (section 5). What the scene's seven drawn
   versions taught survives in DESIGN.md and still binds: a diagram that
   needs a legend does not work, and ground is separated from figure by
   value, not by hope.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   17. SCENE 3 — THE CLAIM (p04/p18)
   The only full cobalt ground and the only use of --t-claim on the page.
   Both facts are what make this the loudest moment rather than one of
   several competing for the same job.
   -------------------------------------------------------------------------- */

.claim-in {
  display: grid;
  grid-template-columns: minmax(0, 1.18fr) minmax(0, 0.62fr);
  gap: clamp(36px, 5vw, 88px);
  align-items: center;
}
.claim { max-width: 13ch; }
/* Lime on cobalt is 5.81:1 — the turn is a colour change, not a new shape. */
.claim-turn { display: block; color: var(--lime); }

/* The third block inside the same <p>, 2026-09-01. Why it is inside rather
   than beside is measured, not stylistic: claim.js routes the avatar stream
   off .claim's own box and the field's clear zone off .claim-say's, so a
   sibling paragraph would be cleared by the field and crossed by the stream.
   Measured at 1440x900 before this line existed: the claim ended at 551.7 in
   scene coordinates and the lower lane rode 611 to 683, so a sibling seated
   under it with ordinary air would have started at 572 and run into that lane
   at two lines or at three. Inside the box the existing routing accounts for
   it on this resize and every one after, with no edit to a file this round
   may only read. Measured after, across three seconds and 504 samples of
   every stream item, the largest overlap with this line is 0px.
   That puts the whole job of separating the two registers on the type, since
   the tag no longer does it: the body face against the claim's display, 400
   against 700, --t-lede against --t-claim, its own measure, and pretty
   against balance, which is for a headline of three lines and not for a
   sentence of two. It is not a third beat and carries none of the arming
   below: it stands with beat one, script or no script. Colour is
   --text-body, which .g-cobalt has already driven to --ground for this
   ground: #EDE9FB on #3B44D6, 5.92:1. */
.claim-note {
  display: block;
  margin-top: clamp(24px, 2.4vw, 34px);
  max-width: 44ch;
  font-family: var(--f-body);
  font-size: var(--t-lede);
  font-weight: 400;
  letter-spacing: -0.01em;
  line-height: 1.4;
  color: var(--text-body);
  text-wrap: pretty;
}
/* The drawing runs past its own box on purpose, so the box is only ever a
   measure — which is exactly what --map-w needs it to be. Containing its
   inline size does not clip it: that takes paint containment, and this has
   layout containment only. See the unit law above section 10's .draw. */
.claim-map {
  overflow: visible;
  container-type: inline-size;
  --map-w: 100cqw;
}

.cl-d1 { --len: calc(0.1539 * var(--map-w)); }   /*  80.0 over 520 */
.cl-d2 { --len: calc(0.2403 * var(--map-w)); }   /* 124.9 over 520 */
.cl-d3 { --len: calc(0.4798 * var(--map-w)); }   /* 249.5 over 520 */
.cl-d4 { --len: calc(0.5384 * var(--map-w)); }   /* 279.9 over 520 */

.cl-m1 { --len: calc(0.1334 * var(--map-w)); }   /*  52.0 over 390 */
.cl-m2 { --len: calc(0.2127 * var(--map-w)); }   /*  82.9 over 390 */
.cl-m3 { --len: calc(0.5038 * var(--map-w)); }   /* 196.5 over 390 */
.cl-m4 { --len: calc(0.5255 * var(--map-w)); }   /* 204.9 over 390 */

.claim-say { display: grid; }

/* This scene swaps composition at 900, not at the page's usual 760, and the
   number is the one the layout already uses: 900 is where the two columns
   become one, which is the moment the drawing stops being a column standing
   beside the claim and becomes a band lying under it. Held to 760, the range
   between the two breakpoints showed the tall desktop drawing capped to a
   third of the row it now had to itself, with the rest of the row empty — the
   same drawing, measured against nothing, standing in a space it was never
   composed for.

   No max-width on the wrapper, deliberately. The band is drawn to run past the
   right edge of the screen — its ring reaches x=432 of a 390 viewBox, so it
   clears the viewport whenever it is given the full width of the wrap, which
   it is at every width this rule covers. Capped, it would pull back inside the
   page and become a complete circle sitting in a box, which is the one thing
   this drawing must not be. Two class levels on the swap so it beats
   `.map .sys`. */
@media (max-width: 900px) {
  .claim-in { grid-template-columns: 1fr; gap: 48px; }
  .claim { max-width: 14ch; }
  .claim-map .map-d { display: none; }
  .claim-map .map-m { display: block; }
}

/* THE FIELD (claim.js), the page's second real-time moment since 2026-08-30.
   Same shape as the hero upgrade in section 23: the canvas ships invisible
   and the script that draws into it is the only thing that ever shows it, so
   no-JS, reduced-motion and blocked-script readers keep a clean cobalt ground
   with nothing behind the text. Negative z, and the scene's own isolation
   (section 5) is what makes that safe: -1 cannot escape the scene's stacking
   context, so the canvas paints above this ground and below everything in
   flow, text and disc included, without .claim-in needing a position of its
   own. pointer-events off so the field can never sit in an elementsFromPoint
   stack between a reader and the page. */
.claim-field {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
  opacity: 0;
  pointer-events: none;
  transition: opacity 380ms var(--ease-out);
}
.claim-scene.field-on .claim-field { opacity: 1; }

/* THE STREAM (claim.js), the second layer of the same moment since
   2026-08-31. Small roster avatars ride a winding path through the scene:
   each .stream-i is an absolutely positioned 44px wrapper whose offset-path
   comes off one custom property claim.js writes on the layer, re-derived
   from the measured text box on every resize, and whose offset-distance the
   field's own rAF loop advances every frame. So there is one real-time
   system here, not two: the stream starts, parks, resumes and dies exactly
   where the field does, and this stylesheet never animates it.

   Same shipping contract as the canvas above: the layer is invisible until
   claim.js lifts it (stream-on, granted only once a path is routed), and
   since the wrapper ships empty there is nothing to hide anyway. Clip
   rather than visible overflow, deliberately: the path's endpoints sit off
   the scene's left edge so items enter and leave at the frame, and without
   the clip they would ride into the roster and method scenes. z -1 with
   the canvas, later in the tree, which is the whole z story: field under
   stream under everything in flow, text and drawing included.

   While the stream runs the field dims one step, 1.0 to 0.62 on the canvas
   element, because two layers at the field's solo strength read as one
   dense crowd rather than a ground and a figure. Same specificity as the
   field-on rule above, later in the file, so it wins while both classes
   stand. The 44px box is border-box like every box on this page, so the
   white 2px ring sits inside it, the exact construction .marq-pic uses at
   48px; white for the marquee's own measured reason, cobalt on this ground
   vanishes. offset-rotate 0deg keeps the faces upright, a portrait riding
   a curve must not bank like a vehicle. pointer-events off on the layer,
   nothing inside is a link or a target, and the whole thing is aria-hidden
   in the markup: in the network band the faces identify, here they
   decorate. */
.claim-stream {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: clip;
  opacity: 0;
  pointer-events: none;
  transition: opacity 380ms var(--ease-out);
}
.claim-scene.stream-on .claim-stream { opacity: 1; }
.claim-scene.stream-on .claim-field { opacity: 0.62; }

.stream-i {
  position: absolute;
  top: 0;
  left: 0;
  width: 44px;
  height: 44px;
  offset-path: var(--stream-path, none);
  offset-rotate: 0deg;
  offset-distance: 0%;
}
.stream-i img {
  width: 100%;
  height: 100%;
  border: 2px solid var(--white);
  border-radius: 50%;
  object-fit: cover;
}

/* The two beats of the headline. Beat one is the existing .rise primitive on
   the first sentence, fired by the scene's own is-in; block, because
   translate does not apply to an inline box. Beat two: claim.js adds
   claim-armed while the scene is still below the viewport (never this
   stylesheet, so a 404 on the script strands nothing) and claim-resolved when
   continued scroll fires the reveal. The 300ms delay is choreography against
   the field: the convergence starts at the same tick and runs 760ms, so the
   sentence settles at 680ms and the lanes land 80ms behind it, inside one
   perceptual beat. Measured with both clocks in the field-measure pass. */
.claim-open { display: block; }
.claim-scene.claim-armed .claim-turn { opacity: 0; translate: 0 18px; }
.claim-scene.claim-resolved .claim-turn {
  animation: claim-turn-in 380ms var(--ease-out) 300ms forwards;
}
@keyframes claim-turn-in { to { opacity: 1; translate: 0 0; } }

@media (prefers-reduced-motion: reduce) {
  /* The same rule as section 10's safety net, for the same reason: parked at
     the value the keyframe lands on, so stillness asked for mid-session hands
     back the whole claim instantly rather than freezing its absence. */
  .claim-scene.claim-armed .claim-turn { opacity: 1; translate: 0 0; animation: none; }
}

@media (max-width: 760px) {
  /* The contract's width guard, the hero's own number: below 760 the mobile
     composition is finished as it is and claim.js never builds, this only
     guarantees a mid-session resize cannot leave a live canvas visible. The
     stream shares the guard since 2026-08-31, for the same reason and the
     same crossing: parked items are frozen, not removed, so without this a
     window dragged under 760 would strand a still ribbon of faces on the
     mobile composition. */
  .claim-field, .claim-stream { display: none; }
}

/* --------------------------------------------------------------------------
   18. SCENE 5 — THE OPERATING LINE, MERGED WITH WHAT YOU GET (p17)
   One spine, capped by a node at each end, with all four stations hanging
   off it now (the fourth promoted from the old What You Get scene's fourth
   card; see the HTML comment on this scene for the merge reasoning). The
   four service waypoints that used to hang below a turn are now endpoints
   on the channels diagram, where two of them already were under other
   names — so the styles that dressed them (.op-turn, .op-pill-sm) have gone
   with them rather than being left to be found by the next reader.

   .op-head has gone the same way: it dressed "The operating line" heading,
   which the merge dropped in favour of What You Get's own (.get-head,
   below), so nothing wears .op-head any more.
   -------------------------------------------------------------------------- */

/* THE SPINE STANDS ON THE PAGE'S LEFT MARGIN.
   It used to stand 30px inside it: .op-row carried margin-left: 30px, which
   put every station's left edge at 130 under a heading at 100 at 1440, with
   the drawn spine and both cap nodes following it out. Four modules and a
   vertical line, all a nudge to the right of the only left margin the page
   has, is the second half of the same defect the hero carried.

   Everything in this scene is now centred on one x, and it is the wrap's own
   edge. The four numbers below are the whole of it and they are stated
   together so they cannot drift apart:

     .opline::before  left: -1px    a 2px line, so 99..101, centre 100
     .op-cap          margin-left: -15px   30px wide, 85..115, centre 100
     .op-row          margin-left: 0       station's left edge lands on 100
     .op-node         left: -13px          26px wide, 87..113, centre 100

   The nodes straddle the margin rather than sitting inside it, which is what
   a bullet does and what the drawing wants: the spine threads through the
   modules, so the thread has to cross the edge they start on. */
.opline { position: relative; }

/* The spine. It runs between the two cap nodes and never past them, so the
   line visibly starts and ends on a node like every other connector. */
.opline::before {
  content: "";
  position: absolute;
  left: -1px;
  top: 15px;
  bottom: 15px;
  width: 2px;
  background: var(--stroke);
}

.op-cap {
  display: block;
  position: relative;
  width: 30px;
  height: 30px;
  margin-left: -15px;
  border: 2px solid var(--stroke);
  border-radius: 50%;
  background: var(--white);
}
.op-cap::after {
  content: "";
  position: absolute;
  inset: 7px;
  border-radius: 50%;
  background: var(--stroke);
}

.op-rows { display: grid; gap: clamp(18px, 2vw, 28px); }

/* THE BRANCH REACHES ACROSS THE SCENE FROM 1200 UP.
   Measured at 1440 before this: a station's row ran from x=100 to x=1023
   inside a wrap that ends at 1340, so the last 317px of every row was empty
   and the four explanations all sat left of the scene's own midline at 720.
   That is most of why this scene's ink came out 4.9:1 left-heavy while no
   other scene on the page is worse than 2.4:1.

   The gap is what moves, not the columns. Widening the explanation column
   instead would have pushed its measure past 70 characters, and a longer
   line is a worse paragraph, not a better composition. Widening the gap
   lengthens the connector, which is the one element here that is supposed to
   cover distance: p17's whole device is a labelled pill on a spine reaching
   sideways to what it means, and a 40px reach reads as a stub of that rather
   than as the thing itself. .op-say::before takes its width from this same
   token, so the line grows with the gap and can never come up short of the
   text it lands on.

   15vw capped at 220: 180px at 1200, 216px at 1440, 220px from 1512 up. At
   1440 that puts the explanations at 636..1196, so they straddle the midline
   with most of their body in the half that had nothing in it. */
@media (min-width: 1200px) {
  .opline { --op-gap: clamp(60px, 15vw, 220px); }
}

.op-row {
  display: grid;
  grid-template-columns: minmax(210px, 320px) minmax(0, 560px);
  gap: var(--op-gap);
  align-items: center;
  margin-left: 0;
  padding-block: clamp(14px, 1.6vw, 22px);
}

/* THE FOUR STATIONS ARE LABELS, AND THEY NO LONGER PRETEND OTHERWISE.
   They are <h3>, they have no role, no tabindex and cursor: auto, and their
   computed transform, background, box-shadow and border-colour were identical
   at rest and on hover — correctly, because there is nothing here to press.
   What was wrong is that they wore the affordance anyway: a bordered capsule
   carrying the same 4px offset shadow as the page's real pill CTAs. The page
   was teaching "bordered pill lifts off the ground, therefore it is pressable"
   and then breaking its own lesson four times in a row.

   The lift is what goes. .mod's 4px same-hue shadow is what makes a module
   read as a surface sitting forward of the ground, and this system already
   has a module without one: .mod-empty drops its shadow for exactly this
   reason, "a shadow is what lifts a surface forward and there is nothing on
   this one yet to lift" (section 7). These four have a stronger version of
   the same argument. A connector runs through them — the spine passes behind
   each pill and lands on the node on its own left edge — and a surface with a
   line threaded through it is seated in the drawing, not floating over it. A
   drop shadow on a thing the drawing passes through was incoherent before it
   was misleading.

   What they keep is the module itself: the 2px cobalt border and the lime
   face, which is the vocabulary and says "a component doing work". What they
   do not keep is the offer to be pressed. The scene still has a real state
   and it is the row, not the label: .op-row lights under the pointer (see the
   station spotlight below), which is the correct target, since the station
   and its explanation are one thing.

   Radius stays at 18px against the CTA's 999px, so the two shapes now differ
   in silhouette as well as in depth. */
.op-pill {
  position: relative;
  border-radius: 18px;
  padding: 16px 26px;
  font-size: var(--t-sub);
  line-height: 1.1;
  color: var(--cobalt);
}
/* Two class levels, and for the same reason .mod.mod-empty needs them: the
   ground rules above set a shade on every .mod they hold, at (0,2,0), so a
   single class here loses to .g-peri .mod and the lift survives the rule that
   was written to remove it. Measured before the second class was added: the
   pill still computed rgb(159,178,238) 0 4px 0. */
.mod.op-pill { box-shadow: none; }

/* THE AFFORDANCE, SETTLED: THE ROW IS THE TARGET AND THE STATION SAYS SO.
   The block above took the lift off these four and left one thing unfinished,
   which a third audit then measured: under the pointer the station's computed
   background, border colour, transform, shadow and text colour were all
   byte-identical to their resting values. So the page had a bordered, filled
   capsule that looked addressable, gave nothing back when addressed, and was
   not a control underneath either.

   The two ways out were to stop it looking like a surface, or to make the
   response it already has visibly reach it. The first is not available: a
   2px stroke with a lime face IS a module in this system's vocabulary, which
   DESIGN.md keeps as correctness rather than taste, and stripping the border
   would leave a shape that is not one of the six. So the second: the row's
   own lit state, which has existed since the spotlight was built and until
   now stopped at the ground behind the station, now reaches the station too.

   IT IS DELIBERATELY NOT A PRESS, AND THE DIFFERENCE IS VISIBLE RATHER THAN
   ASSERTED. Nothing moves and nothing lifts. The page's real controls answer
   the pointer by travelling toward their own shadow (.cta, .ctrl, .marq-card
   all translate 2px and shrink the shadow to match); this changes one colour
   and stays exactly where it is. And the trigger is .op-row, so hovering the
   explanation lights the station and hovering the station lights the
   explanation: whatever the pointer is over, the pair answers together, which
   is a thing being pointed at rather than a thing being offered.

   cobalt-deep is the same value the copy inside the pill already uses, so no
   colour is added and the border only ever gains contrast against lime.
   Outside a fine pointer this rule does not exist at all, and .is-lit is only
   ever written by the same script that writes --mx, so a touch reader sees
   the resting state and nothing flickers under a finger. */
@media (hover: hover) and (pointer: fine) {
  .op-pill { transition: border-color var(--dur-base) var(--ease-out); }
  .op-row.is-lit .op-pill { border-color: var(--cobalt-deep); }
}
@media (prefers-reduced-motion: reduce) {
  .op-pill { transition: none; }
}

/* The node sits on the pill's own left edge, so the spine threads through
   the modules rather than running alongside them. It is also the connector
   dot on the channels scene's mobile spine, which is why it outlived the
   waypoints it was written for. */
.op-node {
  position: absolute;
  left: -13px;
  top: 50%;
  translate: 0 -50%;
  width: 26px;
  height: 26px;
  border: 2px solid var(--stroke);
  border-radius: 50%;
  background: var(--white);
}
.op-node::after {
  content: "";
  position: absolute;
  inset: 6px;
  border-radius: 50%;
  background: var(--stroke);
}

.op-num { display: block; margin-bottom: 7px; }

/* The branch. Nothing on this page floats, so an explanation is wired to
   its station the same way every other pair of elements is: a 2px connector
   with a node at each end. The gap and the branch read the same token, so
   the line can never come up short of the module it lands on. */
.op-say {
  position: relative;
  font-size: var(--t-body);
  color: var(--text-body);
  text-wrap: pretty;
}
/* The branch stops at the node and the node stops --op-dot clear of the text.
   It used to sit at `right: calc(100% - 5px)`, which put a 10px cobalt dot
   five pixels inside the copy — measured overlap 5x10px on all seven rows,
   so every explanation on this scene had its first glyph sitting under a
   node. Nothing on this page may run over text.

   That was corrected to 2px, which is clearance rather than air: it reads as
   a node pressed against the sentence it points at. 12px is the figure both
   pseudo-elements now take, so the line and the node it carries move
   together and the branch can never come up short of what it lands on.
   Raising --op-gap instead would have widened the branch and the column at
   once and left the dot exactly where it was.

   The branch also hung low for as long as it has existed: it is 2px tall and
   sat at top:50% with no translate, so the whole of it fell below the line it
   was supposed to straddle and its centre sat 1px under the node's. Both take
   translate: 0 -50% now, so the line and the node it ends on are level. */
.op-say::before {
  content: "";
  position: absolute;
  right: calc(100% + var(--op-dot));
  top: 50%;
  translate: 0 -50%;
  width: calc(var(--op-gap) - var(--op-dot));
  height: 2px;
  background: var(--stroke);
}
.op-say::after {
  content: "";
  position: absolute;
  right: calc(100% + var(--op-dot));
  top: 50%;
  translate: 0 -50%;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--stroke);
}
.op-pill::after {
  content: "";
  position: absolute;
  left: calc(100% - 5px);
  top: 50%;
  translate: 0 -50%;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  border: 2px solid var(--stroke);
  background: var(--white);
}

/* -- the station spotlight ----------------------------------------------- --
   Four stations, no card surface between them, so the thing that responds to
   the pointer is the row itself: a radial wash of ground colour, which is
   element six of the vocabulary and the one gradient DESIGN.md's palette
   already licenses ("no gradients except the very soft radial ground washes
   seen in the deck"). No blur filter and no glow: both are banned outright
   and a radial gradient is the sanctioned way to get a soft edge without
   either.

   IT LIGHTENS, AND THAT IS A CONTRAST DECISION RATHER THAN A TASTE ONE. This
   ground is periwinkle and everything written on it is dark: .op-say sits at
   4.67:1 in body ink and .op-pill's copy at 5.57:1 in cobalt-deep. A cobalt
   wash would have driven the ground down toward the ink and taken both of
   those below AA wherever it fell. White drives the ground the other way, so
   the wash can only ever raise the contrast of the text standing in it.

   THE COORDINATES ARE WRITTEN ON THE ROW, NEVER ON THE WASH. --mx lands on
   .op-row and is read from here; the pseudo-element is painted from a value
   it inherits and is never itself addressed by script. That is the whole
   reason every number below can be changed, clamped, switched to a different
   radius at a different width or turned off under reduced motion without a
   line of JavaScript changing: the script knows where the pointer is and
   nothing else at all. It writes a raw offset from the row's own left edge;
   every correction applied to it lives here, beside the value it corrects.

   IT TRAVELS IN X ONLY, AND THAT IS GEOMETRY RATHER THAN LAZINESS. A station
   measures about 1210 by 134 at 1440: a band, not a field. A wash that also
   tracked y would have to be clipped by the row it belongs to, and a wash
   clipped while it still carries alpha draws a hard horizontal line across
   empty ground between two stations, which reads as a rendering fault rather
   than as light. Measured before this was changed: with the pointer parked
   6px inside a station's top edge, the clip landed at alpha 0.34 and was
   plainly visible in the gap above it. The two ways out are mutually
   exclusive on a row this short, because half the row's height is smaller
   than the radius the wash needs to fade over, so following y and never
   clipping cannot both be had. Following x is the half that carries
   something: the stations are wide. This is the same finding the selection
   lens already records in DESIGN.md, arrived at the same way.

   NOTHING IS EVER CLIPPED, BY CONSTRUCTION RATHER THAN BY LUCK. The wash is
   an ellipse whose vertical radius is 50% of its own box, so it reaches zero
   exactly at the top and bottom edges however tall the row turns out to be;
   and its centre is clamped to stay --wash-r from the left and right edges,
   so it reaches zero before them too. Both halves are stated once, in terms
   of --wash-r, so the radius and the clearance it needs cannot drift apart.

   --wash-out is what the wash is allowed to spill past the row, and it is
   the reason the fade finishes in the quiet ground between stations rather
   than at the first line of a sentence. */
@media (hover: hover) and (pointer: fine) {
  .op-row {
    position: relative;
    /* The wash is a negative-z child, so the row has to own a stacking
       context or it would paint behind the scene's own ground and vanish. */
    isolation: isolate;
    --wash-out: 30px;
    --wash-r: 130px;
  }

  .op-row::before {
    content: "";
    position: absolute;
    inset: calc(-1 * var(--wash-out));
    z-index: -1;
    opacity: 0;
    transition: opacity var(--dur-base) var(--ease-out);
    background: radial-gradient(
      var(--wash-r) 50% at
        clamp(
          var(--wash-r),
          calc(var(--mx, 50%) + var(--wash-out)),
          calc(100% - var(--wash-r))
        )
        50%,
      rgb(255 255 255 / 0.50) 0%,
      rgb(255 255 255 / 0.20) 43%,
      rgb(255 255 255 / 0) 100%
    );
  }

  .op-row.is-lit::before { opacity: 1; }
}

/* Asked for stillness: no wash at all. Written after the block above so
   source order settles it, since a media query adds no specificity of its
   own and both selectors are otherwise identical. */
@media (prefers-reduced-motion: reduce) {
  .op-row::before { content: none; }
}

.op-ask { margin-top: clamp(40px, 5vw, 64px); margin-left: 0; font-size: var(--t-lede); }

/* At 390px the branch would be longer than the text it points at, so the
   row stacks and the connector is dropped rather than shipped stunted. */
@media (max-width: 760px) {
  .op-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 14px;
    align-items: start;
  }
  .op-pill { justify-self: start; }
  .op-pill::after, .op-say::before, .op-say::after { content: none; }
  .op-say { margin-left: 26px; }
}

/* --------------------------------------------------------------------------
   18b. THE LEDE ABOVE THE SPINE (formerly SCENE 5b — WHAT YOU GET)
   What You Get was its own scene here, periwinkle, between the operating
   line's old lavender and the channels' lime. It is one scene with the
   operating line now (section 18's HTML comment carries the merge
   reasoning); only its opening survives as its own block — the label,
   heading, reach line and lede that used to frame four cards, now framing
   the spine instead.

   .get-head and .get-lede follow the roster and team ledes: a capped
   max-width so the line breaks stay short, margin doing the spacing rather
   than an empty element.

   .get-grid and .get-card are gone with the cards: three of the four
   restated an operating-line station and the fourth is now that station's
   promoted fourth pill, so nothing on the page wears them any more. Their
   text-colour override does not need reinventing elsewhere either — .op-say
   already sits directly on this section's periwinkle GROUND rather than on
   a lime MODULE, so it correctly inherits --text-body (--cobalt-deep here)
   rather than needing the lime-module pairing .get-card used to set
   explicitly (--cobalt/--ink-soft, root tokens section 2). .op-pill sets
   its own --cobalt for the same reason a lime module always has to: see
   section 7.
   -------------------------------------------------------------------------- */

/* THE OPENING IS TWO COLUMNS FROM 1200 UP.
   Same shape and the same reasoning as .team-open in section 21: a heading
   block and the paragraph that expands it, side by side rather than stacked,
   so the paragraph lands in the half of the scene that was empty.

   Measured before: this scene carried 34.3% of ink in its left half and 7.0%
   in its right, a 4.9:1 split where no other scene on the page is worse than
   2.40:1 and most are inside 1.5:1. The lede is the widest single block in
   the opening, so it is the one that moves.

   1200, not the page's usual 1180 or 1024, and the number is the wrap's own:
   below it the two columns would each be under 540px and the lede's 62ch
   measure would set it in eight or nine short lines, which trades one bad
   composition for another. Above it the columns are wide enough for the lede
   to keep the four-to-five-line shape it has when it runs full width.

   align-items: start so the heading and the lede share a top edge; the two
   blocks are different heights and the scene's left axis is the thing that
   has to agree, not their baselines. */
.get-open { margin-bottom: 0; }

@media (min-width: 1200px) {
  .get-open {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: clamp(32px, 5vw, 96px);
    align-items: start;
  }
  /* The lede has the column now, so its own cap would only re-impose a
     narrower measure inside it and leave the right half half-used again. */
  .get-lede { max-width: none; }
}

/* The 14px of top margin died 2026-08-31 with the mono eyebrow it was air
   under: the heading is "The Moda Method" itself now, and in the two-column
   opening above 1200 the dead label's air would have dropped the heading's
   top 14px below the lede's, off the shared top edge the grid aligns. */
.get-head { max-width: 16ch; margin: 0 0 20px; }

/* The reach line, staged since the Copy phase and placed here directly
   (round 10). Same two-beat shape the hero opens with: a short assertion at
   --t-sub, cobalt, then the longer paragraph beneath it explains rather than
   repeats it. Sub scale rather than lede scale, so it reads as a claim and
   not as the first line of the paragraph that follows. */
.get-reach {
  max-width: 30ch;
  margin-bottom: 16px;
  font-family: var(--f-display);
  font-size: var(--t-sub);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--cobalt-deep);
}

.get-lede { max-width: 62ch; margin-bottom: clamp(48px, 6vw, 88px); }

/* --------------------------------------------------------------------------
   19. SCENE 6 — THE CHANNELS (p08)
   The deck's strongest diagram. One message in, thirteen named endpoints
   out. Breadth is the argument here, so the endpoints are named — they are
   channels, not clients, so naming them costs nothing against the restraint
   law. No logos anywhere on this page.

   Thirteen equals were the wrong drawing. Creator work is what separates
   Moda from the agencies it gets compared to, and it was sitting at the
   weight of Facebook. The fan now leaves the machine in two ranks:

     near  five modules at 22px in a column at x=884, on 16px nodes, the
           first thing out of the machine — the creator work;
     far   eight names at 17px on the ground itself, on 10px nodes, out on
           the arc of destinations — where that work travels.

   The separation is stated in the vocabulary rather than in words: a module
   is a component doing work, a label is the name of a place. Five things
   Moda makes; eight places they go. A caption saying which half matters
   would be the admission that the drawing had not said it.

   Softening the far connectors was tried and rejected. Opacity is this
   file's documented depth device, but .c-soft's 0.55 composites cobalt to
   #8791CC on the lime ground — 2.50:1, under the 3:1 every line on this page
   is held to, and it would take 0.65 to clear it. The far rank is quieter
   through what it arrives at, not through a thinner line: all thirteen
   connectors stay 2px cobalt at full strength.

   The scene sits on lime, and that decides two things. Anything lime outside
   the machine has no face left, so the message capsule enters in periwinkle
   instead. And the near rank is periwinkle modules, which is what the
   vocabulary asks for and what ties them to the machine that made them — so
   the pale periwinkle halo they used to ride on is gone. It was a body at
   0.55 whose only job was to stand the white capsules off the lavender; with
   the capsules carrying real fill and the ground carrying real colour, it had
   nothing left to do but sit at the same value as the modules on top of it.

   The middle of the diagram is one body now. What stood there was an
   assembly — two rounded squares, a dashed line, a toggle, a readout, a loose
   node and the wordmark — and seven parts that never resolve into an object
   leave a machine-shaped hole at the centre of a drawing about a machine. The
   instrument set into the body is the roster disc's aperture at the roster's
   own proportions, because that device already means selection on this page
   and the two scenes should read as one system.
   -------------------------------------------------------------------------- */

/* ROUND 15, 2026-09-01: THE SCENE COMPRESSES TO ONE SCREEN, and the four
   rules below carry the whole of it. Measured before, at 1440x900, the
   scene stood 1200.2px tall: a reader arrived at a heading, scrolled, and
   met the machine's far endpoints a full screen later, which unpicks a
   diagram whose argument is one message reaching everything at once. The
   budget after: two seam-tight pads (74.88 each, the declarations in
   section 5 re-checked and unchanged, tight/tight as before), a one-line
   heading, a tightened head gap, and the fan capped at 1000px, which is
   what brings the whole scene under the 900px fold. Measured after, at
   1440x900: 897.3px, one screen. Each figure is at its own rule below.
   The sub-1181 spine composition is untouched by all four rules and
   still stacks to its own height. */
.ch-head { margin-bottom: clamp(20px, 2vw, 28px); }
/* On the heading itself, or the ch is measured against 17px body type.
   The 15ch cap and the 14px top margin died 2026-09-01: the cap forced
   "Where the message travels" to two lines (105.8px measured at 1440) on
   no argument beyond where an older, longer heading happened to break,
   and one line hands 66.9px straight back to the fold budget above. The
   margin was air under a kicker that died 2026-08-30. */
.ch-head h2 { max-width: none; }

/* The supporting line under the heading, 2026-09-01. It carries the .lede
   scale and nothing of its own but a seat: 20px of air is what .close-lede
   already takes under its own heading, and the 54ch cap is what holds it to
   two lines at 1440 instead of running the full 1240px measure, which no
   running text on this page does.
   It is also a debit against the fold budget the comment above defends, and
   that is recorded rather than hidden: the scene measured 897.3px at
   1440x900 with nothing under the heading and measures 978.9px with this line
   under it, so the round-15 compression's one-screen result is spent. It is
   spent on the only thing the fan cannot say for itself, which is the order
   its endpoints fire in, and the figure is left here rather than left to be
   rediscovered. */
/* .ch-reach carries the international claim and .ch-lede explains the
   drawing under it, in that order, since 2026-09-08. The claim goes first
   because it is the only thing in this scene a competitor cannot copy, and
   the scene was floating without it: a heading, a diagram and a caption
   about the diagram. Both take the page's lede measure so the pair reads as
   two paragraphs of one voice rather than as a statement and a footnote. */
.ch-reach { margin-top: 20px; max-width: 54ch; }
.ch-lede { margin-top: 20px; max-width: 54ch; }

/* THE FAN IS CAPPED AT 1000px SINCE 2026-09-01, and the cap is the round-15
   compression's main lever: the drawing's 960/1440 aspect made it 874.7px
   tall at the old 1312px render, one screen for the fan alone. At 1000 it
   renders 666.7px tall and the whole scene fits 900. The box stays a
   .wrap-wide child, so the cap centres it; centred rather than pinned to
   the wrap's left spine because the fan's ink would otherwise end 300px
   short of the right margin and hand the scene the same left-heavy ink
   imbalance the method scene was measured into (section 18b, 4.9:1).

   THE CAP ALSO CLOSES A LATENT JAM. Far pins ride a 50-unit pitch, and a
   far pill is 40.4px tall, so below a 1163px render the pills in that rank
   overlap: the old min(1440px, ...) width passed through exactly that band
   at 1181..1300 viewports (36.9px pitch at 1181, 3.5px of overlap), shipped
   and unmeasured because the harness checks 1440 and 1180 and the jam lived
   between them. At a constant 1000px render the pitch is 34.7px at every
   fan width, and the far pill slims to meet it (the .ch-far media rule
   below).

   The clip is the edge the endpoint band is measured against. Every pin is
   placed as a percentage of this box while its label is sized in pixels, so
   a label that outgrows the box is cut here rather than pushing the page
   sideways. Both ranks are fitted against this edge at the 1000px the fan
   now always renders at; measured there, the tightest of the thirteen
   clears it by 6.9px, and the arc's own 12-unit pullback (the coordinates
   comment below) still holds. 6.9 is thinner than the 30px the old fan
   carried and it is constant: the cap makes the fan the same 1000px at
   every viewport that draws it, so the clearance no longer varies with
   the window and cannot be eaten by a narrower one.

   --map-w is the width the drawing actually renders at, and it is here for
   the same reason the selection scene states it: every drawn length below is
   measured in screen pixels rather than user units, so it has to know. It
   carries the same 1000px cap as the box, or every --len on the fan would
   be measured against a width the drawing no longer renders at. */
.ch-map {
  position: relative;
  /* the old 12px top margin died into the head's own tightened
     margin-bottom above: one owner for the head-to-drawing gap */
  width: min(1000px, 100% - var(--gutter) * 2);
  overflow: clip;
  --map-w: min(1000px, 100vw - var(--gutter) * 2);
}

/* The label is pinned in percentages, so its containing block has to be the
   drawing and nothing else. On mobile .ch-map also holds the endpoint list,
   which made the same percentage land inside the machine instead of on the
   message pill. */
.ch-stage { position: relative; }

/* -- the near rank. A module is a capsule in lime or periwinkle with a
      same-hue shadow ~4px down, and lime is the ground here, so the face
      comes from .mod-peri rather than being written out a second time. The
      capsule only has to say how big it is.

      Line-height is stated for the same reason .roster-name states it: a
      module inherits the 1.55 of running text, which at 22px would add 12px
      of leading inside a pill holding one line. -- */
.ch-near {
  border-radius: 999px;
  padding: 14px 24px;
  font-size: var(--t-lede);
  line-height: 1.2;
  font-weight: 500;
}

/* -- the far rank. Now the same module family as the near rank (2px cobalt
      border, periwinkle fill, cobalt-deep text — never plain cobalt, which
      fails AA at 4.21:1 on periwinkle) rather than bare ground text, but
      visibly smaller so the rank difference stays legible: padding cut
      roughly in half (8px/16px against near's 14px/24px) and the font stays
      at 17px, already a step below near's 22px, so the two ranks are told
      apart on both axes at once rather than just one.

      Line-height is stated for the same reason .ch-near states it: inherited
      1.55 running-text leading would add far more padding-that-isn't-padding
      inside a pill than a smaller pill can carry.

      The shadow is kept but halved (2px against near's 4px, both against the
      same --shade-peri) — a full 4px shadow on a smaller pill would carry as
      much visual weight as the near rank's own, undoing the size difference
      the padding and font are making. A lighter shadow instead of none, so
      the far rank still reads as a raised module and not a flat label. -- */
.ch-far {
  border-radius: 999px;
  padding: 8px 16px;
  font-size: var(--t-body);
  line-height: 1.2;
  font-weight: 500;
  box-shadow: 0 2px 0 var(--shade-peri);
}
/* On the fan only, the far pill slims to the pitch the 1000px cap gives
   it, 2026-09-01. The arc's eight pins ride 50 user units apart, 34.7px
   at the capped render, and the 8px-padded pill is 40.4px tall: the rank
   would overlap itself by 5.7px, every pill lapping its neighbour. 3px of
   vertical padding makes the pill 30.4px against the 34.7px pitch, 4.3px
   of air, measured; the horizontal padding gives up 2px in sympathy so
   the capsule keeps its proportions. Scoped to min-width 1181 because the
   spine composition below it stacks on its own 14px gap, has no pitch to
   meet, and lets a long label wrap to two lines, which 3px of padding
   cannot dress. The rank distinction the base rule argues (smaller pill,
   smaller type, halved shadow) only widens here: near stays 14px-padded
   at 22px type, far drops further below it. */
@media (min-width: 1181px) {
  .ch-far { padding: 3px 14px; }
}

.ch-end .op-node { display: none; }

/* -- the drawn lengths, and the trap that put them here.

      .draw fills stroke-dasharray from --len, and a pulse fills its gap from
      the same figure, so both need the path's length as the renderer measures
      it. Under vector-effect: non-scaling-stroke that is not the length in
      user units getTotalLength() reports — it is that length in CSS pixels on
      screen, and pathLength does not correct it. Measured against a
      vector-effect: none control on this very scene: the same path laid its
      dash pattern out against two different figures at two widths. So a --len
      stated in user units is right at exactly one viewport and wrong at every
      other, which is the selection scene's finding and the reason these live
      in the stylesheet at all — an inline custom property beats a media
      query, so on the elements the narrow composition could never correct
      them.

      Each figure is the path's user length over its viewBox width, times the
      rendered width, plus 1.5%. The margin is the selection scene's: 100vw
      counts a scrollbar that this element's own width does not, and
      overshooting finishes a stroke a shade early where undershooting leaves
      a hair of it permanently undrawn.

      --pd is the pulse's place in its rank's cycle, spaced evenly so no two
      lines in a rank ever fire together. Positive, not negative: a negative
      delay starts the animation mid-travel, which would put pulses on lines
      the arrival has not finished drawing yet. -- */
.ch-in  { --len: calc(0.0705 * var(--map-w)); }                  /* 100.0 over 1440 */

.ch-cn1 { --len: calc(0.3005 * var(--map-w)); --pd: 4.32s; }     /* 426.3 */
.ch-cn2 { --len: calc(0.2388 * var(--map-w)); --pd: 1.44s; }     /* 338.9 */
.ch-cn3 { --len: calc(0.1931 * var(--map-w)); --pd: 0s;    }     /* 274.0 */
.ch-cn4 { --len: calc(0.2388 * var(--map-w)); --pd: 2.88s; }     /* 338.9 */
.ch-cn5 { --len: calc(0.3005 * var(--map-w)); --pd: 5.76s; }     /* 426.3 */

.ch-cf1 { --len: calc(0.4350 * var(--map-w)); --pd: 0s;    }     /* 617.2 */
.ch-cf2 { --len: calc(0.4213 * var(--map-w)); --pd: 5.4s;  }     /* 597.7 */
.ch-cf3 { --len: calc(0.4055 * var(--map-w)); --pd: 10.8s; }     /* 575.3 */
.ch-cf4 { --len: calc(0.3875 * var(--map-w)); --pd: 16.2s; }     /* 549.8 */
.ch-cf5 { --len: calc(0.3875 * var(--map-w)); --pd: 2.7s;  }     /* 549.8 */
.ch-cf6 { --len: calc(0.4055 * var(--map-w)); --pd: 8.1s;  }     /* 575.3 */
.ch-cf7 { --len: calc(0.4213 * var(--map-w)); --pd: 13.5s; }     /* 597.7 */
.ch-cf8 { --len: calc(0.4350 * var(--map-w)); --pd: 18.9s; }     /* 617.2 */

/* -- the pulse. A scene whose whole subject is something travelling outward
      was completely still, so light now runs from the core to each endpoint.

      It is a node riding its connector, not a lit stretch of the connector,
      and that is a contrast finding rather than a preference. A pulse painted
      over the line becomes the line for as long as it is there, so anything
      bright enough to read as light would take that segment under the 3:1
      every line on this page holds: white measures 1.28:1 on lime, white at
      half strength over cobalt composites to #9DA1EA and 1.99:1, periwinkle
      1.38:1. That is the same finding that removed .c-soft from the far rank,
      pointing the other way. Going darker instead — cobalt-deep at 7.68:1 —
      is safe but invisible, because a 2px cobalt line already is that dark.

      A node has the answer the page had already written: a white core inside
      a cobalt ring is how every endpoint here is drawn, and white bounded by
      cobalt is a node rather than a low-contrast line. A zero-length dash
      under a round cap paints exactly that — a dot — so the casing path draws
      the ring and the core path the centre. The connector underneath keeps
      its 2px and its 5.81:1 for its whole length, at every instant.

      Rank is carried by how often and how large, which is the entire point of
      the scene. The creator rank runs a 13px pulse on a 7.2s cycle, the
      channels a 9px one on 21.6s: three times as often, half again the size.
      Travel takes 40% of the near cycle and 22% of the far, which puts every
      one of the thirteen between 85 and 135 px/s at 1440 — the near rank is
      more present, not more frantic. For the rest of the cycle the dash sits
      past the end of its own path, so it is not painted at all; nothing is
      faded out, and nothing is left parked on the drawing.

      Every figure here is in px on purpose. Under non-scaling-stroke the
      renderer lays both the dash pattern and the stroke width out in screen
      pixels, so a pulse stated in user units would grow and shrink with the
      viewport while the endpoints it travels between did not. -- */
.ch-p, .ch-pc {
  fill: none;
  stroke-linecap: round;
  stroke-dasharray: 0.01px var(--len);
  /* The settled composition, and so also the reduced-motion state and the
     screenshot: every pulse parked 78% of the way out. It still says which
     rank matters, because the near pulse is half again the size of the far
     one and that difference is a diameter rather than a movement.

     78 rather than the middle of the run, and the number is geometry. Every
     far connector spends its middle third in the bundle of vertical runs the
     thirteen routes share, so a pulse parked there lands in the one place on
     the drawing that is already dense; at 78% each is out on its own turn or
     its own approach, which is where a reader can see thirteen of them and
     count two sizes. */
  stroke-dashoffset: calc(-0.78 * var(--len));
}
.ch-p  { stroke: var(--stroke); }
.ch-pc { stroke: var(--core); }
.ch-pn.ch-p  { stroke-width: 13; }
.ch-pn.ch-pc { stroke-width: 7; }
.ch-pf.ch-p  { stroke-width: 9; }
.ch-pf.ch-pc { stroke-width: 4; }

/* Parked at the origin until the scene has arrived, where the exit port's own
   face covers it, exactly as .draw holds its connector undrawn. Without this
   a pulse sits lit on a line that has not been drawn yet. */
.motion .ch-p, .motion .ch-pc { stroke-dashoffset: 0; }

/* 1.4s clears the longest arrival — a 440ms delay plus the 900ms draw — so no
   pulse ever runs on an unfinished line. */
.motion .is-in .ch-pn {
  animation: ch-pulse-near 7.2s linear infinite;
  animation-delay: calc(1.4s + var(--pd, 0s));
}
.motion .is-in .ch-pf {
  animation: ch-pulse-far 21.6s linear infinite;
  animation-delay: calc(1.4s + var(--pd, 0s));
}
@keyframes ch-pulse-near {
  0%   { stroke-dashoffset: 0; }
  40%  { stroke-dashoffset: calc(-1 * var(--len)); }
  100% { stroke-dashoffset: calc(-1 * var(--len)); }
}
@keyframes ch-pulse-far {
  0%   { stroke-dashoffset: 0; }
  22%  { stroke-dashoffset: calc(-1 * var(--len)); }
  100% { stroke-dashoffset: calc(-1 * var(--len)); }
}

/* Coordinates live here, never in an inline style. An inline custom property
   beats every stylesheet rule, so the media query below cannot move a pin
   that carries its own --x. That is the fault which left the roster names at
   their desktop positions on a phone, and this page has now paid for it
   twice. These thirteen were the last inline coordinates on it.

   Read against the viewBox, which is offset: x is x/1440, y is (y+90)/960.
   The near five are a column at x=884, pinned 30 units clear of their node.
   The far eight sit on an arc of radius 927 about (48.5, 390) — the point
   the message enters at — pinned 24 units clear of their smaller node. The
   longest string in each rank takes the innermost position of that rank, so
   the two labels that could reach the clip are the two furthest from it.

   The box grew by thirty units at each end, which is what gives the outermost
   node 50 units of clearance instead of 18. The pitch of both ranks is
   untouched.

   The arc came in by twelve units, and the reason is measured rather than
   compositional. Every connector now finishes on its endpoint's centre rather
   than short of it, so the far routes' bounding boxes reach twelve units
   further right than they used to — and the gate's proximity check reads
   bounding boxes. At 1181 that put the near rank's labels 1.6px from the box
   of a curve whose nearest actual stroke is 118 units away. Twelve units back
   restores the 8px the previous fan had, moves the longest far label further
   from the clip, and costs the arc nothing a reader could see.

   --pd is here as well as on the connectors because the spine composition
   below has no thirteen lines to carry it: there, the same figure paces the
   node each endpoint sits on. */
.ch-n1 { --x: 63.47%; --y: 30.21%; --pd: 4.32s; }
.ch-n2 { --x: 63.47%; --y: 40.10%; --pd: 1.44s; }
.ch-n3 { --x: 63.47%; --y: 50%;    --pd: 0s; }
.ch-n4 { --x: 63.47%; --y: 59.90%; --pd: 2.88s; }
.ch-n5 { --x: 63.47%; --y: 69.79%; --pd: 5.76s; }

.ch-f1 { --x: 62.43%; --y: 6.25%;  --pd: 0s; }
.ch-f2 { --x: 64.03%; --y: 11.46%; --pd: 5.4s; }
.ch-f3 { --x: 65.42%; --y: 16.67%; --pd: 10.8s; }
.ch-f4 { --x: 66.60%; --y: 21.88%; --pd: 16.2s; }
.ch-f5 { --x: 66.60%; --y: 78.13%; --pd: 2.7s; }
.ch-f6 { --x: 65.42%; --y: 83.33%; --pd: 8.1s; }
.ch-f7 { --x: 64.03%; --y: 88.54%; --pd: 13.5s; }
.ch-f8 { --x: 62.43%; --y: 93.75%; --pd: 18.9s; }

/* The message label rides on the pill the SVG already drew, rather than the
   pill being drawn twice — once as geometry and once as a box. That pill is
   periwinkle now the ground is lime, so the label takes --cobalt-deep with
   it: 5.57:1, where plain cobalt would have been 4.21:1. */
.ch-msg {
  position: absolute;
  left: 2.6%;
  top: 50%;
  translate: 0 -50%;
  color: var(--cobalt-deep);
}

/* The fan becomes a spine and the endpoints stop being pins. Same markup,
   same thirteen strings, same two ranks — only the composition changes.

   This switches at 1180px, not at the page's usual 760px, and the number is
   arithmetic rather than taste. Pins are spaced as a fraction of the map's
   rendered height, which is the container width times 900/1440, while a pill
   is a fixed number of pixels tall — so every gap in the fan closes as the
   page narrows, and one of them closes first. The tightest is between two
   near modules: 95 units apart against 58.4px of module, which runs out at a
   container of 885px. At the 1062.9px container a 1181px viewport gives, it
   still measures 11.7px, and the far rank's 50-unit pitch measures 10.6px
   against a 26.4px label. Both are still gaps here; below this they are not.

   Thirteen endpoints made this tighter than eleven did, which is why the box
   grew to 900 units rather than the pitch being squeezed. The band this
   protects is the one a laptop or a half-screen window actually sits in. */
@media (max-width: 1180px) {
  .ch-head { max-width: none; }

  /* The upright drawing is 390 across and never renders wider than 430, so
     every drawn length here is measured against a different viewBox and a
     different rendered width. Same rule, same 1.5% margin. */
  .ch-map  { --map-w: min(430px, 100vw - var(--gutter) * 2); }
  .ch-min  { --len: calc(0.4527 * var(--map-w)); }              /* 173.9 over 390 */
  .ch-mout { --len: calc(0.6687 * var(--map-w)); --pd: 0s; }    /* 256.9 over 390 */
  /* Centred on the pill the SVG draws, not guessed near it. The pill's
     centre sits at y=42 of the 430-unit viewBox, so 9.8% with a -50%
     translate lands on it at every width. It used to overshoot the pill's
     top edge by a pixel — small, but it is the same class of defect as all
     the others and the gate treats it as one. */
  .ch-msg { top: 9.8%; left: 7%; translate: 0 -50%; }

  /* the fan cannot be rethought at this width, so it is replaced rather than
     squeezed; the machine keeps its designed size instead of ballooning */
  .ch-map .map-d { display: none; }
  .ch-map .map-m { display: block; max-width: 430px; }

  /* The spine begins where the drawing's last connector lands, which is x=30
     of a 390-unit viewBox — so the inset is that fraction of the width the
     drawing actually renders at, not a flat 30px. It was flat, and the two
     only agreed when the SVG happened to render at 390: at 500px it lands at
     430 and the spine sat 4px to the left of the line it continues. */
  .ch-ends {
    position: relative;
    display: grid;
    gap: 14px;
    margin: -18px 0 0 calc(min(430px, 100%) * 0.0769);
    padding-top: 22px;
  }
  /* The spine ends on the last node, so `bottom` is half the height of the
     last endpoint. That height changed with the far rank's own pill (see
     .ch-far above): a one-line far label used to be bare text, 17px at 1.55
     line-height, 26px tall, half of which was 13px. Now it is a bordered
     module — 2px border, 8px vertical padding, 17px at the pill's own 1.2
     line-height — 40px tall, half of which is 20px. That is also why the
     far rank is ordered to finish on "Facebook": the longest string in the
     rank wraps to two lines on a narrow phone, and had it been last the
     spine would stop short of the node it ends on. */
  .ch-ends::before {
    content: "";
    position: absolute;
    left: -1px;
    top: 0;
    bottom: 20px;
    width: 2px;
    background: var(--stroke);
  }

  /* The spine's own pulse: the same travelling node the fan carries, drawn in
     CSS because the spine is. It runs on percentage offsets rather than a
     translate, so it reaches the end of a spine whose height is set by
     thirteen endpoints of unknown length. Centred on the 2px line at left:-1,
     so it stays on the spine at every stop and never reaches a label. */
  .ch-ends::after {
    content: "";
    position: absolute;
    left: -6px;
    top: 42%;
    width: 14px;
    height: 14px;
    box-sizing: border-box;
    border: 2px solid var(--stroke);
    border-radius: 50%;
    background: var(--white);
    translate: 0 -50%;
  }
  .motion .ch-ends::after { top: 0; }
  .motion .is-in .ch-ends::after {
    animation: ch-spine 7.2s linear infinite;
    animation-delay: 1.4s;
  }
  .ch-end {
    /* static broke the absolutely-positioned node inside every capsule, so
       all thirteen dots collapsed onto a single point and twelve connectors
       appeared to end on nothing. relative restores each pill as its own
       positioning context without reintroducing the pin coordinates. */
    position: relative;
    left: auto;
    top: auto;
    translate: none;
    justify-self: start;
    margin-left: 22px;
    /* pins never wrap; a stacked endpoint must, or the longest label is
       silently clipped at the viewport edge */
    white-space: normal;
    max-width: calc(100% - 22px);
    border-radius: 22px;
  }
  .ch-end .op-node { display: block; left: -37px; }

  /* The two ranks survive the change of composition, because a stacked list
     of thirteen equals would throw away the whole point of the scene. Five
     modules at 22px on a 26px node, then eight names at 17px on a 16px node:
     the same two statements the fan makes, made vertically. The spine is the
     one thing both ranks share, so the smaller node is re-centred on it
     rather than left to sit off the line. */
  .ch-far .op-node { width: 16px; height: 16px; left: -32px; }
  .ch-far .op-node::after { inset: 3px; }

  /* One gap wider than the rest, where the near rank ends. */
  .ch-near + .ch-far { margin-top: 10px; }

  /* The rank difference the fan makes with thirteen pulses, made with
     thirteen nodes instead: a creator node takes on charge every 7.2s and a
     channel node every 21.6s, on the same cycles and the same phases the fan
     uses. Scale rather than colour, so nothing on the spine changes value and
     the reduced-motion state is simply the authored size — where the two
     ranks already differ, 14px of core against 10px. */
  .motion .is-in .ch-near .op-node::after {
    animation: ch-charge 7.2s var(--ease-out) infinite;
    animation-delay: calc(1.4s + var(--pd, 0s));
  }
  .motion .is-in .ch-far .op-node::after {
    animation: ch-charge 21.6s var(--ease-out) infinite;
    animation-delay: calc(1.4s + var(--pd, 0s));
  }
}

@keyframes ch-spine {
  0%   { top: 0%; }
  40%  { top: 100%; }
  100% { top: 100%; }
}
@keyframes ch-charge {
  0%, 62%, 100% { scale: 1; }
  74%           { scale: 1.4; }
}

/* --------------------------------------------------------------------------
   20. BACKGROUND, AS CREDENTIALS (p14)
   Merged into one scene with the team below it, and section 21 carries the
   merge's reasoning. The corridor drawing that used to carry this scene is
   gone; what it was doing here, section 21 explains and buries. What stays
   is the four credentials' own type: a plain vertical run of prose,
   deliberately not a row or a grid, because four claims held level with
   each other is one step from the stat row the restraint law bans.
   -------------------------------------------------------------------------- */

/* Plain cobalt on periwinkle is 4.21:1 and fails as text. Outlines are fine
   at 3:1, so --stroke stays cobalt and only the type steps down. A field
   label reads --stroke as well, and the roster's ask now sits on this ground,
   so it steps down with the rest. */
.g-peri .label,
.g-peri .field label { color: var(--cobalt-deep); }

/* The hover shade follows the same rule as .g-peri .mod: white driven darker
   would sit above this ground, so the ground drives itself darker instead. */
.g-peri .field input:hover { box-shadow: 0 4px 0 var(--shade-peri); }

/* One edge of every panel catches the light. White on periwinkle is the
   brightest value available without leaving the palette.

   The panels stay white and they are not modules — they are light landing on
   a surface, which is the whole subject of the scene. They do take the
   module's ~4px offset shadow, because a panel with no shadow reads as a hole
   cut in the ground rather than a plane in front of it. On this ground that
   shadow is --shade-peri, by the rule set out on .sh above: a shade has to be
   darker than what it lands on, and white's own shade is not. */
.edge-lit {
  stroke: var(--white);
  stroke-width: var(--w);
  stroke-linecap: round;
  fill: none;
}

/* The heading stands alone and the credentials sit BELOW the portraits,
   2026-08-31. They sat beside the heading for one round, in a two-column
   grid this rule carried, and that grid died with the arrangement: the
   owner moved the record under the people it is a record of, so the faces
   lead the scene and the four sentences read as what these three have done
   rather than as the case for scrolling to them. What survives every
   rearrangement is the form: a plain vertical run of prose, never a row or
   a grid, because the restraint law bans figures held level with each
   other and three of these four carry one ("over 5 years", "9 and 10
   figures", "20 years+"). A stack breaks no such rule; a row or a 2x2 of
   these four would, which is why it is neither. */
.team-open { margin-bottom: clamp(64px, 8vw, 120px); }

/* THE CREDENTIAL STATIONS, 2026-09-01. The plain vertical run (.bg-say,
   whose rule this replaces at the same seat under the portraits) becomes
   stations on a short spine: the method scene's own device, rebuilt here
   under .cred-* classes so neither scene's rules can reach the other. The
   geometry is the opline's, restated rather than shared on purpose: spine
   a 2px stroke at left -1px running cap-centre to cap-centre, 30px caps at
   margin-left -15px, 26px nodes straddling the same edge at left -13px,
   all centred on the wrap's own left margin, the four numbers that must
   never drift apart (section 18 states the same four for the opline).

   What the stations do NOT take from the method scene is as deliberate as
   what they do: no lime pills, no mono numbers, no branch, no spotlight.
   A pill would make each sentence a module face, and a face invites the
   eye to weigh the four against each other, which is one step from the
   stat row the restraint law bans and this page has already spent its one
   licence on (section 21b). The station is the sentence itself, prose
   beside a node.

   The node sits on the FIRST LINE of its sentence, not the row's middle:
   .bg-fact is 28px at line-height 1.16, so the first line box is 32.48px
   and its centre 16.24px from the row's top. Written as
   calc(var(--cred-fs) * 0.58) rather than as 16px, 2026-09-08: half of
   1.16 is 0.58, so the offset re-derives itself from whatever size
   --cred-fs holds. At 28px that is 16.24px, the number this paragraph
   has always stated; at the 22px the same variable takes below 768 it
   is 12.76px, which the hard 16px got wrong by 3.2px.

   Updated 2026-09-08: the reason used to be that two of the four wrapped.
   It is not any more, now .bg-fact runs 46ch instead of 30ch, so all
   three sentences hold one line from 1024 up. The rule keeps its seat
   anyway, because .bg-fact drops to 22px below 768 and the text is left
   free to wrap there like everywhere else on mobile, so the node still
   needs the first line and not the row's middle to sit on.

   Arrival is the existing .rise primitive on each row (markup carries the
   --d stagger), so the scene's own .is-in staggers the four in once and
   the section-10 reduced-motion net parks them finished; without .motion
   nothing is ever hidden. The spine and caps do not animate, exactly as
   the opline's never have. */
.cred-line {
  position: relative;
  margin: clamp(56px, 7vw, 96px) 0 0;
}
/* THE SPINE IS DRAWN PER ROW NOW, 2026-09-08, and the reason is this page's
   own connector rule rather than taste. One absolutely positioned run down
   .cred-line was replaced first, from cap-centre to cap-centre, by a run
   measured 14px short of each outer node. Measured, that left the stroke
   poking exactly 1px past a 13px node radius: too little to read as a
   deliberate overshoot, more than enough to read as a rendering fault.

   Neither end state was right, because a dangling stroke is not in this
   page's vocabulary at all. Every connector on this site starts and ends on
   an element; hero.js states the same rule for the GL scene in the same
   words. So the spine is no longer one long line that happens to pass behind
   some circles. It is a connector per gap, from each node's centre to the
   next node's centre, and the last row draws none because it has nothing
   below it to land on.

   The height is exact rather than eyeballed. The connector starts at its own
   node's centre, --cred-fs * 0.58 from the row's top, and must reach the
   next node's centre, which is (row height + gap + the same 0.58) from that
   same top. The two 0.58 terms cancel, so the height is the row's own height
   plus the gap: calc(100% + var(--cred-gap)). That holds whether the
   sentence takes one line or three, which the old single run could not,
   because its bottom was measured off .cred-rows' padding and silently
   assumed the last claim never wrapped. Below 768 it does.

/* .cred-cap and its ::after died 2026-09-08: two empty end stations at
   30px, larger than the 26px nodes that actually carried a sentence, so
   the emptiest circles on the spine pulled the most attention. The markup
   dropped both end caps, and the spine above no longer runs cap-centre to
   cap-centre because there are no caps to run between: it is a connector per
   gap now, node centre to node centre. */
.cred-rows {
  display: grid;
  /* Both are read back below: --cred-gap by the per-row connector, --cred-fs
     by .bg-fact and by the node's own offset, so the three cannot drift. */
  --cred-gap: clamp(24px, 2.6vw, 38px);
  --cred-fs: var(--t-sub);
  gap: var(--cred-gap);
  padding-block: clamp(18px, 2vw, 28px);
}
.cred-row {
  position: relative;
  padding-left: 34px;
}
.cred-row:not(:last-child)::after {
  content: "";
  position: absolute;
  left: -1px;
  top: calc(var(--cred-fs) * 0.58);
  height: calc(100% + var(--cred-gap));
  width: 2px;
  background: var(--stroke);
}
.cred-node {
  position: absolute;
  left: -13px;
  top: calc(var(--cred-fs) * 0.58);
  translate: 0 -50%;
  width: 26px;
  height: 26px;
  border: 2px solid var(--stroke);
  border-radius: 50%;
  background: var(--white);
}
.cred-node::after {
  content: "";
  position: absolute;
  inset: 6px;
  border-radius: 50%;
  background: var(--stroke);
}

.bg-fact {
  font-family: var(--f-display);
  font-size: var(--cred-fs);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.16;
  color: var(--cobalt-deep);
  max-width: 46ch;
}
/* The four mono eyebrows the facts used to carry died 2026-08-30: each
   repeated its own sentence's first word in caps above it, an index of a
   list of four, which is decoration in the measuring voice. The
   margin-bottom the facts carried while they were a bare run died
   2026-09-01 into .cred-rows' own gap, one rhythm owner instead of two. */
/* max-width raised from 30ch to 46ch, 2026-09-08: 30ch measured 545px
   against 1206px on offer at 1440, which was the only reason any of these
   sentences wrapped. The longest of the three current claims, "Ran
   performance marketing for 9-figure spend environments.", measures 785px
   at 28px/600, so 46ch leaves it margin rather than fitting it exactly.

   Below 768 there is only 657px on offer and that same sentence still
   needs 785px at 28px, so .bg-fact is held to 22px there instead:
   785 x 22/28 = 617px, inside 657px. Above 768 nothing forces a break and
   .bg-fact stays at --t-sub. Below 768 it may still wrap, same as
   everywhere else on mobile; nothing here forces a single line. */
@media (max-width: 768px) {
  .cred-rows { --cred-fs: 22px; }
}

/* --------------------------------------------------------------------------
   21. SCENE 7 — THE TEAM, MERGED WITH BACKGROUND (p03/p14)
   The partner asked for depth on this scene and got a card row, which is the
   default grid the direction bans outright — and the middle card sat 17px
   low, so three identical relationships were drawn with three different
   connector lengths.

   It is p03's corners now: a portrait disc set into an overlapping pair of
   large bodies, in lime and cobalt, both carrying real thickness. Depth is
   the composition's job, not the layout's — the front bodies are 160, 142
   and 114 units of radius, sitting at three different heights in three boxes
   of three different depths, so the cards land staggered on their own. Every
   person is a cobalt body behind and to the left with a lime body in front
   holding the portrait, which is what keeps each seam between neighbours a
   colour change: two lime circles meeting merge into one shape. Because the depth
   is in the drawing, the drop from portrait to card is the same 120 units on
   every person, which is what three identical relationships deserve.

   The corridor drawing was tried here as a full-bleed backdrop for the three
   bodies and then cut. It was built and screenshotted before the verdict, so
   this records what it cost rather than a guess: at 1440 the box is wider
   than the drawing's own 720x760, so "slice" scaled it 1.72x and the outer
   floor ellipse arrived at rx 740, larger than any portrait and reading as
   the subject of the scene. Scaling was only the visible half. The deeper
   fault is that a corridor and a portrait are both built from this page's
   six elements, the same stroke weights and the same nodes, so layering
   them offers no figure-ground separation to win: the backdrop cannot
   recede from the thing in front of it when both are the same line. The
   collision gate passed it throughout, because no text collided — a
   reminder that the gate measures overlap and never composition. */

.team-head { max-width: 20ch; margin: 14px 0 0; }

/* The line under it, 2026-09-01. Same .lede scale as the channels line one
   scene up, so the two supporting lines on this half of the page read as one
   register rather than two. The 46ch cap is stated in the sentence's own
   size and not in the heading's: 20ch of --t-h2 and 46ch of --t-lede are
   nearly the same rendered width, measured at 1440 as 681.0px against
   671.0px, so the two blocks stack as one column rather than as a wide line
   under a narrow one. Colour comes from --text-body, which .g-peri has
   already driven to --cobalt-deep for this ground: 5.57:1, where plain
   cobalt would sit at 4.21:1 and fail. */
.team-lede { margin-top: 16px; max-width: 46ch; }


/* position/z-index are kept, though nothing sits behind the field any more:
   they establish the stacking context the three people are staggered inside,
   which is what the z-index values below this point are for. */
.team-field {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(24px, 3vw, 56px);
  align-items: start;
}

/* Each body is drawn a third wider than its column and pulled back by half
   the difference, so neighbouring bodies overlap the way they do on p03 —
   about 50px of overlap at 1440. max-width has to be cleared because the
   reset caps every svg at 100% and would silently undo this.

   The percentage is identical on all three, so the scale is too, and with it
   the 120 units of drop. That is what keeps the three connectors the same
   length in pixels while the bodies are three different sizes. */
.team-body { position: relative; }
.team-body .sys { width: 132%; max-width: none; margin-left: -16%; }

/* --map-w is the width the drawing renders at, and here that is not the box:
   the overhang above is part of the composition, so the 132% belongs in this
   figure rather than in each ratio below. One class for all three connectors,
   because all three viewBoxes are 400 wide and all three drops are 120 units
   — which is the same fact the comment above states about their pixels. See
   the unit law above section 10's .draw. */
.team-body {
  container-type: inline-size;
  --map-w: calc(1.32 * 100cqw);
}
.tm-c { --len: calc(0.3000 * var(--map-w)); }   /* 120.0 over 400 */

/* Nearest body in front, furthest behind, and every card above every body.
   .team-person deliberately takes no z-index of its own: giving it one would
   make each person a stacking context and trap its card underneath the next
   person's body. */
.tp-near .team-body { position: relative; z-index: 3; }
.tp-mid  .team-body { z-index: 2; }
.tp-far  .team-body { z-index: 1; }
.team-card { z-index: 4; text-align: left; }

/* The third seat is a person now, so nothing in this scene is drawn dashed
   any more and .n-dash has gone with the vacancy it existed for. .c-dash
   stays, but not for the reason this comment used to give: the selection
   scene was rebuilt and no longer uses it. Its one remaining user is the hero's
   ring closure, the route that is available rather than taken — one path,
   and the whole of the "dashed means potential" argument now that the legend
   naming it has gone. .team-name-empty went the same way as .n-dash: it
   greyed the words "Third seat", and there is a name there instead. */
.team-owns { margin: 10px 0 16px; font-size: var(--t-body); color: var(--text-body); }

/* 21a. THE HOVER, 2026-09-02, ASKED FOR BY THE OWNER AND REBUILT RATHER THAN
   PORTED. He supplied a React TeamSection component and asked for its effect
   here. It cannot land as written: React, Tailwind, shadcn and lucide-react
   are absent from this repository by decision, and its demo pulls portraits
   off Unsplash, which is an external request this page does not make. So the
   idea was taken and the implementation was not, the same way the claim
   scene's field and its avatar stream were taken from two other components he
   supplied. What was kept, and what was refused, is recorded in DESIGN.md
   under the same date; the two refusals worth repeating beside the code are
   that there are no photographs of Rob, Stan or Huib and none may be
   invented, so the drawn circles stay and the photo slots stay open, and that
   the component's social row, background grid, register button and logo are
   all shapes this page's vocabulary does not have.

   WHAT ANSWERS THE POINTER IS THE PERSON, NOT THE CARD. .team-person is the
   container and both responses hang off its :hover, which is the station
   spotlight's rule stated in CSS instead of in motion-features.js: the state
   goes on the container and never on the layer that moves. Pointing at the
   drawing lights the card and pointing at the card lights the drawing,
   because a person here is one thing in two pieces. No script is needed for
   it, so none was added; the spotlight needs one only because it feeds
   coordinates, and nothing here tracks a position.

   THE WASH. A lime field rising from the bottom edge of the card, which is
   the one part of the component's motion that survives contact with this
   page. It is a Field out of the six, not a new shape, and it is flat lime at
   full strength: no gradient standing in for a fade, no glow, no blur, all
   three of which the component uses and all three of which are banned here.
   It rises by height off a bottom edge rather than by a transform, and that
   is deliberate twice over. A scaled layer would report its full size to the
   collision gate while showing nothing, so the resting state would measure as
   a card-sized object that is not there; height reports 0 when it is 0.

   z-index: -1 puts it above the card's own white background and below every
   glyph in the card, which is what makes it a surface the text sits on rather
   than a layer over the text. .team-card is already a stacking context, from
   .mod's position: relative and the z-index: 4 above, so no isolation is
   needed to hold the negative layer in. overflow: hidden is the clip: the
   card's outer radius is 18px over a 2px border, so its inner curve is 16px,
   and a square-cornered wash at full height would otherwise paint lime past
   that curve at the two top corners. Clipping to the card's own shape is one
   declaration and cannot drift from the radius the way a restated 16px would.

   Contrast holds in both states. This ground is periwinkle, so --text is
   cobalt and --text-body is cobalt-deep; on lime those measure 5.81:1 and
   better, against 5.92:1 and 5.57:1 on the white card. Nothing becomes
   unreadable at any point in the rise.

   THE RING. The portrait ring takes the pointer as a control does, and the
   page already owns that language exactly: .cta, .ctrl and .marq-card all
   travel half way to their own shadow and let the shadow shrink to meet them.
   The ring is drawn over a .sh-white circle 10 units below it, so travelling
   5 units closes half that gap and the shadow does the rest on its own, with
   nothing to keep in step by hand. 5 units and not 5px, because the property
   is on an SVG element and resolves in the viewBox, so the travel scales with
   the drawing exactly as the 120-unit connector drop does. The stroke goes to
   cobalt-deep at the same time, which is the .op-pill precedent: one colour
   already in the scene, gaining contrast, adding nothing to the palette.

   THE OVERLAP IS WHY THE WASH IS INSIDE THE CARD. The three bodies are drawn
   132% of their column and pulled back 16%, so neighbours overlap by about
   50px at 1440, and a wash that reached past a card would put lime over the
   next person's drawing at exactly that seam. Everything here paints inside
   the card's own padding box or inside the person's own svg, so the overlap
   is untouched: the gate sweeps clean at all eight widths hovered as well as
   at rest. .team-card is registered in _collide.js's PSEUDO_HOSTS from the
   same date so the wash is measured rather than assumed.

   Both durations are --dur-base, 220ms, well inside the 400ms cap, and one
   value for both so the pair reads as a single answer rather than two. Under
   a coarse pointer none of this is compiled at all, and under
   prefers-reduced-motion the wash has no content box to draw and the ring has
   no transition, which is stated after this block so source order settles it.
   Neither the wash nor the ring carries information: the card says who the
   person is and what they own, and it says it identically in all states.

   HEIGHT AND NOT TRANSFORM, AND THE DESIGN HOOK IS RIGHT TO ASK, 2026-09-02.
   The hook flags an animated height as layout thrash, which is true of an
   element in the flow and false here: this pseudo is position:absolute, so it
   is out of flow and its height cannot reflow anything. Measured during a
   real hover at 1440, every frame landed on vsync, median 16.7ms, p95 16.7ms,
   max 16.8ms, and the two neighbouring cards did not move by a pixel.

   The reason it is height rather than a scaleY is the gate, not taste. A
   transformed box still measures its full height in layout, so the collision
   gate would read a lime field permanently lying over this card's own name
   and role. At height 0 the resting box genuinely is 0 and the gate sees what
   a reader sees.

   The warning is left standing rather than silenced, and that is deliberate.
   The hook's ignore-value mechanism cannot reach this rule at all: its filter
   reads a value through extractFindingIgnoreValue, which returns empty for
   every rule outside a fixed list of six, and layout-transition is not one of
   them. What remains is ignore-rule, which would blind every file, or a
   wildcard scoped to this stylesheet, which is the only stylesheet here and so
   amounts to the same thing. Either would also hide a future width, padding or
   margin transition, and those are the cases the rule exists to catch. One
   recurring line of hook output is the cheaper price. Anyone who adds a layout
   transition here owes it the measurement above rather than this precedent. */
/* THE FACES, 2026-09-08. The single most expensive gap on this site since it
   was built: a three-person agency selling trust, showing three empty white
   discs. Two independent design reviews and every cold reader in the series
   named it. The owner supplied the photographs and here they are.

   Only the fill moves. Each circle keeps its cx, cy, r and its 2px cobalt ring
   exactly as drawn, and the pattern that replaces var(--core) is defined in
   that person's own SVG (see the defs in index.html). That is why this is
   three declarations and not a rebuild: the drawing already had the right
   shape in the right place, it was only ever holding a space.

   The hover below is untouched and still works, because it moves the stroke
   and the whole circle, never the fill. Nothing here needs a fallback: if the
   image 404s the circle paints var(--core) again and the composition is
   exactly what it was yesterday.

   THE THREE PHOTOGRAPHS ARE NOT ONE SET AND THE OWNER KNOWS IT. A black and
   white studio portrait, a colour photograph outdoors in the sun and a dark
   low key portrait, put side by side in three circles on a page that
   otherwise speaks one language. It was put to him with an offer to level
   them, and he chose to ship them as supplied. Recorded here so the next
   session reads a decision rather than an oversight. */
.tp-mid  .team-port { fill: url(#port-rob); }
.tp-near .team-port { fill: url(#port-stan); }
.tp-far  .team-port { fill: url(#port-huib); }


@media (hover: hover) and (pointer: fine) {
  .team-card { overflow: hidden; }
  .team-card::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 0;
    z-index: -1;
    background: var(--lime);
    transition: height var(--dur-base) var(--ease-out);
  }
  .team-person:hover .team-card::before { height: 100%; }

  .team-port {
    transition:
      stroke var(--dur-base) var(--ease-out),
      translate var(--dur-base) var(--ease-out);
  }
  .team-person:hover .team-port {
    stroke: var(--cobalt-deep);
    translate: 0 5px;
  }
}

/* Asked for stillness: no hover at all here, which is stricter than the
   .op-pill rule one scene up and deliberately so. That rule keeps its colour
   change and drops only the transition, because a border going darker is not
   motion and an instant change is the honest answer to the request. This
   scene's response is a rise and a travel, and half of a gesture arriving
   instantly is worse than none of it: the ring would jump 5 units the moment
   a pointer crossed it. So the wash has no content box to generate and the
   ring is put back to both of its resting values, rather than being left to
   snap. Written after the block above so source order settles it, since a
   media query adds no specificity and the selectors are otherwise equal. */
@media (prefers-reduced-motion: reduce) {
  .team-card::before { content: none; }
  .team-port { transition: none; }
  .team-person:hover .team-port { stroke: var(--stroke); translate: none; }
}

@media (max-width: 900px) {
  /* The .team-open one-column override that led this block died 2026-08-31
     with the two-column grid it overrode. */
  .team-field { grid-template-columns: 1fr; gap: clamp(40px, 7vw, 64px); }
  /* One column, so a body wider than its column has nothing left to overlap
     and would only crop itself at the viewport edge. It goes back to its box,
     which moves the disc centres with it — and the drawn length with them. */
  .team-body .sys { width: 100%; margin-left: 0; }
  .team-body { --map-w: 100cqw; }
  .team-card { max-width: 470px; }
}

/* --------------------------------------------------------------------------
   21b. THE NUMBERS ROW, first half of the network scene
   Its own scene under a mono kicker until 2026-08-30; merged under the
   network scene's display heading because two headings stood over two
   halves of one argument, how big the network is and who is in it. The
   kicker and its rule died with the merge, and the row sits
   on the merged scene's g-soft, where the root --text cobalt pair carries
   the type: .num-n reuses .claim, the page's one claim-scale type rule,
   and cobalt on ground-soft measures 6.55:1, re-measured for the merge
   rather than carried from the lavender era's 5.92:1.

   One stat row of four, licensed once here by DESIGN.md's stat-row
   amendment ("One stat row of four is now permitted, once, in the Numbers
   scene, and nowhere else on the page"). The three dividers between the
   four figures are connectors, not borders: each is a real .c path riding
   between two .n nodes, wrapped in .sys so vector-effect:
   non-scaling-stroke applies the same way section 6's `.sys [class]` rule
   already applies it everywhere else, which keeps the row inside the
   six-element vocabulary instead of inventing a border-as-decoration
   seventh shape.

   Two SVGs per divider, the same .map-d / .map-m toggle section 13
   already uses: num-div-d is the vertical stroke (viewBox 16x96) for the
   row layout above 760px, num-div-m is the horizontal stroke (viewBox
   96x16) for the stacked layout below it. Only one is ever in the
   document flow at a time. -------------------------------------------- */

/* THE 8px BASELINE DRIFT, AND WHAT IT ACTUALLY WAS
   Measured settled at 1440: 800+ and 25M+ sat at top 417.7 and 3X and 100M+
   at 425.8, a uniform 8.1px lower. Every one of the four carries the same
   90.16px line-height and the same .claim rule, so the type was never the
   cause and changing it would have chased the symptom.

   The cause is one declaration: align-items: center on this row. The figures
   are flex items whose heights are set by their own labels, and .num-lbl is
   capped at 18ch, so "AI-native creators in the network" and "Combined reach
   across the roster" wrap to three lines while "Average ROAS for clients"
   and "Impressions delivered" wrap to two. Measured item heights: 153px,
   153px, 137px, 137px. Centring a 137px box against a 153px row lowers its
   top by exactly (153 - 137) / 2 = 8px, which is the drift, to a tenth of a
   pixel.

   So the row aligns its figures at the start instead, which is where a
   ledger's numbers belong: the four figures share a top edge and the labels
   under them are free to be as many lines as they need. The dividers keep
   the old behaviour explicitly, because centring is right for them: each is
   a 96px stroke that should sit in the middle of the row's height rather
   than hang from its top. */
.num-row {
  display: flex;
  align-items: start;
  /* the distance the dead kicker used to hold; the row owns it now that it
     stands directly under the merged scene's heading */
  margin-top: clamp(40px, 5vw, 72px);
}

.num-fig {
  flex: 1 1 0;
  min-width: 0;
  text-align: center;
}

/* tabular-nums, and it is here for a mechanical reason rather than a
   typographic preference. Measured in this face at claim scale, digit
   advances run from 27.33px for "1" to 55.89px for "8": proportional. The
   count-up rolls each digit through a column of other digits, so a
   proportional advance would make the rolling figure a different width from
   the published one, and the figure would jump sideways at the moment it
   landed. Tabular figures make every digit one advance wide, so the roll and
   the settled string occupy the identical box. It is also, independently,
   the right setting for four numbers meant to be read as a ledger. */
.num-n { margin: 0 0 14px; font-variant-numeric: tabular-nums; }

/* The digits, while the count-up is running and only while it is running.
   motion-features.js creates this span, reserves the finished digits' own
   width on it, and takes the whole thing back out again when the figure
   lands, so nothing wears this class in the settled page, under reduced
   motion, or with no JavaScript at all.

   WHY A ROLL RATHER THAN A NUMBER COUNTING UP. The previous version
   interpolated the value itself, which meant the page rendered "326+",
   "536+", "691+", "740+", "777+", "788+" and "797+" as fully legible text
   under the label "AI-native creators in the network". Measured: seven
   distinct wrong figures, the last three of them within 3% of the published
   one and each held for 40ms or more. On a page whose whole discipline is
   that every figure is registered in PROVENANCE.md and reproduced character
   for character, a claim the page cannot support is not made acceptable by
   being brief. The ease-out made it worse rather than better, because an
   ease-out spends most of its duration a few units short of its target,
   which is exactly the dwell that must not exist.

   A dial cannot state a wrong figure, because it never comes to rest on one.
   Each digit is a column of glyphs travelling behind a hard clip, so what is
   on screen between the first frame and the last is glyphs in transit, cut
   by the clip edge: visibly a counter running, never a number standing
   still. The columns stop on the published digits and on nothing else. It is
   also the only form that works for 3X, which has four integer states in
   total and could not have been counted honestly at any duration.

   No blur, no glow, no gradient and no new shape: a dial is type behind a
   clip, and the clip is the same hard edge this system uses everywhere.

   overflow: visible clip, not hidden. The vertical axis is the one that has
   to cut; the horizontal one must not, because a glyph's ink can exceed its
   own advance by a fraction and clipping that would shave the sides off the
   digits mid-roll. Chrome keeps the pair as authored, verified by
   measurement: computed overflow-x visible, overflow-y clip. */
/* nowrap, and the suffix lives in here with the dials. The published figures
   are single unbreakable words: "100M+" has no break opportunity in it, so
   it overflows its column rather than wrapping. Split into a row of dials
   plus a suffix, break opportunities appear between the parts, and measured
   at 1440 the "+M" of 100M+ dropped to a second line the moment the roll
   started. This makes the rolling figure behave the way the published one
   already does. */
.num-dials { display: inline-block; white-space: nowrap; }

.num-dial {
  display: inline-block;
  vertical-align: top;      /* the dial's top meets the line box top, which  */
  overflow: visible clip;   /* puts the glyph inside it on the same baseline */
  overflow-clip-margin: 0;  /* as the suffix standing beside it              */
}

.num-strip { display: block; will-change: transform; }

/* THE GLYPHS ARE GENERATED CONTENT, AND THAT IS THE WHOLE OF ROUND 10's
   TASK 9. They used to be text nodes, so for the 400ms of the roll the
   element's textContent read

     800+6789012345678885678901234567890002345678...

   fifty-nine characters of digit salad. Nothing a screen reader could ever
   hear: the dials are aria-hidden and .num-sr carries the published string,
   so innerText and the accessible name were both exactly "800+" throughout,
   which is why this survived a round of accessibility work untouched. What
   it did reach was everything that reads the DOM rather than the
   accessibility tree: a Cmd-A and copy taken during the roll, a crawler, an
   extension, a model reading the page. Those get the source of truth for a
   page whose entire discipline is that a figure appears only if
   PROVENANCE.md has a row for it, and what they got was a number that has
   never existed.

   content: attr() puts the digit in the box without putting it in the tree.
   Generated content is laid out and painted exactly as a text node is, so
   the column, the tabular advance, the cell height and the clip are all
   unchanged and nothing about the animation had to move; but it is not a
   child node, so textContent, innerText, a Range over the scene and a
   selection copy all skip it. The text layer now carries the published
   figure and nothing else, at every moment of the roll.

   The digit is authored into data-g by motion-features.js. A dial with no
   attribute renders an empty box rather than a wrong glyph, which is the
   correct failure for this page. */
.num-g { display: block; }
.num-g::before, .num-suffix::before { content: attr(data-g); }

/* The published figure, spoken and never seen, for the 400ms the dials are
   in front of it. Clipped to nothing rather than moved off-canvas with a
   large negative offset: an off-canvas run of claim-scale type at 92px is a
   scrollable region on any platform whose scroll container is not the one
   this page's overflow rules assume. */
.num-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  white-space: nowrap;
  clip-path: inset(50%);
}

/* OUT OF THE MONO VOICE, 2026-09-01, round 15. The four labels wore .label
   (12px Geist Mono, caps, 0.09em tracking) since the row was built, and
   the mono voice is this page's measuring voice: right for a date or a
   follower count, wrong for the four short sentences these actually are.
   They read in the body voice now, Geist at --t-body (the type system's
   six sizes hold: 17px is the body step, and inventing a 15 or 16 would be
   a seventh), sentence case as authored in the markup (the caps were
   text-transform, never the source: "AI" and "ROAS" are the markup's own
   capitals and survive unchanged), no tracking, --ink-soft. Measured on
   this scene's ground-soft: #4A4F7A on #F7F6FC is 7.26:1, clear of AA's
   4.5 with room. The labels stay under their figures; 18ch now measures
   against the larger face, so the two long labels wrap to two lines
   instead of three, which the row's start alignment (see .num-row above)
   was built to absorb. */
.num-lbl {
  max-width: 18ch;
  margin-inline: auto;
  font-family: var(--f-body);
  font-size: var(--t-body);
  line-height: 1.4;
  color: var(--ink-soft);
}

.num-div {
  flex: none;
  /* stated, because the row now aligns its items at the start for the
     figures' sake and a 96px stroke hanging from the top edge of a 153px row
     would read as a mistake. This is the alignment the row used to give it
     for free. */
  align-self: center;
  display: flex;
  align-items: center;
  justify-content: center;
  padding-inline: clamp(16px, 2.6vw, 40px);
}

.num-div-d { width: 16px; height: 96px; }
.num-div-m { width: 96px; height: 16px; display: none; }

/* Mobile is its own composition (DESIGN.md, "Mobile is a different
   composition, not a reflow"), not four claim-scale numbers squeezed into
   a 390px row. Below 760 the row becomes a vertical stack, each figure
   full width and centred, and the divider swaps from the vertical stroke
   riding beside a figure to the horizontal one riding between two stacked
   figures, the same primitive turned the other way rather than a second
   shape. */
@media (max-width: 760px) {
  .num-row { flex-direction: column; align-items: stretch; }
  .num-fig { padding-block: 6px; }
  /* The stack has one figure per row, so there is no baseline to hold and
     nothing for align-self to do; handed back so the divider fills the
     column the same way it always did here. */
  .num-div { align-self: stretch; padding-block: clamp(20px, 6vw, 36px); padding-inline: 0; }
  .num-div-d { display: none; }
  .num-div-m { display: block; }
}

/* --------------------------------------------------------------------------
   21c. THE CREATOR MARQUEE, second half of the network scene
   Ground is g-soft since the 2026-08-30 merge, so --text and --text-body
   resolve to the root cobalt pair here (section 5). The cards below carry
   a cobalt-deep face of their own, so their text sets white (9.30:1) and
   --ground (7.82:1) explicitly rather than inheriting a colour it would be
   invisible against, the same move any module makes when its own fill
   differs from the ground under it. The face against this ground measures
   8.48:1, recomputed for the merge rather than carried from the periwinkle
   era's 5.57:1.

   Two layouts share one DOM, and since 2026-08-31 the strip is two rows,
   ten creators each, counter-scrolling, on compact horizontal cards.
   Under .motion each .marq-track is a non-wrapping row doubled by its own
   .marq-dup, animated by exactly -50% of its own width (one trailing gap
   on the track makes -50% the exact period; the measured note on the
   .motion .marq-track rule below records why); the second row plays the
   same keyframes in reverse. Without .motion (no script, or prefers-reduced-motion) each
   track is a plain flex-wrap grid holding only its real ten, .marq-dup
   stays display:none, and no mask ever applies. That is the whole of the
   reduced-motion contract: not a degraded loop, the same twenty names
   laid out the other way, in two static rows. -------- */

.marq-head h2 { max-width: 22ch; }

/* The strip's eyebrow: a sample, not the list. It stands inside the wrap,
   directly above the full-bleed band, so it reads as the strip's own label
   rather than a fourth figure in the row above it. */
.marq-sample { display: block; margin-top: clamp(48px, 6vw, 88px); }

.marq {
  position: relative;
  /* Full-bleed: the one place on the page a section's content runs past
     its own .wrap to both viewport edges, which the edge fade below needs
     in order to read as a strip rather than a card with soft corners.
     body already carries overflow-x: clip (section 3), so this never
     opens a horizontal scrollbar the way a bare 100vw sometimes does. */
  width: 100vw;
  margin-left: calc(50% - 50vw);
  /* 16px, not the old clamp: the sample label above is the strip's own
     eyebrow since 2026-08-30, and an eyebrow sits close to what it names. */
  margin-top: 16px;
  overflow: hidden;
}
/* The seam between the two rows, 2026-08-31: the same clamp as the
   track's own inter-card gap, so the pair reads as one grid that happens
   to move rather than two unrelated widgets stacked. Overrides the 16px
   above, which is the eyebrow's seat and belongs to the first row only. */
.marq + .marq { margin-top: clamp(14px, 1.6vw, 22px); }

/* The one narrow exception to the no-soft-edges rule (DESIGN.md,
   Anti-patterns), and only here: an alpha mask, not a colour gradient, so
   the cards are genuinely transparent at the two ends rather than merely
   dimmed. Percentage stops rather than a fixed pixel fade, so the fade
   scales with the strip instead of needing a breakpoint of its own.
   Gated under .motion: the static wrapped fallback below is never faded,
   since every one of its twenty names has to stay fully legible. */
.motion .marq {
  mask-image: linear-gradient(to right, transparent 0%, #000 6%, #000 94%, transparent 100%);
  -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 6%, #000 94%, transparent 100%);
}

.marq-track {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  justify-content: center;
  /* One value, three duties since 2026-08-31: the inter-card gap, the
     seam between the two rows, and the trailing pad that makes the loop
     period exact (the note on the .motion rule below). A property, so
     the three can never drift apart. */
  --marq-gap: clamp(14px, 1.6vw, 22px);
  gap: var(--marq-gap);
  padding-inline: var(--gutter);
  padding-block: 6px;
}
/* Under .motion the row is prepared immediately, at rest, the same way
   section 10's .motion .draw prepares a hidden connector before its own
   .is-in ever fires — so nothing snaps from a wrapped grid into a row
   the moment the scene scrolls into view. Only the loop itself waits. */
.motion .marq-track {
  flex-wrap: nowrap;
  justify-content: flex-start;
  width: max-content;
  padding-inline: 0;
  /* One trailing gap, so -50% is the exact period. Measured 2026-08-31
     during the two-row rebuild: with the duplicate run laid in as
     display:contents children, the pattern the loop repeats is one run
     plus one inter-run gap, but the track's box used to end flush after
     the last clone, making its width two runs plus one gap and the -50%
     point half a gap short of the period. Both rows missed by the same
     half gap at 1440, 10.97px and 11.03px against a 22px gap, a small
     backward step at every wrap, and the retired one-row strip carried
     the identical flaw unmeasured. Ending the box with one more gap
     makes -50% land exactly one period on; re-measured after the change,
     the clone-to-real offset equals the half-track to 0.03px. */
  padding-right: var(--marq-gap);
}

/* The duplicate pass, one per row since 2026-08-31. display:none by
   default so the no-motion reader never receives it at all;
   display:contents under .motion so its ten anchors become direct flex
   items of their own .marq-track instead of one wide flex child, while
   the wrapper itself still carries aria-hidden. */
.marq-dup { display: none; }
.motion .marq-dup { display: contents; }

@keyframes marq-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* The loop, gated behind .is-in like every other arrival on the page.
   Two rows since 2026-08-31. Row one keeps right to left (translateX runs
   toward negative X): cards enter from the right and age out on the left,
   the same direction this page's own text already reads. Row two plays
   the identical keyframes in reverse, left to right, so the pair reads as
   passing traffic rather than one surface sliding sideways; the wrap is
   the same exact -50% either way, only met from the other end.

   Durations are derived from measurement, not carried over, and since
   2026-08-31 the measure is GRVTY's tempo rather than the retired single
   row's. Their ticker runs 60s linear infinite, and what the eye
   compares across two sites is never the duration, it is px/s; theirs
   works out near 30, ours ran double that (the retired row at 60.4px/s,
   then 44s and 48s here giving 61.1 and 57.4 against half-tracks
   re-measured at 1440 with the trailing gap counted: row one 2689.5px,
   row two 2756px). Slowed to the reference pace: 2689.5/30 = 89.65,
   rounded to 90s, which advances at 29.9px/s; 2756/30 = 91.9, held a
   few percent slower at 96s, 28.7px/s, because two rows on one clock
   read as a single mirrored mechanism and the drift between them is
   what keeps the strip organic, the same reason the 44/48 pair was
   split. Re-measured after the change: 29.9 and 28.7px/s, both inside
   the 30px/s target's ten percent band.

   Custom properties rather than a second, longer selector for row two,
   deliberately: a five-class .marq-b rule on the track would out-specify
   the four-class reduced-motion net below and leave row two looping for
   a reader who toggles reduced motion mid-session. Inherited variables
   set on the row keep every animation declaration at this one selector
   and this one specificity, so the pause rules and the net keep exactly
   the cascade fights they already won. */
.motion .network-marquee.is-in .marq-track {
  animation: marq-scroll var(--marq-dur, 90s) linear infinite var(--marq-dir, normal);
}
.marq-b { --marq-dur: 96s; --marq-dir: reverse; }

/* Three ways to hold it still, because only one of them needs a pointer.
   :hover is free on any browser that has one; the third, pointerdown, is
   handled by the small script at the foot of the page for the touch readers
   no CSS state here can reach, and it only ever toggles .is-held, which this
   rule already understands. Keyboard focus is the fourth and it lives in its
   own rule below, for a reason recorded there. Since 2026-08-31 every one
   of these states is scoped to its own row: each row is its own .marq, so
   :hover, .is-held and the focus rule below all pause the row they fire
   in and never the other, and the foot script toggles .is-held on the
   .marq the pointer actually went down in.

   Repeats the full .motion .network-marquee.is-in chain rather than the
   shorter .marq one: the animation-name rule above carries four class
   selectors in its own chain, and a three-selector pause rule loses that
   fight on specificity regardless of which one comes later in the file,
   leaving the strip silently unpausable. Matching the chain exactly, then
   appending the state, is the plain fix rather than reaching for !important. */
.motion .network-marquee.is-in .marq:hover .marq-track,
.motion .network-marquee.is-in .marq.is-held .marq-track {
  animation-play-state: paused;
}

/* Keyboard focus pauses too, and it has to: a strip that keeps moving while
   it holds focus is disorienting, which is the accessibility reason this
   state exists at all.

   It was :focus-within, and :focus-within was a permanent freeze rather than
   a pause. Every card is an <a target="_blank">, so a click focuses the link
   and opens the profile in a second tab; this document never navigates away,
   nothing ever takes focus off that link, and :focus-within therefore goes on
   matching for the rest of the session. Measured at 1440 before the change,
   pointer parked off the strip after the click so :hover was not what was
   being read: two samples 600ms apart both reported translateX -56.79px and
   playState paused. :has(:focus-visible) draws the line the platform already
   draws between the two intents, and the same run measured it: a
   mouse-clicked card gives :focus-within true and :has(:focus-visible)
   false, a card reached by Tab gives both true.

   Its own rule rather than a third line in the group above, deliberately.
   One unrecognised selector invalidates an entire selector list, so on a
   browser without :has() a fourth line there would take :hover and the touch
   hold down with it and leave the strip unpausable by any route. Split out,
   it can only ever cost the one state it cannot express. */
.motion .network-marquee.is-in .marq:has(:focus-visible) .marq-track {
  animation-play-state: paused;
}

/* The safety net for a reader who toggles reduced-motion mid-session,
   after .motion was already granted at load. Placed here rather than
   beside section 10's own net, so it wins the cascade against the rule
   two blocks up without having to out-specify it — the same resolution
   section 10 gives .drift, .shimmer and .sway: stop the loop wherever it
   already sits rather than reflow into the wrapped grid, since a card
   mid-strip is still one full, undistorted card and nothing here reads
   as broken by staying put. */
@media (prefers-reduced-motion: reduce) {
  .motion .network-marquee.is-in .marq-track { animation: none; }
}

/* The face is --cobalt-deep, not the white .mod default. Both figures were
   recomputed here rather than carried over, sRGB to linear then
   0.2126R + 0.7152G + 0.0722B: white text on cobalt-deep is 9.30:1, and the
   card against the ground-soft it sits on since the merge is 8.48:1. Both
   clear AA, the second by more than the 3:1 a non-text surface needs, which
   is what lets the card be read as a card without leaning on its border.
   No palette addition: cobalt-deep is a value the page already owns, turned
   around into a face.
   --stroke goes white on the same reasoning .g-cobalt uses (section 6) when
   the surface under the 2px line becomes cobalt: cobalt on cobalt-deep is
   1.32:1 and would be a border in the markup and nothing on the screen. The
   line weight is untouched, only what colour it is drawn in.

   Horizontal since 2026-08-31, per the owner's reference: the face on the
   left, the two text lines in a column on the right, one compact chip
   instead of the old vertical plaque. 12px of gap seats the face against
   the text block; the padding is 10px vertical for a chip's height, and
   12px on the avatar side against 18px on the text side, because a round
   face and a ragged text end need different insets to read as equally
   deep inside the card. Colours, ring and type pairs are exactly the
   measured ones above, rearranged and not retuned. */
.marq-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 18px 10px 12px;
  background: var(--cobalt-deep);
  --stroke: var(--white);
  text-decoration: none;
  white-space: nowrap;
  transition: translate var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
}
/* The seat, restated for the merged scene's ground. On periwinkle the card
   took .g-peri .mod's shade for free; on ground-soft the .mod base would
   hand it --shade-white, a different hue from the hover state below. One
   element, one shade family: --shade-peri, the same answer the rest of
   this page already gives on this ground. */
.network-marquee .marq-card { box-shadow: 0 4px 0 var(--shade-peri); }
/* The press-down affordance every other control on the page already uses
   (.cta, .ctrl): translate a couple of pixels toward the shadow and let
   the shadow itself shrink to match, so a card reads as operable before
   the pointer ever leaves it. :focus-visible needs nothing extra — the
   global ring (section 3) already covers it. */
.marq-card:hover {
  translate: 0 2px;
  box-shadow: 0 2px 0 var(--shade-peri);
}

/* THE AVATAR, 2026-08-30. The argument for the face is in the markup's
   comment; this block only draws it, inside the node vocabulary: a 48px
   circle in the same 2px ring every node on the page carries. The ring is
   white by the card's own stroke discipline, fixed two rules up: cobalt on
   cobalt-deep measured 1.32:1 and vanished, so every 2px line on this face
   is drawn in white, and the fallback node's ring and core follow for the
   same reason (unstyled, the svg would fill black, which DESIGN.md bans
   outright). Both variants hold one box: the sheet is border-box (section
   3), so the photo's 2px border sits inside the same 48px the drawn ring
   does (r 23 at stroke 2 spans 22 to 24 and meets the box edge exactly),
   and the forty cards keep one height whether a card carries a face or the
   node. The node draws its own ring, so the border comes off it rather
   than doubling the line. cover, because the sources are 96px squares
   today and the circle crop is a promise this rule makes, not one it asks
   of the files. Since the 2026-08-31 horizontal rebuild the face sits
   beside the text block rather than over it: the card's own 12px gap
   seats it, the old 4px margin under it is gone with the stack it spaced,
   and 48px stays 48px, because the fallback node's geometry above is
   derived for exactly this box and a scaled svg would thin its 2px ring,
   which the line-weight law bans. */
.marq-pic {
  flex: none;
  width: 48px;
  height: 48px;
  border: 2px solid var(--white);
  border-radius: 50%;
  object-fit: cover;
}
.marq-pic-node { border: none; }
.mpn-ring { fill: none; stroke: var(--white); stroke-width: 2; }
.mpn-core { fill: var(--white); }

/* The card's text, one column since 2026-08-31: name over handle and
   count, 3px apart, centred against the 48px face by the card's own
   align-items. min-width: 0 is deliberately absent, and so is any width:
   every line in the column is nowrap by inheritance, which is what keeps
   all forty cards one height and lets each card take its natural width
   the way the reference's chips do. */
.marq-text {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

/* The pair on a cobalt-deep face is the pair .g-cobalt already fixed for a
   cobalt one (section 6): white for the name, --ground for the two quiet
   lines under it, so the card keeps its three levels instead of flattening
   into one weight of white. Measured on cobalt-deep: white 9.30:1, --ground
   7.82:1. The old --cobalt / --ink-soft pair was tuned for the white face
   this card no longer has, and would read at 1.32:1 and 1.19:1 here. */
.marq-name {
  font-family: var(--f-display);
  font-weight: 700;
  font-size: 18px;
  line-height: 1.2;
  color: var(--white);
}
/* A flex row rather than the block it was, so the X mark beside the handle
   is centred against the line instead of hung off its baseline. The reset
   (section 3) makes every svg display:block, so an inline mark would have
   taken a line of its own here: measured at 1440, the handle box went from
   21.7px to 33.7px, which is exactly one 14px line plus the 12px mark. */
.marq-handle {
  display: flex;
  align-items: center;
  gap: 5px;
  font-family: var(--f-body);
  font-size: 14px;
  color: var(--ground);
}
/* THE FOLLOWER COUNT IS BACK ON THE CARD, BY THE OWNER, 2026-08-30.
   It was removed a round earlier because the strip was headed "Twenty names,
   none of them ranked" and the only number on each card was the exact ranking
   dimension the selection scene disqualifies. That reasoning was sound and the
   owner has overruled it on a different ground: the counts are what make a
   network tangible to a buyer who has never heard of any of these twenty
   people, and an unnamed roster proves nothing at all.

   The contradiction the removal was answering is answered here instead, in
   the framing rather than by hiding data. Nothing over the strip claims an
   absence of ranking any more: since the 2026-08-30 merge the scene is
   headed "The network." and the strip is owned by the mono label "A sample
   of the roster", which owns the sample without asserting an order the
   numbers would immediately contradict. Station 02 in
   the method scene still says selection is on output rather than size, and
   that stays true: what a creator is picked FOR and what a card SHOWS are two
   different claims, and only the first is a promise.

   The twenty values come from PROVENANCE.md, measured 2026-08-24 and due for
   review 2026-09-24. They are restored as the same strings that were removed,
   so no new confirmation is owed. A refreshed figure needs a new row in that
   file before it appears here.

   Forty spans, not twenty: the .marq-dup clones carry it too, or the two runs
   would render at different heights and the loop would step.

   On the handle's own line since 2026-08-31, after a middot: the compact
   card has two text lines, the name and then handle-and-count together,
   which is the reference's own form. The separator is U+00B7 and not an
   em dash, because the em dash rule in DESIGN.md is absolute; the middot
   span is aria-hidden, punctuation between two values a screen reader
   already separates by pausing. The count keeps its mono micro voice, so
   the line still reads as a handle and a measurement rather than one
   run-on string, and it inherits the handle's 5px gap for spacing. */
.marq-count {
  font-family: var(--f-mono);
  font-size: var(--t-micro);
  letter-spacing: 0.04em;
  color: var(--ground);
}

/* The X mark, once per card. Every card links to that creator's profile on X
   and nothing else on the card says where the link goes; the mark sits
   directly beside the handle it names, which is what makes it nominative use
   rather than a logo. It is the only platform mark on the page and stays so:
   DESIGN.md withholds exactly this permission from the three named partners,
   whose names appear as text and whose logos do not appear at all.
   Sized in em off the handle's own 14px, so it can never outgrow the line it
   sits on whatever that line is set to. 0.86em measures 12.03px there,
   against 11.48px of ink for the @ that follows it and 9.94px of cap height
   for Geist at that size: the mark matches the tallest glyph on the line
   instead of overrunning it, which is what keeps it reading as part of the
   handle rather than as a bullet in front of it. currentColor, so it takes
   --ground from the handle and would follow it anywhere else without a
   second declaration. flex:none because a flex item with an intrinsic aspect
   ratio is otherwise squeezed by a long handle before the text is. */
.marq-x {
  flex: none;
  width: 0.86em;
  height: 0.86em;
  color: inherit;
}

/* --------------------------------------------------------------------------
   22. SCENE 9 — THE CLOSE (p15)
   Full-bleed lime, large bodies, and the card inside the ring rather than
   next to it. Two connectors land on it from nodes riding the ring, so the
   ask belongs to the system the whole page has been drawing.
   -------------------------------------------------------------------------- */

/* Never a max-width on a .wrap element: .wrap already sets margin-inline
   auto, so the two together silently centre the block. Constrain the
   children instead. */
.close-head h2 { max-width: 15ch; }
.close-lede { margin-top: 20px; max-width: 42ch; }

/* .close-list AND .close-list li::before DIED 2026-09-01, WITH THE <ol> THEY
   LAID OUT. It was the one list on the page: three lines of --t-body, each
   marked by a drawn node ring on ::before, and the block itself had come full
   circle from node dots to mono digits to display digits and back.

   What replaces it is .close-const below, the B3 placement the owner chose
   out of six preview pages: the same three sentences, unchanged to the byte,
   now pinned to a drawn constellation with a picture of each section beside
   them. So the marker rule goes not because it was wrong but because there
   is no list left to mark. Every promise now hangs off a real node with a
   real connector running to it, which is the ring the ::before was standing
   in for all along.

   _collide.js carried `.close-list li` in PSEUDO_HOSTS for the dots; that
   entry is retired in the same round and `.close-plan` takes its place, for
   the one pseudo-element the new block paints.

   ----------------------------------------------------------- the constellation
   The roster scene's own picture, borrowed whole: one origin node on the
   wrap's left edge, three connectors, three nodes at three different
   heights, and a block hanging off each. There it carries the three names;
   here it carries the three promises, with a 222x138 thumbnail of the
   section and the sentence at lede scale.

   The box is capped at its own viewBox width so the drawing renders 1:1 and
   the pinned blocks, which are sized in pixels, land on their nodes. Same
   reason .roster-map carries its cap, and the cap sits on THIS element and
   never on the svg inside it: the moment it moves to the svg, 100cqw stops
   being the render width and every dash is laid out against a number the
   renderer is not using. See the unit law above section 10's .draw. */
.close-const {
  width: min(1080px, 100%);
  margin-top: clamp(34px, 4vw, 56px);
  container-type: inline-size;
  --map-w: 100cqw;
}
/* Two class levels, because `.map .sys` above is 0,2,0 and a bare .close-cm
   would lose to it. Same trap `.map .map-m` carries a comment about. The
   swap is at 1200 and not at 760, which is why these cannot simply be
   .map-d/.map-m: this drawing runs out of room a full breakpoint before the
   ring below it does. */
.close-const .close-cd { display: block; }
.close-const .close-cm { display: none; }

/* The drawn lengths. 1080 and 350 are the two viewBox widths; the figure in
   the comment is the path's own length in user units, summed by hand from
   its segments, and each 20x20 rounded corner is a quadratic with its
   control point on the corner, which measures 32.4639 rather than the 31.42
   of the quarter circle it looks like. d1 = 109 + 32.4639 + 130 + 32.4639 +
   80; d3 = 109 + 32.4639 + 140 + 32.4639 + 130. */
.cn-d1 { --len: calc(0.3555 * var(--map-w)); }   /* 383.9 over 1080 */
.cn-d2 { --len: calc(0.4620 * var(--map-w)); }   /* 499.0 over 1080 */
.cn-d3 { --len: calc(0.4110 * var(--map-w)); }   /* 443.9 over 1080 */

/* The chain's three links are one 160-unit drop and two 250-unit ones. Two
   classes rather than three, because two identically-valued rules would be
   two chances for one of them to drift. */
.cn-m1 { --len: calc(0.4571 * var(--map-w)); }   /* 160.0 over 350 */
.cn-m2 { --len: calc(0.7143 * var(--map-w)); }   /* 250.0 over 350 */

/* Each block: the picture, then the promise it is a picture of. .map-pin
   arrives with white-space: nowrap, which is right for a one-word module on
   the roster and wrong for a sentence, so it is put back.

   THE SENTENCE COLUMN IS 300px AND IT WAS 278, 2026-09-02. The complaint was
   one line: "Who we would cast, and / why" left "why" on its own, and an
   orphan is the one wrap failure this page has already fixed twice, in the
   hero headline and in the roster line. Measured at 1440 with the real
   element set to nowrap, the three promises need 428.4, 286.1 and 368.4px on
   one line. Only the middle one is within reach of a column this block can
   carry, so only the middle one is made to fit: 300 clears its 286.1 by 13.9,
   and the other two keep the two balanced lines they already had. They were
   checked rather than assumed at 294, 300, 304, 312, 320 and 330; the first
   sentence re-breaks at 304 and the third at 330, both still balanced, but
   300 is the value that changes nothing except the line it was asked to fix.

   width: max-content IS PART OF THE FIX AND NOT A TIDY-UP. .map-pin is
   absolutely positioned and .close-const is its containing block, because
   container-type: inline-size carries layout containment. A pin with a left
   and no right shrinks to fit whatever is left of that block, which at
   50.926% of 1080 is 530px, so the widest track the middle block could
   actually take was 530 - 222 - 22 = 286, a hair under the 286.1 the
   sentence needs, which is why raising the track alone left it wrapping at
   300 and at 330 alike. Stating the width lets the grid take its tracks.

   The block is 544 wide now and its left edge is pinned 30 units right of a
   node at x=520, so it ends 14 units past the drawing's 1080. Nothing is
   clipped: .close-const has no paint containment, and 14px is spent in a
   gutter that is 60px at 1200 and grows from there. Measured right edges of
   the middle block: 1194.0 at 1440 against a 1340 wrap, 1154.0 at 1200
   against a window of 1200. Below 1200 the fan is a chain and the block is a
   single 296px column, where the sentence has always fitted on one line. */
.close-lead {
  display: grid;
  grid-template-columns: 222px minmax(0, 300px);
  gap: 22px;
  align-items: center;
  white-space: normal;
  width: max-content;
}
/* Lede scale, the same treatment .roster-invite-say wears, because these
   three sentences are what the drawing says in words. */
.close-lead-say {
  font-size: var(--t-lede);
  line-height: 1.4;
  letter-spacing: -0.01em;
  color: var(--text-body);
}
/* The roster's lesson, and it costs exactly one rule to not relearn it:
   .pop sets `transform-box: fill-box`, which on an HTML element resolves to
   the CONTENT box, and that is the reference box .map-pin's own
   `translate: 0 -50%` is then a percentage of. Put back to the border box,
   which is what .map-pin has always assumed. Two class levels plus the
   ancestor, because it has to beat `.motion .pop`. */
.motion .close-lead.pop { transform-box: border-box; }

/* The three nodes, in percentages taken straight from the viewBox, so the
   pins and the drawing cannot drift apart. Each block's left edge is 30
   units right of its node, which clears the node's 18-unit ring by 12.
   x = (node x + 30) / 1080, y = node y / 520. */
.close-n1 { --x: 25.926%; --y: 17.308%; }   /* node 250,90  */
.close-n2 { --x: 50.926%; --y: 50.000%; }   /* node 520,260 */
.close-n3 { --x: 30.556%; --y: 84.615%; }   /* node 300,440 */

/* A thumbnail is a module: 2px cobalt outline, white face, lime shade,
   exactly like the card the form sits in. Local padding and radius only,
   because .mod's clamp inset is built for a block of copy. One size ships,
   so the preview's base-plus-1.5x pair is written out at its result here;
   the 2px line weight is the one length that did not scale, which is the
   rule the whole system is drawn under. */
.close-thumb {
  width: 222px;
  height: 138px;
  padding: 0;
  border-radius: 20px;
  box-shadow: 0 4px 0 var(--shade-lime);
  display: grid;
  place-items: center;
}

/* A roster face in the node's own 2px ring, the same construction the
   network band's .marq-pic uses. Cobalt rather than white, because these sit
   on a white module rather than a cobalt card. */
.close-face {
  display: block;
  border: 2px solid var(--stroke);
  border-radius: 50%;
  object-fit: cover;
  background: var(--core);
}

/* 01. The niches: a cluster. Two rows of three, spaced rather than
   overlapped, so every ring stays a whole ring. */
.close-cluster { display: grid; grid-template-columns: repeat(3, 45px); gap: 12px; }
.close-cluster .close-face { width: 45px; height: 45px; }

/* 02. The cast: two faces and a selection. The candidate carries the dashed
   ring this system uses for potential; the cast face carries the solid ring
   and the densest node state in the vocabulary, a core filled with the
   stroke colour, seated on a white ring so it reads clear of the ring
   behind it. */
.close-pair { display: flex; align-items: center; gap: 24px; }
.close-pair .close-face { width: 66px; height: 66px; }
.close-cand { border-style: dashed; }
.close-cast { position: relative; display: block; }
.close-cast .close-mark {
  position: absolute;
  right: -4px;
  bottom: -4px;
  width: 24px;
  height: 24px;
  box-sizing: border-box;
  border-radius: 50%;
  background: var(--stroke);
  /* 2px written out, not var(--w). --w is 2 without a unit because its job is
     SVG stroke-width, where unitless is correct. Dropped into a CSS border
     shorthand it makes the whole declaration invalid at computed-value time,
     and the border silently falls back to currentColor. */
  border: 2px solid var(--white);
}

/* 03. The first campaign: three nodes on one connector, each carrying a lime
   module. The connector runs from the centre of the first column to the
   centre of the last, so it starts and ends on a node and never overshoots
   either. 9.5px is arithmetic and not a nudge: the node is 21px, its centre
   is at 10.5, and a 2px line is centred on that at 9.5.

   This is the one pseudo-element the block paints, and it is carried in
   _collide.js's PSEUDO_HOSTS from 2026-09-01. */
.close-plan {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  width: 156px;
}
.close-plan::before {
  content: "";
  position: absolute;
  top: 9.5px;
  left: 16.666%;
  right: 16.666%;
  height: 2px;
  background: var(--stroke);
}
.close-step { display: grid; justify-items: center; gap: 13px; }
.close-node {
  width: 21px;
  height: 21px;
  box-sizing: border-box;
  border: 2px solid var(--stroke);
  border-radius: 50%;
  background: var(--core);
}
.close-slab {
  width: 39px;
  height: 15px;
  box-sizing: border-box;
  border: 2px solid var(--stroke);
  border-radius: 999px;
  background: var(--lime);
}

/* Below 1200 the fan becomes a chain, exactly as the roster scene's does:
   one spine on the wrap's left edge, the same origin node at the top of it,
   the same three nodes down it at an even pitch, and each block stacked
   rather than spread because 296px is what a phone leaves beside the spine.
   The topology is the only thing that changes and every connector still runs
   node to node.

   Over 350x820: the spine runs at x=18 and every block's left edge is at
   x=48, which is 30 units clear of the spine and 14 clear of a node's own
   16-unit ring. x = 48/350, y = node y / 820. */
@media (max-width: 1199px) {
  .close-const .close-cd { display: none; }
  .close-const .close-cm { display: block; }
  .close-const { width: min(350px, 100%); }
  .close-lead { grid-template-columns: minmax(0, 296px); gap: 14px; }
  .close-n1 { --x: 13.714%; --y: 24.390%; }   /* node 18,200 */
  .close-n2 { --x: 13.714%; --y: 54.878%; }   /* node 18,450 */
  .close-n3 { --x: 13.714%; --y: 85.366%; }   /* node 18,700 */
}

/* Both drawings are width:100% of this box, so the box is --map-w. See the
   unit law above section 10's .draw. */
.close-map {
  position: relative;
  margin-top: clamp(28px, 3vw, 44px);
  container-type: inline-size;
  --map-w: 100cqw;
}

.cs-d1 { --len: calc(0.1854 * var(--map-w)); }   /* 266.9 over 1440 */
.cs-d2 { --len: calc(0.0445 * var(--map-w)); }   /*  64.0 over 1440 */
.cs-d3 { --len: calc(0.1908 * var(--map-w)); }   /* 274.7 over 1440 */
.cs-d4 { --len: calc(0.0445 * var(--map-w)); }   /*  64.0 over 1440 */
.cs-d5 { --len: calc(0.1665 * var(--map-w)); }   /* 239.6 over 1440 */
.cs-d6 { --len: calc(0.1659 * var(--map-w)); }   /* 238.9 over 1440 */

.cs-m1 { --len: calc(0.3346 * var(--map-w)); }   /* 130.5 over 390 */
.cs-m2 { --len: calc(0.2389 * var(--map-w)); }   /*  93.1 over 390 */

.close-card {
  --x: 50%;
  --y: 50%;
  translate: -50% -50%;
  width: min(520px, 88%);
  white-space: normal;
}
.close-mod { background: var(--white); box-shadow: 0 4px 0 var(--shade-lime); }
.close-tag { display: block; margin-bottom: 18px; }
/* .close-micro ("No call. No deck. One page.") died 2026-08-31 with its
   markup and its BRAND.md voice example: the heading already promises the
   one page, and a slogan under a submit button was the card selling itself
   twice.

   The seat is refilled 2026-09-01 and the class name comes back with it,
   carrying a different sentence and a different job. The line that died
   restated the heading; this one says what the map is built from, which the
   heading never promises and the product field only implies. The treatment
   is .roster-micro's, deliberately and to the pixel, because it is the same
   pattern under the other form on this page and two different micro lines
   would be a size the scale does not have. The ground here is the card's
   white and not the scene's lime, so the figure that binds is --ink-soft
   #4A4F7A on #FFFFFF: 7.80:1. */
.close-micro {
  margin-top: 16px;
  max-width: 44ch;
  font-size: var(--t-body);
  color: var(--text-body);
}

/* .hero-micro, 2026-09-08: new markup, a <p> directly under .hero-acts.
   Modelled on .close-micro above, to the same four declarations, because
   this page already has one pattern for a quiet line under a call to
   action and a second one would be a size the scale does not have.
   margin-top is 16px rather than .hero-acts' own 26px on purpose: the
   button's own gap to the text above it is 26px, so a tighter 16px reads
   this line as the button's caption instead of a new paragraph. */
.hero-micro {
  margin-top: 16px;
  max-width: 44ch;
  font-size: var(--t-body);
  color: var(--text-body);
}

/* Two columns while .close-how shared this row: it sat in the 1fr column
   and .close-alt in the auto one, which is what put .close-alt flush against
   the right edge of a 1240px wrap. .close-how was removed as off-message, and
   with it gone .close-alt is this grid's only item, so a two-column split
   built for a pair has nothing left to split. Collapsed to a plain block, it
   sits exactly like .close-head and .close-lede above it instead of an
   unconstrained line stretching the full width of the wrap. */
.close-foot { margin-top: clamp(36px, 4vw, 64px); }
.close-alt { max-width: 42ch; font-size: var(--t-body); }

/* At 390px the ring cannot enclose a form, so the card stops being a pin and
   the bodies become a band behind it. */
@media (max-width: 760px) {
  /* The ring is 230 units in a 300-unit viewBox, so it reaches 80 units past
     the top of its own box — straight through the lede above it. On desktop
     that overhang is the point, because the ring encloses the card. Here the
     card sits below the drawing instead, so the ring encloses nothing and the
     overhang is only a stroke crossing a paragraph. Cropping it keeps the
     scale and reads as a body running off the edge, which is language this
     page already speaks. */
  .close-map { display: grid; overflow: clip; }
  .close-card {
    position: static;
    translate: none;
    width: 100%;
    margin-top: -34px;
  }
}

/* --------------------------------------------------------------------------
   23. THE HERO UPGRADE IS GONE, 2026-09-08, BY THE OWNER

   This section held .hero-gl, the two .gl-on rules that cross-faded the WebGL
   scene in over the drawing and the drawn rig out from under it, and the
   media query that parked the canvas below 760px. All four are removed with
   hero.js and vendor/ogl. The stage keeps only the drawing now.

   The reason is measured rather than felt. The shader carried a depth of
   field, blur = 1.0 + 2.6 * max(0.0, vDepth - uFocus), and that blur is the
   half-width of the soft edge in SCREEN pixels: 2.0px at the focus plane, and
   the three bodies sat far behind it at 3.08, 3.99 and 5.42, which is a 6.2,
   8.0 and 10.8px band against the drawing's 2px stroke. The band is
   symmetrical around the true edge, so the silhouettes thickened with it. It
   was never a resolution problem, and raising the dpr cap could not have
   helped: the canvas already rendered at dpr 2, 1218x1246 behind a 609x623
   box, and a blur defined in screen pixels does not shrink when you add more
   of them.

   The second reason is the one that settles it. The GL scene was an
   independent reconstruction, not an upgrade of the same picture, and it was
   the poorer of the two: no slider in the pill, no toggle in the capsule, no
   plus marks in the nodes, no outlines on the bodies, no lime field. Section
   The real-time rule in DESIGN.md, under Depth and 3D, required that the
   canvas be a layer over a finished picture.
   It was not; it was a replacement for one, and a worse one.

   .hero-stage > .hero-sys { position: relative } went too. Its only stated
   job was to make the stage the canvas's sizing box, and with no canvas to
   size there is nothing left for it to do: the SVG is the stage's only child
   and in flow.

   Nothing about the drawn hero changed in this round, and nothing needed to.
   Under 760px the canvas was already parked, so a phone has been shown this
   exact composition since the first build. -------------------------------- */
