/* ============================================================
   ui-core.css — the UI (WEB) medium engine  (2.0)
   ------------------------------------------------------------
   NOTE ON THE HEADER: deck-core.css and doc-core.css name the
   house in their first line. This file deliberately does not.
   A medium engine that cannot reference a brand should not name
   one either, and `grep -i <house>` over this file returning
   nothing is a cheap, mechanical check that the boundary held.
   ------------------------------------------------------------
   The third medium. deck-core.css owns a fixed 1920x1080 stage
   and doc-core.css a fixed 1190x1684 page: both are canvases,
   both are print thinking. This one is not a canvas at all.
   It is fluid, it has breakpoints, and — a first for this
   system — it has interactive states. Nothing in the deck or
   document engines has ever had to answer "what does this look
   like under the cursor, and what does it look like when a
   keyboard lands on it". Sections 4 and 5 are that new ground.

   Transcribed from the Master UI file, page "COMPONENTS & ASSETS":
   eight atomic components, each built in Figma as a matrix of
   Brand x State x Mode. The Brand axis is not implemented here —
   that is what the brand packs are for, and the whole point of
   the token contract is that this file cannot tell Group from
   Forge. The State and Mode axes ARE implemented, as CSS states
   and as modifier classes respectively.

   The engine consumes ONLY the token contract in tokens.css.
   No brand name, no asset path, no hex colour appears below.
   Brand differences arrive through --c-*, --font-*, --tr-* and
   the two radius tokens in §1.

   ── THE MODE AXIS ────────────────────────────────────────────
   Mode is the axis the design system had no model for. Read off
   the Figma matrices, it is a reassignment of INK first and
   PLATE second — not a single "dark mode" switch:

     Default      plate = Background Base
                  ink   = Primary 100
                  the accent appears only on hover/active

     Alternative  plate = Background Base, unchanged
                  ink   = Accent Color
                  Only the ink moves. The component is already
                  wearing the accent at rest, so its hover has
                  nowhere accent-ward to go — and in Figma it
                  instead lifts the plate to Background Highlight.
                  Alternative and Default are therefore the same
                  two appearances in the opposite order: Default
                  rests on the plate and hovers to accent,
                  Alternative rests on accent and hovers to the
                  bright plate. Newsletter SignIn is the clearest
                  case; compare its two Mode rows.

     Inverted     plate = Primary 100 (the ink becomes the ground)
                  ink   = the INVERTED ink ramp, 100/60/20
                  BOTH move, and the plate stops coming from the
                  background tokens altogether: an inverted
                  component's resting fill in Figma is Inverted
                  Primary 20 — a translucent tint of its own new
                  ink — floating on the ink ground. So Inverted is
                  not "Default on a dark slide". It is a second,
                  self-contained ramp.

   The inverted ink is white for Group, Studios and Forge and a
   near-black green for Partners. That is a per-brand value, so it
   is a CONTRACT token — --c-ink-inverse, set by each brand pack
   from its Inverted Primary — and this engine only reads it. It
   defaults to --c-bg-core, which is near-white on a light-ground
   brand and dark on a dark-ground one, so a brand pack that has
   not declared it still inverts sensibly rather than breaking.
   Knockout ink on an accent plate (§4) uses --c-bg-core directly
   for the same light-ground/dark-ground reason.

   ── HOW THE MODE CLASSES WORK ────────────────────────────────
   deck-core's .slide--dark reassigns --c-ink in the same rule that
   reads it (`background: var(--c-ink); --c-ink: var(--c-bg-base)`).
   Custom properties are substituted with the value declared on the
   element itself, so that background resolves to the NEW ink and
   the rule paints bone rather than black. Verified in Chromium.
   This engine keeps the same idea — a modifier class on a container
   that reassigns the plate/ink tokens for everything inside — but
   routes it through a private --ui-* indirection layer (§2) so the
   reassignment can never read itself. Components consume --ui-ink
   and --ui-plate, never --c-ink and --c-bg-base directly.

   Sections:
     1. Web token group   (type / space / radius / breakpoints)
     2. The Mode axis     (.ui-mode--*)
     3. Page shell + grid (+3.1 vertical rhythm, 3.2 base elements)
     4. Shared interaction: focus, hover, motion
     5. The eight atomic components (+5.9 cards)
     6. Composed components: navbar, footer
     7. Print

   Component inventory (Figma node -> class):
     Buttons             15312:6876        -> .ui-btn
     Tags                40004583:75980    -> .ui-tag
     Links               40004583:77527    -> .ui-link
     Pills               40004583:76086    -> .ui-pill
     Search Bar          40004583:77691    -> .ui-search
     Form                40004583:77238    -> .ui-field
     Newsletter SignIn   40004583:76215    -> .ui-signup
     Active Page Number  40004583:77634    -> .ui-pager

   Spec + budgets: reference/UI-COMPONENTS.md
   ============================================================ */

/* ============================================================
   1. WEB TOKEN GROUP
   ------------------------------------------------------------
   These are the real web values from the style guide, typed in
   rather than re-derived from the deck scale. The deck (--fs-*)
   and document (--fsd-*) scales are separate and unrelated:
   a deck body is 23px because it is read across a room, a web
   body is 14px because it is read at arm's length. Never map
   one onto the other.
   ============================================================ */
:root {
  /* --- Display type, three sizes x three viewports ----------
     Every display size steps down twice, and the step is steep:
     H1 loses 62px between desktop and tablet. That is deliberate
     — 128px is a hero size that simply does not fit an 834pt
     frame with 20pt margins. Line-height is 100% at every size,
     unlike the deck's 0.95, because web display type is set in
     shorter measures and the tighter leading collided.
     Tracking comes from the brand (--tr-display): -6% for the
     Concrette brands, -2% for Forge's grotesque. */
  --fsw-h1-d: 128px;  --fsw-h1-t: 70px;  --fsw-h1-m: 48px;
  --fsw-h2-d:  64px;  --fsw-h2-t: 48px;  --fsw-h2-m: 36px;
  --fsw-h3-d:  46px;  --fsw-h3-t: 40px;  --fsw-h3-m: 32px;

  /* Live display sizes. Reassigned by the breakpoints in §3;
     mobile-first, so the mobile value is the default. */
  --fsw-h1: var(--fsw-h1-m);
  --fsw-h2: var(--fsw-h2-m);
  --fsw-h3: var(--fsw-h3-m);

  /* --- The General block ------------------------------------
     Shared across every viewport and DOES NOT SCALE. H4 down to
     Tagline are the same px on a phone and on a 1440 desktop.
     That is what makes the display sizes read as display: the
     small end of the scale holds still while the top moves. */
  --fsw-h4:      20px;  --lhw-h4:      1.2;
  --fsw-h5:      16px;  --lhw-h5:      1.2;
  --fsw-body:    14px;  --lhw-body:    1.3;
  --fsw-button:  14px;  --lhw-button:  1.3;
  --fsw-tagline: 14px;  --lhw-tagline: 1.3;
  --lhw-display: 1;     /* 100% at every display size */

  /* Body weights. Light is the running-text weight.
     ⚠ The style guide specifies Body Bold at weight 700. We do
     not licence 700 — the GT Standard L licence covers Light
     (300) and Medium (500) only, with no Semibold either. So
     --fww-body-bold is 500, the heaviest real face we hold.
     Do not "fix" this to 700: _house.css sets
     `font-synthesis: none`, so a 700 request would fall back to
     500 anyway, and asking for a weight we do not own is how the
     trial-font problem happened the first time. */
  --fww-body:      300;
  --fww-body-bold: 500;

  /* --- Spacing scale ---------------------------------------
     The web scale is its own thing: it starts finer than the
     deck's 40px module (a 5px step exists here and nowhere else)
     and runs much further up. XXS and XS are control-interior
     spacing; S upward is layout. */
  --s-w-xxs:    5px;
  --s-w-xs:    10px;
  --s-w-s:     20px;
  --s-w-m:     40px;
  --s-w-l:     60px;
  --s-w-xl:   120px;
  --s-w-xxl:  240px;
  --s-w-xxxl: 320px;

  /* --- Flow rhythm (see §3.1) --------------------------------
     Two values, both taken off the spacing scale rather than
     invented: the shell's own top and bottom padding, and the gap
     that opens before a display heading. Mobile-first, so these
     are the small-frame values and the 640px query raises them.

     PROVISIONAL. The Master UI's section padding is a property of
     the page SECTIONS, which are not transcribed yet — when they
     land, these two should be re-derived from what the sections
     actually use rather than left as a reasonable guess. They are
     on-scale, which is the part that matters for now. */
  /* MEASURED, no longer provisional for the phone. The mobile section
     frames (e.g. 40004588:5091, 373 wide) draw 80px of block padding,
     not the 60 that was reasoned here first. 80 is not on the web
     spacing scale — XS 10, S 20, M 40, L 60, XL 120 — so it is two M
     rather than a new step, the same way the desktop 80 is expressed.
     The tablet and desktop values are still the earlier reasoning. */
  --ui-pad-block:     calc(var(--s-w-m) * 2);  /* 80px, drawn */
  --ui-flow-section:  var(--s-w-l);            /* 60px  */

  /* Descender clearance for a cap-trimmed display heading. See the
     .ui-h1 rule in §3.2 for the measurements. A brand whose display
     face drops further than 0.22em should raise this. */
  --ui-display-descender: 0.22em;

  /* --- Radius ----------------------------------------------
     Two web radii, and they are a real brand differentiator —
     the one place the square deck/document system bends. Group
     and Partners are near-straight at 4px for both. Studios and
     Forge round hard: 20px on components, 30px on buttons, which
     on a 34px control is a full pill.

     --radius-component and --radius-button are declared in the
     CONTRACT (tokens.css) and set by each brand pack from the
     token source. This engine only consumes them — it does not
     re-declare them, because a medium engine that re-declared a
     contract token would silently outrank a brand that had not
     been regenerated. */

  /* --- Grid ------------------------------------------------
     Straight off the Figma grids. Note the margin collapse:
     desktop reserves 170px of margin, tablet only 20px. The
     content column is therefore WIDER in absolute terms on
     desktop but the page is far more inset — the layout is not a
     smooth scale, it is three separate grids. Mobile drops the
     gutter to 10 but raises the margin to 30, the reverse of
     tablet. */
  --grid-cols-d:  12;  --grid-gutter-d: 20px;  --grid-margin-d: 170px;
  --grid-cols-t:   9;  --grid-gutter-t: 20px;  --grid-margin-t:  20px;
  --grid-cols-m:   9;  --grid-gutter-m: 10px;  --grid-margin-m:  30px;

  /* Live grid values, mobile-first. */
  --grid-cols:   var(--grid-cols-m);
  --grid-gutter: var(--grid-gutter-m);
  --grid-margin: var(--grid-margin-m);

  /* Reference frame widths, for max-width clamps only. Not
     breakpoints — see the note in §3. */
  --frame-d: 1440px;
  --frame-t:  834px;
  --frame-m:  393px;

  /* --- Control metrics -------------------------------------
     Every interactive control in the Figma set is 34px tall with
     15px of inline padding, measured off the symbols: a button is
     64x34 with its text at x=15, a tag 89x34, a form field
     265x34, a collapsed search 34x34. One height for the whole
     kit, which is what lets a button sit in a form row without
     any per-component nudging.

     15px is XS + XXS. It is not a scale step of its own; do not
     add one. And 34px is a deliberately tight target — see the
     touch-target note in §4. */
  --ui-control-h:  34px;
  --ui-control-px: calc(var(--s-w-xs) + var(--s-w-xxs));

  /* The pager dot, measured at 14x14 on a 19px pitch (a 5px XXS
     gap). Its own token because 14px is not a spacing step and
     must not become one — it is a glyph size, like a cap height,
     not a distance. */
  --ui-dot: 14px;

  /* Control borders are the FINE weight. --rule-w (2px) is
     reserved for structural line work — table rules, arch arcs.
     On a 34px control a 2px stroke is 6% of the height and the
     outline states start shouting over the filled ones, which is
     exactly the hierarchy tokens.css set out to protect. */
  --ui-border-w: var(--rule-w-fine);
}

/* ============================================================
   2. THE MODE AXIS
   ------------------------------------------------------------
   A private indirection layer, and the three modifier classes
   that reassign it. Put .ui-mode--* on any container — a section,
   a footer, a single component — and everything inside re-skins.
   They nest: an .ui-mode--inverted footer can hold an
   .ui-mode--alternative signup block.

   Components below read ONLY these --ui-* names. That is what
   makes the reassignment safe: --ui-plate is declared FROM
   --c-ink inside .ui-mode--inverted, and because no rule
   reassigns --c-ink itself, the substitution cannot feed back
   into its own definition the way .slide--dark's does.
   ============================================================ */
:root {
  /* The inverted ink ramp, 100/60/20. Three steps, not four —
     the web ink ramp has no 10% step, and the inverted ramp
     skips 40% as well. Straight off the contract's
     --c-ink-inverse, which each brand sets from its Inverted
     Primary: white on the light-ground brands, deep green on the
     dark-ground one. It falls back to --c-bg-core when a brand
     has not declared it, which is why this engine can be pointed
     at a brand pack that predates the token. */
  --ui-ink-inv:    var(--c-ink-inverse);
  --ui-ink-inv-60: var(--c-ink-inverse-1);
  --ui-ink-inv-20: var(--c-ink-inverse-3);
  /* A 40% step the Figma inverted ramp does not have. It exists only
     as the inverted LIFTED plate: at 100% the lifted plate would be
     solid inverted ink, which is the same colour as inverted ink, so
     a filled+active control came out white-on-white (caught in the
     proof render). 40% is the strongest tint that still leaves the
     ink readable on top of it. */
  --ui-ink-inv-40: color-mix(in srgb, var(--c-ink-inverse) 40%, transparent);

  /* Mode = Default. The web ink ramp is 100/60/40/20 — note it
     stops at 20 and has no 10% step, unlike the print ramp in
     tokens.css which runs 60/40/20/10. --c-ink-4 is a print tint;
     nothing in this engine uses it.

     --ui-ground is the page; --ui-plate is what a control sits on
     top of it in. Three plate names, one job each — and note that
     NO component below ever names a --c-bg-* token directly. They
     all go through these two, which is the only reason Inverted
     works: reassign the pair and every component follows.

     ⚠ DEPARTURE FROM THE FIGMA FILL, on purpose. The Figma
     components fill their resting plate with Background BASE. Taken
     literally that is invisible: Base is also the page ground, so a
     button on a real page would be bone-on-bone with no edge. It
     reads in Figma only because the artboards sit on Figma's own
     dark grey canvas, which is not a brand colour and not a page.
     tokens.css is explicit that Base is the ground and that cards
     now sit LIGHTER than the ground, so the resting plate here is
     Core (one step forward) and the lifted plate is Highlight.
     That preserves the two-step relationship the Figma draws while
     keeping it visible on an actual page. */
  --ui-ground:    var(--c-bg-base);
  --ui-plate:     var(--c-bg-core);
  --ui-plate-up:  var(--c-bg-high);   /* the "lifted" plate: hover, active */
  --ui-ink:       var(--c-ink);
  --ui-ink-60:    var(--c-ink-1);
  --ui-ink-40:    var(--c-ink-2);
  --ui-ink-20:    var(--c-ink-3);

  /* The COPY ink. On the WEBSITE this is the ink — Colors/Primary,
     including on the brand whose Primary is a saturated orange, which
     is checked and not assumed: node 40004583:68583 (the CONTENT
     section, Forge) sets its H2 in Primary 100 and its body in
     Primary 60, with no Black ramp on the node at all. The brand's
     text IS its colour, and that is the design.

     The token still exists because there is exactly ONE place the
     file departs from that: the FOOTER binds its links to
     Colors/Black 60 rather than to Primary 60. §6.2 reassigns these
     three there and nowhere else, so the departure lives where the
     file puts it instead of leaking across the engine.

     An earlier pass had this pointing at --c-ink-body engine-wide.
     That was wrong — it turned a Forge page black except for its
     headings. Reverted 13 Aug on Lasse's correction, and confirmed
     against the node above. */
  --ui-ink-copy:    var(--c-ink);
  --ui-ink-copy-60: var(--c-ink-1);
  --ui-ink-copy-20: var(--c-ink-3);


  /* Ink that has to read ON the accent plate — a hovered button's
     label, a signup arrow. This cannot be a fixed white: three
     brands take a saturated mid-tone accent that wants light ink,
     and Partners takes a bone accent that wants dark ink.
     --c-bg-core is light on the first three and dark on the
     fourth, so it is the correct token for "whatever reads on the
     accent" without the engine knowing which brand is loaded.
     ⚠ Contrast is genuinely thin on a pale accent — see the
     legibility note in UI-COMPONENTS.md. */
  --ui-ink-on-accent: var(--c-bg-core);

  /* The "filled/selected" plate — a hovered button, an active pill, a
     hovered signup submit. Normally the accent, which is the whole
     point of an accent.

     ⚠ It is a token and not a literal var(--c-accent) because of what
     the proof render caught in Inverted mode. Forge and Partners both
     bind accent TO ink (they are monochrome brands), and Inverted makes
     the ink the GROUND — so on those two brands an accent fill is
     exactly the colour of the surface behind it and the control
     vanished completely. The Figma matrices cannot show this: their
     inverted rows are drawn on Figma's own neutral grey canvas, not on
     the brand's ink. Inverted therefore reassigns this to the solid
     inverted ink (below), which is guaranteed to contrast with the
     inverted ground on all four brands. The brand still reads — it
     just reads in the label rather than the plate. */
  --ui-fill-active:   var(--c-accent);
}

/* Mode = Default, stated explicitly so a container can be reset
   back out of another mode. */
.ui-mode--default {
  --ui-ground:    var(--c-bg-base);
  --ui-plate:     var(--c-bg-core);
  --ui-plate-up:  var(--c-bg-high);
  --ui-ink:       var(--c-ink);
  --ui-ink-60:    var(--c-ink-1);
  --ui-ink-40:    var(--c-ink-2);
  --ui-ink-20:    var(--c-ink-3);
  --ui-ink-copy:    var(--c-ink);
  --ui-ink-copy-60: var(--c-ink-1);
  --ui-ink-copy-20: var(--c-ink-3);
  --ui-ink-on-accent: var(--c-bg-core);
  --ui-fill-active:   var(--c-accent);
  background: var(--ui-ground);
  color: var(--ui-ink);
}

/* Mode = Alternative. Ink only: the plate does not move, the ink
   becomes the accent. Because the component already wears the
   accent at rest, its hover lifts the PLATE instead (§5), which
   is the exact inverse of Default's behaviour. */
.ui-mode--alternative {
  --ui-ink:    var(--c-accent);
  --ui-ink-60: var(--c-accent-60);
  --ui-ink-40: var(--c-accent-40);
  --ui-ink-20: var(--c-accent-20);
  /* Copy follows the ink here. Alternative exists to put the accent
     on the type, so exempting running text would defeat it — which
     is also why Alternative is a wrong choice for a text-heavy
     region on the pale-accent brands. §6.2 says so for the footer. */
  --ui-ink-copy:    var(--c-accent);
  --ui-ink-copy-60: var(--c-accent-60);
  --ui-ink-copy-20: var(--c-accent-20);
  background: var(--ui-ground);
  color: var(--ui-ink);
}

/* Mode = Inverted. Both axes move. The ground becomes the brand
   ink; the ink becomes the inverted ramp; and the resting plate
   for a component is the 20% inverted tint — a translucent wash
   of its own ink — rather than a background token, which is why
   --ui-plate points at --ui-ink-inv-20 and not at a --c-bg-*.
   That translucency is load-bearing: it lets an inverted control
   sit on a photograph or a coloured section and still read. */
.ui-mode--inverted {
  --ui-ground:    var(--c-ink);
  --ui-plate:     var(--ui-ink-inv-20);
  --ui-plate-up:  var(--ui-ink-inv-40);
  --ui-ink:       var(--ui-ink-inv);
  --ui-ink-60:    var(--ui-ink-inv-60);
  --ui-ink-40:    var(--ui-ink-inv-60);
  --ui-ink-20:    var(--ui-ink-inv-20);
  /* No second ramp needed: Inverted Primary is already a neutral
     (white, or the deep green on the dark-ground brand), so the
     reason --ui-ink-copy exists does not apply here. */
  --ui-ink-copy:    var(--ui-ink-inv);
  --ui-ink-copy-60: var(--ui-ink-inv-60);
  --ui-ink-copy-20: var(--ui-ink-inv-20);
  --ui-fill-active:   var(--ui-ink-inv);
  --ui-ink-on-accent: var(--c-ink);
  background: var(--ui-ground);
  color: var(--ui-ink);
}

/* ============================================================
   3. PAGE SHELL + GRID
   ------------------------------------------------------------
   ⚠ THE BREAKPOINTS BELOW WERE CHOSEN HERE, NOT READ FROM FIGMA.
   The file defines three frames — 1440 / 834 / 393 — and no
   min-width breakpoints at all. Frame widths are not breakpoints:
   switching to the desktop grid at exactly 1440px would leave
   every laptop between 834 and 1440 on the tablet grid, and the
   170px desktop margin would be absurd at 1000px anyway.

   So: tablet grid from 640px, desktop grid from 1120px. 640 is
   above every phone in the frame set and below every tablet;
   1120 is where 12 columns plus a proportionally reduced margin
   stops feeling cramped. The desktop margin is clamped rather
   than fixed at 170px for the same reason. Revisit these with
   design — they are an implementation judgement.
   ============================================================ */
.ui-page {
  box-sizing: border-box;
  width: 100%;
  margin: 0;
  padding-inline: var(--grid-margin);
  /* --ui-ground, not --c-bg-base. .ui-page and .ui-mode--* land on the
     same element with the same specificity, and .ui-page is declared
     later, so a literal --c-bg-base here silently beat
     .ui-mode--inverted's dark ground and the whole inverted section
     rendered bone. Going through the indirection layer makes the two
     classes order-independent. */
  background: var(--ui-ground);
  color: var(--ui-ink);
  font-family: var(--font-sans);
  font-weight: var(--fww-body);
  font-size: var(--fsw-body);
  line-height: var(--lhw-body);
  letter-spacing: var(--tr-body);
  padding-block: var(--ui-pad-block);
}

/* ------------------------------------------------------------
   3.1 VERTICAL RHYTHM
   ------------------------------------------------------------
   The deck and document engines do not need this section and that
   is exactly why it has to exist here. In those media every block
   is absolutely positioned against a fixed canvas, so `margin: 0`
   on a heading is correct — the layout owns the spacing and a
   stray margin would fight it. This medium is FLOW. Take the same
   `margin: 0` into a flow context and the first thing anyone
   builds has its H1 lying on top of its body copy, its display
   type clipped off the top of the viewport, and no rhythm at all.
   That is not a hypothetical: it is what the template did before
   this section was written.

   So the engine owns vertical rhythm too, through one owl
   selector per flow container. Rules:

     · The gap between two stacked blocks is --ui-flow (M/40px).
     · A display heading gets a much bigger gap ABOVE it (XL on
       desktop, stepping down with the viewport) because a section
       break is a bigger idea than a paragraph break.
     · The block immediately AFTER a heading gets a small gap (S),
       because it belongs to that heading. Distance says "these
       two things are one thing".
     · Nothing gets a margin from being first or last; the shell's
       own padding-block does that, so a section can sit flush
       against the one above it.

   Applies to the DIRECT CHILDREN of a flow container, and the only
   automatic flow container is the page shell itself. Anything else
   opts in with `.ui-flow`.

   That opt-in is not fussiness, it is a bug fix. An earlier version
   also treated every `.ui-grid` cell as a flow container, via
   `.ui-grid > * > * + *`. Put a component directly in a cell —
   `<article class="ui-card" style="--span:4">`, which is the
   obvious thing to write — and the cell IS the component, so the
   rule reached straight into its internals and shoved the card's
   text block 40px off its media well. Found in a card render.

   The general lesson, and it applies to every section below: a
   descendant selector in a medium engine will eventually land
   inside a component someone else wrote. Keep the reach to one
   level and make the container say so.
   ------------------------------------------------------------ */
.ui-page,
.ui-flow {
  --ui-flow: var(--s-w-m);
}

.ui-page > * + *,
.ui-flow > * + * {
  margin-top: var(--ui-flow);
}

/* Big air before a display heading — a new idea starting. */
.ui-page > * + .ui-h1, .ui-page > * + .ui-h2, .ui-page > * + .ui-h3,
.ui-flow > * + .ui-h1, .ui-flow > * + .ui-h2, .ui-flow > * + .ui-h3 {
  margin-top: var(--ui-flow-section);
}

/* …and very little after one, because what follows belongs to it. */
.ui-page > .ui-h1 + *, .ui-page > .ui-h2 + *, .ui-page > .ui-h3 + *,
.ui-page > .ui-h4 + *, .ui-page > .ui-h5 + *,
.ui-flow > .ui-h1 + *, .ui-flow > .ui-h2 + *, .ui-flow > .ui-h3 + *,
.ui-flow > .ui-h4 + *, .ui-flow > .ui-h5 + * {
  margin-top: var(--s-w-s);
}

/* A tagline sits under the thing it describes, closer still. */
.ui-page > * + .ui-tagline,
.ui-flow > * + .ui-tagline {
  margin-top: var(--s-w-xs);
}

/* ------------------------------------------------------------
   3.2 BASE ELEMENTS INSIDE A UI PAGE
   ------------------------------------------------------------
   tokens.css styles the bare elements — `p`, `h1`, `h2`, `h3` —
   at the DECK scale, because that is the medium it was written
   for: 23px body, 166px display. An element selector beats
   inheritance, so a plain `<p>` inside a card renders at 23px on
   a page whose body size is 14. Found when a footer's newsletter
   block came out 52px too tall.

   So the medium reclaims its own base elements. Note the
   `:where()` on BOTH halves: it zeroes the specificity of the
   whole selector, so this reset always loses to `.ui-body`,
   `.ui-h1` and to anything an author writes. A plain
   `.ui-page p` would be (0,1,1) and would have silently
   out-specified every `.ui-h*` class in this file — the same
   load-order trap that bit the navbar's logo.
   ------------------------------------------------------------ */
:where(.ui-page, .ui-flow) :where(h1, h2, h3, h4, h5, h6, p, li, dt, dd,
    blockquote, figcaption, address, th, td, caption, label, legend) {
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
  margin: 0;
}

/* The grid itself. 9 columns on small frames, 12 on desktop,
   with the gutter as the column gap. Children opt into a span
   with --span; the default is the full width. */
.ui-grid {
  display: grid;
  grid-template-columns: repeat(var(--grid-cols), minmax(0, 1fr));
  column-gap: var(--grid-gutter);
  row-gap: var(--s-w-m);
}
/* Mobile: every cell is full width, and --span is ignored. The phone
   grid in the Figma source is NINE columns, not four — a drawing
   convention, not a layout anyone builds two columns on. Honouring
   --span:4 there gives a 138px column, which stacks a two-pill row
   into two lines and reads as a bug. So --span only takes effect
   from the tablet breakpoint up, and the author writes desktop spans
   without having to think about the phone. */
.ui-grid > * { grid-column: 1 / -1; }

@media (min-width: 640px) {
  :root {
    --grid-cols:   var(--grid-cols-t);
    --grid-gutter: var(--grid-gutter-t);
    --grid-margin: var(--grid-margin-t);
    --fsw-h1: var(--fsw-h1-t);
    --fsw-h2: var(--fsw-h2-t);
    --fsw-h3: var(--fsw-h3-t);
    /* One step up the scale, 80 -> 120. There is no third step at
       desktop: the type gets much bigger between tablet and desktop
       but the air around a section does not need to. */
    --ui-pad-block:    var(--s-w-xl);
    --ui-flow-section: var(--s-w-xl);
  }
  .ui-grid > * { grid-column: span var(--span, var(--grid-cols)); }
}

@media (min-width: 1120px) {
  :root {
    --grid-cols:   var(--grid-cols-d);
    --grid-gutter: var(--grid-gutter-d);
    /* 170px is the 1440 frame margin — 11.8% of the frame. Below
       1440 that would eat the content column, so it scales and
       only reaches its full value at the design width. */
    --grid-margin: clamp(var(--s-w-m), 11.8vw, var(--grid-margin-d));
    --fsw-h1: var(--fsw-h1-d);
    --fsw-h2: var(--fsw-h2-d);
    --fsw-h3: var(--fsw-h3-d);
  }
}

/* --- Web display type -------------------------------------
   Same recipe as the deck: brand display face, brand display
   tracking, cap-trimmed. Only the sizes and the 100% leading
   differ. Cap-trim matters more here than in print because a web
   heading usually sits directly above a control or a rule, and
   the untrimmed 0.15em of leading above the cap line reads as a
   spacing bug. */
.ui-h1, .ui-h2, .ui-h3 {
  font-family: var(--font-display);
  /* The contract's display weight, not a literal 400 — see the note
     on the same property in tokens.css. The web source is explicit
     for the brand this matters to: its Desktop/H2 style is the
     display face at Light 300, and asking for 400 rendered the
     Medium instead. */
  font-weight: var(--fw-display);
  line-height: var(--lhw-display);
  letter-spacing: var(--tr-display);
  color: var(--ui-ink);
  /* Not margin: 0. text-box: trim-both cap alphabetic trims the box to
     the cap line and the baseline, which is what makes a measured
     Figma y-coordinate type straight in — but descenders then hang
     OUTSIDE the box and land on whatever follows. Measured in
     Chromium at 128px: Concrette drops 0.211em below the trimmed box
     and GT Standard 0.180em (both faces share a 0.703em cap height,
     which is why they substitute for each other so cleanly). 0.22em
     clears the deeper of the two.

     In em, so it scales with the heading, and as margin-BOTTOM so the
     em resolves against the heading's own size rather than the 14px
     body text below it. It collapses with the flow gap in §3.1 and
     the larger wins.

     The descender is CLEARANCE, not spacing — it only gets the next
     block back to where an untrimmed box would have started. The flow
     gap is added on top of it, or the body copy sits on the
     descenders with a pixel to spare and looks like a mistake. */
  margin: 0 0 calc(var(--ui-display-descender) + var(--s-w-s));
  text-wrap: balance;
  text-box: trim-both cap alphabetic;
}
.ui-h1 { font-size: var(--fsw-h1); }
.ui-h2 { font-size: var(--fsw-h2); }
.ui-h3 { font-size: var(--fsw-h3); }

/* H4 and H5 are the General block, not display: they are set in
   the body face and they do not scale. H4 is a card or block
   heading, H5 the smallest heading in the system.

   Weight 300, not 500. An earlier pass made them bold on the
   assumption that a heading must be heavier than its body; the
   `General/H4` and `General/H5` variables are both GT Standard L
   **Light 300**, checked live on a card node, and the token source
   records the same. These headings are distinguished by SIZE
   alone — 20px and 16px against a 14px body — which is what makes
   a card title sit quietly instead of shouting. Use `.ui-body--bold`
   when you actually want weight. */
.ui-h4, .ui-h5 {
  font-family: var(--font-sans);
  font-weight: var(--fww-body);
  letter-spacing: var(--tr-body);
  color: var(--ui-ink-copy);
  margin: 0;
}
.ui-h4 { font-size: var(--fsw-h4); line-height: var(--lhw-h4); }
.ui-h5 { font-size: var(--fsw-h5); line-height: var(--lhw-h5); }

.ui-body {
  font-family: var(--font-sans);
  font-weight: var(--fww-body);
  font-size: var(--fsw-body);
  line-height: var(--lhw-body);
  letter-spacing: var(--tr-body);
  color: var(--ui-ink-copy);
  margin: 0;
  text-wrap: pretty;
}
/* Body Bold — weight 500, see the licence note in §1. */
.ui-body--bold { font-weight: var(--fww-body-bold); }
.ui-body--dim  { color: var(--ui-ink-copy-60); }

/* Tagline — same metrics as body, but it is a role, not a size:
   the line under a logo, over a section, beside a figure. Dimmed
   by default because it is always secondary to something. */
.ui-tagline {
  font-family: var(--font-sans);
  font-weight: var(--fww-body);
  font-size: var(--fsw-tagline);
  line-height: var(--lhw-tagline);
  letter-spacing: var(--tr-body);
  color: var(--ui-ink-copy-60);
  margin: 0;
}

/* ============================================================
   4. SHARED INTERACTION: FOCUS, HOVER, MOTION
   ------------------------------------------------------------
   New ground. Print has no hover and no focus, so nothing in
   deck-core or doc-core establishes a house treatment for either
   and there was no precedent to follow.
   ============================================================ */

/* --- The focus ring ---------------------------------------
   :focus-visible only. :focus would ring a mouse click too, and
   on a system this quiet a permanent ring after every click
   looks broken.

   The ring is three layers, and it has to be, because "use the
   accent" is not by itself safe on four brands. Group's accent is
   a mid blue and Forge's a hot orange — both fine as a ring on
   bone. Studios' accent is a pale amber, which against a bone
   plate is barely a boundary at all, and Partners' accent is bone
   itself, which on a bone plate is invisible. So:

     1. a 2px gap in the current PLATE, separating ring from
        control so the ring never merges with a filled edge;
     2. the 3px accent ring proper;
     3. a 20%-ink halo OUTSIDE it, which gives the ring a defined
        outer edge on every brand. Ink is the one colour the
        contract guarantees contrasts with the plate — that is
        what ink means — so layer 3 is what makes a pale-accent
        ring legible rather than decorative.

   Result: on Group and Forge you read the accent, on Studios and
   Partners you read the accent plus its ink boundary. Same ring,
   no brand-specific code. */
.ui-btn:focus-visible,
.ui-tag:focus-visible,
.ui-link:focus-visible,
.ui-pill:focus-visible,
.ui-search:focus-visible,
.ui-search__input:focus-visible,
.ui-search__toggle:focus-visible,
.ui-field__control:focus-visible,
.ui-signup__input:focus-visible,
.ui-signup__submit:focus-visible,
.ui-pager__item:focus-visible {
  outline: 3px solid var(--c-accent);
  outline-offset: 2px;
  box-shadow: 0 0 0 2px var(--ui-plate),
              0 0 0 6px var(--ui-ink-20);
}

/* Remove the inner ring where a wrapper already carries one, so
   a focused search input does not draw two concentric rings. */
.ui-search:focus-within .ui-search__input:focus-visible {
  outline: none;
  box-shadow: none;
}

/* --- Hit area ---------------------------------------------
   34px is the Figma control height and it is below the 44px
   comfortable touch target. Rather than break the measured
   geometry, controls keep their 34px box and grow an invisible
   pseudo-element to 44px on pointer-coarse devices. The visual
   spec is preserved; the thumb target is not. */
@media (pointer: coarse) {
  .ui-btn, .ui-tag, .ui-pill, .ui-search__toggle,
  .ui-signup__submit, .ui-pager__item {
    position: relative;
  }
  .ui-btn::after, .ui-tag::after, .ui-pill::after,
  .ui-search__toggle::after, .ui-signup__submit::after,
  .ui-pager__item::after {
    content: "";
    position: absolute;
    left: 0; right: 0;
    top: 50%;
    transform: translateY(-50%);
    min-height: 44px;
  }
}

/* --- Motion ------------------------------------------------
   One duration and one easing for the whole kit, from the
   contract. State changes are colour changes, so --dur-fast is
   right: anything slower on a hover reads as lag.
   Reduced motion kills the transition entirely rather than
   shortening it — a colour crossfade has no motion to reduce, so
   the honest response is to cut it. */
.ui-btn, .ui-tag, .ui-link, .ui-pill,
.ui-search, .ui-search__toggle, .ui-search__input,
.ui-field__control, .ui-signup, .ui-signup__input,
.ui-signup__submit, .ui-pager__item {
  transition: background-color var(--dur-fast) var(--ease),
              border-color     var(--dur-fast) var(--ease),
              color            var(--dur-fast) var(--ease);
}

@media (prefers-reduced-motion: reduce) {
  .ui-btn, .ui-tag, .ui-link, .ui-pill,
  .ui-search, .ui-search__toggle, .ui-search__input,
  .ui-field__control, .ui-signup, .ui-signup__input,
  .ui-signup__submit, .ui-pager__item {
    transition: none;
  }
  /* The search bar's width animation is real motion, not a
     crossfade, so it is the one thing that must actually stop. */
  .ui-search { transition: none; }
}

/* ============================================================
   5. THE EIGHT COMPONENTS
   ------------------------------------------------------------
   A shared control base first. Every one of the eight is built
   on the same 34px box with 15px inline padding and the Button
   type role, which is why they can be mixed in a row without
   alignment work.
   ============================================================ */
.ui-btn, .ui-tag, .ui-pill, .ui-field__control,
.ui-signup__input, .ui-search__input {
  box-sizing: border-box;
  min-height: var(--ui-control-h);
  padding-block: 0;
  padding-inline: var(--ui-control-px);
  font-family: var(--font-sans);
  font-weight: var(--fww-body);
  font-size: var(--fsw-button);
  line-height: var(--lhw-button);
  letter-spacing: var(--tr-body);
  border: var(--ui-border-w) solid transparent;
  appearance: none;
  -webkit-appearance: none;
}

/* ------------------------------------------------------------
   5.1 BUTTONS   (Figma 15312:6876)
   ------------------------------------------------------------
   Axes: Brand x State(Default, Hover) x Mode(Default,
   Alternative, Inverted). 64x34 at the "Login" label — the width
   is content-driven, 64px is just what five characters plus
   2x15px padding comes to.

   Default mode: rests on the plate in full ink, hovers to a solid
   accent fill with knockout ink. Alternative: rests on the plate
   in ACCENT ink, hovers by lifting the plate to Highlight and
   keeping the accent ink — the accent never fills, it only ever
   colours the label. Inverted: rests on the translucent inverted
   tint, hovers to the same solid accent fill as Default.

   The button is the only component in the set that uses
   --radius-button; everything else uses --radius-component. On
   Studios and Forge that is 30px against a 34px box, i.e. a pill,
   which is what the Figma shows.
   ------------------------------------------------------------ */
.ui-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-w-xxs);
  background: var(--ui-plate);
  color: var(--ui-ink);
  border-radius: var(--radius-button);
  border-color: transparent;
  cursor: pointer;
  text-decoration: none;
  text-align: center;
  white-space: nowrap;
}

/* State = Hover. :hover and :focus-visible are NOT combined here
   — the focus ring is the focus affordance, and firing the hover
   fill on focus as well would make a keyboard user think they had
   already activated the thing. */
.ui-btn:hover,
.ui-btn[data-state="hover"] {
  background: var(--ui-fill-active);
  color: var(--ui-ink-on-accent);
}

/* Alternative: the hover lifts the plate rather than filling
   with accent, because the ink is already accent. */
.ui-mode--alternative .ui-btn:hover,
.ui-mode--alternative .ui-btn[data-state="hover"] {
  background: var(--ui-plate-up);
  color: var(--ui-ink);
}

/* Outline button. Not a Mode and not a State — it is how Forge's
   Default/Default button is drawn in Figma, and because Forge's
   ink IS its accent the generic rule below reproduces it exactly.
   Exposed as a modifier so the other three brands can reach the
   same shape deliberately rather than by accident. */
.ui-btn--outline {
  background: transparent;
  border-color: var(--ui-ink);
  color: var(--ui-ink);
}
.ui-btn--outline:hover,
.ui-btn--outline[data-state="hover"] {
  background: var(--ui-fill-active);
  border-color: var(--ui-fill-active);
  color: var(--ui-ink-on-accent);
}

.ui-btn:disabled,
.ui-btn[aria-disabled="true"] {
  background: transparent;
  border-color: var(--ui-ink-20);
  color: var(--ui-ink-40);
  cursor: not-allowed;
}

/* ------------------------------------------------------------
   5.2 TAGS   (Figma 40004583:75980)
   ------------------------------------------------------------
   Axes: Brand x State(Inactive, Active) x Filled(No, Yes) x
   Mode(Default, Alternative). 89x34 at "Category". Note there is
   no Inverted mode for tags in the file.

   Filled is a second axis, not a state: it decides whether the
   tag carries a plate at all. Filled=No is an outline chip,
   Filled=Yes a solid one. Both then read Inactive (dimmed) or
   Active (full).

     Filled=No   Inactive: transparent, 20% ink border, 60% ink
                 Active:   transparent, full ink border, full ink
     Filled=Yes  Inactive: Core plate, no border, 60% ink
                 Active:   Highlight plate, no border, full ink
     Alternative Inactive: Highlight plate, 60% accent ink
                 Active:   solid accent plate, knockout ink

   A tag is a filter control, so Active is a persistent selected
   state set in markup (aria-pressed), not a hover. Hover is a
   separate, lighter signal.
   ------------------------------------------------------------ */
.ui-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--s-w-xxs);
  background: transparent;
  color: var(--ui-ink-60);
  border-color: var(--ui-ink-20);
  border-radius: var(--radius-component);
  cursor: pointer;
  white-space: nowrap;
}
.ui-tag:hover { color: var(--ui-ink); border-color: var(--ui-ink-60); }

/* State = Active. */
.ui-tag[aria-pressed="true"],
.ui-tag--active {
  color: var(--ui-ink);
  border-color: var(--ui-ink);
}

/* Filled = Yes. The plate replaces the border: a solid chip with
   a hairline as well reads as two shapes at this size. */
.ui-tag--filled {
  background: var(--ui-plate);
  border-color: transparent;
}
.ui-tag--filled:hover { border-color: transparent; }
.ui-tag--filled[aria-pressed="true"],
.ui-tag--filled.ui-tag--active {
  background: var(--ui-plate-up);
  border-color: transparent;
}

/* Alternative mode. Rests on the bright plate, and Active fills
   with the accent — the one place a tag takes a colour plate. */
.ui-mode--alternative .ui-tag {
  background: var(--ui-plate-up);
  border-color: transparent;
  color: var(--c-accent-60);
}
.ui-mode--alternative .ui-tag[aria-pressed="true"],
.ui-mode--alternative .ui-tag--active {
  background: var(--ui-fill-active);
  color: var(--ui-ink-on-accent);
}

/* ------------------------------------------------------------
   5.3 LINKS   (Figma 40004583:77527)
   ------------------------------------------------------------
   Axes: Icon(yes, no, only) x State(Default, Active). No Brand
   and no Mode axis in the file — a link is ink and nothing else,
   so it re-skins through --ui-ink for free.

   State is a two-step ink change and nothing more: Default sits
   at 60% ink, Active at 100%. There is NO underline anywhere in
   the Figma set, and no accent — the emphasis device is opacity.
   That is a deliberate departure from _house.css's .bf-prose a,
   which underlines and accents links in DOCUMENTS. Body-copy
   links stay underlined for accessibility; these are navigation
   links, where position and the icon carry the affordance.

   Icon=yes puts a chevron AFTER the label (a disclosure); the
   Pills component puts the same chevron BEFORE it. Icon=only is
   the bare arrow, and it needs an accessible name.
   ------------------------------------------------------------ */
.ui-link {
  display: inline-flex;
  align-items: center;
  gap: var(--s-w-xxs);
  font-family: var(--font-sans);
  font-weight: var(--fww-body);
  font-size: var(--fsw-button);
  line-height: var(--lhw-button);
  letter-spacing: var(--tr-body);
  color: var(--ui-ink-copy-60);
  text-decoration: none;
  cursor: pointer;
  background: none;
  border: 0;
  padding: 0;
}
.ui-link:hover,
.ui-link[aria-current],
.ui-link--active { color: var(--ui-ink-copy); }

/* Icon = only. Square hit box so a bare arrow is still clickable
   at a sensible size. */
.ui-link--icon-only {
  justify-content: center;
  min-width: calc(var(--fsw-button) * 1.5);
}

/* The chevron rotates when its disclosure is open. This is the
   engine's only real motion, hence the reduced-motion guard. */
.ui-link__icon { flex: none; transition: transform var(--dur-fast) var(--ease); }
.ui-link[aria-expanded="true"] .ui-link__icon { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
  .ui-link__icon { transition: none; }
}

/* ------------------------------------------------------------
   5.4 PILLS   (Figma 40004583:76086)
   ------------------------------------------------------------
   Axes: Brand x Icon(Yes, No) x State(Inactive, Active). No Mode
   axis. Same 34px box as a tag, but a pill is a NAVIGATION or
   filter-group member rather than a metadata chip, and its
   Active state is the loud one in the whole kit: a solid accent
   plate with knockout ink.

   Inactive rests on the Base plate at 60% ink — one step quieter
   than a button, which rests at full ink. That difference is the
   whole point: a row of pills should read as one object with one
   member selected, not as six competing buttons.

   The chevron, when present, LEADS the label. Compare Links,
   where it trails. Not an inconsistency to normalise: a leading
   chevron reads as "this pill opens a menu", a trailing one as
   "this link expands in place".
   ------------------------------------------------------------ */
.ui-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--s-w-xxs);
  background: var(--ui-plate);
  color: var(--ui-ink-60);
  border-color: transparent;
  border-radius: var(--radius-component);
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}
.ui-pill:hover { color: var(--ui-ink); }

.ui-pill[aria-pressed="true"],
.ui-pill[aria-current],
.ui-pill--active {
  background: var(--ui-fill-active);
  color: var(--ui-ink-on-accent);
}

/* Outline pill — the Forge shape again, exposed for the same
   reason as .ui-btn--outline. */
.ui-pill--outline {
  background: transparent;
  border-color: var(--ui-ink-20);
}
.ui-pill--outline:hover { border-color: var(--ui-ink-60); }
.ui-pill--outline[aria-pressed="true"],
.ui-pill--outline[aria-current],
.ui-pill--outline.ui-pill--active { border-color: var(--c-accent); }

.ui-pill__icon { flex: none; }

/* A row of pills. The gap is XS, not the grid gutter: pills are one
   control that happens to have members, so they sit closer together
   than two independent blocks would. */
.ui-pill-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-w-xs);
}

/* The generic control row, for a handful of INDEPENDENT controls side
   by side — two buttons, a button and a link, a set of tags. S, one
   step wider than .ui-pill-group, because these are separate objects
   and the gap is what says so. Wraps; every control in the kit is
   34px tall, so a wrapped row stays on a rhythm. Use .ui-field-row
   for form fields (it takes the grid gutter and splits 50/50) and
   .ui-pill-group for a single-select set. */
.ui-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-w-s);
}

/* ------------------------------------------------------------
   5.5 SEARCH BAR   (Figma 40004583:77691)
   ------------------------------------------------------------
   Axes: Brand x State(Inactive, Active). The most interactive
   thing in the set and the one that genuinely could not exist in
   the print engines: Inactive is a 34x34 icon-only square,
   Active is an 89x34 field with the icon and a live input. The
   State axis is a WIDTH change, not a colour change.

   Both states are outline-only on all four brands — the one
   component in the kit with no filled variant at all. Radius is
   --radius-component, so it is a rounded square on Group and
   Partners and a full circle on Studios and Forge when collapsed,
   which is exactly what the Figma shows.

   Expansion is driven by :focus-within, so it works from the
   keyboard with no JS. 89px is the Figma width; it is a resting
   width, not a maximum — set --ui-search-w for a real search
   field. Collapse-on-blur means a typed query disappears from
   view, so a real implementation should pin it open once the
   input has content.
   ------------------------------------------------------------ */
.ui-search {
  --ui-search-w: 89px;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  gap: var(--s-w-xxs);
  width: var(--ui-control-h);
  min-height: var(--ui-control-h);
  padding-inline: var(--s-w-xxs);
  background: transparent;
  border: var(--ui-border-w) solid var(--ui-ink-20);
  border-radius: var(--radius-component);
  color: var(--ui-ink-60);
  overflow: hidden;
  transition: width var(--dur-base) var(--ease),
              border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}

/* State = Active. */
.ui-search:focus-within,
.ui-search--active,
.ui-search[data-state="active"] {
  width: var(--ui-search-w);
  padding-inline: var(--ui-control-px);
  border-color: var(--ui-ink);
  color: var(--ui-ink);
}

/* The collapsed state is a button; the expanded state is a field.
   Same element, so the toggle keeps its 24px icon box and simply
   stops being the only thing in the box. */
.ui-search__toggle {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--s-w-s);
  height: var(--s-w-s);
  padding: 0;
  background: none;
  border: 0;
  color: inherit;
  cursor: pointer;
}

.ui-search__input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  padding-inline: 0;
  background: none;
  border: 0;
  color: var(--ui-ink);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}
.ui-search:focus-within .ui-search__input,
.ui-search--active .ui-search__input,
.ui-search[data-state="active"] .ui-search__input { opacity: 1; }
.ui-search__input::placeholder { color: var(--ui-ink-40); opacity: 1; }

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

/* ------------------------------------------------------------
   5.6 FORM   (Figma 40004583:77238)
   ------------------------------------------------------------
   Axes: Type(Default, Selection) x State(Inactive, Active) x
   Brand. 265x34 in the file, but that is the Figma frame width —
   a field in use is full-width in its column, which is how the
   .ui-field wrapper is written.

   Outline-only in every state and on every brand: a form field
   never takes a plate. The State axis is a two-step border and
   ink change, the same two steps as Links —

     Inactive: 20% ink border, 40% ink placeholder
     Active:   full ink border, full ink value

   Active is :focus, not :hover. A field under the cursor has not
   been entered, and lighting up its border as if it had is the
   single most common form-UI error.

   Type=Selection is the same box with a trailing chevron. The
   native select arrow is suppressed and the chevron supplied as a
   child element so it takes --ui-ink like every other icon; a
   native arrow cannot be re-coloured reliably.
   ------------------------------------------------------------ */
.ui-field { display: block; width: 100%; }

.ui-field__label {
  display: block;
  margin-bottom: var(--s-w-xxs);
  font-family: var(--font-sans);
  font-weight: var(--fww-body-bold);
  font-size: var(--fsw-h5);
  line-height: var(--lhw-h5);
  color: var(--ui-ink-copy);
}

.ui-field__control {
  display: block;
  width: 100%;
  background: transparent;
  border-color: var(--ui-ink-20);
  border-radius: var(--radius-component);
  color: var(--ui-ink-copy);
}
.ui-field__control::placeholder { color: var(--ui-ink-40); opacity: 1; }
.ui-field__control:hover { border-color: var(--ui-ink-40); }
.ui-field__control:focus { border-color: var(--ui-ink-copy); outline: none; }

/* An input is inline-ish by default and a textarea is not, so the
   textarea needs the padding back on the block axis. */
.ui-field__control--area {
  padding-block: var(--s-w-xs);
  min-height: calc(var(--ui-control-h) * 3);
  line-height: var(--lhw-body);
  resize: vertical;
}

/* Type = Selection. */
.ui-field--select { position: relative; }
.ui-field--select .ui-field__control {
  padding-inline-end: calc(var(--ui-control-px) + var(--s-w-s));
  cursor: pointer;
}
.ui-field__chevron {
  position: absolute;
  right: var(--ui-control-px);
  bottom: calc((var(--ui-control-h) - var(--s-w-s)) / 2);
  width: var(--s-w-s);
  height: var(--s-w-s);
  color: var(--ui-ink-60);
  pointer-events: none;
}
.ui-field--select .ui-field__control:focus ~ .ui-field__chevron { color: var(--ui-ink); }

/* Field rows. The gutter is the gap, so a form row lines up with
   the page grid without being a grid itself. */
.ui-field-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--grid-gutter);
}
/* One field per line on a phone, two from the tablet breakpoint up.
   Same reasoning as the grid spans: a 160px field with a label above
   it and a placeholder inside it is not a field, it is a gesture. */
.ui-field-row > .ui-field { flex: 1 1 100%; }
@media (min-width: 640px) {
  .ui-field-row > .ui-field { flex: 1 1 calc(50% - var(--grid-gutter)); }
}

/* ------------------------------------------------------------
   5.7 NEWSLETTER SIGNIN   (Figma 40004583:76215)
   ------------------------------------------------------------
   Axes: Brand x State(Default, Filled, Hover) x Mode(Default,
   Alternative). 265x34 total: a 232px field butted against a
   34x34 submit button, sharing one border.

   This is the component that decides what Mode means, because it
   is the only one whose two modes are visibly the same two
   appearances in opposite order:

     Default mode      resting submit = Base plate, ink arrow
                       hover          = accent plate, knockout
     Alternative mode  resting submit = accent plate, knockout
                       hover          = Highlight plate, accent

   Read those four cells and the Alternative axis stops being
   mysterious: it is Default's hover promoted to the resting
   state, with the resting appearance demoted to hover.

   The three States are input content, not interaction: Default is
   an empty field showing a placeholder, Filled has a value, Hover
   is the submit button under the cursor. Filled needs no CSS — a
   field with a value simply shows it — so it is documented but
   not styled, and only Hover is a real rule.

   The submit button inherits --radius-component on its outer
   corners only, so on Studios and Forge it reads as the rounded
   end-cap of the bar rather than a pill floating inside it. That
   is the whole reason this is one bordered wrapper containing two
   borderless children instead of two adjacent controls.
   ------------------------------------------------------------ */
.ui-signup {
  box-sizing: border-box;
  display: flex;
  align-items: stretch;
  width: 100%;
  max-width: 265px;
  min-height: var(--ui-control-h);
  background: transparent;
  border: var(--ui-border-w) solid var(--ui-ink-20);
  border-radius: var(--radius-component);
  overflow: hidden;
}
.ui-signup:focus-within { border-color: var(--ui-ink); }

.ui-signup__input {
  flex: 1 1 auto;
  min-width: 0;
  background: none;
  border: 0;
  color: var(--ui-ink);
  border-radius: 0;
}
.ui-signup__input::placeholder { color: var(--ui-ink-40); opacity: 1; }

.ui-signup__submit {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--ui-control-h);
  padding: 0;
  background: var(--ui-plate);
  border: 0;
  color: var(--ui-ink);
  cursor: pointer;
}
.ui-signup__submit:hover,
.ui-signup__submit[data-state="hover"] {
  background: var(--ui-fill-active);
  color: var(--ui-ink-on-accent);
}

/* Alternative mode: resting and hover swap, exactly as above. */
.ui-mode--alternative .ui-signup__submit {
  background: var(--ui-fill-active);
  color: var(--ui-ink-on-accent);
}
.ui-mode--alternative .ui-signup__submit:hover,
.ui-mode--alternative .ui-signup__submit[data-state="hover"] {
  background: var(--ui-plate-up);
  color: var(--ui-ink);
}

.ui-signup__icon { flex: none; }

/* ------------------------------------------------------------
   5.8 ACTIVE PAGE NUMBER   (Figma 40004583:77634)
   ------------------------------------------------------------
   Axes: State(1, 2, 3) x Numbers(No, Yes). No Brand and no Mode
   axis — it is pure ink, so it re-skins for free.

   Two forms of the same control, and the file ships both at
   exactly 52x14: three 14px dots on a 19px pitch (a 5px gap, the
   XXS step), or three numerals in the same 52px. Numbers=No is a
   carousel indicator, Numbers=Yes a pager.

   State is which of the three is current, and the treatment is
   the ink ramp again: current at 100%, the rest at 60%. The dot
   form fills the current dot and outlines the others — a filled
   dot and a ring are distinguishable at 14px, whereas two rings
   at different opacities are not, which is why the dot form does
   not simply reuse the numeral form's opacity step.

   14px is far below any sane hit target, hence the coarse-pointer
   rule in §4. Mark up as a nav with aria-current on the current
   item; the dots need accessible names because a filled circle
   is not a page number.
   ------------------------------------------------------------ */
.ui-pager {
  display: inline-flex;
  align-items: center;
  gap: var(--s-w-xxs);
}

.ui-pager__item {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--s-w-xs);
  padding: 0;
  background: none;
  border: 0;
  font-family: var(--font-sans);
  font-weight: var(--fww-body);
  font-size: var(--fsw-button);
  line-height: var(--lhw-button);
  letter-spacing: var(--tr-body);
  color: var(--ui-ink-60);
  cursor: pointer;
  text-decoration: none;
  font-variant-numeric: tabular-nums;
}
.ui-pager__item:hover { color: var(--ui-ink); }
.ui-pager__item[aria-current],
.ui-pager__item--active { color: var(--ui-ink); }

/* Numbers = No. The dot form. */
.ui-pager--dots .ui-pager__item {
  width: var(--ui-dot);
  height: var(--ui-dot);
  min-width: 0;
  flex: none;
  border: var(--ui-border-w) solid var(--ui-ink-60);
  border-radius: var(--radius-pill);
  font-size: 0;
  line-height: 0;
}
.ui-pager--dots .ui-pager__item:hover { border-color: var(--ui-ink); }
.ui-pager--dots .ui-pager__item[aria-current],
.ui-pager--dots .ui-pager__item--active {
  background: var(--ui-ink);
  border-color: var(--ui-ink);
}

/* ------------------------------------------------------------
   5.9 CARDS   (Figma 15324:4332 — set A 40004510:9721,
                set B "Colorful" 40004510:15316)
   ------------------------------------------------------------
   The first COMPOSITE in this engine. Everything in 5.1–5.8 is a
   single 34px control; a card is a container that holds three of
   the atoms already built above (a Tag, a Link, and the H4/Body
   type roles) on a plate of its own. So most of this section is
   not new painting — it is a plate, a media well, and one rule
   about where the atoms sit.

   Axes: Brand x Card(Image, Icon, Text) x State(Default, Hover).
   ⚠ There is NO Mode axis in the file — same as Links, Pills,
   Search, Form and Pager (see divergence 6). Cards still re-skin
   under .ui-mode--* because everything below reads --ui-*, but
   that behaviour is an extrapolation, not a transcription.

   Drawn sizes: Image and Icon are 353x377, Text is 353x300.
   353 is not an arbitrary width — it is exactly FOUR desktop
   columns (4 x 73.33 + 3 x 20 = 353.3), which is why a card row
   is written as .ui-grid with --span:4 and needs no grid of its
   own. See the content budget in UI-COMPONENTS.md.

   ── THE PLATE STACK ──────────────────────────────────────────
   This is the component that finally uses all three background
   plates at once, and it uses them as DEPTH, exactly as
   tokens.css describes it (plates get lighter as they come
   forward):

     card    Background Highlight  → --ui-plate-up   frontmost
     well    Background Core       → --ui-plate      recessed
     tag     Background Base       → --ui-ground     punched back
                                                     to page level

   Note what that means for the resting state: a card ALREADY
   sits on the brightest plate. There is nowhere further forward
   for it to go, which is why the Hover state in the file does not
   lift the card — see the State note below. It also means the
   card is the one component that does NOT need divergence 2's
   Base→Core correction: Highlight is already a visible step off
   the page ground on all four brands.

   ⚠ The well is the weak link, and it is a TOKEN problem rather
   than an engine one. Two of the four brands set Background Core
   and Background Highlight to the SAME value, so on those two an
   empty well is invisible against the card. The Figma works
   around it per brand — the two affected cells fill the well with
   Background Base at 40% and at 50% opacity respectively, two
   hand-tuned alphas that no token expresses. This engine cannot
   see brands, so it takes the plate the other two cells use
   (Core) and accepts that an EMPTY well does not read on the
   collapsed pair. A well in production holds a photograph or an
   illustration, which is the whole reason it exists, so the cost
   is confined to a placeholder. The fix belongs in the token
   source: give every brand three distinct plates.

   ── STATE ────────────────────────────────────────────────────
   Hover is far quieter than anything in 5.1–5.8, and it is quiet
   on purpose. Read across the Default/Hover rows of set A and
   exactly ONE thing moves: the Tag goes from its Base plate with
   a 60% ink label to a solid accent plate with knockout ink. The
   card plate, its border, the title, the body and the arrow are
   pixel-identical between the two states — verified layer by
   layer on the Image and Icon cells, not inferred.

   That reads as a deliberate consequence of the plate stack: the
   card cannot lift, so the accent has to appear somewhere, and
   the tag is the one element on the card that already had a
   plate to swap. It is a weak affordance for a whole-card link
   and it is worth a design conversation, but it is what the file
   says, so it is what this does.

   ── COLORFUL (set B) ─────────────────────────────────────────
   Set B is NOT a fourth card type. Its Default cells are
   byte-for-byte the Text card's Default cells (compare
   40004510:15317 with 40004510:9848 — same plate, same radius,
   same 150px/20px text padding, same outline tag, only the tag
   label differs). The whole of "Colorful" lives in the Hover
   state, and there it does the one thing set A refuses to do:
   it fills the ENTIRE card with the accent and switches the card
   to knockout ink. Title 100%, body 60%, tag inverted to a
   knockout plate with an accent label.

   So: Colorful is a second, louder HOVER for the Text card, not a
   new shape. That is why it is a modifier here and not a type.

   Set B exists for two of the four brands only, and the file does
   not say why. What can be said from the tokens: on a brand whose
   accent is bound to its ink an accent-filled card carries no
   colour at all — it is just an inverted card — so Colorful
   degenerates there, which accounts for one of the two absences.
   The other absence tracks the same brand split as the radius
   pair: the two brands that round hard get it, the two that stay
   near-square do not, and tokens.css is explicit that in this
   family "accent is a signal, not a decoration". A whole card of
   accent is decoration. This engine cannot enforce a per-brand
   restriction — .ui-card--colorful is brand-neutral CSS and will
   render on anything — so if that restriction is real it has to
   live in the brand packs or in validate.mjs.
   ------------------------------------------------------------ */

/* The card. All nine private tokens are declared here rather
   than in §2 because they are card-local: nothing outside this
   section reads them, and keeping them off :root means the
   section can be lifted out whole.

   Reading --ui-card-plate in the same block that declares it is
   the pattern the file header warns about, and here it is the
   benign direction — this rule wants the value it just declared.
   Every REASSIGNMENT below (hover, --colorful) is on a different
   selector and is consumed by a DESCENDANT, which is the same
   trick §2 uses to keep the Mode classes from reading
   themselves. */
.ui-card {
  /* 5px card padding. XXS is the only step this fine, and it
     exists on web precisely for this kind of inset frame: the
     card is a mount, the well is the mounted thing. */
  --ui-card-pad:   var(--s-w-xxs);
  /* 300px = XXL + L. The drawn Text-card height, and the floor
     for all three types in the file. */
  --ui-card-min-h: calc(var(--s-w-xxl) + var(--s-w-l));

  --ui-card-plate:     var(--ui-plate-up);
  --ui-card-well:      var(--ui-plate);
  --ui-card-edge:      var(--ui-ink-20);
  /* The copy ink, not the plain ink. A card is a title, a paragraph
     and a chip — running text on a plate — so it belongs on the
     same ramp as every other body region. On three brands the two
     tokens are identical; on the fourth this is 2.77:1 → 15.00:1
     for the title and 1.96:1 → 5.15:1 for the body. See §2 and
     divergence 14. */
  --ui-card-ink:       var(--ui-ink-copy);
  --ui-card-ink-dim:   var(--ui-ink-copy-60);
  --ui-card-tag-plate: var(--ui-ground);
  --ui-card-tag-ink:   var(--ui-ink-60);

  box-sizing: border-box;
  position: relative;   /* containing block for the tag */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* justify-end plus the min-height IS the Text card's layout.
     The file also puts 150px of top padding on the text
     container, which is redundant: with content bottom-aligned
     inside a 300px box the title cap lands at 193px from the top
     either way (measured both ways, they agree exactly). Dropping
     the 150px means a card with a long title grows downward
     instead of carrying 150px of dead air, which is the right
     behaviour in a flow medium. */
  justify-content: flex-end;
  width: 100%;
  min-height: var(--ui-card-min-h);
  padding: var(--ui-card-pad);
  background: var(--ui-card-plate);
  color: var(--ui-card-ink);
  /* --radius-button, NOT --radius-component. The token source
     names this value "Buttons/Cards Radius" and the file proves
     it: the near-square brands draw the card at 4px, where the
     two radii are equal and tell you nothing, but the rounded
     brands draw it at 30px — the BUTTON value, not the 20px
     component value. §5.1's claim that the button is the only
     component using --radius-button is now out of date. */
  border: var(--ui-border-w) solid var(--ui-card-edge);
  border-radius: var(--radius-button);
  /* Every card in the file is a whole-card link, so the card is
     normally an <a>: text-decoration kills the underline the
     anchor would otherwise put through the title, and the cursor
     is stated so a card built on a <div> or an <article> with a
     scripted activation behaves like the anchor version. A card
     that is genuinely not interactive should not use this class. */
  text-decoration: none;
  cursor: pointer;
}

/* ⚠ §3.1's owl selector reaches TWO levels down —
   `.ui-grid > * > * + *` — so any component dropped straight into
   a grid cell gets 40px of margin between its own children. §3.1
   says component internals are never touched; that is true of
   .ui-page and .ui-flow but not of .ui-grid. A card in a grid
   cell had its text block shoved 40px off its media well until
   this reset went in. Same specificity as §3.1's rule (one class
   plus universals), so it wins on source order alone — which is
   fine inside ui-core.css and fine as a fragment loaded after it.
   .ui-signup has the same exposure and no reset. */
.ui-card > * + * { margin-top: 0; }

/* --- Mode = Inverted ---------------------------------------
   EXTRAPOLATION, flagged as one. There is no Mode axis for cards
   in the file, so nothing here is transcribed — but the naive
   version (let the three plate names fall where §2 puts them) was
   wrong in the render, twice over, and both faults are worth
   recording because they are properties of translucent plates
   rather than of cards:

   1. The inverted plates are TINTS, not colours, so a well
      painted in --ui-plate (20%) sitting inside a card painted in
      --ui-plate-up (40%) composites to 52% and comes out LIGHTER
      than the card it is recessed into. The depth order inverts.
   2. A card resting on the 40% plate is the brightest surface in
      the mode, and on a brand whose ink is a saturated mid-tone
      that is where the inverted ink stops reading: the title
      measured 2.14:1 on 40% and 2.71:1 on 20%. §2 is explicit
      that an inverted component's RESTING fill is the 20% tint;
      the card was the one component not using it.

   So inverted mode reassigns the stack down one step and sends
   the well to the opaque inverted ground — a window punched back
   to page level, which is what Background Base means in the other
   two modes anyway. Depth order and §2's resting-plate rule are
   both preserved. The contrast is still a brand exception on one
   brand, and a worse one than the already-declared
   web/inverted-section failure.

   3. The tag was the third fault and it is the reason for the
      hairline. Promoting it to the 40% plate (the obvious answer,
      since 40% reads against both a 20% card and an opaque well)
      composited to 52% and left its 60%-ink label at 2.36:1 on
      three brands and 1.45:1 on the fourth. So the tag KEEPS the
      opaque ground it uses in the other two modes, where the same
      label measures 5-6:1 — and because that makes it the same
      colour as the well it sits on, it takes a 20%-ink hairline to
      draw its edge. That is an invention, stated as one; §5.2
      already owns a border slot on the tag, so it costs one
      declaration and buys a legible label. */
.ui-mode--inverted .ui-card {
  --ui-card-plate: var(--ui-plate);
  --ui-card-well:  var(--ui-ground);
}
.ui-mode--inverted .ui-card .ui-card__tag { border-color: var(--ui-ink-20); }

/* --- The media well ---------------------------------------
   343 x 245 in the file, on both the Image and the Icon card —
   the same well, differently filled. Expressed as an aspect
   ratio rather than a fixed 245px height because this is a fluid
   medium and a card in a narrow column would otherwise letterbox
   its image. 343/245 is exactly 7/5, and at the drawn card width
   it computes to 244px against the file's 245.

   The inner radius is derived, not measured: outer radius minus
   the 5px mount. The file hand-sets 2px on the near-square
   brands (where the derivation gives 0) and 26px on the rounded
   ones (where it gives 25) — a 1-2px difference, which is the
   price of one rule instead of two magic numbers. */
.ui-card__media {
  position: relative;
  box-sizing: border-box;
  display: block;
  width: 100%;
  aspect-ratio: 7 / 5;
  flex: none;
  background: var(--ui-card-well);
  border-radius: max(0px, calc(var(--radius-button) - var(--ui-card-pad)));
  overflow: hidden;
}

/* Card = Image. The image bleeds to the well's edges and is
   cropped, never letterboxed: the well's ratio is the designed
   ratio and the photograph gives way, not the layout. The markup
   supplies the file; nothing here names one. */
.ui-card__image {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* No radius of its own — overflow:hidden on the well already
     clips it, and a second radius would double the corner. */
}

/* Card = Icon. The same well, padded by M and with the glyph sat
   on the FLOOR of it rather than centred — 40 + 165 + 40 = 245,
   which is how the icon well comes out the same height as the
   image well. The glyph is a per-brand isometric illustration
   asset (it carries accent, plate and ink inside the SVG), so it
   is supplied by the markup and only boxed here. */
.ui-card--icon .ui-card__media {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: var(--s-w-m);
}
.ui-card__glyph {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
}

/* Card = Text. No declarations, and that is the finding rather
   than an omission: min-height + justify-end above already
   reproduce the drawn 300px text card exactly, so the modifier
   carries nothing. It stays in the API because the markup should
   still state which of the three types it is, and because it is
   where a future divergence between the text card and the other
   two has to land. */

/* --- The text block ---------------------------------------
   S padding, S gap. The only thing worth noting is that the
   file's paddings are measured to CAP and BASELINE, not to the em
   box: every text node in every cell is cap-trimmed. Without the
   trim on the two type roles below the block reads about 4px
   taller at each end and the 20px gap opens to 23px. */
.ui-card__text {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: var(--s-w-s);
  width: 100%;
  padding: var(--s-w-s);
}

/* Title row: the H4 takes the space, the arrow is pinned right.
   min-height is the control height because in the file this row
   IS a 34px Link sitting beside the title, and dropping the Link
   for a bare icon must not change the row's height. */
.ui-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-w-xs);
  width: 100%;
  min-height: var(--ui-control-h);
}

/* Title. Wears .ui-h4 in the markup, so family / size / leading
   come from §3.2 and only the colour and the flex behaviour are
   set here.

   ⚠ WEIGHT DISCREPANCY, reported not patched. The Figma style
   bound to every card title is General/H4, and its definition in
   the file is the body face at weight 300 (Light). §3.2's .ui-h4
   is --fww-body-bold, i.e. 500. Card titles therefore render one
   step heavier here than in the file. Forking the weight inside a
   component would leave the engine in two minds about what H4 is,
   so the role is left alone and the discrepancy is written down
   instead — it needs settling in §3.2 or in the style guide, not
   here. */
.ui-card__title {
  display: block;
  flex: 1 1 auto;
  min-width: 0;
  color: var(--ui-card-ink);
  overflow-wrap: break-word;
  text-box: trim-both cap alphabetic;
}

/* Body. Wears .ui-body; 60% ink, which is the Body role's --dim
   step. A card body is always secondary to its title. */
.ui-card__body {
  color: var(--ui-card-ink-dim);
  text-box: trim-both cap alphabetic;
}

/* The arrow. This is the Links component at Icon=only, and the
   file draws it at FULL ink rather than Links' resting 60% — the
   exported glyph is solid Primary 100 in every cell — so it reads
   as an Active link, matching the title beside it rather than
   sitting a step behind it.

   It keeps that component's 34px box even though the glyph is
   ~15px, which is why it is a wrapper span in the markup and not
   the <svg> itself: measured flush, a bare icon put its glyph 26px
   from the card's right edge against the file's 34.5px, because
   the file's 34px Link box carries ~9.5px of optical padding
   around the glyph. Reproducing the box reproduces the alignment
   and needs no magic number.

   ⚠ A span, not an <a>: the card is already the link. */
.ui-card__arrow {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--ui-control-h);
  height: var(--ui-control-h);
  color: var(--ui-card-ink);
}

/* --- The tag ---------------------------------------------
   In the file this is an instance of the Tags component, so the
   markup wears .ui-tag .ui-tag--filled and this rule only moves
   it and re-plates it. Two-class selector on purpose:
   `.ui-mode--alternative .ui-tag` in §5.2 is also (0,2,0), and a
   single-class selector here would lose the plate to it and the
   tag would vanish into the card in Alternative mode.

   ⚠ Must be a <span>, not a <button>: the card is the link, and a
   button inside an anchor is invalid HTML and unreachable.

   The tag's own radius stays --radius-component from §5.2 even
   though the file draws the in-card instance at the 30px cards
   radius — on a 34px chip anything over 17px is a full pill, so
   the two values are visually identical here.

   Offset: S from the card's OUTER edge, which is what the file
   measures (19-20px across the cells). The containing block for
   an absolutely positioned child is the padding box, whose edge
   is already one border-width in, so the border has to come back
   off — the 5px padding does not, because it is inside that box. */
.ui-card .ui-card__tag {
  position: absolute;
  inset-block-start: calc(var(--s-w-s) - var(--ui-border-w));
  inset-inline-start: calc(var(--s-w-s) - var(--ui-border-w));
  background: var(--ui-card-tag-plate);
  border-color: transparent;
  color: var(--ui-card-tag-ink);
}

/* --- State = Hover ---------------------------------------
   The whole of set A's hover, and it is two token
   reassignments consumed by the tag. Nothing on the card itself
   moves. :hover and [data-state="hover"] only — NOT
   :focus-visible, for the reason given in §5.1: firing the hover
   appearance on focus makes a keyboard user think they have
   already activated the card. */
.ui-card:hover,
.ui-card[data-state="hover"] {
  --ui-card-tag-plate: var(--ui-fill-active);
  --ui-card-tag-ink:   var(--ui-ink-on-accent);
}

/* --- Colorful ---------------------------------------------
   Set B's hover: the accent becomes the GROUND of the card and
   every ink on it goes knockout.

   The pair used is --ui-fill-active / --ui-ink-on-accent rather
   than --c-accent / the inverted ramp, and that choice is
   load-bearing rather than stylistic. The file knocks the title
   and body out in Inverted Primary and the tag out in Background
   Core — two different knockout tokens on one surface, which is the same slip divergence 4 already flags
   between two of the button cells. Unifying on
   --ui-ink-on-accent fixes that AND survives the Mode axis: §2
   keeps that token paired with --ui-fill-active, so under
   .ui-mode--inverted, where the fill becomes the solid inverted
   ink, the label flips to the brand ink instead of staying on the
   inverted ink and painting the label in the exact colour of its
   own plate. Reaching for --ui-ink-inv here would have done
   precisely that, and the proof render is where it showed up.

   The 60% step for the body is mixed rather than declared: there
   is no --ui-ink-on-accent-60 in §2 and inventing one at :root
   for a single card would be the wrong place to put it. Same
   idiom as §2's --ui-ink-inv-40.

   The border goes transparent rather than away, so the card does
   not change size. The file is split on this — one cell keeps an
   accent-tinted 20% border, which is invisible on an accent plate
   by construction, and the other has no border at all — so
   neither cell is expressing an intention worth preserving. A
   20%-INK hairline (which is what --ui-ink-20 resolves to) left
   on a saturated plate reads as a dirty edge on any brand whose
   ink is a dark neutral. */
.ui-card--colorful:hover,
.ui-card--colorful[data-state="hover"] {
  --ui-card-ink:       var(--ui-ink-on-accent);
  --ui-card-ink-dim:   color-mix(in srgb, var(--ui-ink-on-accent) 60%, transparent);
  --ui-card-tag-plate: var(--ui-ink-on-accent);
  --ui-card-tag-ink:   var(--ui-fill-active);
  background: var(--ui-fill-active);
  border-color: transparent;
  color: var(--ui-ink-on-accent);
}
/* The well has no place on a Colorful card in the file (set B is
   drawn on the Text card only), but the modifier is orthogonal to
   the type, so if someone combines them the well must not stay a
   background plate sitting in the middle of a solid accent field.
   A translucent knockout wash keeps the recess without
   introducing a colour. */
.ui-card--colorful:hover .ui-card__media,
.ui-card--colorful[data-state="hover"] .ui-card__media {
  background: color-mix(in srgb, var(--ui-ink-on-accent) 20%, transparent);
}

/* --- Focus -----------------------------------------------
   §4's ring, with one substitution: the 2px gap layer is painted
   in the CARD's plate, not --ui-plate, because a card rests on
   Highlight and a Core-coloured gap would read as a hairline
   rather than as clearance. Same three layers otherwise, for the
   same pale-accent reason. */
.ui-card:focus-visible {
  outline: 3px solid var(--c-accent);
  outline-offset: 2px;
  box-shadow: 0 0 0 2px var(--ui-card-plate),
              0 0 0 6px var(--ui-ink-20);
}

/* --- Motion ----------------------------------------------
   Colour crossfades only, at --dur-fast like the rest of the kit.
   The tag and the two type roles are listed separately because
   they change through inherited custom properties, and a custom
   property change does not transition on its own — the element
   that PAINTS the value has to carry the transition. */
.ui-card,
.ui-card__media,
.ui-card__tag,
.ui-card__title,
.ui-card__body,
.ui-card__arrow {
  transition: background-color var(--dur-fast) var(--ease),
              border-color     var(--dur-fast) var(--ease),
              color            var(--dur-fast) var(--ease);
}
@media (prefers-reduced-motion: reduce) {
  .ui-card,
  .ui-card__media,
  .ui-card__tag,
  .ui-card__title,
  .ui-card__body,
  .ui-card__arrow {
    transition: none;
  }
}


/* ============================================================
   6. COMPOSED COMPONENTS
   ------------------------------------------------------------
   §5 is atoms: eight controls, each 34px tall, each one thing.
   This section is the furniture that holds them. A navbar is a
   plate carrying links, a button and a search field; a footer is
   three bands carrying a link nav and the signup. Almost nothing
   here paints anything new — the work is layout, and the discipline
   is to reuse §5 rather than restyle it.

   One rule learned the hard way in both of these: a selector in a
   composed component out-specifies the atom it contains. If §6
   writes `.ui-nav .ui-btn { background: X }` it silently kills
   §5.1's hover, its Alternative lift and its print flattening. Every
   override below is scoped as narrowly as it can be, and where an
   atom's own state rules must keep winning the override carries
   `:not(:hover):not([data-state="hover"])`.

   These are also the first things in the engine that need
   vocabulary §5 never had: a full-bleed bar that is NOT on the
   page grid, a breakpoint that swaps one whole arrangement for
   another rather than resizing it, and a disclosure that has to
   open with no JavaScript.
   ============================================================ */

/* ------------------------------------------------------------
   6.1 NAVBAR
   ------------------------------------------------------------
   Figma 40004510:15056  "Navbar (Desktop + Tablet)"  1440x75
     Brand x Mode(Default, Inverted) — 8 variants. The Brand axis
     is not implemented here (see the file header), so its four
     values are listed by node id in file order only:
       Default   15057 · 15119 · 15181 · 15243
       Inverted  15088 · 15150 · 15212 · 15274

   Figma 40004560:29496  "Mobile Navbar"  393x75
     Brand x State(Default, Closed Button) x Mode(Default,
     Inverted) — 16 variants, same four brands in the same order:
       Default  / Default        29495 · 29888 · 30700 · 30992
       Closed Button / Default   29497 · 29914 · 30756 · 31048
       Default  / Inverted       30153 · 30179 · 30728 · 31020
       Closed Button / Inverted  30205 · 30231 · 30784 · 31076
     Brand-by-brand notes, including one source slip in this
     matrix, are in UI-COMPONENTS.md — that document may name a
     brand and this file may not.

   ── WHAT THE FILE ACTUALLY DRAWS ─────────────────────────────
   Two frames, three layouts, and only two of the three axes the
   §5 components use. There is NO State axis on the desktop frame
   at all: no hover, no scrolled state, no open state. The mobile
   frame's State axis has exactly two values and they are not an
   open/closed pair — see the note on "Closed Button" below.

   Desktop/tablet, per get_design_context on 40004510:15057:
     bar      flex, space-between, padding 20, fill Background CORE
     logo     a 151x35 "Logo Placeholder" holding a 36.3x35 mark
     links    flex, gap 40, flush right: 4 Links then 1 Button
     Link     34px tall, 5px padding, 14px Button type,
              Primary 60 at rest and Primary 100 for the current
              item ("Strategies" is the 100 in every variant)
     Button   "Login", 15px inline padding, fill Background BASE,
              label Primary 60, radius = Buttons/Cards Radius
   Mobile, per 40004560:29495:
     bar      the same, but padding 10 inline / 20 block
     right    a 34x34 "Menu Button" — the same node as Login with
              a 24x24 burger in place of the label

   The bar is 75px because it is 35px of logo between 20px of
   padding. It is not a magic number and it does not need a token
   of its own; --ui-nav-h below computes it.

   ── THE BAR IS NOT ON THE PAGE GRID ──────────────────────────
   This is the single most useful thing the frame name gives away.
   "Desktop + Tablet" is ONE frame for two breakpoints, and the
   reason it can be is that the bar's inset is its own 20px
   padding — not --grid-margin, which collapses from 170px to 20px
   between those two frames (§1). Put the navbar on the grid and
   it would have to be redrawn twice; keep it full-bleed with a
   fixed padding and one drawing covers both. So there is no
   tablet rule in this section, because the source has no tablet
   variant to transcribe. The only thing that moves between the
   two is nothing at all.

   Consequence worth stating: the logo is 20px from the viewport
   edge while the page content below it starts 170px in on a 1440
   frame. That is deliberate in the source, not a mistake, and it
   is why .ui-nav must be a sibling of .ui-page rather than a
   child of it.

   ── THE PLATE MODEL ──────────────────────────────────────────
   The navbar is the first component that is itself a plate with
   other plates on top of it, and the Figma is precise about the
   relationship:

     Mode = Default   bar = Background Core
                      controls on it = Background Base
     Mode = Inverted  bar = NO FILL AT ALL
                      controls on it = Inverted Primary 20

   Read that carefully, because it inverts §2's usual direction.
   Everywhere else in the engine a control sits one step FORWARD
   of its surface (--ui-plate over --ui-ground). Here the bar has
   taken the forward step, so its controls have to step BACK to
   stay visible: Core bar, Base button. Hence --ui-nav-control,
   which is --ui-ground in Default and --ui-plate in Inverted.

   Why not simply shift the whole pair up one — Highlight bar,
   Core controls — which is what §2's DEPARTURE note does for the
   atoms? Because two of the four brands collapse Highlight onto
   Core (they set --c-bg-high to the same value as --c-bg-core),
   so a Highlight bar with Core controls is one flat colour on half
   the family and the CTA disappears. Base and Core differ on all
   four. Measured in the brand packs, not assumed.

   In Inverted the bar has no fill because an inverted navbar is
   meant to sit ON something — the ink ground of the section
   below it, a dark hero, a photograph — and the 20% inverted wash
   on its controls is what keeps them readable there (§2). Two
   cases follow, and they need different rules:

     .ui-mode--inverted on an ANCESTOR — the bar is transparent
       and the ancestor's ground shows through. What the file
       draws.
     .ui-mode--inverted on .ui-nav ITSELF — there is no ancestor
       ground to show, so the bar paints --ui-ground (the brand
       ink) and becomes the dark thing. Not in the file; without
       it a standalone inverted bar renders as a 20% wash of the
       inverted ink over the ordinary page ground, with inverted
       ink as text on top of it — illegible. Caught in the proof
       render.

   ── ALTERNATIVE MODE IS NOT IN THE FILE ──────────────────────
   The navbar's Mode axis has two values, Default and Inverted.
   There is no Alternative variant of the navbar anywhere in the
   source. Nothing here special-cases it, so an
   .ui-mode--alternative navbar gets the Default plates with
   accent ink — which is what Alternative means (§2: ink only) and
   is almost certainly right, but it is an extrapolation and not a
   transcription. Same standing as divergence 6 in
   UI-COMPONENTS.md.

   ── THE BREAKPOINT ───────────────────────────────────────────
   640px, which is §3's tablet breakpoint. The navbar does not get
   a breakpoint of its own: below 640 it is logo + toggle, at 640
   and above it is logo + links + CTA. The frames say 393 and 834
   and there is nothing between them, so this is the same
   judgement §3 already made and it is better to make it once.

   Measured in Chromium rather than reasoned about, because the
   arithmetic is easy to get wrong: the drawn set — the 36px
   logomark, four links and the CTA — needs 571px, so it fits from
   640 with 69px of slack. What does NOT fit is a full LOCKUP in
   the logo slot: at 35px tall the house lockups are 171 to 344px
   wide, and 171px + four links + CTA needs 706px, so between 640
   and 706 that combination overflows. The whole budget table is in
   UI-COMPONENTS.md. Past the budget the row overflows the bar
   rather than wrapping, exactly as a long button label breaks a
   row in §5.1 — the budget is the author's to keep.
   ------------------------------------------------------------ */
.ui-nav {
  /* The logo's drawn height, and the only measured value in this
     section. 35px is not a spacing step and must not become one —
     it is an asset height, like --ui-dot is a glyph size. Change
     it and the bar changes height with it, which is the correct
     dependency: the bar is as tall as its logo plus its padding. */
  --ui-nav-logo-h: 35px;
  --ui-nav-h: calc(var(--ui-nav-logo-h) + var(--s-w-s) * 2);   /* 75px */

  /* The icon box in the menu toggle. 24px is the sprite's own
     drawing grid — the burger is drawn 14.762 x 8 on a 24-unit
     square and the source places it in a 24x24 box at scale 1.0.
     Sizing the box is how you size these icons; rescaling the
     symbol is what breaks stroke weight across the set. */
  --ui-nav-icon: 24px;

  /* The plate for a control sitting ON the bar: one step BACK
     from the bar, not forward. See the plate model above. */
  --ui-nav-control: var(--ui-ground);

  box-sizing: border-box;
  display: flex;
  align-items: center;
  /* space-between is what the source uses: logo hard left, links
     hard right, and nothing in the middle. The gap is a FLOOR, not
     a drawn measurement — it is what stops a wide logo touching the
     first link when the row runs out of slack (see the budget). */
  justify-content: space-between;
  gap: var(--s-w-s);
  width: 100%;
  min-height: var(--ui-nav-h);
  /* Mobile-first: 10px inline on the phone frame, 20px from 640
     up. Note this is the mobile GUTTER, not the 30px mobile
     margin — the bar is not on the grid, so it does not use the
     grid's margin at any width. */
  padding-block: var(--s-w-s);
  padding-inline: var(--s-w-xs);
  background: var(--ui-plate);
  color: var(--ui-ink);
  font-family: var(--font-sans);
  font-weight: var(--fww-body);
  font-size: var(--fsw-body);
  line-height: var(--lhw-body);
  letter-spacing: var(--tr-body);

  /* Named so the mobile panel can be positioned against the
     bottom of the bar wherever the bar happens to be, and SCOPED
     so that two navbars on one page do not both resolve to the
     last one. Both are progressive: the panel has a working
     fallback in 6.1.3 for engines without anchor positioning. */
  anchor-name: --ui-nav-anchor;
  anchor-scope: --ui-nav-anchor;
}

/* Mode = Inverted, the case the file draws: an ancestor is
   inverted and the bar carries no fill of its own. */
.ui-mode--inverted .ui-nav {
  background: transparent;
  --ui-nav-control: var(--ui-plate);
}

/* Mode = Inverted on the bar itself. Declared AFTER the
   descendant rule and at the same specificity so it wins, which
   is the only ordering that works: .ui-nav.ui-mode--inverted and
   .ui-mode--inverted .ui-nav are both (0,2,0).

   Reading --ui-plate here is safe even though .ui-mode--inverted
   declares it on this same element — we are reading a name we do
   not write, so there is no self-substitution (the failure mode
   the whole --ui-* layer exists to avoid; see the file header). */
.ui-nav.ui-mode--inverted {
  background: var(--ui-ground);
  --ui-nav-control: var(--ui-plate);
}

/* Opt-in only. The source does not draw a sticky bar, a scrolled
   state or a shadow, so this is a modifier and not the default.
   ⚠ Two things to know before using it: an INVERTED bar has no
   fill, so sticky + inverted means page content scrolls visibly
   through the bar — pin the Default bar or give it a ground. And
   the engine has no z-index scale; 10 is a floor chosen here, not
   a token, and a page with its own stacking layers should raise
   it rather than fight it. */
.ui-nav--sticky {
  position: sticky;
  top: 0;
  z-index: 10;
}

/* ------------------------------------------------------------
   6.1.1 THE LOGO SLOT
   ------------------------------------------------------------
   The source's "Logo Placeholder" is a 151x35 frame with a 10px
   corner radius and no fill, holding a 36.3x35 logomark. Two of
   those three numbers are not transcribed, on purpose:

     · 151px is a placeholder width, not a logo. A real lockup is
       whatever width its wordmark makes it. The HEIGHT is the
       real measurement — it sets the bar height — so this slot
       fixes the height and lets the width be content.
     · the 10px radius is on a frame with no fill, so it draws
       nothing. It is also not a brand radius: the Radius frame
       gives 4px or 20px, never 10. Same 10px appears on every
       Link node in the navbar, equally invisible. Treated as
       leftover, not as spec.

   The markup supplies the art. This engine cannot know a brand's
   asset path, and the house layer already owns the two shapes a
   logo can take: a single-file <img> whose colourway is picked by
   CSS, or a composed mark + inline-<symbol> wordmark that takes
   currentColor. The rule below sizes a DIRECT img/svg child, so
   the single-file form needs nothing; a composed lockup keeps its
   own internal proportions and takes the height from its own
   custom property in the markup.
   ------------------------------------------------------------ */
.ui-nav__logo {
  display: inline-flex;
  align-items: center;
  flex: none;
  min-height: var(--ui-nav-logo-h);
  color: var(--ui-ink);
  text-decoration: none;
}
/* Direct children only. A composed lockup wraps its mark and its
   wordmark in its own element, and that element scales the two
   parts against each other — reaching into it from here would
   flatten both to the same height and break the lockup.

   ⚠ HEIGHT AND WIDTH ONLY. Not `display`. The house layer ships
   both colourways of a logo in the markup and hides one with
   `display: none` at (0,1,0); a `display: block` here is (0,1,1)
   and beat it, so the bar rendered BOTH the dark and the light
   lockup side by side — 342px of logo where 171px was intended,
   which is what pushed the toggle off the right edge of a 393
   frame. Caught in the proof render. An engine rule has no
   business setting `display` on brand-supplied art. */
.ui-nav__logo > img,
.ui-nav__logo > svg {
  height: var(--ui-nav-logo-h);
  width: auto;
}

/* §4's focus ring, repeated for one new selector. The ring
   recipe is not re-invented here — it is copied verbatim,
   including the reason it has three layers — because §4's
   selector list was written before this component existed and
   cannot name it. When this section is merged into ui-core.css,
   add .ui-nav__logo to §4's list and delete this rule. */
.ui-nav__logo:focus-visible {
  outline: 3px solid var(--c-accent);
  outline-offset: 2px;
  box-shadow: 0 0 0 2px var(--ui-plate),
              0 0 0 6px var(--ui-ink-20);
}

/* ------------------------------------------------------------
   6.1.2 THE LINKS
   ------------------------------------------------------------
   Straight §5.3 Links, in a 40px row. M is the only drawn gap:
   logo-to-links is space-between (the bar's own S gap is just a
   floor, so the two never touch when the content is wide), and the
   Button sits 40px from the last link like any other member. The
   source's own numbers confirm the row is uniform — the five items
   measure 72, 74, 44, 61 and 64 wide at x = 0, 112, 226, 310 and
   411, which is 40 between every pair.

   The two things added to a bare .ui-link:

     · a 34px box with 5px (XXS) of padding, which is what the
       source draws and what makes every item in the bar — links,
       CTA, toggle — the same height with no per-item nudging.
       border-box, or the padding would make it 44.
     · nothing else. State is §5.3's two-step ink change, 60% at
       rest and 100% for the current item, and the file draws
       exactly that: one Primary 100 link per variant. Mark it
       with aria-current and §5.3's existing rule lights it.

   The 40px row itself is in 6.1.4, with the rest of the large-
   frame arrangement, because below 640 the same element is a
   stacked panel and the two want different gaps.
   ------------------------------------------------------------ */
.ui-nav__menu { box-sizing: border-box; }

.ui-nav .ui-link {
  box-sizing: border-box;
  min-height: var(--ui-control-h);
  padding: var(--s-w-xxs);
}

/* ── The CTA ────────────────────────────────────────────────
   Reuses §5.1 with two changes, both of them read off the file:

     plate  --ui-nav-control, i.e. one step back from the bar.
            §5.1's --ui-plate IS the bar's colour, so an untouched
            .ui-btn here is plate on plate, with no edge.
     label  60% ink, not 100%. Every navbar variant in the file
            sets the Login label to Primary 60 — the CTA is built
            from the same node as the menu toggle and rests at the
            same weight as an inactive link. That is a hierarchy,
            not a slip: everything in the bar rests at 60% and the
            current page is the only thing at 100%. A full-ink
            CTA would out-shout the current-page marker.

   Written as :not(:hover) rather than as a plain override so that
   every hover rule in §5.1 — including .ui-mode--alternative's
   lift-the-plate hover, which is only (0,3,0) — keeps winning.
   A flat `.ui-nav .ui-btn` is (0,2,0) and, loading after §5, would
   silently kill the hover fill. */
.ui-nav .ui-btn:not(:hover):not([data-state="hover"]) {
  background: var(--ui-nav-control);
  color: var(--ui-ink-60);
}

/* One of the four brands — the monochrome one whose ink IS its
   accent — draws both its CTA and its menu toggle as an outline
   instead of a fill, with a 20% ink border. That is quieter than
   §5.1's --outline modifier, which borders at full ink, and the
   navbar is why: a full-ink border on a bar this quiet reads as a
   second heading. Declared after the rule above and at the same
   specificity, so it overrides the fill and inherits the 60%
   label. WHICH brand wants it is a markup decision — this engine
   cannot tell one brand from another, exactly as §5.1 says of the
   same modifier. */
.ui-nav .ui-btn--outline:not(:hover):not([data-state="hover"]) {
  background: transparent;
  border-color: var(--ui-ink-20);
}

/* ------------------------------------------------------------
   6.1.3 THE MOBILE MENU
   ------------------------------------------------------------
   ⚠ THE PANEL IS NOT IN THE FIGMA. The mobile frame's State axis
   is (Default, Closed Button) and both values are the same 75px
   bar; nothing anywhere on the Navbar page draws what the toggle
   reveals. So the toggle and its two icons are transcribed and
   the panel is invented. It is marked as such below.

   ── "Closed Button" ──────────────────────────────────────────
   It is not the closed state of the bar. State=Default draws the
   toggle with a burger; State=Closed Button draws the SAME toggle
   with a cross — the Figma names the button by its FUNCTION, "the
   button that closes", not the bar by its state. Evidence, from
   40004560:29497 vs 29495: identical bars, identical 34x34 Menu
   Button, and the only difference is the glyph, where the cross
   is drawn as a 13px plus rotated -45 degrees inside an 18.385px
   box. The sprite's cross symbol is that same geometry already
   resolved (arms of 9.19 units from centre = 13 x cos45), so the
   markup uses the cross symbol and the rotation is not needed.

   Read as an interaction the pair is unambiguous: burger = closed,
   cross = open. This section spells it "open" and the toggle
   swaps its own glyph, which is why both icons ship in the markup.

   ── NO JAVASCRIPT, AND WHAT THAT COSTS ───────────────────────
   The menu is a `popover` invoked by `popovertarget`. That buys,
   with no script at all:

     · a real <button> with a real accessible name;
     · implicit aria-expanded on the invoker. Verified in
       Chromium 141 by reading the accessibility tree: the button
       reports role=button expanded=false, and expanded=true after
       activation. Nothing sets the attribute, and nothing should
       — a hand-written aria-expanded would be a lie the moment
       the state changed, and there is no CSS that can update it;
     · keyboard activation (Enter and Space, because it is a
       button), Escape to close, and light dismiss on an outside
       click. A <details>/<summary> disclosure gives neither of
       the last two, and reports role=DisclosureTriangle rather
       than button — also verified rather than assumed.

   The two things it does NOT do without JS: close itself when a
   link inside it is followed on a single-page router (a real
   navigation tears the page down and the question disappears),
   and animate. Neither is required, so this component ships with
   no JS and no caveat about a menu that cannot open.

   The same markup is one element at both breakpoints. Above 640
   the popover is neutralised into an ordinary flex row in the bar
   — author rules beat the UA's `[popover]:not(:popover-open)
   { display: none }` regardless of specificity, so it takes one
   declaration and no duplicate markup.
   ------------------------------------------------------------ */

/* The toggle is a .ui-btn — in the file it is literally the same
   node as the Login button with its label replaced by an icon, so
   it inherits the plate, the radius, the hover fill and §4's
   focus ring for free. All this adds is the square box: 34x34,
   no inline padding, an icon-sized child.

   On the two brands whose --radius-button is 30px that is a 30px
   radius on a 34px box, i.e. a circle. That is what the file draws
   (40004560:30700 renders a true circle), so it is right. */
.ui-nav__toggle {
  flex: none;
  width: var(--ui-control-h);
  min-width: var(--ui-control-h);
  padding-inline: 0;
}
/* Two classes, not one, and the second one is load order rather
   than taste: the brand house layer loads AFTER this engine, and
   its icon class sets a 1.5em box at (0,1,0). A single-class rule
   here ties and loses, which rendered the burger at 21px instead
   of 24. (§5.6's .ui-field__chevron has the same latent tie and
   loses it the same way — worth fixing there too.) */
.ui-nav .ui-nav__icon {
  flex: none;
  width: var(--ui-nav-icon);
  height: var(--ui-nav-icon);
}

/* Glyph swap. The close icon is hidden until the menu is open —
   and again two classes, for the same load-order reason: the
   brand icon class sets `display: inline-block` at (0,1,0) and
   loads later, so a one-class `display: none` here lost and the
   bar rendered the burger AND the cross, the second one hanging
   out of the 34px box. Caught in the proof render at 393. */
.ui-nav .ui-nav__icon--close { display: none; }

/* Two selectors, two rules, deliberately not one list: a
   selector list containing an unknown pseudo-class is invalid as
   a whole, so pairing :popover-open with [data-state] would take
   the static form down with it on any engine that lacks popover.
   [data-state="open"] is the static hook — it is what lets a
   screenshot show the open bar — and it must survive alone. */
.ui-nav:has(.ui-nav__menu:popover-open) .ui-nav__icon--open { display: none; }
.ui-nav[data-state="open"] .ui-nav__icon--open { display: none; }
.ui-nav:has(.ui-nav__menu:popover-open) .ui-nav__icon--close { display: block; }
.ui-nav[data-state="open"] .ui-nav__icon--close { display: block; }

/* The panel. Everything here is INVENTED — the file draws no
   panel — so it is kept to the smallest set of decisions that
   makes the toggle do something honest:

     surface  the bar's plate over an opaque floor — see the
              two-property background below. It reads as the bar
              getting taller, and it MUST be opaque because it
              overlays page content.
     gap      S (20px). M would be the row's gap, but 40px of air
              between two 34px rows reads as two separate lists
              rather than one menu.
     padding  the bar's own inline padding, so the panel's edge
              lines up with the bar's. The links carry 5px of
              their own, so their text sits 5px right of the logo;
              pulling the panel in by 5px to fix that would
              misalign the CTA's plate instead. It is 5px.
     height   capped at the viewport less the bar, and scrolls. A
              nav with more items than fit is a real case and a
              panel that runs off the bottom of a phone is not.
              The cap assumes the bar is near the top of the
              viewport, which is where a navbar lives; anchor()
              is only valid in the inset properties, so the cap
              cannot be expressed against the anchor as well.

   Positioned against the bottom edge of the bar BY NAME, so a bar
   that is not at the top of the viewport still gets its panel in
   the right place — which matters for a sticky bar mid-scroll and
   for any page carrying more than one navbar.

   ⚠ AND THE POSITION SCHEME IS PART OF THAT, not a detail. An open
   popover is in the top layer, where the containing block is the
   initial one no matter what its ancestors do, so:

     fixed    + anchor()  is WRONG once the page scrolls. anchor()
              resolves in the anchor's own layout coordinates and a
              fixed box does not scroll with them, so the panel
              lands at the bar's DOCUMENT position measured from
              the viewport's top. Measured: with the page scrolled
              so the bar sat at y=0..75, the panel opened at
              y=866 — the bar's document offset. Caught by
              measuring, not by looking; the unscrolled proof page
              renders it correctly and hides the bug completely.
     absolute + anchor()  is right. Against the initial containing
              block, anchor() and the box are in the same
              coordinates, and the panel scrolls with the page.

   So the @supports block switches the scheme as well as the insets.
   The fallback for engines with no anchor positioning stays FIXED
   at the bar's own height, which is correct for the ordinary case
   of a bar at the top of the viewport and wrong nowhere else that
   a fallback can reach. */
.ui-nav__menu {
  position: fixed;
  top: var(--ui-nav-h);
  left: 0;
  right: 0;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s-w-s);
  width: auto;
  max-block-size: calc(100dvh - var(--ui-nav-h));
  overflow: auto;
  margin: 0;
  padding-block: var(--s-w-s);
  padding-inline: var(--s-w-xs);
  /* Plate over ground, on one box, in two properties. The floor is
     --ui-ground, which is opaque in every mode; the plate arrives
     as an image on top of it. In Default the plate is Core, fully
     opaque, and the floor never shows. In Inverted the plate is a
     20% wash of the inverted ink — the same wash every inverted
     control wears — and the floor is what stops page content
     showing through it. One declaration pair, both modes, no
     mode-specific rule needed. */
  background-color: var(--ui-ground);
  background-image: linear-gradient(var(--ui-plate), var(--ui-plate));
  color: var(--ui-ink);
  border: 0;
  border-radius: 0;
  /* The engine has no z-index scale, and a panel that overlays the
     page needs one: an OPEN popover is in the top layer and needs
     nothing, but the [data-state] static form is an ordinary fixed
     box, and page content with its own stacking context — anything
     with opacity < 1 counts — paints over it. Caught in the proof
     render, where the note under the bar showed through the panel.
     Same floor as .ui-nav--sticky; raise both together. */
  z-index: 10;
}

@supports (top: anchor(bottom)) {
  .ui-nav__menu {
    position: absolute;
    position-anchor: --ui-nav-anchor;
    top: anchor(bottom);
    left: anchor(left);
    right: anchor(right);
  }
}

/* Open. Split for the reason given above; both beat the UA's
   display:none because author rules always do. */
.ui-nav__menu:popover-open { display: flex; }
.ui-nav[data-state="open"] .ui-nav__menu { display: flex; }

/* ------------------------------------------------------------
   6.1.4 FROM 640px: THE DRAWN DESKTOP + TABLET BAR
   ------------------------------------------------------------
   One breakpoint, and it does three things: widens the bar's
   padding to the 20px the two large frames share, drops the
   toggle, and turns the popover back into a row. There is no
   second breakpoint because the source has no third layout —
   1440 and 834 are the same drawing (see the note at the top).
   ------------------------------------------------------------ */
@media (min-width: 640px) {
  .ui-nav { padding-inline: var(--s-w-s); }

  .ui-nav__toggle { display: none; }

  /* De-popover. Every property here undoes a UA popover default
     rather than expressing a design decision, which is why they
     are grouped and not commented individually. */
  .ui-nav__menu {
    display: flex;
    flex-direction: row;
    align-items: center;
    /* M. The one gap in the bar, and the source's own coordinates
       confirm it: 40px between every one of the five items. */
    gap: var(--s-w-m);
    position: static;
    inset: auto;
    max-block-size: none;
    overflow: visible;
    padding: 0;
    background: none;
    /* The row is content-width and must not be squeezed by
       space-between; the logo is flex:none, so this pair keeps
       the links flush right at every width. */
    flex: 0 1 auto;
    min-width: 0;
  }
}

/* ============================================================
   6.2 FOOTER   (Figma 15312:7883)
   ------------------------------------------------------------
   A fragment written to drop into ui-core.css as §6.2. It is a
   COMPOSITE, not a ninth atom: everything interactive in it is
   already in §5 — the links are .ui-link, the email capture is
   .ui-signup — and this section is only the layout that holds
   them plus three text roles and an oversized watermark. When
   this and its sibling sections land, the PRINT block currently
   numbered §6 becomes §7.

   Transcribed from the Master UI file, page "SECTIONS", node
   15312:7883 ("Footer"). Four Brand variants per device frame:

     Desktop  40004510:15397   1440 wide
              B1  40004510:15398   h 450.32
              B2  40004510:15451   h 434.64
              B3  40004510:15511   h 427.32
              B4  40004510:15568   h 427.33
     Tablet   40004544:9336     834 wide
              B1  40004544:9335    h 380.12
              B2  40004544:9249  · B3 40004546:11808
              B4  40004545:9519
     Mobile   40004536:26610    393 wide
              B1  40004536:26609   h 573.10
              B2  40004536:27084 · B3 40004536:28004
              B4  40004536:29655   h 632.36

   B1..B4 are the four Brand variants in file order — this engine
   cannot tell them apart and does not name them; the node ids are
   the authority and the reference doc carries the mapping.

   Brand is the ONLY axis. There is no State axis (the states all
   belong to the components inside) and — read this before
   inventing one — NO MODE AXIS. See the inverted note in 6.2.5.

   ── WHY THE FOUR BRANDS ARE DIFFERENT HEIGHTS ────────────────
   Worth stating because it looks like a bug in the source and is
   not. The stack is identical in all four; two things move:

     1. CONTENT. B1's first link column has five links, the other
        three variants' have four. One link row is 23px, and
        450.32 − 427.32 = 23.00 exactly. That is the whole desktop
        difference between B1 and B3/B4.
     2. THE WATERMARK'S INK BOX. B2 sits 7.31px above B3/B4
        because its sub-brand word carries a 'd' whose ascender
        rises above the cap line, so that word's ink bounding box
        is 169.64 tall where the other three measure 162.32. The
        type SIZE is identical on all four — the house word
        measures 583.58 x 162.32 on every variant, to the
        hundredth of a pixel.

   So it is not the display face set at four sizes. It is one link
   row plus one ascender. Which also means: if a brand ships its
   watermark as ONE ink-tight asset, a single height token sets
   that one variant 4.3% smaller than drawn. Export the watermark
   words on a common vertical box and that disappears — see
   6.2.4.

   ── MOBILE IS TALLER THAN DESKTOP ────────────────────────────
   573 against 450 on B1. That is the tell that the columns
   STACK rather than shrink: the newsletter block leaves the right
   edge of the top row and drops below the link columns, and the
   three stack gaps widen from S to M at the same time. The three
   link columns do NOT stack — they stay side by side and narrow
   to exactly three of the nine phone grid columns (117.67px in a
   373px content box, gutter 10). That is the one place in this
   engine where the phone grid is used as a real layout grid
   rather than a drawing convention (compare §3's --span note).

   Spec + budgets: reference/UI-COMPONENTS.md §9.
   ============================================================ */

/* ------------------------------------------------------------
   6.2.1 FOOTER TOKENS
   ------------------------------------------------------------
   Mobile-first, like §1: these are the phone values and the two
   breakpoints raise them. Everything here is a measured value off
   the Figma frames, and everything that COULD be a spacing step
   is one — the footer turned out to be built almost entirely on
   XS/S/M, which is why there are only three real numbers below.
   ------------------------------------------------------------ */
:root {
  /* The footer's own block padding. 20px above the top row and
     below the bottom row at every frame — the Footer frame sits at
     y=20 inside a variant that is 40px taller than its content. */
  --ui-footer-pad: var(--s-w-s);

  /* Gap between the three bands (top row / watermark / legal).
     S on desktop and tablet, M on the phone. The phone needs the
     extra air because the bands there are a stack of unlike things
     rather than one row plus two rules of furniture. */
  --ui-footer-stack: var(--s-w-m);

  /* Column title to its link list. XS on the phone, S above it —
     measured from the title's CAP-TRIMMED box, which is why 6.2.2
     trims it. Untrimmed, this gap reads 8px too big. */
  --ui-footer-col-gap: var(--s-w-xs);

  /* Link column width. A fixed 125px on desktop and tablet — note
     that this is NOT a grid column: 12 columns of the 1400px
     footer content box would be 98.33px, and the file draws 125
     with the 20px gutter between. On the phone the columns become
     equal fractions, which lands them exactly on the 9-column
     phone grid. */
  --ui-footer-col-w: minmax(0, 1fr);

  /* The newsletter block. Right-aligned in the top row, and it
     does not scale smoothly: 410 / 340 / 245. The phone value IS
     a grid measure (six of nine columns = 245.3px); the other two
     are drawn widths. */
  --ui-footer-news-w: 245px;

  /* The Link component's row box, measured on the instances in
     every column of every frame: 23px, with the 10px cap-trimmed
     text centred at y=6.5. It is 14px/130% (18.2px) plus 5px of
     leading, i.e. the XXS step — but it is a COMPONENT height like
     --ui-control-h, not a spacing step, so it gets its own token
     and is applied as a min-height rather than as a gap. Applied
     as a gap, a five-link column comes out 111px instead of 115px
     and the whole footer drifts. It also lifts the pointer target
     on the footer's densest control from 18px to 23px. */
  --ui-footer-link-h: 23px;

  /* The watermark's type size, expressed as the height of one
     word's ink box. 162.32 / 92.12 / 80.11 measured; rounded to
     the pixel. The tablet value is the desktop value scaled by the
     ratio of the two content boxes (794/1400 = 0.567) — the phone
     value deliberately is not: at 0.567 the phone watermark would
     be 46px tall and stop reading as a watermark at all. */
  --ui-footer-logo-h: 80px;
}

@media (min-width: 640px) {
  :root {
    --ui-footer-stack:   var(--s-w-s);
    --ui-footer-col-gap: var(--s-w-s);
    --ui-footer-col-w:   125px;
    --ui-footer-news-w:  340px;
    --ui-footer-logo-h:  92px;
  }
}

@media (min-width: 1120px) {
  :root {
    --ui-footer-news-w: 410px;
    --ui-footer-logo-h: 162px;
  }
}

/* ------------------------------------------------------------
   6.2.2 THE SHELL AND THE THREE BANDS
   ------------------------------------------------------------
   .ui-footer is a flex COLUMN with a gap, not a set of margins.
   Two reasons, both learned in §3.1: a gap cannot collapse into a
   neighbour, and the owl selectors in §3.1 only reach the direct
   children of a flow container — so a footer dropped inside
   .ui-page gets its own 40px flow gap above it (correct) while its
   internals stay untouched (also correct).

   It paints --ui-ground, not --c-bg-base. Same reason .ui-page
   does: put .ui-mode--inverted on the <footer> itself and the two
   classes land on one element with equal specificity, so a literal
   --c-bg-base here would silently beat the mode.

   The footer sets the body type on itself and every text role
   below inherits it. That is deliberate: the footer is the one
   region in the system where EVERY string is the same 14/130/300
   setting, and re-declaring it four times would invite someone to
   change one of them.

   ── PLACEMENT ────────────────────────────────────────────────
   The footer is a PAGE-LEVEL BAND, not a section inside the
   content shell. It carries --grid-margin itself and belongs
   AFTER .ui-page, as a sibling:

     <main class="ui-page"> … </main>
     <footer class="ui-footer"> … </footer>

   That is what the file draws — a band the full width of the
   device — and it is the only arrangement in which a painted
   footer's ground reaches the viewport edges. Nested inside
   .ui-page instead, an .ui-mode--inverted footer renders as a
   dark CARD floating in a bone page with the page margin showing
   on both sides, which is exactly how the first proof looked.

   Because the padding is the page's own --grid-margin either way,
   the footer's columns line up with the sections above it. Nesting
   is still allowed and the guard below stops it double-padding.
   ------------------------------------------------------------ */
.ui-footer {
  /* THE ONE PLACE THE FILE USES THE SECOND INK.
     Every section on the site sets its copy in Colors/Primary — the
     brand's own colour, orange included. The footer does not: its
     links are bound to Colors/Black 60, read live off all four
     desktop footer nodes. On three of the four brands Black and
     Primary are the same value and this reassignment is a no-op; on
     the fourth it is the difference between 1.79:1 and 5.15:1 for the
     smallest, densest text on the page.

     Scoped to the footer on purpose. It is a departure from the
     brand's own rule, so it belongs exactly where the file puts it
     and nowhere else. If design wants footer links in the brand
     colour too, delete these three lines. */
  --ui-ink-copy:    var(--c-ink-body);
  --ui-ink-copy-60: var(--c-ink-body-1);
  --ui-ink-copy-20: var(--c-ink-body-3);

  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: var(--ui-footer-stack);
  padding-block: var(--ui-footer-pad);
  padding-inline: var(--grid-margin);
  background: var(--ui-ground);
  color: var(--ui-ink);
  font-family: var(--font-sans);
  font-weight: var(--fww-body);
  font-size: var(--fsw-body);
  line-height: var(--lhw-body);
  letter-spacing: var(--tr-body);
}

/* Nested rather than a sibling: the shell already paid the inline
   margin, so the footer must not pay it twice. */
.ui-page .ui-footer { padding-inline: 0; }

/* The top band: link columns left, newsletter right, justified
   apart. space-between rather than a measured gap because the gap
   is what absorbs the frame difference — 575px of it on desktop,
   39px on tablet, both of which fall out of this rule with no
   breakpoint of their own. column-gap is a FLOOR (S), so the two
   blocks can never touch at an in-between width.

   row-gap is M because on the phone this row becomes a stack and M
   is the gap the file draws there. */
.ui-footer__top {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  row-gap: var(--s-w-m);
  column-gap: var(--s-w-s);
}

/* ------------------------------------------------------------
   6.2.3 LINK COLUMNS
   ------------------------------------------------------------
   grid-auto-flow: column, so the number of columns is whatever the
   markup ships. Three is the drawn case; the budget in the doc is
   three on desktop and tablet, and three is also the phone
   maximum, because a fourth 1fr column on a 373px frame is 83px
   wide and "Strategies" no longer fits on one line.

   The nav is 100% wide on the phone (which is what forces the
   newsletter onto its own row) and content-width above it.
   ------------------------------------------------------------ */
.ui-footer__nav {
  flex: 1 1 100%;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: var(--ui-footer-col-w);
  column-gap: var(--grid-gutter);
}
@media (min-width: 640px) {
  .ui-footer__nav { flex: 0 0 auto; }
}

.ui-footer__col {
  display: flex;
  flex-direction: column;
  gap: var(--ui-footer-col-gap);
  min-width: 0;
}

/* The column title. Full ink against the links' 60%, which is the
   same 100/60 step the whole engine uses for hierarchy and state.

   ⚠ THE SOURCE DISAGREES WITH ITSELF HERE, and this is the one
   place in the footer where a decision had to be taken rather than
   a value read. ONE variant sets the titles and the newsletter
   lead in Accent Color; the next sets both in Primary 100; and the
   remaining two cannot cast a vote, because they bind accent TO
   ink and the two readings are the same pixels there. Full ink is
   the default here because it is what three of the four variants
   literally contain, and because it is the only reading legible on
   all four: on the pale-accent brand, accent on its own ground
   measures 1.28:1. The accent reading is available as
   .ui-footer--accent below.

   text-box trims the box to the cap line and the baseline, which
   is what makes --ui-footer-col-gap the 20px the file measures
   rather than 28px of half-leading plus a gap. Same technique as
   §3.2's display headings, at 14px instead of 128px.

   The 5px inline padding is not decoration: it is the Link
   component's own inline padding, and the titles carry it so the
   title cap aligns with the link text below it rather than with
   the column edge. Consequence, faithful to the file: the nav
   block's text sits 5px right of the legal line's. */
.ui-footer__col-title {
  margin: 0;
  padding-inline: var(--s-w-xxs);
  font: inherit;
  color: var(--ui-ink-copy);
  text-box: trim-both cap alphabetic;
}

/* A real list, unstyled. No gap — the 23px row box does the
   spacing, see the --ui-footer-link-h note. */
.ui-footer__links {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
/* The li is the flex line; the link inside it is a flex item, so it
   hugs its own text instead of stretching the column width and
   handing the user a hover target the width of the footer. */
.ui-footer__links > li { display: flex; }
.ui-footer__links .ui-link {
  min-height: var(--ui-footer-link-h);
  padding-inline: var(--s-w-xxs);
  /* THE COPY INK, not the ink. The Figma binds these links to
     Colors/Black 60 rather than to Primary 60 — read live off the
     Forge desktop footer, the one brand where the two differ. It is
     the difference between 1.79:1 and 5.15:1 on the base ground, and
     it is the file itself implementing the remedy the token source
     has recommended since the contrast pass. On the other three
     brands --ui-ink-copy IS the ink and nothing changes. */
  color: var(--ui-ink-copy-60);
}
.ui-footer__links .ui-link:hover,
.ui-footer__links .ui-link[data-state="hover"],
.ui-footer__links .ui-link--active {
  color: var(--ui-ink-copy);
}

/* ------------------------------------------------------------
   6.2.4 NEWSLETTER
   ------------------------------------------------------------
   §5.7's .ui-signup, unchanged except for two things the
   component's own notes anticipated.

   FIRST, the width. .ui-signup is capped at 265px because that is
   the Figma component frame; the footer instance is 410px on
   desktop, 340 on tablet, 245 on the phone — always the full width
   of the newsletter block. So the cap is released here and the bar
   fills its column. This is the "raise it for a full-width footer"
   case in §5.7.

   SECOND, the submit button rests FILLED. The footer instance
   draws an accent submit with a knockout arrow at rest, which is
   Alternative's resting appearance — but its input value is
   Primary 100, which is Default's ink. So the footer takes
   Alternative's submit swap and nothing else. Putting
   .ui-mode--alternative on the block would also retint the typed
   email accent, which the file does not show; and it would move
   the ink on the lead and the legal note too. Four lines here is
   the honest transcription.
   ------------------------------------------------------------ */
.ui-footer__newsletter {
  flex: 0 1 var(--ui-footer-news-w);
  /* min-width: 0 is not boilerplate here, it is a fix. A flex item's
     automatic minimum size is its MIN-CONTENT size, and the
     min-content size of an <input> is its intrinsic 20-character
     default — about 198px at 14px, plus 30px of control padding, the
     34px submit and the 2px border. That floor is 264px, so on the
     phone the newsletter refused to come down to its drawn 245px and
     sat 19px wide of the grid. §5.7's own min-width: 0 on the input
     does not help: it lifts the input's automatic minimum, not the
     min-content contribution the wrapper inherits from it. */
  min-width: 0;
  max-width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--s-w-s);
}

.ui-footer__newsletter .ui-signup { max-width: none; }

.ui-footer__newsletter .ui-signup__submit {
  background: var(--ui-fill-active);
  color: var(--ui-ink-on-accent);
}
.ui-footer__newsletter .ui-signup__submit:hover,
.ui-footer__newsletter .ui-signup__submit[data-state="hover"] {
  background: var(--ui-plate-up);
  color: var(--ui-ink);
}

/* The lead line above the field. Full ink, same argument and same
   accent opt-in as the column title — in the file these two always
   agree with each other. */
.ui-footer__lead {
  margin: 0;
  font: inherit;
  color: var(--ui-ink-copy);
  text-box: trim-both cap alphabetic;
}

/* The small-print line under the field, and the copyright line at
   the bottom of the footer. Both 60% ink, both cap-trimmed, and
   both allowed to wrap — the file draws them on two lines at every
   frame narrower than desktop.

   ⚠ `font: inherit` IS LOAD-BEARING ON ALL THREE TEXT ROLES, and
   this cost a render to find. tokens.css styles the bare `p`
   element at the DECK body size — 23px/1.2, because that file is
   shared with a medium that reads across a room. An element
   selector beats inheritance, so a footer paragraph that merely
   inherits 14px from .ui-footer silently comes out at 23px and the
   newsletter block renders 52px too tall. §5's components dodge
   this by re-declaring the whole type recipe; the footer inherits
   one setting from its shell instead, so it has to say so. */
.ui-footer__note,
.ui-footer__legal {
  margin: 0;
  font: inherit;
  color: var(--ui-ink-copy-60);
  text-box: trim-both cap alphabetic;
}

/* ------------------------------------------------------------
   6.2.5 THE WATERMARK
   ------------------------------------------------------------
   The oversized brand name, dropped to 10% ink and clipped by
   nothing — it is not the house arch, it does not bleed, it simply
   sits in the middle band of the footer at a size no other element
   in this medium goes near (162px of ink box against a 128px H1).

   THE COLOUR IS A CONTRACT TOKEN THAT DOES NOT EXIST YET.
   The Figma variable is Colors/Footer Logo, and it is a real
   per-brand value: 10% BLACK on all three light-ground brands and
   10% of the bone ink on the dark-ground one. Note what that means
   for the light-ground brand whose ink is not black — its footer
   logo is 10% black, NOT 10% of its own ink. So this cannot be
   read off the ink ramp: on three of the four --c-ink-4 is
   byte-identical to it, and on that one it is not. That is exactly
   why the source carries a dedicated token, and why this engine
   consumes --c-footer-logo and does not derive it.

   The generator does not emit it yet, so the var() fallback is
   --c-ink-4 (the print ramp's 10% step — the only 10% the contract
   has; the web ramp stops at 20%). The fallback is correct on
   three of the four brands and WRONG on the one whose ink is a
   saturated mid-tone, where it paints a 10% tint of that ink
   instead of 10% black. Do not fix it here: it belongs in the
   token source and the generator. See the reference doc's
   divergence list.

   LAYOUT. One child per word, sized by HEIGHT, justified apart and
   allowed to wrap. That is not a stylistic choice, it is what the
   file does: one two-word variant is drawn with a 174px word gap
   and another with 62px, both ending flush with the right
   edge of the content box — i.e. the words are justified across
   the footer, and the gap is the residue. space-between reproduces
   both to within a pixel (174.9 and 60.6). On the phone the
   longest name wraps, and its second word lands on its own line
   at 373px wide with a 10px leading; flex-wrap plus a row-gap
   of XS reproduces that too, and a single-word brand is left
   aligned because space-between has nothing to justify against.

   align-items: flex-end because the words bottom-align on the
   baseline, not on their ink-box tops — one sub-brand word starts
   7.3px higher than the house word and both end at the same y.

   MARKUP CONTRACT, and it matters: each word must be an inline
   <svg> (or one svg for the whole name) exported on a COMMON
   vertical box. Height-sizing ink-tight assets of different
   ascender depth sets them at different type sizes — the same
   7.3px that makes one variant's footer taller than the next.

   overflow: hidden is a guard, not a design: at the desktop frame
   the longest brand name overflows its own content box by 1.43px
   IN THE SOURCE, and a name long enough to overflow at a narrow
   width has to clip rather than hand the page a horizontal
   scrollbar.
   ------------------------------------------------------------ */
.ui-footer__logo {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-w-xs) var(--s-w-s);
  padding-block: var(--ui-footer-pad);
  overflow: hidden;
  color: var(--c-footer-logo, var(--c-ink-4));
}
.ui-footer__logo-word {
  flex: none;
  display: block;
  height: var(--ui-footer-logo-h);
  width: auto;
  fill: currentColor;
}

/* ── WHAT AN INVERTED FOOTER DOES ─────────────────────────────
   The file has no Mode axis on this section at all, so this is a
   decision and it is stated rather than smuggled in.

   An .ui-mode--inverted footer keeps its layout and every measure,
   and re-skins through §2's inverted ramp exactly like the eight
   atoms: ground = brand ink, titles = inverted ink at 100%, links
   at 60%, the signup on the translucent 20% plate with its submit
   filled in solid inverted ink. All of that comes free from the
   --ui-* indirection; nothing below is needed for it.

   ONE THING DOES NOT COME FREE. The watermark's 10% token is
   defined against the LIGHT ground — 10% black on black is
   nothing at all, so an inverted footer on a black-ink brand
   would simply lose its watermark. Inverted therefore steps it to
   the mode's own 20% ink, the faintest step the inverted ramp
   actually has (§2 does not give the inverted ramp a 10%, and this engine
   is not the place to invent one). Measured: 20% white on black is
   1.66:1, against 1.25:1 for the 10% watermark on a light bone
   ground — fractionally louder than the light-ground case, which
   is the right direction on a dark ground. On the dark-ground
   brand, whose inverted ground is bone, it is 1.50:1.

   ⚠ Not recommended on a brand whose inverted ground is its
   accent: divergence 10 in the reference doc measures white on
   that orange at 3.28:1, and a footer is 14px body copy from
   top to bottom — the one thing 3.28:1 must not be used for.

   One selector, and it covers both placements: the mode class on
   the <footer> itself or on any ancestor of it, because either way
   the watermark is a descendant of the element carrying it. */
.ui-mode--inverted .ui-footer__logo { color: var(--ui-ink-20); }

/* ── The accent-lead variant's treatment ─────────────────────
   Column titles and the newsletter lead in accent. Exposed as a
   modifier for the same reason .ui-btn--outline is: it is how one
   brand's cell is actually drawn, and a modifier lets the others
   reach it deliberately instead of by accident. --ui-fill-active
   rather than --c-accent, so it survives Inverted on the two
   brands that bind accent to ink — on those, a literal accent here
   would be the ground colour.

   Measured before you use it (WCAG 2.1, accent composited on the
   brand's own base ground): 2.49:1 on the blue-accent brand,
   1.28:1 on the amber one, 2.34:1 on the orange one and 11.44:1 on
   the dark-ground one. Only the last passes AA for 14px text, and
   only because its accent IS its ink. This is
   the same finding as divergence 4 in the reference doc, on a
   different pairing. */
.ui-footer--accent .ui-footer__col-title,
.ui-footer--accent .ui-footer__lead {
  color: var(--ui-fill-active);
}

/* ------------------------------------------------------------
   6.2.6 THE LEGAL BAND
   ------------------------------------------------------------
   One sentence with one link in it, and it is one sentence on
   purpose. The desktop file draws the copy and the studio credit
   as a 370px text block plus an 89px Link instance beside it; the
   phone file draws the whole thing as a single two-line
   paragraph with no Link instance at all. An inline link inside a
   wrapping paragraph is both of those with one piece of markup —
   it reads as one line on desktop and rewraps on the phone —
   whereas a flex row would need a breakpoint to stop being a row.
   (It also means the phone keeps a real link where the file
   flattened it to text, which is a divergence in the right
   direction.)

   It stays a BLOCK. Both flex and grid were tried first and both
   are wrong for the same reason: they blockify element children
   into items, so the trailing <a> left the sentence and became a
   second row — a two-line copyright at 1100px wide, caught in the
   first render. Only normal block layout keeps a link inline
   inside a wrapping paragraph.

   min-height is the file's 23px row box (the Link instance's
   height, which is what the desktop band is measured from), and
   align-content — which applies to block containers in current
   Chromium, not just to flex and grid — centres the single line
   inside it. That is where the file's y=6.5 comes from. When the
   sentence wraps on a phone the content passes 23px and the box
   grows, which is the 28px the phone frame draws.
   ------------------------------------------------------------ */
.ui-footer__legal {
  min-height: var(--ui-footer-link-h);
  align-content: center;
}

/* ============================================================
   6.3 PAGE SECTIONS
   ------------------------------------------------------------
   Eighteen sections, transcribed from the Master UI components
   page. §5 is atoms and §6.1/6.2 are the page's furniture; this
   is the page's middle — the bands an author stacks between a
   navbar and a footer to make an actual page.

   Almost none of it paints anything new. A section is a HEAD (a
   display title and one lead paragraph) plus a BODY (whatever the
   section is for), and the body is nearly always §5's atoms or
   §5.9's cards arranged on §3's grid. The work is layout, measure
   and rhythm.

   ── ONE SHELL, EIGHTEEN MODIFIERS ────────────────────────────
   `.ui-section` is declared ONCE, in 6.3.2, with five tokens:

     --ui-section-inset      the band's inline inset (--grid-margin,
                             so a section lines up with .ui-page
                             and with the footer)
     --ui-section-lead       head → body. M on a phone, two M from
                             the tablet breakpoint up.
     --ui-section-body-gap   body-internal: cards to their pager
     --ui-section-title-max  82.73% = 910 of the 1100 content box
                             = ten of twelve desktop columns
     --ui-section-lead-max   49.09% = 540 of 1100 = six columns

   The two measures are FRACTIONS, not pixels, because that is the
   only form that survives three different grids. Both go to 100%
   below the tablet breakpoint: a 49% measure on a 333px content
   box is four words a line.

   Separately, 6.3.3 owns the reading column — `.ui-prose` and the
   `.ui-copy` text role — because running text is one object used
   in six different sections, and a 540px measure is what makes
   14px prose readable. `--ui-measure` and `--ui-section-lead-max`
   are the same number at the desktop frame and different jobs: one
   caps an article column, the other caps a section's lead.

   ── WHAT THE SECTIONS DO NOT HAVE ────────────────────────────
   No Mode axis in the file, for any of the eighteen. They re-skin
   under `.ui-mode--*` because everything below reads `--ui-*`, but
   that is extrapolation, not transcription — the same caveat as
   divergence 6. Nor is there a Brand axis: the sections are drawn
   once and the brand arrives through the contract.

   ── A NOTE ON HOW THIS SECTION WAS ASSEMBLED ─────────────────
   The three families below were transcribed in parallel and each
   arrived with its own section shell and its own token names:
   --ui-sec-gap, --ui-section-gap and --ui-section-lead were three
   names for one number, and there were three competing definitions
   of `.ui-section` itself. They were reconciled onto the vocabulary
   above before merging. If a future family is added the same way,
   reconcile first — three shells that agree by accident are three
   shells that will disagree after the next edit.
   ============================================================ */

/* ------------------------------------------------------------
   6.3.1 SECTION TOKENS
   ------------------------------------------------------------
   Declared on .ui-section rather than at :root, for §5.9's
   reason: nothing outside this section reads them, and keeping
   them off the root means the whole block can be lifted out. The
   media queries below re-declare them on the same selector,
   which is why they are mobile-first like everything in §1.
   ------------------------------------------------------------ */
.ui-section {
  /* The band's own inline inset. --grid-margin, so a section's
     content column lines up with .ui-page's and with the
     footer's. The intro overrides it; see the header. */
  --ui-section-inset: var(--grid-margin);

  /* Head to body. Two M steps at the desktop frame (see header);
     one M below the tablet breakpoint, which is the same halving
     --ui-pad-block already does at that breakpoint. Extrapolated,
     like every other small-frame value in this section. */
  --ui-section-lead: var(--s-w-m);

  /* Body-internal gap: cards to their page indicator, indicator
     to cards. Measured 20 in Testimonials (392 -> 412) and in
     Team (34 -> 54). Pills raises it to M, which is what that
     section draws (85 -> 125). */
  --ui-section-body-gap: var(--s-w-s);

  /* The two text measures, as fractions of the content box
     because that is the only form that survives three grids:
       910 / 1100 = 82.73%  = 10 of 12 desktop columns (title)
       540 / 1100 = 49.09%  =  6 of 12 desktop columns (lead)
     Both go to 100% below the tablet breakpoint — a 49% measure
     on a 333px content box is 163px, which is four words a line.
     Extrapolated; there is no small frame to read. */
  --ui-section-title-max:      100%;
  --ui-section-lead-max: 100%;
}

/* THE BREAKPOINTS BELOW WERE READ, TWICE, AND THE SECOND READING
   OVERTURNED THE FIRST. Worth recording because the mistake is easy to
   repeat.

   These values were originally reasoned from the grid. Then CONTENT's
   tablet frame was read and appeared to say head→body is 40 at tablet,
   so the step to 80 was moved to the desktop breakpoint. Then the other
   nine groups' tablet frames were read and eight of them draw **80** —
   STATS 40004588:5381, TEAM :4916, TESTIMONIALS :5288, GALLERY :6497,
   LIST :5753, STACKED 40004584:111868, ALIGNED 40004588:4730,
   COLUMNS :4823. The original reasoning was right and CONTENT is the
   exception: its DESKTOP head→body is 60, not 80, so it was never
   going to reach 80 at tablet. §6.3.23 sets its own value.

   One frame is a data point. Eight frames are a rule. The lesson is not
   "measure" — the first correction was measured too — it is that a
   single instance cannot tell you whether you are looking at the
   pattern or at the exception.

   The title cap and the lead measure keep the later reading: those were
   consistent across every frame opened. */
@media (min-width: 640px) {
  .ui-section {
    --ui-section-lead:     calc(var(--s-w-m) * 2);   /* 80, drawn */
    --ui-section-lead-max: 430px;                    /* 67 characters */
  }
}

@media (min-width: 1120px) {
  .ui-section {
    --ui-section-title-max: 82.73%;
    --ui-section-lead-max:  49.09%;
  }
}

/* ------------------------------------------------------------
   6.3.2 THE SHELL AND THE SHARED HEAD
   ------------------------------------------------------------
   Every one of the five is the same two blocks stacked: a HEAD
   (display title + one lead paragraph) and a BODY (whatever the
   section is actually for). Four of the five are literally that;
   the intro replaces the pair with a band and a row.

   The type recipe is set here and inherited, exactly as §6.2
   does for the footer and for the same reason: every string in
   these sections except the display titles and the stat numerals
   is the same 14/130/300 setting, and re-declaring it six times
   invites someone to change one of them.

   ⚠ AND EVERY TEXT ROLE BELOW CARRIES `font: inherit`. This is
   §6.2.4's lesson, not a new one: tokens.css styles the bare `p`
   element at the DECK body size (23px, because that file is
   shared with a medium read across a room), an element selector
   beats inheritance, and §3.2's reset only reaches inside
   .ui-page / .ui-flow. A section placed as a page-level band —
   which is how it is meant to be placed — is outside both, so a
   lead paragraph that merely inherits comes out 23px. Same for
   <blockquote> and for the UA's own margins on <figure>, <dl>
   and <dd>, which is why those are zeroed explicitly rather than
   left to §3.2.
   ------------------------------------------------------------ */
.ui-section {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: var(--ui-section-lead);
  width: 100%;
  padding-block: var(--ui-pad-block);
  padding-inline: var(--ui-section-inset);
  /* --ui-ground, not --c-bg-base: .ui-section and .ui-mode--*
     land on the same element with the same specificity, so a
     literal contract token here would silently beat the mode
     class exactly as it did in .ui-page and .ui-footer. */
  background: var(--ui-ground);
  color: var(--ui-ink);
  font-family: var(--font-sans);
  font-weight: var(--fww-body);
  font-size: var(--fsw-body);
  line-height: var(--lhw-body);
  letter-spacing: var(--tr-body);
}
/* A section owns its own head→body distance through the `gap` above, so
   it must NOT also inherit §3.1's flow rhythm. When `.ui-page` and
   `.ui-section` land on the same element — which is how every proof page
   in templates/sections/ is written, and the obvious thing to write —
   the shell's owl adds `margin-top` on top of the flex gap and the
   distance DOUBLES: measured 160px at tablet and 80px at mobile against
   a drawn 80 and 40.

   It survived a whole measurement pass because the pass read
   `getComputedStyle(section).rowGap`, which reports the gap (80) and not
   the distance between the boxes (160). Eight groups were recorded as
   correct on that reading. If you are verifying spacing, measure
   `nextBox.top − prevBox.bottom`; a token's computed value only tells
   you what one of the contributors is.

   Declared here, after §3.1, and winning on order rather than
   specificity — both selectors are (0,1,0). That is acceptable only
   because both live in this file, twenty lines apart in reading order,
   with this note between them. validate.mjs also warns when the two
   classes share an element, which is the real fix. */
.ui-section > * + * { margin-top: 0; }


/* Nested rather than a sibling: the shell already paid the inline
   margin, so the section must not pay it twice. Higher
   specificity than .ui-section--intro's own inset, deliberately —
   a nested intro gets the page's margin and loses its 20px
   bleed, which is the honest outcome of nesting a full-bleed
   hero inside a padded shell. */
.ui-page .ui-section { --ui-section-inset: 0px; }

/* The head. gap is S, and the arithmetic is worth writing out
   because "the file says 40" does not translate to `gap: 40px`.

   The file measures 40px from the title's BASELINE to the lead's
   CAP (h2 box ends at y=45, lead starts at y=85). §3.2's display
   headings are cap-trimmed, so their box also ends at the
   baseline — but they already carry
   margin-bottom: (--ui-display-descender + S), which is 14.08px
   of descender clearance plus 20px, i.e. 34.08px of the drawn 40
   before this rule contributes anything. So:

     gap S  ->  14.08 + 20 = 34.1px   (6px tighter than drawn)
     gap M  ->  14.08 + 40 = 54.1px  (14px looser than drawn)

   S is both the closer of the two and the one §3.1 already
   prescribes — "the block immediately after a heading gets a
   small gap (S), because it belongs to that heading". The 6px is
   a stated divergence rather than a magic 25.92px gap, and it is
   the price of §3.2 owning that margin. If it ever matters,
   the fix is a --ui-h2 margin token in §3.2, not a number here. */
.ui-section__head {
  display: flex;
  flex-direction: column;
  /* M, and the display heading's own margin is zeroed below, so the
     title-box → lead-box distance is exactly this. The file draws 40 at
     ALL THREE widths and the head was giving 54.08 / 50.55 / 47.91.

     Why it drifted, and why the fix is here rather than in §3.2: a
     display heading carries `margin-bottom: descender + S` because in a
     FLOW context that margin is the only thing between it and the next
     block, and margins collapse there so the larger of the two wins. A
     flex container does not collapse margins — it ADDS the gap to them —
     so in here the two spacings stack and the distance grows with the
     font size, which is exactly the wrong direction: the drawn number is
     constant.

     So the container that owns its spacing takes it over completely.
     §3.2's margin stays for flow, where it is correct.

     ⚠ Note the sign. Both §6.3.2's earlier comment and
     RESPONSIVE-FINDINGS.md record this as the head being ~6px TIGHTER
     than drawn. It was looser, by 14 / 10 / 8. Those notes were reading
     the numbers for `gap: 0`. */
  gap: var(--s-w-m);
  width: 100%;
}
/* Zeroed only inside the head, and only for the display sizes: this is
   the one container in the engine that supplies the whole distance
   itself. `:where()` keeps the selector at (0,1,0) so an author's own
   class still wins. */
.ui-section__head > :where(.ui-h1, .ui-h2, .ui-h3) { margin-bottom: 0; }

/* Worn beside .ui-h1 / .ui-h2, which supply family, size,
   leading, tracking and the cap trim. All this adds is the
   measure. */
.ui-section__title {
  max-width: var(--ui-section-title-max);
}

/* The lead paragraph, and it is one paragraph in two inks: the
   file sets the opening clause at Primary 100 and the remainder
   at Primary 60, on all four grid-aligned sections and on the
   intro. That is primitives.css's bold-then-dim headline device
   applied to body copy, and it is a device, not an accident —
   the two spans are separate text runs in every cell.

   THE COPY INK, not the ink. This is the decision integration
   note 14 in the reference doc leaves open, taken here for the
   section prose and stated as a decision: a section lead is
   running text, which is the case --ui-ink-copy exists for. On
   three of the four brands the two tokens are the same value and
   nothing changes; on the fourth it is the difference between
   1.96:1 and 12.63:1 at 14px. The file binds Primary here, so
   this is a divergence — see the reference doc. Reverting is two
   words. */
.ui-section__lead {
  margin: 0;
  font: inherit;
  max-width: var(--ui-section-lead-max);
  color: var(--ui-ink-copy);
  text-wrap: pretty;
  text-box: trim-both cap alphabetic;
}
/* The dimmed remainder. Its own class rather than §3.2's
   .ui-body--dim because that one is bound to --ui-ink-60 and
   this run has to follow the copy ink; reaching into
   .ui-body--dim from here to re-colour it would be the descendant
   override §6's header forbids. */
.ui-section__lead-dim { color: var(--ui-ink-copy-60); }

/* Centred head. Pills and Stats draw both title and lead centred
   in the 6-column measure (text x=280 in an 1100 box, i.e. 540
   centred); Testimonials and Team draw them left, with the title
   on the wider 10-column measure. Two arrangements, one
   modifier each way, no third case in the file. */
.ui-section--pills  .ui-section__head,
.ui-section--stats  .ui-section__head {
  align-items: center;
  text-align: center;
  --ui-section-title-max: var(--ui-section-lead-max);
}

/* The body. A column with one gap; what goes in it is the
   section's own business. */
.ui-section__body {
  display: flex;
  flex-direction: column;
  gap: var(--ui-section-body-gap);
  width: 100%;
  min-width: 0;
}

/* ── The carousel foot ──────────────────────────────────────
   Testimonials and Team both carry the same three-part row: a
   back Link, an Active Page Number, a forward Link, at 5px
   (XXS) gaps in a 34px box. That is §5.3 and §5.8 unchanged —
   the only thing this adds is the row and where it sits.

   Testimonials centres it BELOW the cards (x=485 in an 1100 box,
   dead centre); Team right-aligns it ABOVE them (right edge at
   1100). Both are drawn, both are here, neither is a default.

   ⚠ THE PAGING ITSELF IS NOT IN THE FILE AND IS NOT HERE.
   Nothing draws a second page of testimonials or a second page of
   the team; the indicator is drawn on page 2 of 3 in one section
   and page 1 of 3 in the other, and the Team frame HIDES its back
   arrow on page 1 (40004583:67177 is hidden="true"), which is the
   only interaction evidence in either. Wiring the arrows needs
   JavaScript, which this engine does not ship, so the arrows are
   real controls with real accessible names and the Team track is
   independently swipeable and scrollable without them. Mark the
   current page with aria-current and §5.8 lights it. */
.ui-section__nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-w-xxs);
  min-height: var(--ui-control-h);
}
.ui-section__nav--end { justify-content: flex-end; }

/* A one-line note under a block — the Pills section's "And
   more…", drawn as a 540-wide centred body line at 60% ink. That
   is §3.2's .ui-tagline exactly, so the markup wears .ui-tagline
   and this only supplies the measure and the centring. */
.ui-section__note {
  max-width: var(--ui-section-lead-max);
  margin-inline: auto;
  text-align: center;
}

/* ------------------------------------------------------------
   6.3.4 GENERAL INTRO   (Figma 40004583:65387)
   ------------------------------------------------------------
   The page opener: an oversized art band, a hairline, and one
   row of H1-plus-standfirst under it. It is the only section in
   the set drawn at the full 1440, the only one inset 20px rather
   than 170, and the only one whose name carries a unit.

   ── "100vh", AND WHAT IT IS NOT ──────────────────────────────
   The frame is 1440 x 766 and named "General Intro (100vh)". 766
   is a plausible desktop viewport height and the two child
   frames account for it exactly (566 band + 200 row), so the
   name is a layout instruction and not a label: this section is
   meant to fill the first screen.

   Implemented as `min-height: 100svh`, and both halves of that
   were chosen against alternatives:

     min-height, not height
       A fluid medium cannot promise 766px of content fits in
       766px of viewport. At 393 x 600 the H1 is 48px, the row has
       stacked, and the content alone is ~280px; at 393 x 400 it
       is taller than the frame. `height` clips or overflows;
       `min-height` grows. Rejected: `height: 100vh`, and
       `aspect-ratio`, for the same reason.

     svh, not vh, and not dvh
       On a phone `100vh` resolves to the LARGE viewport — the
       height the page would have with the browser chrome
       retracted — so a 100vh hero is taller than what is
       actually on screen at load, its call to action starts
       below the fold, and the page jumps as the chrome hides.
       That is the single most reported bug of this pattern.
       `100dvh` is worse in a different way: it re-resolves while
       the chrome animates, so the whole page reflows during the
       first scroll. `100svh` is the height with the chrome
       SHOWN, which is the only one of the three that means "the
       first screen" at the moment the reader arrives; when the
       chrome retracts the section is merely a little shorter than
       the viewport, which nobody notices. Rejected: 100vh
       (jumps), 100dvh (reflows), 766px (clips).

       The `100vh` declaration is kept immediately above as the
       fallback for engines without the small-viewport units, in
       the ordinary two-declaration idiom. Verified at 393x600 in
       Chromium: no clipping, no horizontal overflow.

     --ui-intro-offset
       The file reserves the navbar's 75px at the TOP of the band
       (the arch art starts at y=75) and does not draw a bar
       there, which reads as the bar sitting ON the hero — and on
       a Default-mode bar it does, because §6.1's bar is
       Background Core and the file's hero ground is Background
       Core too, so the two are one surface. If instead the bar is
       an ordinary band above the section, the first screen is the
       bar PLUS the hero and 100svh is 75px too tall. Set
       --ui-intro-offset to the bar's height in that arrangement.
       It defaults to 0 because that is what the file draws, and
       it is a length so calc() stays valid.

   ── THE BAND ─────────────────────────────────────────────────
   1440 x 566, and what is in it is a field of nine nested
   circles — the house arch motif, massively oversized and
   clipped so only the springing arcs read — with the first eight
   of them FILLED in a descending ramp of the accent. Sampled off
   the render: 100%, 80%, 60%, 40%, 20%, 10%, 5%, then outlines
   only.

   None of that is here, and it must not be. It is brand art: the
   arch is a per-brand motif that the house layer already owns as
   a component, the ramp needs 10% and 5% accent steps the
   contract does not have (it stops at 20), and an engine that
   cannot name a brand cannot draw one's motif. So the band is a
   SLOT — a positioned, clipping box with a hairline under it —
   and the markup supplies the art, exactly as §6.1.1 does for the
   logo and §5.9 for the card glyph. Two things the reference doc
   records for whoever fills it: the house arch component is
   stroke-only today and has no filled variant, and the accent
   ramp below 20% does not exist as a token.

   The band is FULL-BLEED — negative inline margins cancelling the
   section's inset — for one concrete reason: the hairline under
   it runs the whole 1440 in the render (sampled at x=0 and
   x=1439) while the content row is inset. Cancelling the inset
   on the band puts the rule where the file draws it and costs
   nothing else, since the art inside is positioned anyway.

   The hairline is 1px, not 2px. tokens.css calls structural line
   work — "table rules, arch arcs, SECTION rules" — 2px, and this
   is a section rule by any reading. The file draws one pixel of
   Primary 100 (sampled: a single black row at y=565 with bone
   above and below). Transcribed as drawn, flagged rather than
   corrected, because the web medium already sets --ui-border-w to
   the fine weight for its own reasons and a 2px rule under a
   128px title is a different design.

   The 240px floor on the band is an invention, stated as one:
   without it a short viewport can flex the band to nothing and
   the hero loses its art entirely.
   ------------------------------------------------------------ */
.ui-section--intro {
  --ui-section-inset: var(--s-w-s);
  /* Not the navbar's height: see the note above. A length, not 0,
     so the calc() below stays valid. */
  --ui-intro-offset: 0px;
  min-height: calc(100vh  - var(--ui-intro-offset));
  min-height: calc(100svh - var(--ui-intro-offset));
  /* The band absorbs whatever the viewport gives; the content row
     is content-sized. justify-content is not needed — two flex
     items, one of which grows. */
  padding-block: 0;
  gap: 0;
}

.ui-intro__band {
  position: relative;          /* the art's containing block */
  flex: 1 1 auto;
  min-height: var(--s-w-xxl);  /* invented floor, see above */
  margin-inline: calc(-1 * var(--ui-section-inset));
  overflow: hidden;            /* the clip IS the arch design */
  border-block-end: var(--ui-border-w) solid var(--ui-ink);
}

/* The content row: title left, standfirst right, justified
   apart. 783 and 500 are the drawn boxes and only the second is
   transcribed. 783 is a MEASURE for a 128px word — "Terms" sets
   about 330px inside it — so the title takes the free space
   instead, which is §6.1.1's reasoning about the 151px logo
   placeholder applied to type: a longer title gets more room
   rather than wrapping inside a box that was never a
   measurement. The 117px the file leaves between the two blocks
   is the residue of justify-between, not a gap, so the gap here
   is a floor. */
.ui-intro__row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-w-m);
  flex: none;
  padding-block: var(--s-w-s);
}
.ui-intro__title {
  flex: 1 1 auto;
  min-width: 0;
}
/* 500px, and it may shrink but not grow: the standfirst is a
   fixed column in the file at every one of the four brand
   variants. 100% basis below the tablet breakpoint, via the
   wrap — a 500px aside beside a 48px H1 on a 393 frame is not a
   row, and there is no drawn frame to say otherwise. */
.ui-intro__aside {
  flex: 0 1 var(--ui-intro-aside-w, 500px);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* M between the standfirst and its call to action, and S of
     block padding on the whole aside — both drawn
     (40004583:65413: gap 40, py 20). The padding is what drops
     the first line of the standfirst below the title's cap. */
  gap: var(--s-w-m);
  padding-block: var(--s-w-s);
  max-width: 100%;
  min-width: 0;
}

/* ⚠ The call to action is a .ui-btn here and the file draws a TAG
   instance. The layer is named "Button", the component behind it
   is Tags (40004510:14691), and on this brand the two are the
   same 34px box with the same 15px inline padding and the same
   radius — the only difference is the label, which the file sets
   at Primary 60 where a button rests at Primary 100. A section
   call to action that navigates is a button, not a filter chip,
   so the class is .ui-btn, the label rests at full ink, and the
   60% reading is reported rather than reproduced. Reproducing it
   would mean re-colouring an atom from a section, which is what
   §6's header exists to prevent. No rule here; the atom is
   untouched. */

/* ------------------------------------------------------------
   6.3.5 PILLS   (Figma 40004583:65632 · full-bleed 40004583:65732)
   ------------------------------------------------------------
   A cloud of sector chips under a centred title: what the house
   invests in, said as thirteen labels rather than a paragraph.
   Each chip carries one of the thirteen SECTOR icons from the
   asset set (the icon leads the label, which is the direction
   §5.4 already records for pills) and the section closes with a
   one-line "and more" note.

   TWO VARIANTS, and they are different layouts rather than two
   states of one:

     contained   40004583:65632 — 8 chips + 5 chips, both rows
                 centred inside the 1100 content box, nothing
                 clipped.
     full bleed  40004583:65732 — 13 chips per row, the rows
                 centred on a 1400 box and overflowing BOTH ends
                 (first chip at x=-225.5, last ending at
                 x=1625.5, and both rows are exactly 1851 wide so
                 both are true-centred), with the overflow faded
                 out at each edge.

   ── THE "OPACITY LAYER" IS AN EDGE FADE, NOT A SCRIM ─────────
   Worth being explicit because the name invites the other
   reading. 40004583:65768 is not over a photograph and not over a
   video: it is two 318 x 135 rounded rectangles, one at each end
   of the marquee, each a horizontal linear gradient from
   Background Core to the same colour at zero alpha
   (40004583:65769 / :65770, the second mirrored). It is the
   standard trick for making a row that overflows its container
   look deliberate. There is no scrim and no image anywhere in
   this section, so there is no scrim contrast number to report —
   the only thing the gradient sits over is chips whose own
   contrast is measured in the reference doc.

   Reproduced as two pseudo-elements painted in --ui-ground
   rather than in Background Core, which is the one place this
   section had to take a decision about the Mode axis: the file's
   gradient is a literal ground colour and would be a bone smear
   across an inverted section. --ui-ground follows the mode, so
   the fade is correct in all three. A mask would have been more
   general still — it works over art as well as over a flat
   ground — but every way of writing an opaque mask stop needs a
   literal colour keyword, and this engine does not get to use
   one. Painted gradient it is; the limitation is that this fade
   assumes a flat section ground, which is what the file draws.

   318px is a drawn value and not a step, so it is clamped rather
   than fixed: at 1400 the clamp resolves to 317.8px, which is the
   drawn value to a fifth of a pixel, and on a 393 frame it
   resolves to 89px instead of eating three quarters of the row.

   ── THE CHIP GEOMETRY IS NOT §5.4's ─────────────────────────
   Read off the instances (40004583:65641 and its twelve
   siblings), every chip in this section is 40px tall with 20px
   of inline padding. §5.4's pill is 34 and 15. The instances
   keep the component's own 12px block padding — 12 + 10 + 12 = 34
   is where the kit's control height comes from — so the 40 is an
   instance resize sitting on top of it, but it is a consistent
   one across all thirteen chips in all three variants, and the
   20px padding is not something a resize does by itself.

   Transcribed, with two constraints on how:

     · the override sets min-height and padding-inline and
       NOTHING ELSE. Neither property appears in any of §5.4's
       state rules, in .ui-pill--outline, or in §7's print
       flattening, so there is nothing here for a (0,2,0) selector
       to shadow. That is the whole of why this is safe, and it is
       why the rule carries no :not() chain — there is no state
       rule to protect. Confirmed under print emulation with
       getMatchedStylesForNode rather than assumed.
     · it goes through two section-local tokens, so an author who
       wants the kit's control height back writes
       --ui-pill-h: var(--ui-control-h) on the section instead of
       fighting a selector.

   The row gap is 5px (XXS), measured between every adjacent pair
   in both rows and between the two rows. §5.4's .ui-pill-group
   gaps at XS. Same treatment and the same argument: gap is not a
   state or a print property. A denser gap is what makes thirteen
   chips read as one object.
   ------------------------------------------------------------ */
.ui-section--pills {
  --ui-section-body-gap: var(--s-w-m);   /* 85 -> 125 in the file */
  --ui-pill-h:  40px;
  --ui-pill-px: var(--s-w-s);
  --ui-marquee-fade: clamp(var(--s-w-l), 22.7%, 318px);
}

.ui-section--pills .ui-pill {
  min-height:     var(--ui-pill-h);
  padding-inline: var(--ui-pill-px);
}
.ui-section--pills .ui-pill-group { gap: var(--s-w-xxs); }

/* The contained variant. Two centred rows; nothing else. */
.ui-pill-cloud {
  display: flex;
  flex-direction: column;
  gap: var(--s-w-xxs);
  align-items: center;
}
.ui-pill-cloud .ui-pill-group { justify-content: center; }

/* The full-bleed variant. Full-bleed the same way the intro's
   band is — cancel the section's inset — then clip. */
.ui-pill-marquee {
  position: relative;
  margin-inline: calc(-1 * var(--ui-section-inset));
  display: flex;
  flex-direction: column;
  gap: var(--s-w-xxs);
  overflow: hidden;
}
/* nowrap plus justify-center is the whole layout: a row wider
   than its box centres and overflows equally at both ends, which
   is exactly what the file's coordinates describe. No transform
   and no duplicated content. */
.ui-pill-marquee .ui-pill-group {
  flex-wrap: nowrap;
  justify-content: center;
  min-width: 0;
}
.ui-pill-marquee::before,
.ui-pill-marquee::after {
  content: "";
  position: absolute;
  inset-block: 0;
  width: var(--ui-marquee-fade);
  pointer-events: none;
}
.ui-pill-marquee::before {
  inset-inline-start: 0;
  background: linear-gradient(to right, var(--ui-ground), transparent);
}
.ui-pill-marquee::after {
  inset-inline-end: 0;
  background: linear-gradient(to left, var(--ui-ground), transparent);
}

/* Not animated. The file gives the rows no motion — the name is
   "Frame", there is no variant pair and no prototype link — so
   nothing here moves. If a real marquee is added later it needs
   duplicated content and a prefers-reduced-motion guard, neither
   of which belongs in a transcription. */

/* ------------------------------------------------------------
   6.3.6 TESTIMONIALS   (Figma 40004583:65899)
   ------------------------------------------------------------
   Three quotes side by side under a left-aligned title, with a
   page indicator under them. What the section is FOR: attributed
   client voice — the only place in the system where somebody
   else's words carry the page.

   ── THE TESTIMONIAL CARD IS §5.9's CARD. LITERALLY. ─────────
   This was worth checking rather than assuming, and the answer is
   unambiguous. get_design_context on the wrapper (40004583:65907)
   returns:

     bg Background Highlight · 1px Background/Primary 20 border ·
     radius 4 (the cards radius) · padding 5 · flex column ·
     items-start · justify-end · width 353

   That is §5.9's .ui-card rule declaration for declaration,
   including the 5px mount, the hairline, the cards radius rather
   than the component radius, and the bottom alignment. And 353 is
   four desktop columns, so the row is .ui-grid with --span:4 and
   needs no grid of its own — the same three-up / two-up / one-up
   reflow the card budget already documents.

   So .ui-quote REUSES .ui-card and adds two things:

     1. --ui-card-min-h, reassigned. That token is declared on
        .ui-card precisely so it can be reassigned; the drawn
        height is 392 (382 inner + the 5px mount twice) against
        the card's own 300 floor.
     2. cursor. §5.9 states plainly that "a card that is
        genuinely not interactive should not use this class",
        because .ui-card ships cursor: pointer for the whole-card
        link every card in that set is. A testimonial has no link
        in it — no arrow, no tag, nothing focusable — so the
        cursor is put back and the note is answered rather than
        ignored. One declaration, and it can shadow nothing:
        cursor appears in no state rule and in no print rule.

   Everything else arrives free and that is the point of reusing
   it: the inverted plate stepping of divergence 15, §7's print
   flattening, and the `.ui-card > * + *` reset that stops
   .ui-grid's owl selector shoving the card's internals apart.
   Writing a second plate would have meant writing all three
   again.

   392 is measured, not derived. The nearest sum of §1's steps is
   XXL + XL + S + XS = 390, and it is not used: the row's three
   cards have to agree with each other to the pixel and 392 is
   what the file says. Same standing as --ui-footer-link-h's 23px
   or --ui-nav-logo-h's 35px — a component height, not a spacing
   step. Applied as a min-height, so a quote longer than the
   budget grows the card instead of overflowing it.

   ── THE PANEL IS NOT AN ELEMENT ─────────────────────────────
   The file draws a 343 x 382 panel inside the mount, padded S all
   round, holding the quote at the top and the attribution at the
   bottom. That panel HAS NO FILL — it is an auto-layout wrapper,
   not a surface — so there is nothing between the card's 5px and
   the panel's 20px, and the two collapse into one 25px inset.
   Which is lucky, because the markup could not have nested them
   anyway: <figcaption> must be a direct child of its <figure>,
   so a wrapper element between them is invalid HTML. Hence
   --ui-card-pad reassigned to XXS + S and justify-content on the
   card itself, and no panel element at all. Two elements fewer
   than the Figma tree and the same pixels.

   ⚠ Reassigning --ui-card-pad is a token change, which is what
   that token is for, but it also feeds §5.9's derived well radius
   (outer radius minus the mount). A quote has no well, so nothing
   reads it. A .ui-quote that grew a media well would need the
   pad back at XXS.

   ── THE ATTRIBUTION ────────────────────────────────────────
   A 50 x 72 portrait, then the name at 100% ink over the role at
   60%, bottom-aligned with the portrait, S between the two
   columns and XS between the two lines. The name is body type,
   not a heading — 14px, cap-trimmed, one line.

   The portrait is the house halftone treatment. That treatment
   is already a brand component and the black-and-white / full
   colour split is already a per-brand token, so this engine
   consumes neither: it sizes a box and the markup adds the
   brand's portrait class to the same element. Radius is
   --radius-soft, which the contract reserves in as many words for
   "portrait placeholders only" — see the note in 6.3.7 for what
   is uncertain about that choice.
   ------------------------------------------------------------ */
.ui-quote {
  /* 392 = 382 drawn panel + 2 x the 5px mount. Measured. */
  --ui-card-min-h: 392px;
  /* The mount and the unfilled panel, collapsed: 5 + 20. */
  --ui-card-pad: calc(var(--s-w-xxs) + var(--s-w-s));
  /* Quote at the top, attribution at the bottom. .ui-card is
     justify-end / items-start; both move, neither appears in a
     state or a print rule. */
  justify-content: space-between;
  align-items: stretch;
  gap: var(--s-w-s);
  /* <figure> carries UA margins and §3.2 cannot reach a
     page-level band. */
  margin: 0;
  cursor: auto;
}

/* The quote. 60% ink — a testimonial is somebody else's voice and
   the file sets it one step back, which is the same 100/60 step
   the whole engine uses for hierarchy. Copy ink, for the reason
   given on .ui-section__lead. */
.ui-quote__text {
  margin: 0;
  font: inherit;
  color: var(--ui-ink-copy-60);
  text-wrap: pretty;
  text-box: trim-both cap alphabetic;
}

.ui-quote__who {
  display: flex;
  align-items: flex-end;      /* the file bottom-aligns these */
  gap: var(--s-w-s);
  width: 100%;
}

/* 50 x 72 in the file. Width plus aspect-ratio rather than two
   fixed lengths, so a narrower column shrinks the thumbnail
   instead of squashing the sitter. 50/72 reduces to 25/36. */
.ui-quote__portrait {
  flex: none;
  display: block;
  width: var(--ui-quote-portrait-w, 50px);
  aspect-ratio: 25 / 36;
  overflow: hidden;
  border-radius: var(--radius-soft);
  background: var(--ui-ground);
}
.ui-quote__portrait > img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.ui-quote__id {
  display: flex;
  flex-direction: column;
  gap: var(--s-w-xs);
  min-width: 0;
}
/* <cite> is italic in every UA and this one is not italic in the
   file. font-style is the only thing that needs saying. */
.ui-quote__name {
  font: inherit;
  font-style: normal;
  color: var(--ui-ink-copy);
  text-box: trim-both cap alphabetic;
}
.ui-quote__role {
  font: inherit;
  color: var(--ui-ink-copy-60);
  text-box: trim-both cap alphabetic;
}

/* ------------------------------------------------------------
   6.3.7 STATS   (Figma 40004583:66094)
   ------------------------------------------------------------
   Seven numbers in an asymmetric mosaic under a centred
   two-line title. What it is FOR: the size of the thing, said
   once, in figures.

   ── THE NUMERAL IS ON THE SCALE ────────────────────────────
   This is the question worth asking of any stats block, because a
   stat is where a brand usually breaks its own type scale, and
   here it does not. The numeral is bound to Desktop/H2:
   Concrette Regular, 64px, 100% leading, -6% tracking, Primary
   100, cap-trimmed — byte-identical to the section's own title
   style and exactly §1's --fsw-h2. Every one of the seven
   measures 45px tall, which is 64 x the 0.703 cap ratio §3.2
   records for both display faces. So the numeral wears .ui-h2
   and this section adds no type token and no size of its own.

   One consequence to be deliberate about: the numeral is display
   type at the same size as the heading above it, so it is set in
   --ui-ink and not in the copy ink the 14px prose uses. That is
   the split §2 describes — display in the brand's ink, prose in
   the copy ink — and it is the reason a stat reads as a figure
   rather than as a sentence.

   The label is a single cap-trimmed body line at 60% ink,
   centred, S under the numeral. Both are centred in both axes
   inside the tile: the file's 52.5px top and bottom offsets
   inside a 180px box are (180 - 45 - 20 - 10) / 2, i.e. the pair
   is centred, not padded.

   ── THE TILE IS §5.9's CARD AGAIN ──────────────────────────
   Same finding as the testimonial and the same evidence
   (40004583:66104): Background Highlight, 1px Primary 20, radius
   4, padding 5, column, items-start, justify-end. So .ui-stat
   reuses .ui-card, reassigns --ui-card-min-h to the drawn 190,
   and puts the cursor back. 190 IS on the scale, unlike the
   quote's 392: XL + L is the 180px inner box and the mount adds
   2 x XXS, so it is written as that sum rather than as a number.

   ── THE MOSAIC ─────────────────────────────────────────────
   The file draws it as three nested column groups, which is a
   Figma convenience rather than a structure. Flattened, the
   whole thing is ONE grid, two rows of 190 with a 20px gutter,
   four tracks wide:

     212.5 | 212.5 |      353      | 262      row 1
     212.5 | 212.5 |   (spanning)  | 262      row 2

   212.5 + 20 + 212.5 + 20 + 353 + 20 + 262 = 1100 exactly, and
   the third track's tile spans both rows: 190 + 20 + 190 = 400,
   which is the drawn height of the tall card. Seven tiles, one of
   them double-height, and the numbers are used as ratios (fr) so
   the proportions hold at any width — at 1100 they reproduce the
   drawn tracks to the pixel, because fr distributes what is left
   after the gutters and the gutters are the same 20.

   None of the four tracks is a whole number of grid columns
   (four columns is 353, which the tall tile is, and that is the
   only one that lands). So this mosaic is NOT on the page grid
   and is not written as .ui-grid. Stated rather than smoothed
   over: the widths are a composition, and forcing them onto
   twelve columns would change the drawing.

   Two re-trackings, both judgement calls, because the file has no
   small frames:

     below 640   one column. A 212px tile is 40px of padding
                 around a 64px numeral; two of them on a 333px
                 content box is 156px each and the label wraps to
                 three lines.
     640 - 1119  two equal columns, and the tall tile does NOT
                 span. A double-height tile in a two-track grid
                 leaves a hole that auto-placement fills with the
                 wrong stat.
     1120 up     the drawn mosaic.

   Markup order is READING order — first row left to right, then
   second row — which is what puts the tall tile third. Auto
   placement then reproduces the drawing with no explicit
   line numbers: the tall tile takes column three of both rows and
   the last tile of row two flows past it into column four.
   ------------------------------------------------------------ */
.ui-stats {
  /* 190 = the 180px inner box (XL + L) inside the 5px mount. */
  --ui-stat-min-h: calc(var(--s-w-xl) + var(--s-w-l) + var(--s-w-xxs) * 2);
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-auto-rows: minmax(var(--ui-stat-min-h), auto);
  gap: var(--grid-gutter);
  /* <dl> carries UA block margins and §3.2 cannot reach a
     page-level band. */
  margin: 0;
  padding: 0;
}

@media (min-width: 640px) {
  .ui-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1120px) {
  .ui-stats {
    /* The drawn track widths, as ratios. */
    grid-template-columns: 212.5fr 212.5fr 353fr 262fr;
  }
  .ui-stat--tall { grid-row: span 2; }
}

/* Same collapse as the quote, and the same two reasons: the
   file's inner "Text container" has no fill, so 5 + 20 is one
   25px inset; and <dt>/<dd> must be direct children of the <div>
   that groups them inside a <dl>, so a wrapper element between
   the tile and the pair would be invalid.

   The drawn 180px inner height is not set either — the tile's
   min-height plus centring produces it, and a label that wraps to
   two lines then grows the tile rather than escaping it. */
.ui-stat {
  --ui-card-min-h: var(--ui-stat-min-h);
  --ui-card-pad:   calc(var(--s-w-xxs) + var(--s-w-s));
  align-items: center;
  justify-content: center;
  gap: var(--s-w-s);
  text-align: center;
  cursor: auto;
}

/* The numeral is the <dd> and the label the <dt>, so the pair is
   associated by the description list itself rather than by
   proximity — a stat's label is its accessible name and a screen
   reader reads "portfolio companies, fourteen". The file draws
   the numeral ABOVE the label, which is the opposite of the
   required DOM order, so the numeral is re-ordered visually. One
   property, and it is the smallest honest way to have both: the
   markup keeps term-before-definition and the render keeps
   figure-over-label.

   No font declarations: the numeral wears .ui-h2 and must keep
   every part of that recipe. */
.ui-stat__value {
  order: -1;
  margin: 0;
  color: var(--ui-ink);
}
.ui-stat__label {
  margin: 0;
  font: inherit;
  color: var(--ui-ink-copy-60);
  text-box: trim-both cap alphabetic;
}

/* ------------------------------------------------------------
   6.3.8 TEAM   (Figma 40004583:67169 · card 40004583:67181)
   ------------------------------------------------------------
   A horizontal run of people under a left-aligned title, with
   the page indicator ABOVE the run and right-aligned. Four cards
   are drawn in an 1100 box that holds three, and the fourth is
   clipped at the section's own right edge — so the run is a
   carousel, and the clipped card is the affordance.

   ── THE TEAM CARD IS NOT §5.9's CARD ───────────────────────
   Also worth checking rather than assuming, and here the answer
   goes the other way. 353 is the same width as a card — it is
   four desktop columns, same as everything else in this file —
   but 40004583:67181 has:

     no plate          (no fill at all)
     no border         (no hairline)
     no radius         (on the card; the portrait has its own)
     no mount          (no 5px padding; the portrait is flush to
                        all three of its edges and full width)
     no bottom align   (plain column flow, top down)
     a 353 x 450 portrait, i.e. a PORTRAIT ratio well, against
     the card's 7/5 landscape one

   Putting .ui-card on that would add a plate, a hairline and a
   5px inset the file does not draw, and would then need all four
   overridden — six declarations to fight a component instead of
   four to write one. It is a different thing that happens to be
   the same width, so it is its own block. .ui-card's own notes
   support the split: a card is a mount holding a recessed well,
   and this is the opposite arrangement — art at page level with
   type hanging off the bottom of it.

   ── THE TRAILING LINK IS A DISCLOSURE ──────────────────────
   The four cards are not four copies. Card one is 608 tall, shows
   a four-line bio, and its Link carries a CROSS. Cards two to
   four are 524 tall, their bio nodes are hidden="true", and their
   Links carry a PLUS. Read as an interaction that is
   unambiguous — plus opens the bio, cross closes it — and it is
   the same pair, drawn the same way, as §6.1.3's burger and
   cross: the cross is a 13px plus rotated -45 degrees in an
   18.385px box inside a 24px icon box, which is the geometry the
   sprite's cross symbol already has resolved.

   So both glyphs ship in the markup and the section swaps them,
   exactly as the navbar does. Two differences from the navbar,
   both deliberate:

     · the bio is VISIBLE by default and [data-state="closed"]
       hides it. The navbar's panel is furniture and starts shut;
       a bio is content, and a page that ships no JavaScript must
       not hide its content behind a control that cannot move.
     · there is no popover and no :popover-open hook. A bio is
       inline content in the flow of the card, not an overlay, so
       the top layer is the wrong place for it. That does mean
       this is the one control in the engine that needs script to
       be honest: aria-expanded must track the state and no CSS
       can write it. §6.1.3's argument, same conclusion — the
       markup contract in the reference doc gives the button form
       and a <details> alternative for a page with no script.

   ── THE CARRIAGE ───────────────────────────────────────────
   A scroll-snapping flex row. It bleeds past the section's inline
   end — the file clips the fourth card at the section edge, 150px
   beyond the content box — and pays the inset back as padding so
   the last card can still be scrolled fully into view. Both are
   expressed against --ui-section-inset, so a nested section (where
   the inset is 0) simply gets no bleed.

   Native overflow, native snap, no JavaScript, and the scrollbar
   is left visible on purpose: it is the only affordance in this
   component that a keyboard or a mouse can use today, since the
   arrows above need script. The cards' own links keep the track
   reachable by tabbing.

   Card width: the drawn 353 capped at 85% of the visible track,
   so a phone shows one card and the edge of the next instead of
   one card that is wider than the screen. The 85% is invented;
   353 is drawn.

   The portrait's radius is --radius-soft, and this is the one
   measurement in the whole slice I could not resolve. The file
   draws 4px, and on the brand these frames are drawn in, all
   three candidate radii ARE 4px — the two named web radii and
   the contract's --radius-soft — so the cell cannot distinguish
   them. --radius-soft is chosen because the contract reserves it
   in as many words for "portrait placeholders only", because the
   house's own portrait component already uses it, and because a
   30px radius on a 50x72 thumbnail (the testimonial case, same
   choice) is a capsule rather than a photograph. If design meant
   the portraits to follow the brand radius, this is a two-line
   change and it needs asking.
   ------------------------------------------------------------ */
.ui-section--team {
  --ui-person-w: min(353px, 85%);
}

.ui-person-row {
  display: flex;
  align-items: flex-start;
  gap: var(--grid-gutter);
  min-width: 0;
  /* Bleed to the section's inline end, then pay it back inside
     the scroller so the last card can reach the viewport. */
  margin-inline-end: calc(-1 * var(--ui-section-inset));
  padding-inline-end: var(--ui-section-inset);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
}

.ui-person {
  flex: 0 0 var(--ui-person-w);
  display: flex;
  flex-direction: column;
  align-items: stretch;
  min-width: 0;
  scroll-snap-align: start;
}

/* 353 x 450 in the file. Ratio rather than a fixed height, for
   the same reason as §5.9's well: a narrower column must crop the
   sitter, not letterbox the layout. */
.ui-person__portrait {
  display: block;
  width: 100%;
  aspect-ratio: 353 / 450;
  overflow: hidden;
  border-radius: var(--radius-soft);
  background: var(--ui-plate);
}
.ui-person__portrait > img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* S of block padding and S between the name row and the bio,
   both drawn (40004583:67183: py 20, gap 20). */
.ui-person__text {
  display: flex;
  flex-direction: column;
  gap: var(--s-w-s);
  padding-block: var(--s-w-s);
}

/* Name and role left, the toggle pinned right, in a 34px row —
   the row IS a 34px Link box in the file, and the min-height
   keeps it 34 whether the toggle is present or not. Same
   reasoning as §5.9's .ui-card__head. */
.ui-person__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-w-xs);
  min-height: var(--ui-control-h);
}
.ui-person__id {
  display: flex;
  flex-direction: column;
  gap: var(--s-w-xs);
  min-width: 0;
}
/* The name is General/H4 — 20px, 120%, weight 300 — so it wears
   .ui-h4 and only its colour is set here. Note weight 300 is
   what §3.2 already gives .ui-h4; the "one weight heavier"
   divergence the card notes record does not apply. */
.ui-person__name { color: var(--ui-ink-copy); }
.ui-person__role {
  font: inherit;
  color: var(--ui-ink-copy-60);
  text-box: trim-both cap alphabetic;
}

/* The toggle is §5.3's Link at Icon=only, in the 34px box the
   file draws (h 34, padding 5). The 10px radius on the node is
   ignored for §6.1.1's reason: it is on a frame with no fill, it
   draws nothing, and 10 is not a brand radius. */
.ui-person__toggle {
  flex: none;
  box-sizing: border-box;
  min-width: var(--ui-control-h);
  min-height: var(--ui-control-h);
  padding: var(--s-w-xxs);
  justify-content: center;
}
/* Two classes, not one, and it is load order rather than taste:
   the brand house layer loads AFTER this engine and its icon
   class sets a 1.5em box at (0,1,0). A single-class rule here
   ties and loses, which is what rendered the navbar's burger at
   21px instead of 24. 24px is the sprite's own drawing grid. */
.ui-person .ui-person__icon {
  flex: none;
  width: var(--ui-nav-icon, 24px);
  height: var(--ui-nav-icon, 24px);
}

/* Glyph swap. TWO CLASSES ON EVERY ONE OF THESE, for the reason
   §6.1.3 records after losing exactly this fight in the navbar:
   the brand's icon class sets `display: inline-block` at (0,1,0)
   and loads AFTER this engine, so a one-class `display: none`
   here ties and loses, and the card renders the plus AND the
   cross with one of them hanging out of the 34px box.

   Order matters between the second and third rules and the first:
   `.ui-person .ui-person__icon--open` and
   `.ui-person[data-state="closed"] .ui-person__icon--open` are
   both (0,2,0), so the closed state has to come second. */
.ui-person .ui-person__icon--open  { display: none; }
.ui-person[data-state="closed"] .ui-person__icon--open  { display: block; }
.ui-person[data-state="closed"] .ui-person__icon--close { display: none; }
.ui-person[data-state="closed"] .ui-person__bio         { display: none; }

.ui-person__bio {
  margin: 0;
  font: inherit;
  color: var(--ui-ink-copy-60);
  text-wrap: pretty;
  text-box: trim-both cap alphabetic;
}

/* ------------------------------------------------------------
   6.3.9 THE CARD-FAMILY HEAD MODIFIERS
   ------------------------------------------------------------
   Shared by all seven sections in this fragment, and by the
   other section families: anything that wants an H2, an intro
   and a content block reuses this rather than redeclaring it.
   ------------------------------------------------------------ */
.ui-section {


  /* The gap between the heading block and the content is §3.1's
     own flow gap, reassigned. Not a new rule: §3.1 already owns
     one owl selector per flow container and adding a second here
     would mean two rules fighting over the same margin.

     .ui-section and .ui-page land on the same element and both
     are (0,1,0), so this depends on load order — which is safe
     for the same reason §5.9's `.ui-card > * + *` reset is: this
     block is declared after §3.1 inside ui-core.css, and after
     ui-core.css when it is loaded as a fragment. Same trick, and
     the same caveat: it is order, not specificity. */
  --ui-flow: var(--ui-section-lead);
}


/* Alignment = start. The Gallery (65070), and the only structural
   difference in that frame's Title: the block is left-aligned and
   the H2's measure widens to 910. Ten desktop columns computes to
   913.3; the file draws 910, and 3px on a display measure does not
   earn a formula. */
.ui-section__head--start {
  --ui-section-align: start;
  --ui-section-center: 0;
  /* 910 of the 1100 content box, and the shell already declares it
     as that fraction so it survives all three grids. Restated here
     only because a centred head narrows the title to the lead
     measure and a start-aligned one does not. */
  --ui-section-title-max: 82.73%;
}


/* The intro. Wears .ui-body; the measure, the 60% ink and the trim
   are here.

   Cap-trimmed at both ends for the same reason §5.9's card text
   block is: the file's 40px and 195px are measured to cap and
   baseline, and without the trim the block reads ~3px taller at
   each end and the 40px gap opens to 43.

   ⚠ INK: --ui-ink-60, not --ui-ink-copy-60. get_variable_defs on
   all seven frames returns Primary 100 / Primary 60 and no Black
   ramp, so the file binds section prose to the ink. That keeps
   this consistent with §5.9's card body — which matters more than
   it sounds, because a card sits DIRECTLY BELOW this paragraph in
   five of the seven sections, and on the one brand where the two
   ramps differ, splitting them would put near-black intro copy
   above orange card copy in the same band. It is also the same
   open question integration note 14 already records; one decision
   settles both places, and it is not this file's to make. Cost
   on that brand: 1.96:1. */
.ui-section__intro {
  max-inline-size: var(--ui-measure);
  margin-inline: var(--ui-section-center, auto);
  color: var(--ui-ink-copy-60);
  text-box: trim-both cap alphabetic;
}



/* The pager row. In the file this is a "Pages Navigation" frame
   holding a Link, an Active Page Number and a Link on 5px gaps —
   which is §5.8's own gap, so the arrows go INSIDE the .ui-pager
   nav in the markup and this class only has to place the row.

   Right-aligned in the four sections that page a grid, centred
   under the Gallery's rail. A modifier rather than
   `.ui-section--gallery .ui-section__pager`, so the reach stays at
   zero levels.

   min-height is the control height for exactly the reason §5.9
   gives on .ui-card__head: in the file this row IS two 34px Link
   instances with the numerals between them, and §5.3's .ui-link
   sets no height at all, so the row rendered 19px tall and every
   section with a pager came out 15px shorter than its frame
   (measured: 932 against 946, 732 against 747). Stating the row's
   height fixes all four at once and does not touch the atoms.

   ⚠ Worth reporting rather than fixing here: the arrows are still
   19px tall inside that 34px row, and §4's coarse-pointer target
   growth covers .ui-pager__item but not .ui-link. A bare arrow is
   a small touch target on a phone. That is §5.3's to answer. */
.ui-section__pager {
  min-height: var(--ui-control-h);
  align-self: flex-end;
}
.ui-section__pager--center { align-self: center; }

/* ------------------------------------------------------------
   6.3.10 CARDS / STACKED   (Figma 40004583:64044 images,
                            40004583:64089 icons,
                            40004583:64104 text)
   ------------------------------------------------------------
   Two rows of three cards under the heading block, no pager: the
   set is complete and on the page. Reach for it for a fixed set
   of 4-6 — strategies, services, the three things a page is
   about. Use the Aligned version when the set continues.

   The three nodes differ ONLY in which card type fills them, and
   the card is §5.9's, unchanged. So the entire section is the
   grid, and the grid is §3's, with one property changed.
   ------------------------------------------------------------ */

/* Co-class on the .ui-grid element, not a descendant selector, so
   this cannot reach a nested grid an author writes inside a cell.

   row-gap: the file's vertical gap between two Cards Rows is 20px,
   the same value as the gutter — a square 20px mesh, not §3's
   40px row rhythm. §3's default is right for a page of unlike
   blocks; a card grid is one object and 40px pulls it apart.

   --span:4 on the children because 353px IS four desktop columns
   (§5.9) and writing it here saves six inline styles. It sets a
   custom property and nothing else — §3's `.ui-grid > *` rule is
   what reads it — so an author's inline --span still wins, and
   nothing here can shove a component's internals the way
   `.ui-grid > * > * + *` once did. Mobile ignores --span (§3,
   divergence 9) and cards go full width, which is the documented
   1-per-row case. */
.ui-section__cards { row-gap: var(--grid-gutter); }
.ui-section__cards > * { --span: 4; }

/* ------------------------------------------------------------
   6.3.11 CARDS / ALIGNED   (Figma 40004583:64532)
   ------------------------------------------------------------
   One row of three cards and a right-aligned pager. Reach for it
   when the set is longer than the page — a portfolio, an index,
   news — and the row is a window onto it.

   NO DECLARATIONS, and that is the finding rather than an
   omission. See the Stacked/Aligned note in the header: the axis
   is row count plus a pager, both of which live in the markup, so
   .ui-section--cards-aligned is a name for the pattern and a
   place for a future divergence to land. Same standing as
   §5.9's .ui-card--text.

   ⚠ The pager is a nav of real links. The engine has no
   JavaScript and there is no CSS that can page a grid, so those
   links must point at real URLs (or be wired by the page). A
   pager that does nothing is worse than no pager.
   ------------------------------------------------------------ */

/* ------------------------------------------------------------
   6.3.12 COLORFUL CARDS   (Figma 40004583:64351)
   ------------------------------------------------------------
   The Aligned and Stacked sections' bigger sibling: heading
   block, a FILTER BAR, two rows of cards, a pager. Reach for it
   for a browsable index — the one section in this family where
   the reader is expected to narrow the set rather than read it.

   The name is the card, not the section: the six cells are
   §5.9's .ui-card--text .ui-card--colorful with the outline tag
   (divergence 20's note about the text cells), and one cell is
   drawn hovered, so the whole accent-filled card is on show. The
   section itself adds no colour at all — every rule below is the
   filter bar.

   ⚠ Two things carried over from §5.9 rather than restated: the
   Colorful set exists for two brands only and cannot be enforced
   here, and Colorful's 60% knockout body is the one pairing the
   build does not check (divergences 18, and the contrast table).
   A filterable index of colourful cards is the place where both
   turn up six at a time.
   ------------------------------------------------------------ */

/* The bar: a count line, then a row of filter chips with a search
   collapsed at the far end. Two stacked pieces on an S gap
   (drawn: the count's baseline at 10, the control row at 30). */
.ui-section__filter {
  display: flex;
  flex-direction: column;
  gap: var(--s-w-s);
}

/* "Showing 25". Wears .ui-body; 60% ink because it is a status
   line about the set, not part of it. Cap-trimmed so the S gap
   below it is the drawn 20 and not 23 — the file's text node is
   10px tall, which is a 14px cap height and nothing else. */
.ui-section__count {
  color: var(--ui-ink-copy-60);
  text-box: trim-both cap alphabetic;
}

/* Chips left, search right. space-between is what the file draws
   (chips at x=0, the 34x34 collapsed search at x=1066 of 1100),
   and it wraps because a real filter set is longer than three.
   At 393 the drawn set plus the search is 331px against a 333px
   content column — it fits by 2px, and the fourth chip wraps. */
.ui-section__filter-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-w-s);
}

/* The chip set. XXS, which is tighter than both .ui-pill-group
   (XS) and .ui-row (S) — and it should be: those two space
   INDEPENDENT controls, and this is one control with several
   values. 5px is what the file draws (89px chips at x=0, 94, 188).
   Not a third generic row helper; it is this section's chip set. */
.ui-section__filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-w-xxs);
}

/* ------------------------------------------------------------
   6.3.13 COLUMNS   (Figma 40004583:64719)
   ------------------------------------------------------------
   Three columns of type on the page ground — a tag, an H4 and a
   paragraph — with a pager under them. No plate, no well, no
   border: this is the section to reach for when the content is
   PROSE and a card would be a lie about it. A card is a link to
   somewhere else; a column is text you read here. That is the
   whole distinction, and it is why the file draws the two
   patterns at the same 353px width with the same tag on top.

   Same grid as §6.3.2, same --span:4, same 20px gutter. The
   column's own metrics are the card's text block minus the mount:
   S between the tag and the title, S between the title and the
   body, and the type roles are §3.2's own — .ui-h4 at full ink,
   .ui-body at 60%.
   ------------------------------------------------------------ */
.ui-section__column {
  display: flex;
  flex-direction: column;
  align-items: flex-start;   /* the tag is a chip, not a bar */
  gap: var(--s-w-s);

  /* The file insets the column's TEXT by 20 on the right (a 333px
     measure in a 353px column) while the tag sits flush at 0. The
     effect is 40px of air between one column's last word and the
     next column's first — the gutter doubled — which is what
     stops three columns of running text reading as one paragraph
     with gaps in it. Inline-end only, exactly as drawn.

     The 20px at the BOTTOM is the file's too (its text container
     is 144 tall for 124 of content) and it is load-bearing rather
     than dead air: the pager sits S below the ROW, so without it
     the drawn distance from the last line to the pager halves.
     It also doubles the row gap to M when the columns wrap at
     the tablet width, which is the right rhythm for two stacked
     blocks of prose. */
  padding-inline-end: var(--s-w-s);
  padding-block-end: var(--s-w-s);
}

/* Title and body. Both wear a §3.2 type role and both are
   cap-trimmed here for §5.9's reason — the file's 20px gaps are
   measured cap to baseline, and untrimmed they open to 23-24.
   Co-classed like .ui-card__title / .ui-card__body, which is the
   same pattern for the same reason: the trim and the ink belong to
   the composition, the face and the size to the role.

   ⚠ .ui-h4 renders one weight heavier here than in the file, for
   the reason divergence 17 already records. Not re-fixed locally. */
.ui-section__column-title { text-box: trim-both cap alphabetic; }
.ui-section__column-body  {
  color: var(--ui-ink-copy-60);
  text-box: trim-both cap alphabetic;
}

/* ------------------------------------------------------------
   6.3.14 GALLERY   (Figma 40004583:65070)
   ------------------------------------------------------------
   A full-bleed rail of image frames centred on one active frame,
   with a centred pager under it. Reach for it for pictures that
   are the content — people, places, work — where a card's title
   and body would be furniture around them.

   Two things make it the odd one out in this fragment, and both
   are visible in the drawing:

     · its heading block is LEFT-aligned (§6.3.1's --start), the
       only one of the seven that is;
     · its content is NOT on the grid. The five frames are
       353 + 4 x 259 on 20px gaps = 1469px, centred on the 1100
       column, so 184.5px hangs off each side — 34.5px of it past
       the 1400 artboard, where the file cuts it. That is the
       design: a rail that bleeds, with the pager as the control.

   ── THE FRAME SIZES ──────────────────────────────────────────
   Active 353 x 450, inactive 259 x 330, and the second pair is
   not an independent measurement: 330 is 450 inset by L top and
   bottom (450 - 2 x 60), and 259 is the same 11/15 applied to the
   width. So the inactive frame is the active one squeezed by one
   spacing step, which is why they share a ratio to three decimal
   places. 353 is §5.9's four-column card width; 450 is drawn.

   ── THE BLEED, AND THE ONE DEPARTURE IN THIS FRAGMENT ────────
   The file's rail is CENTRED on the active frame and bleeds
   symmetrically. The first version here transcribed that
   literally — `justify-content: center` on a scroll container —
   and it is broken, which the render showed and no amount of
   reading would have:

     a centred flex line in a scroller produces NO scrollable
     overflow on its leading side. Measured in Chromium at three
     widths: at 1440 scrollWidth was 1454 against 1468 of content
     and scrollLeft ranged 0..14, so the leading 14px was gone.
     At 834 that is 277px gone and at 393 it is 498px — the first
     frame of five entirely invisible AND unreachable by mouse,
     touch, keyboard or screen reader. Two of five pictures, in a
     section whose content IS the pictures.

   Symmetric bleed and reachable content are mutually exclusive
   in CSS: every way of centring an overflowing line (center,
   safe center, auto margins on a max-content track) either drops
   the leading overflow or falls back to start. So this ships the
   half that can be made honest — the rail starts on the grid,
   bleeds the TRAILING edge only, and scroll-snaps:

     · nothing is unreachable at any width;
     · the frame sizes, the 20px gaps and the L inset are the
       file's, unchanged;
     · --active is the emphasised frame, which in a start-aligned
       rail is normally the first one.

   What is lost is the symmetry of the drawing, and it is lost
   because the drawing is a RUNTIME STATE — "item 3 of 5, page 2
   of 3" — that no stylesheet can hold. The engine has no
   JavaScript (§6.1 made the same call for the mobile menu and
   got the popover API to cover it; there is no equivalent here).
   A page that ships a script can restore the drawn composition
   exactly by setting the rail's scrollLeft; the pager is where
   that script belongs, since those links have to be wired
   anyway. Worth a design conversation — the alternative reading
   is that the outer frames are decoration, and if design says so
   they should be `aria-hidden` duplicates and the whole problem
   goes away.
   ------------------------------------------------------------ */
.ui-section--gallery {
  /* The active frame's width, clamped to the content column so the
     rail bleeds on a desktop and merely fills a phone. % resolves
     against the rail (the full-bleed width), so subtracting two
     grid margins gives the content column at every width: 353 at
     1440 and 834, 333 at 393. No vw units — they include the
     scrollbar and would put the document 15px over. */
  --ui-gallery-w: min(353px, calc(100% - 2 * var(--grid-margin)));
}

.ui-section__rail {
  display: flex;
  align-items: center;          /* the L inset above and below the
                                  inactive frames, as centring */
  gap: var(--grid-gutter);      /* 20px, drawn */

  /* Trailing bleed. The negative margin is exactly --grid-margin,
     so it cancels .ui-page's padding and the rail's border box
     ends at the viewport edge — the document does not grow, at
     any width, which is the thing to check after touching this.
     The matching padding puts the rail's CONTENT box back on the
     grid, so the last frame can reach the content margin when the
     rail is scrolled to its end instead of stopping against the
     glass. */
  margin-inline-end: calc(var(--grid-margin) * -1);
  padding-inline-end: var(--grid-margin);

  overflow-x: auto;
  overscroll-behavior-inline: contain;
  scroll-snap-type: x mandatory;
}

/* A frame. The inactive size is the default because four of the
   five are inactive.

   Plate: --ui-plate, and this is a decision rather than a
   transcription. The file fills the placeholder with Background
   BASE on an artboard whose ground is Background CORE — the same
   artboard convention divergence 2 corrects for every control in
   §5, one step off from a real page. Read through that
   correction, "one step of separation from the ground" is
   --ui-plate, which is also what §5.9 paints an empty card well
   in. Worth noting that unlike the card well (divergence 13)
   this one reads on ALL FOUR brands: the two that collapse Core
   onto Highlight still keep Base and Core apart (measured in the
   brand packs for §6.1's plate model), and this well sits on the
   ground rather than on a card.

   In production the frame holds a photograph, so the plate is
   what shows while it loads and behind a transparent PNG. */
.ui-section__frame {
  box-sizing: border-box;
  position: relative;
  flex: none;
  inline-size: calc(var(--ui-gallery-w) * 11 / 15);
  aspect-ratio: 353 / 450;
  background: var(--ui-plate);
  border-radius: var(--radius-component);
  overflow: hidden;
  /* start, not center: the rail is start-aligned, so a centre snap
     would fight it and leave the first frame unreachable again. */
  scroll-snap-align: start;
}

/* State = Active: the same frame at full size. Drawn at 4px on the
   near-square brands, so --radius-component and not the cards
   radius — checked on the placeholder node itself, where the
   corner resolves over about 3px. */
.ui-section__frame--active { inline-size: var(--ui-gallery-w); }

/* The picture. Same contract as §5.9's card image: it fills and
   crops, never letterboxes, and the markup supplies the file —
   nothing here names an asset. */
.ui-section__image {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* No radius: the frame's overflow already clips it, and a second
     radius doubles the corner. */
}

/* ------------------------------------------------------------
   6.3.15 LIST + SCORECARD + FORM TOKENS
   ------------------------------------------------------------
   Mobile-first, like §1 and §6.2.1: these are the phone values
   and the 640 query raises them. Everything that could be a
   spacing step is one; there are only four real numbers below and
   each says why it is not a step.
   ------------------------------------------------------------ */
:root {

  /* THE READING MEASURE. 540px, and it is the single most reused
     number in the six frames — the section lead paragraph, the
     Long Form prose column, the Contact form column and the CTA
     lead are all exactly 540 wide. It is also exactly six desktop
     columns (6 x 73.33 + 5 x 20), so it is grid-derived rather
     than typed.

     ⚠ 540px of 14px text is 84 characters per line, measured in
     Chromium with the licensed face (see the content budget in
     the reference doc). The comfortable range is 45–75. The file
     is out of range and this engine transcribes it anyway,
     because it is the drawn measure and it is on the grid — but
     it is a token so a page can lower it, and lowering it to
     ~430px (five columns) is what would bring it into range at
     this type size. Reported rather than silently corrected. */
  /* 540 at desktop, as drawn — 84 characters a line, a shade wide.
     The tablet frames draw 430, which is 67 characters and squarely
     inside the readable 45–75 range; the phone column is narrower than
     either cap so neither binds there. Measured, not reasoned. */
  --ui-measure: 430px;

  /* The display title's own measure, and it IS a set width rather
     than a consequence: the H2 text node is 910px in all three
     sections that carry one, including List - Expanded where the
     string is "How We Build" and could not need it. Roughly ten
     columns; not exactly one, so it stays a measured px value. */

  /* The gap between two blocks of running text, and between a
     subtitle and the paragraph under it. M everywhere, with no
     exceptions in any of the six frames — see the note in 6.3.3
     about why that means prose does NOT use .ui-flow. */
  --ui-prose-flow: var(--s-w-m);

  /* Row pitch in both lists: 20px gap between rows, drawn
     identically in Condensed (0/218/646 at heights 198/408/200)
     and Expanded (0/259/518/777 at height 239). */
  --ui-list-gap: var(--s-w-s);

  /* The Condensed row's illustration well. 260px is the residue
     of the row — 633 (7 cols) less the 353 text column (4 cols)
     less the 20 gutter — so it is grid-derived but not a column
     count. Its height is the illustration's own 118px plus 2xM of
     padding, which is why the ratio is expressed rather than a
     height: the well is a mount, the drawing sets the height. A
     brand exporting its illustrations on a common box should set
     the ratio to that box. */
  --ui-list-well-w: 260px;
  --ui-list-well-ratio: 260 / 198;

  /* The Expanded row's image. 353 x 239 — 353 is four desktop
     columns, the same width as a §5.9 card, and 239 is the drawn
     row height. The ratio is the file's, not derived. */
  --ui-list-media-w: 353px;
  --ui-list-media-ratio: 353 / 239;

  /* The horizontal gap between two controls inside a section:
     XXS, five pixels. Measured on every control row in the set —
     the two Condensed tags (x=0, 94 at width 89), the three
     Expanded filter tags (0, 94, 188), the four Contact chips
     (0, 128, 258, 379) and the three CTA buttons (421, 513, 607).

     ⚠ It is tighter than anything in §5: .ui-pill-group is XS and
     .ui-row is S. Not an inconsistency to normalise — the
     sections group controls that are members of ONE control (a
     filter set, a choice set, one CTA), which is what
     .ui-pill-group already argues for, just one step tighter. */
  --ui-chip-gap: var(--s-w-xxs);

  /* The scorecard's panel padding, and the dot pitch inside it.
     The dots are §5.8's geometry exactly: --ui-dot on a 19px
     pitch, i.e. a 5px XXS gap, four of them in 71px. */
  --ui-scorecard-pad: var(--s-w-s);

  /* The Contact Form's message field. 150px drawn, against
     §5.6's `calc(var(--ui-control-h) * 3)` = 102px. Not a step
     and not derivable; see the note in 6.3.10 about why it is
     overridden here rather than in §5.6. */
  --ui-contact-area-h: 150px;
}
/* …and widens to the drawn 540 at the desktop frame. Declared here,
   directly after the block above, rather than in §1's own 1120 query:
   both are `:root` at (0,1,0), so whichever comes LAST in the file
   wins, and §6.3.1 is far below §1. Keeping the pair adjacent means the
   breakpoint cannot be silently reversed by moving a section. */
@media (min-width: 1120px) {
  :root { --ui-measure: 540px; }
}


@media (min-width: 640px) {
  :root {
    /* 80px. Two M, deliberately not typed as 80. */
  }
}


/* Nested rather than page-level: the shell already paid the
   inline margin, so the section must not pay it twice. Copied
   from §6.2.2, where the same guard exists for the same reason. */
.ui-page .ui-section { padding-inline: 0; }

/* Sections butt flush. Each one carries its own 120px of block
   padding, which is what the file draws, so §3.1's 40px flow gap
   between two of them would be 40px nobody asked for. Two classes
   against §3.1's one, so this wins on specificity and not on load
   order. */
.ui-page > .ui-section + .ui-section,
.ui-flow > .ui-section + .ui-section { margin-top: 0; }

/* ── THE BASE-ELEMENT RESET, AND WHY IT IS REPEATED HERE ─────
   §3.2 reclaims the bare elements — tokens.css styles `p`, `h1`,
   `h2`, `h3` at the DECK scale, 23px body, and an element
   selector beats inheritance. But §3.2's selector is scoped
   `:where(.ui-page, .ui-flow) :where(…)`, and a section that
   paints a ground has to be page-level (see the header), i.e.
   OUTSIDE .ui-page. Without this rule the first bare <p> in a
   sibling section renders at 23px and the block comes out ~52px
   too tall per paragraph — divergence 12, in the one place §3.2
   cannot reach.

   `:where()` on both halves, so the whole thing is (0,0,0) and
   always loses to .ui-copy, to .ui-h4 and to anything an author
   writes. A plain `.ui-section p` would be (0,1,1) and would
   out-specify every type role in the engine.

   Wider element list than §3.2's: these sections introduce real
   lists, a <dl>, a <details>/<summary> disclosure and a
   <fieldset>/<legend>, and those carry UA padding as well as UA
   type — hence `padding: 0`, which §3.2 does not need. `button`
   is deliberately absent: §5 owns it. */
:where(.ui-section) :where(h1, h2, h3, h4, h5, h6, p, ul, ol, li, dl, dt, dd,
    blockquote, figure, figcaption, address, label, legend, fieldset,
    details, summary, th, td, caption) {
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
  margin: 0;
  padding: 0;
}




/* ------------------------------------------------------------
   6.3.3 RUNNING TEXT: THE MEASURE, THE FLOW, AND THE COPY ROLE
   ------------------------------------------------------------
   .ui-prose is the reading column, and it is the same object in
   all six sections: the section lead, the Long Form article body
   and the Apollo intro are one component used three times.

   ── IT DOES NOT USE .ui-flow, AND THAT IS A FINDING ──────────
   §3.1's rhythm gives the block AFTER a heading a small gap (S),
   "because it belongs to that heading". The file does not: every
   gap in the Long Form prose column is exactly M, including the
   one under a Subtitle. Measured, nine gaps in a row —
   46→86→100→140→222→262→273→313→359→399→410→450→496→536→836→876
   →958→998, every step 40.

   That is not a contradiction of §3.1 so much as a case §3.1 does
   not cover: its big-gap-above rule names only .ui-h1/.ui-h2/
   .ui-h3, the DISPLAY headings, and a Long Form subtitle is an
   H4 or an H5 — body face, 20px and 16px, distinguished from
   their paragraphs by size alone (divergence 13). Prose set
   entirely in one face wants one gap.

   So the prose block carries its OWN owl selector rather than
   opting into .ui-flow. Written that way round on purpose: if it
   opted in, this fragment would have to override §3.1's h4/h5
   rule at matching specificity and would win only on load order,
   which is the fragile half of §3.1's own lesson. One level of
   reach, direct children only, container says so — §3.1's rule
   for every section, applied to itself.

   ── THE MEASURE ─────────────────────────────────────────────
   max-width, not width. In the Condensed head the prose lives in
   a 353px column and the 540 cap never binds; on a phone the
   column is 333px and it never binds either. It only does its job
   at tablet and desktop, which is where it is needed. */
.ui-prose {
  /* GAP, not an owl selector, and this is a bug fix rather than a
     preference. The rule used to be `.ui-prose > * + * { margin-top:
     … }` at (0,1,0) — and `.ui-copy { margin: 0 }` a dozen lines
     below it is also (0,1,0), so the later declaration won and every
     paragraph in every prose column in §6.3 computed `margin-top:
     0px`. Six sections, silently unspaced. Measured on this engine's
     own verified proof page before it was believed.

     Reordering the two rules would fix it and would be fragile for
     exactly the reason §3.1 gives: order, not specificity. A flex gap
     is not a margin at all, so no text role can reset it however
     specific it gets — which is also why §6.3.2's head and body use
     one. Subtitles kept their gap throughout, because §3.2 precedes
     the owl; that is why this survived a render check. */
  display: flex;
  flex-direction: column;
  gap: var(--ui-prose-flow);
  max-width: var(--ui-measure);
  min-width: 0;
}
/* The one cost of using flex for the rhythm: flex items stretch, so a
   button at the end of a prose column came out the full width of the
   column instead of hugging its label. Caught in the CONTENT render.
   Paragraphs DO want to stretch, so this is scoped to the interactive
   children rather than solved with align-items. `:where()` keeps the
   selector at (0,1,0) so an author can still override it, and no atom
   rule in §5 sets align-self, so nothing is being fought. */
.ui-prose > :where(a, button) { align-self: flex-start; }

/* Bulleted and numbered lists in running text. Nothing styled these
   before, so a plain `<ul>` inside a prose column got the browser's
   40px indent and its own list marker on a page whose every other
   rhythm is on the web spacing scale.

   The rhythm is drawn: the Bullet Points frames (40004583:68000 /
   :68001, and their small siblings 40004589:14434 · :14437 · :14440 ·
   :14443) put **S between items and XXS within a pair** at both small
   sizes. Item-to-item is therefore S, one step tighter than the prose
   flow's M, because a list is one object where paragraphs are several.

   The marker is a text bullet at the ink, indented by S so the text
   block hangs at the prose measure rather than being pushed inside it.
   `list-style-position: outside` with a matching padding is what keeps
   a wrapped second line aligned with the first, which the browser
   default does not do at this indent. */
:where(.ui-prose) > :where(ul, ol) {
  display: flex;
  flex-direction: column;
  gap: var(--s-w-s);
  margin: 0;
  padding-inline-start: var(--s-w-s);
  list-style-position: outside;
}
:where(.ui-prose) > :where(ul) { list-style-type: disc; }
:where(.ui-prose) > :where(ol) { list-style-type: decimal; }
:where(.ui-prose) > :where(ul, ol) > li { padding-inline-start: var(--s-w-xxs); }
/* display:list-item is what draws the marker, and `display: flex` on the
   same element would remove it. The pair rule below therefore targets a
   wrapper, not the li itself — see the note there. */
:where(.ui-prose) > :where(ul, ol) > li::marker { color: var(--ui-ink-60); }

/* A pair — a term line and its detail — sits at XXS, the tightest step
   on the scale, because the two lines are one item. Drawn that way in
   every bullet frame.

   A GAP, not a margin, and for the third time in this file: the text
   roles carry `margin: 0` at (0,1,0), and a zero-specificity selector
   loses to that however correct it is. Measured 0px before this was a
   gap. Any spacing rule in this engine that a text role could reset
   should be a gap. */
/* Specificity, deliberately, rather than :where(). The first attempt
   wrote this at (0,0,0) and it lost to `.ui-copy { margin: 0 }` —
   measured 0px. The second used a flex gap on the li, which fixed the
   spacing and REMOVED THE BULLET, because a flex container is no longer
   a list-item. Seen in the render, not reasoned.

   `.ui-prose li > p + p` is (0,1,3): it beats the text role's (0,1,0),
   it leaves `display: list-item` alone so the marker stays, and an
   author can still override it with one class plus an element. Not every
   spacing rule can be a gap — the ones on an element whose `display`
   is load-bearing have to win on specificity instead. */
.ui-prose li > p + p,
.ui-prose li > span + span {
  margin-top: var(--s-w-xxs);
}
/* A subtitle inside prose. §3.2's .ui-h4/.ui-h5 already carry the
   right face, size and weight-300 (divergence 13) and `margin: 0`;
   only the ink moves, for the reason in the header.

   `:where()` around the class list zeroes its contribution, so
   the selector is (0,1,0) — the same specificity as .ui-h4
   itself, winning on load order and losing to any colour class an
   author puts on the element. A plain `.ui-prose > .ui-h4` would
   be (0,2,0) and would out-rank the author. */
.ui-prose > :where(.ui-h4, .ui-h5) { color: var(--ui-ink-copy); }

/* ── The copy role ───────────────────────────────────────────
   §3.2's .ui-body with one substitution: the ink. It is a
   separate role rather than an override of .ui-body because the
   difference is exactly the open half of divergence 14 and it
   should be visible in the markup which ink a string is on.

   `font: inherit` is not decoration. Every text role in §6.2 has
   it and the note there records what it cost to find out: a
   <p class="ui-copy"> that merely inherited 14px from the section
   would be overridden by tokens.css's bare `p` at 23px, because
   an element selector beats inheritance. §5's components dodge
   this by re-declaring the whole type recipe; this one inherits
   one setting from its shell, so it has to say so. The
   zero-specificity reset in 6.3.2 also covers it — this is the
   belt to that braces, and both are cheap. */
.ui-copy {
  margin: 0;
  font: inherit;
  letter-spacing: inherit;
  color: var(--ui-ink-copy);
  text-wrap: pretty;
}

/* The dimmed step, and it works as a <span> INSIDE a paragraph as
   well as on the paragraph itself. Both forms are in the file:
   Condensed and Apollo draw a full-ink lead paragraph followed by
   a dimmed one, while List - Expanded draws a single text node
   whose first clause is full ink and whose remainder is 60% —
   which is primitives.css's bold-then-dim device, in the web
   medium.

   ⚠ NOT primitives.css's .dim, which is --c-muted (a 40% PRINT
   tint, reached through a --c-* token) and therefore neither the
   right step nor Mode-aware. */
.ui-copy--dim { color: var(--ui-ink-copy-60); }

/* ── The term/value pair list ────────────────────────────────
   A real <dl>, and one component used in three sections: Apollo's
   four bullet pairs, Long Form's byline (author/role, label/date)
   and the Contact Form's three contact blocks. Identical
   treatment in all three — term at full copy ink, value at 60% —
   and identical geometry: the value sits on the NEXT LINE with no
   gap at all (measured 18px between the two cap boxes, which is
   14px x 130% exactly, i.e. the natural line pitch), and the
   groups are separated by a real gap.

   That gap is the one thing that moves: S in Apollo and Long Form
   (48px pitch on a 28px group), M in the Contact Form (68px pitch
   on the same 28px group). So it is a variable with the majority
   value as its default. */
.ui-deflist {
  --ui-deflist-gap: var(--s-w-s);
  margin: 0;
  font: inherit;
  letter-spacing: inherit;
}
.ui-deflist > dt {
  margin: 0;
  font: inherit;
  letter-spacing: inherit;
  color: var(--ui-ink-copy);
}
.ui-deflist > dd {
  margin: 0;
  font: inherit;
  letter-spacing: inherit;
  color: var(--ui-ink-copy-60);
}
/* Before each term except the first. `dd + dt` rather than a
   :not(:first-child), so a group with three values (the Contact
   Form's address) gets one gap and not four. */
.ui-deflist > dd + dt { margin-top: var(--ui-deflist-gap); }

/* ── A tight control row ─────────────────────────────────────
   §5.4 ships .ui-pill-group at XS and .ui-row at S. The sections
   draw every control row at XXS (see --ui-chip-gap). Rather than
   change either of those — both are transcribed from §5 and both
   have their own reasoning — this is a third, tighter row, and it
   is a modifier of .ui-row so it inherits the wrap and the
   align-items and adds nothing but the gap.

   `.ui-row--tight` is (0,1,0), the same as .ui-row, and loads
   later. It only touches `gap`, which .ui-row has no state rule
   for, so there is nothing to lose. */
.ui-row--tight { gap: var(--ui-chip-gap); }

/* ------------------------------------------------------------
   6.3.16 THE LIST STACK — shared by both list sections
   ------------------------------------------------------------
   A real <ul>. The 20px gap is drawn identically in both
   sections, and the stack is the ONLY thing the two lists share
   — see the note at the top of 6.3.5.
   ------------------------------------------------------------ */
.ui-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--ui-list-gap);
  min-width: 0;
}

/* ------------------------------------------------------------
   6.3.17 LIST — CONDENSED   (Figma 40004583:67366)
                            sub-part "List" 40004583:67373, 633x847
   ------------------------------------------------------------
   ⚠ CONDENSED AND EXPANDED ARE TWO COMPONENTS, NOT ONE WITH A
   MODIFIER, and reading them the other way is the trap the frame
   names set. 1087 against 1605 is not the same content at two
   densities:

     Condensed  a two-column section — an H3 title block in four
                columns on the left, a 7-column ACCORDION on the
                right. Each row is an illustration well plus tags,
                a title and a paragraph that is HIDDEN until the
                row is opened. Its toggle is a plus that becomes a
                cross. It has no filter and no pager.
     Expanded   a full-width H2 title block, then a FILTER bar
                (a result count, three filter tags and the §5.5
                search bar), then rows that are photograph +
                title + category + description + a trailing arrow
                and are whole-row LINKS, then a §5.8 pager. No
                disclosure anywhere.

   The proof is inside Condensed itself: node 40004583:67419, the
   second row's Paragraph, is VISIBLE while 67394 and 67460 in
   rows 1 and 3 carry `hidden="true"`. Condensed is drawn with one
   of its three rows already open, and that open row is 408px tall
   against its siblings' 198. So "Expanded" is not the expanded
   state of "Condensed" — Condensed contains its own expanded
   state. Exactly the shape of §6.1's "Closed Button" finding: the
   Figma names the thing by what it is, and the natural
   interaction reading of the pair is wrong.

   They keep the file's names so a reader can find them, and the
   reference doc says all of this again in the first paragraph.

   ── GEOMETRY ────────────────────────────────────────────────
     section     head 353 (4 cols) at column 1, list 633 (7 cols)
                 at column 6. Column 5 is empty — that is the
                 gutter, and it is a whole column wide.
     row         well 260 | 20 | text 353 (4 cols)
     well        M padding around a 180x118 illustration
     text        top row 34 (tags left, toggle right)
                 → S → title (H4, cap-trimmed 14)
                 → S → the panel
     row height  max(well, text). 198 collapsed, 408 open.

   ── THE 40px UNDER THE COPY ─────────────────────────────────
   The text container is 108 tall on a collapsed row whose content
   is 68, and 408 on an open row whose content is 368. Forty pixels
   below the last line, in every row, open or closed. It is carried
   on the TEXT block and not on the row, because the row's height
   comes from the illustration well and padding on the row would
   lengthen the well with it. Net effect, which is what the source
   draws: 60px between one row's copy and the next row's tags.

   ── THE DISCLOSURE ──────────────────────────────────────────
   <details>/<summary>, and no JavaScript, for the same reasons
   §6.1.3 gives: a real accessible name, implicit aria-expanded on
   the invoker, keyboard activation from Enter and Space, and no
   attribute this engine would have to lie about. §6.1.3 rejected
   <details> for the NAVBAR because it wanted Escape and light
   dismiss from a popover; an accordion wants neither, and
   <details> is the native control for exactly this.

   Two consequences, stated rather than hidden:

     · The row title is NOT a heading. <summary> permits heading
       content, but the accessible name of a disclosure is its
       summary and a heading nested inside one is announced
       inconsistently across screen readers. A page that needs
       these rows in its heading outline should wrap each row in a
       section with its own heading, or move to the scripted
       button + aria-expanded pattern.
     · The summary's accessible name includes the tags, because
       they are inside it and they are real content: "Category
       Category Stage 1: Idea Validation". Informative, and
       better than hiding metadata from a screen reader to tidy
       up a string.

   The tags MUST be <span>, not <button>: an interactive control
   inside a summary is the same invalid-and-unreachable problem
   §5.9 documents for the card's tag.
   ------------------------------------------------------------ */

/* The two-column head/list split. Grid rather than flex, because
   the empty fifth column is a real grid column and
   `grid-column-start` says so in one declaration where a flex
   basis would need arithmetic. Single column below 640 — no
   tablet frame exists, so this is judgement; a 353px head beside
   a 633px list needs 1006px of content box and the tablet frame
   has 794. */
.ui-section--list-condensed .ui-section__body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  row-gap: var(--s-w-m);
  column-gap: var(--grid-gutter);
}
@media (min-width: 1120px) {
  .ui-section--list-condensed {
    /* One block, so the head is not a separate band with 80px
       under it — it is the left column. The section's own gap
       therefore has nothing to space and would only add air above
       the row. */
    gap: 0;
  }
  .ui-section--list-condensed .ui-section__body {
    grid-template-columns: repeat(var(--grid-cols), minmax(0, 1fr));
  }
  .ui-section--list-condensed .ui-section__head { grid-column: 1 / span 4; }
  .ui-section--list-condensed .ui-list        { grid-column: 6 / span 7; }
}

/* The row. align-items: start so the illustration well keeps its
   own height instead of stretching to an open row's 408px, which
   is what the file draws. */
.ui-list--condensed .ui-list__row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--s-w-s);
  min-width: 0;
}

/* The illustration well. Outline-only — sampled off the source at
   a transparent fill with a 20%-ink hairline and the component
   radius (measured 22px on a brand whose --radius-component is
   20 and whose --radius-button is 30, so the token is decidable
   here, unlike everywhere else in this fragment).

   flex-basis rather than width, and a 100% floor, so the well
   drops to the row's full width when the row wraps on a phone. */
.ui-list--condensed .ui-list__well {
  box-sizing: border-box;
  flex: 0 1 var(--ui-list-well-w);
  min-width: 0;
  max-width: 100%;
  aspect-ratio: var(--ui-list-well-ratio);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s-w-m);
  background: transparent;
  border: var(--ui-border-w) solid var(--ui-ink-20);
  border-radius: var(--radius-component);
}
/* The illustration is a per-brand asset carrying its own accent,
   plate and ink — supplied by the markup and only boxed here.
   Same contract as §5.9's .ui-card__glyph, and deliberately not
   `display`: an engine rule has no business setting display on
   brand-supplied art (§6.1.1). */
.ui-list__glyph {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
}

/* The text column. 353px basis — four desktop columns, the same
   measure as a card — and it takes the residue when the row is
   narrower. */
.ui-list--condensed .ui-list__disclosure {
  flex: 1 1 353px;
  min-width: 0;
  /* The 40px under the copy. See the note above. */
  padding-block-end: var(--s-w-m);
}

/* The summary: two rows, tags-and-toggle then title, at the S gap
   the file draws (top row 0..34, title cap box at 54).

   list-style: none plus the ::marker rule kills the UA disclosure
   triangle in every engine that has ever shipped one; `display`
   has to be reset too, because a summary is `display: list-item`
   by default and a list-item cannot be a flex container. */
.ui-list__summary {
  display: flex;
  flex-direction: column;
  gap: var(--s-w-s);
  list-style: none;
  cursor: pointer;
}
.ui-list__summary::-webkit-details-marker { display: none; }
.ui-list__summary::marker { content: ""; }

/* Tags left, toggle hard right, on the 34px control line. The
   min-height is the control height because in the file this row
   IS a 34px Tag beside a 34px Link, and a row that loses its tags
   must not lose its height. Same argument as §5.9's card head. */
.ui-list__top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-w-xs);
  min-height: var(--ui-control-h);
}
.ui-list__tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ui-chip-gap);
  min-width: 0;
}

/* The toggle. §5.3's Link at Icon=only, at its resting 60% ink,
   and it is a <span> because the <summary> is already the
   control — the same rule §5.9 states for the card's arrow.
   aria-hidden in the markup: a plus glyph is not a label, and the
   summary's own name already says what opens.

   The 34px box is §5.3's, kept so the glyph lands where the file
   puts it (the Icon frame is 74 wide with the 34px Link flush
   right at x=40). */
.ui-list__toggle {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--ui-control-h);
  height: var(--ui-control-h);
  color: var(--ui-ink-copy-60);
}
.ui-list__summary:hover .ui-list__toggle { color: var(--ui-ink-copy); }

/* Glyph swap: plus closed, cross open. Two rules, not one list,
   for §6.1.3's reason — a selector list containing an unknown
   pseudo-class is invalid as a whole, and [data-state] is the
   static hook that has to survive on its own so a screenshot can
   show an open row.

   Two classes on the hiding rule, again §6.1.3's reason: the
   brand house layer loads AFTER this engine and its .bf-icon sets
   `display: inline-block` at (0,1,0), so a one-class
   `display: none` here ties and loses. */
.ui-list__summary .ui-list__icon--close { display: none; }
.ui-list__disclosure[open] .ui-list__icon--open,
.ui-list__disclosure[data-state="open"] .ui-list__icon--open { display: none; }
.ui-list__disclosure[open] .ui-list__icon--close,
.ui-list__disclosure[data-state="open"] .ui-list__icon--close { display: inline-block; }

/* The row title. .ui-h4 in the markup, so only the ink and the
   wrapping are here. One class, same element, (0,1,0) — no
   descendant selector into the type role. */
.ui-list__title {
  color: var(--ui-ink-copy);
  overflow-wrap: break-word;
  text-box: trim-both cap alphabetic;
}

/* The panel. S below the summary's title, then prose at its own M
   rhythm. Capped at the measure like every other reading column,
   which at the drawn 353px never binds — it is there for a row
   that gets a wider column. */
.ui-list__panel {
  margin-top: var(--s-w-s);
  max-width: var(--ui-measure);
}
.ui-list__panel > * + * { margin-top: var(--ui-prose-flow); }

/* ------------------------------------------------------------
   6.3.18 LIST — EXPANDED   (Figma 40004583:67746)
   ------------------------------------------------------------
   ⚠ THE ROW IS §5.9's CARD, LAID ON ITS SIDE, WITHOUT THE MOUNT.
   Not a metaphor — read the contract off the two nodes and they
   are the same object:

     media well + a Tag absolutely positioned S from the well's
     top-left corner + an H4 title + a 60% body + a 34px Link at
     Icon=only + the whole thing is one link + and its HOVER
     changes exactly one thing, the tag, from a punched-back plate
     with a 60% label to a solid accent plate with knockout ink.

   The hover is transcribed, not inferred: row 2 of the file
   (40004583:67782) is drawn with its tag filled in the accent, the
   brand's Accent, while rows 1, 3 and 4 draw the same tag at
   Background Base. One row in its hover state, which is how §5.9
   read the card set too.

   What it does NOT have is the card's mount: no plate, no border,
   no radius on the row itself. So it cannot BE a .ui-card —
   §5.9's plate, its 300px min-height and its 5px padding are all
   wrong here, and §5.9 says outright that a card is a mount and
   the well is the mounted thing. It reuses the card's TOKEN
   NAMES and its hover shape instead, so the two agree and a
   change to one is an obvious change to the other.

   ── GEOMETRY ────────────────────────────────────────────────
     filter    count (60% copy) → S → [3 tags | search bar], 34px
     list      S under the filter, rows at a 20px gap
     row       image 353 (4 cols) | 20 | text container 727 (8)
     text      title (H4) → S → category → S → paragraph (447, 5
               cols) with the 34px arrow flush right of the 727
     pager     S under the list, right-aligned, XXS gaps

   ── THE FILTER'S ACTIVE STATE IS THE FILLED AXIS ────────────
   Sampled: tag 1 has a plate and a full-ink label, tags 2 and 3
   are transparent with a 20% border and a 60% label. That is
   §5.2's Filled=Yes/Active beside Filled=No/Inactive — the file
   uses the Filled axis AS the selected signal. So the markup is
   `.ui-tag .ui-tag--filled` + aria-pressed="true" on the current
   filter and a bare `.ui-tag` + aria-pressed="false" on the
   others, and this fragment adds no CSS for it at all.
   ------------------------------------------------------------ */

/* The filter bar. space-between, so the search sits hard right at
   any width and the count/tag block takes the rest — which is
   what the file's x=1066 for a 34px search in an 1100 box means.
   No breakpoint: the bar is three tags and a 34px square. */
.ui-list-filter {
  display: flex;
  flex-direction: column;
  gap: var(--s-w-s);
  min-width: 0;
}
.ui-list-filter__count {
  margin: 0;
  font: inherit;
  letter-spacing: inherit;
  color: var(--ui-ink-copy-60);
  text-box: trim-both cap alphabetic;
}
.ui-list-filter__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-w-s);
  min-height: var(--ui-control-h);
}

/* S between the filter and the list, and between the list and the
   pager. The section's own 80px gap is for head→body; inside the
   body these three bands are 20 apart, measured (filter 0..64,
   list 84..1100, pager 1120). */
.ui-section--list-expanded .ui-section__body {
  display: flex;
  flex-direction: column;
  gap: var(--s-w-s);
}

/* The row. It is the link, so text-decoration and cursor are
   stated here for the same reason §5.9 states them: the title
   must not take the anchor's underline, and a row built on a
   <div> with a scripted activation must behave like the anchor
   version.

   position: relative is the containing block for the tag. */
.ui-list--expanded .ui-list__row {
  --ui-list-media-plate: var(--ui-plate);
  --ui-list-tag-plate:   var(--ui-ground);
  --ui-list-tag-ink:     var(--ui-ink-copy-60);

  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--s-w-s);
  min-width: 0;
  color: var(--ui-ink-copy);
  text-decoration: none;
  cursor: pointer;
}

/* The image well. 353px basis — four desktop columns, exactly a
   §5.9 card's width — expressed as a ratio rather than the drawn
   239px height for §5.9's reason: a fixed height letterboxes the
   photograph in a narrow column, and the well's ratio is the
   designed ratio. */
.ui-list--expanded .ui-list__media {
  position: relative;
  box-sizing: border-box;
  flex: 0 1 var(--ui-list-media-w);
  min-width: 0;
  max-width: 100%;
  aspect-ratio: var(--ui-list-media-ratio);
  overflow: hidden;
  background: var(--ui-list-media-plate);
  border-radius: var(--radius-component);
}
/* The photograph bleeds and is cropped, never letterboxed — the
   markup supplies the file, nothing here names one. No radius of
   its own: overflow: hidden already clips it and a second radius
   would double the corner. Same as §5.9's .ui-card__image. */
.ui-list__image {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The text container: text block left at 447 (five columns), the
   arrow flush right of the 727. */
.ui-list--expanded .ui-list__body {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-w-s);
  flex: 1 1 353px;
  min-width: 0;
}
.ui-list__text {
  display: flex;
  flex-direction: column;
  gap: var(--s-w-s);
  flex: 1 1 auto;
  min-width: 0;
  max-width: var(--ui-measure);
}

/* The category line. Full copy ink, not 60% — sampled at full
   Primary in the source, which puts it level with the title
   rather than with the description under it. Worth noticing
   rather than normalising: title and category are the row's
   identity, the paragraph is its blurb. */
.ui-list__meta {
  margin: 0;
  font: inherit;
  letter-spacing: inherit;
  color: var(--ui-ink-copy);
  text-box: trim-both cap alphabetic;
}

/* The arrow. §5.3's Link at Icon=only in a 34px box, and a
   <span> because the row is already the link — §5.9 again. The
   file draws it at full ink (the exported glyph is solid Primary
   in every cell), one step up from Links' resting 60%, so it
   matches the title beside it. */
.ui-list__arrow {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--ui-control-h);
  height: var(--ui-control-h);
  color: var(--ui-ink-copy);
}

/* The tag. Two-class selector for §5.9's exact reason:
   `.ui-mode--alternative .ui-tag` in §5.2 is also (0,2,0), and a
   single-class rule here would lose its plate to it and the tag
   would vanish into the well in Alternative mode.

   ⚠ Must be a <span>, not a <button>: the row is the link.

   Offset S from the well's outer edge. Unlike §5.9 there is no
   border to subtract — the row has no mount. */
.ui-list__row .ui-list__tag {
  position: absolute;
  inset-block-start: var(--s-w-s);
  inset-inline-start: var(--s-w-s);
  background: var(--ui-list-tag-plate);
  border-color: transparent;
  color: var(--ui-list-tag-ink);
}

/* State = Hover. Two token reassignments consumed by the tag, and
   nothing on the row moves — §5.9's card hover, verbatim. :hover
   and [data-state="hover"] only, NOT :focus-visible: firing the
   hover appearance on focus makes a keyboard user think they have
   already activated the row. */
.ui-list--expanded .ui-list__row:hover,
.ui-list--expanded .ui-list__row[data-state="hover"] {
  --ui-list-tag-plate: var(--ui-fill-active);
  --ui-list-tag-ink:   var(--ui-ink-on-accent);
}

/* Colour crossfade only. The tag is listed separately because it
   changes through inherited custom properties, and a custom
   property change does not transition on its own — the element
   that PAINTS the value has to carry the transition. §5.9's note,
   same trap. */
.ui-list__row, .ui-list__tag, .ui-list__media {
  transition: background-color var(--dur-fast) var(--ease),
              border-color     var(--dur-fast) var(--ease),
              color            var(--dur-fast) var(--ease);
}
@media (prefers-reduced-motion: reduce) {
  .ui-list__row, .ui-list__tag, .ui-list__media { transition: none; }
}

/* §4's ring, repeated for one new selector. Not re-invented — it
   is copied, three layers and all, because §4's selector list was
   written before this component existed and cannot name it. On
   merge, add .ui-list__row and .ui-choice__option to §4's list
   and delete this rule and its twin in 6.3.10. */
.ui-list__row:focus-visible {
  outline: 3px solid var(--c-accent);
  outline-offset: 2px;
  box-shadow: 0 0 0 2px var(--ui-ground),
              0 0 0 6px var(--ui-ink-20);
}

/* The pager band. Right-aligned, XXS gaps, on the 34px line —
   the file draws a hidden previous-arrow Link, the §5.8 pager and
   a next-arrow Link at x=970/1009/1066 in an 1100 box, i.e. 5px
   between each. */
.ui-list__pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--ui-chip-gap);
  min-height: var(--ui-control-h);
}

/* ------------------------------------------------------------
   6.3.19 SCORECARD   (Figma 40004583:67993, drawn as
                      "Apollo Framework"; the diagram itself is
                      40004583:68014)
   ------------------------------------------------------------
   ⚠ THE NAME IN THE FILE IS A PIECE OF CONTENT, NOT A COMPONENT.
   "Apollo" is one house's proprietary operating-system framework
   and "Team / Processes / Resources" are its three dimensions.
   Ship a class called that and the design system owns a component
   that can only ever hold one thing.

   What the frame actually DRAWS, with the content taken out, is a
   maturity scorecard, and that is generic:

     a head bar   an entity name (H3) and a single headline figure,
                  justified apart on one 33px line
     n panels     each a titled group (H4) of labelled rows
     each row     a label and a FOUR-STEP METER

   Three panels of five, five and four rows is the drawn case, not
   a constraint; the section takes whatever the markup ships.
   Named .ui-scorecard here, and the reference doc records that
   its only instance in the source is one brand's framework — which
   is the honest way round: the shape is a component, the content
   is not.

   ── GEOMETRY, and every number falls out ────────────────────
     head       1100 x 73 = S padding + a cap-trimmed 33px H3
     panels     353.33 each at a 20px gutter — four desktop
                columns, exactly a §5.9 card
     panel pad  S all round (313.33 content in a 353.33 panel)
     group title 49 tall = XXS above + the H4's own untrimmed 24px
                line box + S below. Measured to the pixel: the cap
                box lands at y=10 and 5 + 24 + 20 = 49.
     rows       34 — the control height — with the label's 18.2px
                line box centred in it, which puts the cap box at
                y=12.08 against the file's 12.
     rules      a 20%-ink hairline under the group title and under
                every row, INCLUDING the last one in each panel.
     meter      four --ui-dot circles on a 19px pitch, 71px total,
                flush right. §5.8's dot geometry exactly, which is
                why nothing new is measured here.

   ── THE FOUR SYSTEM COLOURS THE CONTRACT DOES NOT HAVE ──────
   The meter is the one place in this chunk that needs a colour
   the token contract cannot supply. get_variable_defs on 68014
   returns four variables that appear nowhere in tokens.css:

     Colors/System Red · System Orange · System Yellow ·
     System Green

   They look house-wide rather than per-brand (two of the four
   values are another sub-brand's ink and another's accent), so
   they belong in the CONTRACT as a fifth colour group, alongside
   the ink and accent ramps — not in a brand pack and certainly
   not here. This engine reads --c-system-* and does not declare
   them; the generator has to emit them. Reported in the reference
   doc, not patched.

   Until it does, all four fall back to full ink, which degrades
   the meter to §5.8's dot pager: N solid dots out of four. The
   count still reads, which is the load-bearing half of the
   information; only the red/amber/green reading is lost.

   ── AND THE RAMP IS THE MARKUP'S JOB, BECAUSE THE FILE IS NOT
      SELF-CONSISTENT ────────────────────────────────────────
   Sampled across all fourteen meters in the frame, the filled
   dots run red → orange → yellow → green by POSITION with a grey
   tail, which reads as a four-step progress ramp. Three rows
   break it: they are drawn red + yellow with the orange step
   skipped. So either the ramp is positional and three cells are
   hand-tuned, or each dot carries an independent status and the
   monotone pattern is a coincidence in eleven of fourteen cases.

   The file does not say which, so the engine does not decide: a
   dot is empty unless the markup gives it a step class. That is
   also the only version that can express both readings.
   ------------------------------------------------------------ */
.ui-scorecard {
  --ui-scorecard-plate: var(--ui-plate-up);
  --ui-scorecard-edge:  var(--ui-ink-20);

  display: flex;
  flex-direction: column;
  gap: var(--s-w-s);
  min-width: 0;
}

/* --- Mode = Inverted -----------------------------------------
   EXTRAPOLATION, flagged as one — there is no Mode axis on this
   section. It is §5.9's inverted card reassignment and it is here
   for §5.9's measured reason: the inverted plates are TINTS, so a
   panel on the 40% plate is the brightest surface in the mode and
   is where the inverted ink stops reading on the brand whose ink
   is a saturated mid-tone (2.14:1 on 40% against 2.71:1 on 20%).
   §2 is explicit that an inverted component's resting fill is the
   20% tint. */
.ui-mode--inverted .ui-scorecard { --ui-scorecard-plate: var(--ui-plate); }

/* The head bar and a panel are the same plate, so they share one
   rule. --radius-component rather than --radius-button: the file
   cannot decide it — the frame is drawn on a brand whose two
   radii are both 4px, so the two readings are the same pixels —
   and a scorecard panel is a plate, not a card or a button.
   §5.9's card had a rounded-brand cell to read; this does not. */
.ui-scorecard__head,
.ui-scorecard__group {
  box-sizing: border-box;
  background: var(--ui-scorecard-plate);
  border: var(--ui-border-w) solid var(--ui-scorecard-edge);
  border-radius: var(--radius-component);
  padding: var(--ui-scorecard-pad);
  min-width: 0;
}

/* Name left, figure right, on one line. The head is the one place
   in this fragment where DISPLAY type appears outside a section
   title, and it stays on --ui-ink for the reason in the header:
   display type carries the brand. */
.ui-scorecard__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-w-s);
}
/* Wears .ui-h3. Both of these zero §3.2's descender margin, which
   is right in flow and wrong inside a padded bar — the file
   measures 20px below a cap-trimmed 33px box and the margin would
   add 30px to it. One class, same element, (0,1,0). */
.ui-scorecard__name,
.ui-scorecard__score { margin: 0; }
.ui-scorecard__score { text-align: end; }

/* The panels. One column below 640, then the drawn row of three.
   No tablet frame exists: at 834 three equal panels are 251px
   each, which holds a 16-character label plus the 71px meter with
   room to spare — checked in the render, not reasoned about. */
.ui-scorecard__groups {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--grid-gutter);
}
@media (min-width: 640px) {
  .ui-scorecard__groups {
    grid-template-columns: repeat(var(--ui-scorecard-cols, 3), minmax(0, 1fr));
  }
}

/* The group title. XXS above, S below, hairline under — see the
   geometry note for where 49px comes from. Wears .ui-h4, so only
   the ink, the padding and the rule are here. */
.ui-scorecard__group-title {
  padding-block: var(--s-w-xxs) var(--s-w-s);
  border-bottom: var(--ui-border-w) solid var(--ui-scorecard-edge);
  color: var(--ui-ink-copy);
}

/* The rows. A real <dl> would be defensible, but a scorecard row
   is a label and a RATING rather than a term and a definition, so
   it is a list of rows and the meter carries its own accessible
   name. */
.ui-scorecard__lines {
  list-style: none;
  margin: 0;
  padding: 0;
}
.ui-scorecard__line {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-w-s);
  min-height: var(--ui-control-h);
  border-bottom: var(--ui-border-w) solid var(--ui-scorecard-edge);
}
.ui-scorecard__label {
  min-width: 0;
  color: var(--ui-ink-copy-60);
  overflow-wrap: break-word;
}

/* ── The meter ───────────────────────────────────────────────
   §5.8's dot geometry, reused rather than re-measured: --ui-dot
   on an XXS gap is the 19px pitch, and four of them come to the
   71px the file draws. The dots are decorative — the meter
   carries role="img" and an aria-label with the score, so a
   screen reader gets "2 of 4" instead of four anonymous circles.
   A filled circle is not a number; §5.8 says the same thing
   about its own dot form. */
.ui-meter {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: var(--ui-chip-gap);
}
/* Empty is the default, and it is --ui-ground: the dot is punched
   back to page level through the panel it sits on, which is what
   §5.9 does with the card's tag and what the file draws here
   (Background Base inside a Highlight panel). */
.ui-meter__dot {
  flex: none;
  width: var(--ui-dot);
  height: var(--ui-dot);
  border-radius: var(--radius-pill);
  background: var(--ui-ground);
}
/* The four steps. Named for the source's own variables so a value
   can be traced back without a lookup table. Each falls back to
   full ink, not to a neighbour, so an un-generated contract gives
   a monochrome N-of-4 rather than three shades of nearly the
   same thing. */
.ui-meter__dot--red    { background: var(--c-system-red,    var(--ui-ink)); }
.ui-meter__dot--orange { background: var(--c-system-orange, var(--ui-ink)); }
.ui-meter__dot--yellow { background: var(--c-system-yellow, var(--ui-ink)); }
.ui-meter__dot--green  { background: var(--c-system-green,  var(--ui-ink)); }

/* ------------------------------------------------------------
   6.3.20 LONG FORM TEXT   (Figma 40004583:68737,
                           prose column 40004583:68750, 540x1032)
   ------------------------------------------------------------
   The article layout, and the section this whole fragment is
   named after: a three-line H2 across the full 1100, then a
   byline column in four columns and the PROSE COLUMN in the last
   six.

   ── THE MEASURE, WHICH WAS THE QUESTION ─────────────────────
   540px, and it is not a guess: "Long Text Placeholder" is
   drawn 540 wide, the section lead in three other frames is 540
   wide, and the Contact Form's field column is 540 wide. It is
   also exactly six desktop columns, which is why it is the
   engine's --ui-measure and not a per-section number.

   The prose column starts at x=560 in the content box, which is
   the start of column SEVEN (6 x 93.33). So the article is
   set in columns 7–12 with 5 and 6 left empty, and the byline in
   1–4. Two empty columns of gutter, drawn deliberately: the
   Condensed section leaves one, this leaves two.

   14px across 540px measures 84 characters per line in Chromium
   with the licensed face. 45–75 is the readable range, so the
   file is 12% over the top of it. Transcribed anyway — see the
   note on --ui-measure for what would fix it and why this engine
   does not.

   ── FLOW ────────────────────────────────────────────────────
   Every gap in the prose column is M, including under a subtitle;
   see 6.3.3 for the measurements and for why prose does not use
   .ui-flow.

   Head → body is L (60) here, not the 80 the other sections use:
   the H2 is the ARTICLE's title rather than a section heading, and
   the file draws 40 to the content-body frame plus that frame's
   own 20px of padding. Measured, and on-scale, unlike 80.
   ------------------------------------------------------------ */
.ui-section--longform { --ui-section-lead: var(--s-w-l); }

.ui-section--longform .ui-section__body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  row-gap: var(--s-w-m);
  column-gap: var(--grid-gutter);
}
@media (min-width: 1120px) {
  .ui-section--longform .ui-section__body {
    grid-template-columns: repeat(var(--grid-cols), minmax(0, 1fr));
  }
  .ui-longform__aside { grid-column: 1 / span 4; }
  .ui-longform__prose { grid-column: 7 / span 6; }
}

/* The byline column. A .ui-deflist and a button, S apart — the
   file draws the Share button 20px under the last value. */
.ui-longform__aside {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s-w-s);
  min-width: 0;
}

/* An inline figure in the prose. The well is a plate rather than
   the recess the file draws, for the reason in the header: the
   file's Background Base is one step BACK from its Core section
   ground, and there is nothing below Base on a Base ground. The
   drawn 540x300 is expressed as a ratio for §5.9's reason. */
.ui-longform__figure {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-w-m);
}
.ui-longform__well {
  position: relative;
  box-sizing: border-box;
  width: 100%;
  aspect-ratio: 9 / 5;
  overflow: hidden;
  background: var(--ui-plate);
  border-radius: var(--radius-component);
}
/* The caption, at 60% copy — sampled at 60% in the source, one
   step behind the prose above it. It is a <figcaption>, so the
   picture and its caption are one object to a screen reader. */
.ui-longform__caption {
  margin: 0;
  font: inherit;
  letter-spacing: inherit;
  color: var(--ui-ink-copy-60);
  text-wrap: pretty;
}

/* ------------------------------------------------------------
   6.3.21 CTA   (Figma 40004583:68842)
   ------------------------------------------------------------
   427px tall and the only section in the set that paints anything
   other than the page ground.

   ── THE GROUND IS THE ACCENT ────────────────────────────────
   Sampled off the rendered frame: the accent, which is the brand's
   Accent Color and nothing else in the palette. So this is an
   accent BAND — the loudest surface in the whole medium, louder
   than the Colorful card's hover, because it is 427px of it.

   Everything on it is knockout: the H2 and the lead are both
   Background Core, which is exactly --ui-ink-on-accent (§2 —
   light on the three light-ground brands, dark on the fourth), so
   the section needs one declaration and no per-brand knowledge.

   ── THE BUTTONS ARE Mode = ALTERNATIVE, AND THAT IS FREE ────
   The three buttons are drawn with a Background Core plate and an
   ACCENT label. That is not a new appearance and it is not the
   Button component's Default cell — it is its ALTERNATIVE cell,
   exactly (§5.1: "Alternative: rests on the plate in ACCENT ink").
   Sampled to confirm: plate Background Core, label the accent.

   So the button row wears .ui-mode--alternative and this fragment
   writes NO button CSS at all. Three things follow for free:

     · a fully specified hover — §5.1's Alternative hover lifts
       the plate to Highlight and keeps the accent ink, which is
       the only sane hover on an accent ground. The Default hover
       would have filled the button with the accent, i.e. with the
       colour of the band behind it, and the control would have
       disappeared. That is divergence 3's failure, and reaching
       for the Mode class avoids it rather than patching it.
     · §7's print flattening still wins, because there is no
       (0,2,0) plate rule here to out-specify it.
     · it degrades correctly on the two brands that bind accent to
       ink: the band becomes the ink ground and the buttons are a
       plate with an ink label.

   ── CENTRED, AND MEASURED AS CENTRED ────────────────────────
   The title text box is 722 wide at x=189 in an 1100 box (189 +
   722 + 189) and the lead is 540 at x=280 (280 + 540 + 280).
   Symmetric to the pixel in both, and the button row is centred
   on x=550. So the CTA is the one centred section in the set.

   ── CONTRAST, AND IT IS THE WORST NUMBER IN THIS CHUNK ──────
   --ui-ink-on-accent on the accent is divergence 4's pairing,
   measured on the rendered engine: 2.95:1 on the blue-accent
   brand, 1.51:1 on the amber one, 2.77:1 on the orange one and
   12.83:1 on the dark-ground one. Divergence 4 recorded those for
   a hovered BUTTON — a transient state on a 5-character label.
   Here the same pairing carries a 64px display line AND a 14px,
   two-line lead paragraph that is on screen permanently. Only one
   of the four brands passes AA for the lead, and only one passes
   AA-large for the title.

   Not corrected here, because the ground is what the file
   specifies and a section fragment is the wrong place to overrule
   a brand's accent. What IS available today, with no new CSS:
   .ui-mode--inverted on the section gives an ink-ground CTA at
   full contrast on all four brands. The reference doc carries the
   numbers and the remedies.
   ------------------------------------------------------------ */
.ui-section--cta {
  background: var(--ui-fill-active);
  color: var(--ui-ink-on-accent);
  text-align: center;
}

/* --ui-fill-active rather than a literal var(--c-accent), for §2's
   reason: on the two brands that bind accent to ink, Inverted
   makes the ink the ground, so a literal accent band would be
   exactly the colour of the page behind it. §2 keeps
   --ui-fill-active paired with --ui-ink-on-accent, so the pair
   above survives all three Modes together. */

/* Centring the head means centring its flex children too, or a
   .ui-prose capped at 540 sits left in an 1100 box. The title's
   910 cap needs the same treatment. */
/* Every text role in the band takes --ui-ink-on-accent, and the
   TITLE most of all. This was a bug: `color` was set on the section
   and inherited, but .ui-h2 declares `color: var(--ui-ink)` on
   itself, so the title ignored the inherited value and painted the
   plain ink. Measured across the matrix, the title came out at
   1.00:1 — literally invisible — in five of eight cells: every
   inverted cell (white on white), plus BOTH brands whose accent is
   bound to their ink, where the band and the ink are the same
   colour by definition. Group and Studios in Default mode were the
   only ones that looked right, and they looked right by accident.

   --ui-ink-on-accent is the token that exists for exactly this: §2
   defines it as "whatever reads on the accent plate", light on the
   three light-ground brands and dark on the fourth, and Inverted
   reassigns it to the brand ink because there the band is the
   inverted ink. One token, all eight cells. */
.ui-section--cta .ui-section__title,
.ui-section--cta .ui-section__lead,
.ui-section--cta .ui-prose { color: var(--ui-ink-on-accent); }

.ui-section--cta .ui-section__head { align-items: center; }
.ui-section--cta .ui-section__title { margin-inline: auto; }
.ui-section--cta .ui-prose { margin-inline: auto; }
.ui-section--cta .ui-row { justify-content: center; }

/* The knockout ink has to reach the copy role as well: .ui-copy
   paints --ui-ink-copy, which on an accent band is the page ink
   and would be invisible-to-illegible. One class deep, no state
   property touched. */
.ui-section--cta .ui-copy { color: var(--ui-ink-on-accent); }
.ui-section--cta .ui-copy--dim {
  /* There is no --ui-ink-on-accent-60 in §2 and inventing one at
     :root for one section would be the wrong place to put it.
     Same idiom as §2's --ui-ink-inv-40 and §5.9's Colorful body. */
  color: color-mix(in srgb, var(--ui-ink-on-accent) 60%, transparent);
}

/* ------------------------------------------------------------
   6.3.22 CONTACT FORM   (Figma 40004583:68921)
   ------------------------------------------------------------
   The first multi-field form in the system, and — worth saying
   first — the section has NO HEADING of its own. The frame
   contains the two-column body and nothing else. The CTA's own H2
   is "Contact Us", so the intended page is CTA then Contact Form,
   and a Contact Form used on its own needs a heading supplied
   around it.

   ── WHAT §5.6 ALREADY COVERS, WHICH IS MOST OF IT ───────────
     · the field, its outline-only treatment in every state, its
       20%/100% border step and its :focus-not-:hover rule;
     · .ui-field-row, which is flex with the grid gutter as its
       gap and 50% from the tablet breakpoint up. The file draws
       two 260px fields at a 20px gutter in a 540 column — 260 is
       exactly (540 − 20) / 2, so the row needs nothing;
     · --area for the message field;
     · --select for the "I am a…" dropdown and its chevron;
     · §4's focus ring on every control.

   ── WHAT THE FILE SPECIFIES THAT §5.6 DOES NOT ──────────────
   Three things, and one of them is the only atom override in this
   fragment.

     1. LABELS. The Contact Form is the ONLY place in the file
        where a form label is drawn at all — the Form component
        (40004583:77238) is a bare 265x34 control — and it draws
        them as General/Body, 14px weight 300, at Primary 100,
        20px above the control. §5.6's .ui-field__label is
        --fsw-h5 (16px) at --fww-body-bold (500) with XXS below.
        The 5-versus-20 gap is the visible half: it changes every
        field row from 64px to 49 and the form's height by 75px.

        Overridden below, scoped to this section's own form
        wrapper, touching only type and one margin — no colour, no
        state, no print property, so nothing in §5.6 can lose.
        REPORTED, NOT FIXED: the right change is for §5.6's label
        role to become Body, since the file's only drawn labels
        are Body. That is a §5.6 edit and this is not §5.6.

     2. PLACEHOLDERS at 60%, not 40%. §5.6's 40% comes from the
        Form component's own Inactive cell, which is the more
        authoritative reading for the component; the Contact Form
        draws 60%. Left alone deliberately — a placeholder is not
        a label, changing it would be a restyle of §5.6's own
        state rule, and 60% is the accessible direction anyway, so
        the divergence costs nothing. Recorded, not patched.

     3. THE FIRST FIELD IS A CHIP SET, NOT AN INPUT. Node
        40004583:68937 is named "Select" and holds four Tag
        instances at the XXS gap, one of them filled with a
        full-ink label and three transparent with 20% borders and
        60% labels. So it is a single-choice control drawn as
        §5.2 tags — Filled=Yes/Active against Filled=No/Inactive,
        the same idiom the Expanded list's filter uses.

        A filter can be a <button> with aria-pressed. A form field
        cannot: it has to submit a value. So this is a <fieldset>
        of real radios with visually-hidden inputs and <label
        class="ui-tag"> chips, and 6.3.10's choice rules below are
        what make a checked radio look like §5.2's Active tag.

   ── WHAT THE FILE DOES NOT SPECIFY, AND IS NOT INVENTED ─────
   There is NO validation anywhere in this frame. No required
   marks, no asterisks, no helper text, no error colour, no error
   message slot, no success state. The four Colors/System values
   exist in the file but they are the scorecard's meter, not form
   feedback. So:

     · there are NO :invalid or :user-invalid rules below. A
       system that has not decided what an error looks like should
       not ship one, and :invalid in particular fires before the
       user has typed anything, which is how forms come out red on
       first paint.
     · `required` IS in the markup on the two fields a contact
        form cannot work without, and the required marker below is
        an INVENTION, stated as one. It exists because a required
        field needs a visible indication as well as the attribute,
        and the file offers none to copy. It is a marker in the
        label, not a colour, so it needs no error token and it
        prints.
   ------------------------------------------------------------ */
.ui-section--contact .ui-section__body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  row-gap: var(--s-w-m);
  column-gap: var(--grid-gutter);
}
@media (min-width: 1120px) {
  .ui-section--contact .ui-section__body {
    grid-template-columns: repeat(var(--grid-cols), minmax(0, 1fr));
  }
  .ui-contact__aside { grid-column: 1 / span 4; }
  .ui-contact__form  { grid-column: 7 / span 6; }
}

/* The details column. M between the contact blocks, which is the
   one place --ui-deflist-gap moves off its S default (68px pitch
   on a 28px group, against 48 in the other two sections). */
.ui-contact__aside {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s-w-m);
  min-width: 0;
}
.ui-contact__aside .ui-deflist { --ui-deflist-gap: var(--s-w-m); }

/* The form column. M between rows — measured at a 104px pitch on
   64px rows — and capped at the measure, which is what makes the
   two-up field row come out at the drawn 260px. */
.ui-contact__form {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s-w-m);
  min-width: 0;
  max-width: var(--ui-measure);
}
/* A field or a field row is full width inside that column; the
   submit button is not, and align-items: flex-start above is what
   keeps it at its content width. */
.ui-contact__form > .ui-field,
.ui-contact__form > .ui-field-row { align-self: stretch; }

/* The label divergence — see note 1 above. Scoped to this
   section's form wrapper rather than to .ui-field__label, so
   §5.6's role is untouched everywhere else in the system. */
.ui-contact__form .ui-field__label {
  margin-bottom: var(--s-w-s);
  font-size: var(--fsw-body);
  font-weight: var(--fww-body);
  line-height: var(--lhw-body);
  color: var(--ui-ink-copy);
}

/* The message field's height. §5.6 hard-codes
   `min-height: calc(var(--ui-control-h) * 3)` = 102px; the file
   draws 150. min-height only — no colour, no border, no state, so
   §5.6's focus and print rules all still win. The better fix is a
   --ui-field-area-h token in §5.6; reported. */
.ui-contact__form .ui-field__control--area {
  min-height: var(--ui-contact-area-h);
}

/* The required marker. INVENTED — see the note above. Kept out of
   the accessible name with aria-hidden in the markup, because
   `required` on the control is what a screen reader should
   announce; the glyph is for everyone else. */
.ui-field__req {
  color: var(--ui-ink-copy-60);
  padding-inline-start: 0.25em;
}

/* ── The chip choice set ─────────────────────────────────────
   A <fieldset> whose <legend> is the field label and whose
   options are real radios with .ui-tag labels.

   The inputs are clipped rather than hidden: `display: none` and
   `visibility: hidden` both take a control out of the tab order,
   and a choice set you cannot reach from the keyboard is a defect.
   position: absolute also takes them out of the flex flow, so the
   chips sit at the XXS gap with nothing between them. */
.ui-choice {
  border: 0;
  margin: 0;
  padding: 0;
  min-width: 0;   /* a fieldset's UA min-width is min-content */
}
.ui-choice__set {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ui-chip-gap);
}
.ui-choice__input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.ui-choice__option { cursor: pointer; }

/* State = checked, and this is the one place in the fragment that
   REPRODUCES an atom's own appearance instead of reusing it. It
   has to: §5.2 keys Active off aria-pressed, and no CSS can put
   an attribute on a radio's label. Three declarations, copied from
   §5.2's Filled=Yes/Active cell, and they have to be kept in sync
   with it.

   The sibling combinator, not :has(), on purpose — it is (0,2,0)
   where `.ui-choice__option:has(> .ui-choice__input:checked)`
   would be (0,3,0) and would out-rank §5.2's own
   .ui-mode--alternative Active rule. At (0,2,0) it ties §5.2's
   hover and Filled rules and wins on load order alone, which is
   the least it can win by and still work.

   The plate is --ui-plate, not --ui-plate-up: the file draws all
   four chips on one plate and moves only the ink, so there is no
   second plate to transcribe, and Core is the one step forward
   from the section ground that §5.2's .ui-tag--filled already
   uses at rest. */
.ui-choice__input:checked + .ui-choice__option {
  background: var(--ui-plate);
  border-color: transparent;
  color: var(--ui-ink);
}

/* §4's ring again, for the same reason as 6.3.6's: the ring has
   to move to the LABEL, because the input it belongs to is
   clipped to a pixel and a ring around that is a ring around
   nothing. Verified in the render, not assumed. */
.ui-choice__input:focus-visible + .ui-choice__option {
  outline: 3px solid var(--c-accent);
  outline-offset: 2px;
  box-shadow: 0 0 0 2px var(--ui-ground),
              0 0 0 6px var(--ui-ink-20);
}

/* ------------------------------------------------------------
   6.3.23 CONTENT   (Figma parent frame 40004583:68539 "CONTENT")

     image left    40004583:68550 · :68561 · :68572 · :68583   1400x943
     text only     40004583:68594 · :68604 · :68614 · :68624   1400x803
     image right   40004583:68634 · :68645 · :68656 · :68667   1400x943
     image only    40004583:68678 · :68685 · :68692 · :68699   1400x940
     quote only    40004583:68706 · :68713 · :68720 · :68727   1400x503

     tablet   40004588:5102 · 40004596:16489 · :16519 · :16549   794x1220
     mobile   40004588:5091 · 40004596:16478 · :16508 · :16538   373x1191
   ------------------------------------------------------------
   The statement band, and the sixteenth section family — missed in
   the first import because a flat scan of frame names read its
   "Content" and "Content Body" sub-parts as sub-parts of other
   sections. It is a family of its own: a three-line display
   sentence at the ten-column title measure, then a two-column
   body — an image well on one side, a stack of prose plus one
   button on the other.

   ── THE VARIANT AXIS IS NOT THE HEIGHT ───────────────────────
   The set draws four distinct desktop heights and it is
   tempting to read four variants off them. There are five, they
   are laid out as five COLUMNS on the canvas (the four frames in
   each column are the four sub-brands, top to bottom), and the
   two 943s are not a repeat: they are mirror images of each
   other. Read off the nodes rather than the heights:

     text only    943 - 140     "Content body" is `justify-end`
                                and holds the prose column alone.
     image left   943           `justify-between`, well FIRST.
     image right  943           `justify-between`, well LAST.
     image only   940           no head at all (its "Content
                                Title" is hidden="true" —
                                40004583:68680) and one wide well.
     quote only   503           head and body both CENTRED, one
                                full-ink line, no well, no button.

   Which means TEXT ONLY AND IMAGE LEFT ARE ONE LAYOUT. The
   prose column sits in the same six columns in both; the only
   difference is whether a well is present. That is the same
   finding 6.3.11 records for Stacked vs Aligned — the axis lives
   in the markup — so `.ui-section--content` with no well IS the
   text-only frame, and there is no `--content-text` class.

   ── THE GRID IS 6.3.20's GRID ────────────────────────────────
   Measured on 40004583:68583 (the body frame is 1100 wide):
   well at x=0 w=353, prose at x=560 w=540. 353 is four desktop
   columns and 560 is the start of column SEVEN (6 x 93.33), so
   the body is columns 1-4 and 7-12 with 5 and 6 left empty —
   the Long Form layout exactly, two empty columns of gutter and
   all. The mirror puts the well at x=747, which is the start of
   column NINE, i.e. 9-12 against prose 1-6. So both halves are
   4 + 6 with a two-column trench, and neither is a `gap`: the
   trench is what `justify-between` leaves over.

   Nothing here re-declares the shell. `.ui-section`,
   `__head`, `__title`, `__body` are 6.3.2's, the reading column
   is 6.3.3's `.ui-prose` + `.ui-copy`, the button is §5.1's
   `.ui-btn` untouched, and the picture is 6.3.14's
   `.ui-section__image`. The only thing this section paints is
   the well.

   ── THE FIVE MEASURED FACTS THAT ARE NOT EXTRAPOLATED ────────
   This is the first section family with tablet and mobile frames
   read (the coverage audit's item 1: 140 of 358 frames were
   never opened, and every reflow decision in 6.3.4-6.3.22 is
   the engine's opinion rather than the file's instruction). The
   four tablet and four mobile frames are ONE composition — the
   image variant, once per brand — and they are identical to each
   other to four decimal places. What they say:

     1. BLOCK PADDING IS 80 ON A PHONE, not §1's provisional 60.
        Measured: the phone frame's Title starts at y=80 and its
        body ends 80 above the frame edge; both larger frames
        draw 120. §1 says outright that --ui-pad-block is
        "PROVISIONAL … when [the sections] land, these two should
        be re-derived from what the sections actually use". This
        is that measurement. Declared on the section rather than
        at :root, because raising it at :root would move all
        nineteen sections on the strength of one family's phone
        frame — that is a §1 edit and a design conversation.

     2. HEAD -> BODY IS 40 ON BOTH SMALL FRAMES and 60 on
        desktop. Not 6.3.1's M -> 2xM. See --ui-section-lead
        below for the arithmetic, which is where the interesting
        part is.

     3. THE TITLE TAKES THE FULL CONTENT WIDTH AT TABLET, not
        6.3.1's 82.73%: the text node is 614 wide in a 614 box
        (4 lines at 48px), and 333 in a 333 box on the phone
        (5 lines at 36px). Only the desktop frame draws the ten-
        column measure. 6.3.1 raises --ui-section-title-max at
        640; on the evidence of the only tablet frame in the file
        that HAS a title, it should raise it at 1120.

     4. THE WELL CHANGES RATIO ON A PHONE. 353x450 desktop and
        295x375.77 tablet are the same ratio to four decimals
        (0.7844 / 0.7850) — the tablet well is the desktop well
        scaled by 0.835 — but the phone draws 333x364, which is
        0.915 and a visibly squarer box. Two ratios, both drawn,
        neither derived.

     5. THE PROSE MEASURE IS 430 AT TABLET, not 540. 430px of
        14px text is 67 characters a line, which is INSIDE the
        45-75 comfortable range that --ui-measure's own note says
        the drawn 540 (84 characters) misses by 12%. The file
        knows about the measure at tablet; it just does not say
        so at desktop.

   What the small frames do NOT cover: text only, image only and
   quote only have no tablet or mobile frame at all, and neither
   does the mirrored well. Their reflow below is extrapolated
   from the one composition that was drawn — better evidence than
   6.3.4-6.3.22 had, and still not a transcription.

   ── ONE THING READ AND DELIBERATELY NOT FOLLOWED ─────────────
   The tablet frame insets its content by 90px, giving a 614
   content column in a 794 frame — one tablet column plus one
   gutter inside the 20px grid margin (70.4 + 20 = 90.4, and the
   residue is 7 of the 9 tablet columns, centred). The engine's
   --grid-margin-t is 20, so at 834 it gives a 794 column where
   the file draws 614: a 180px disagreement.

   Not followed, because --ui-section-inset is what makes a
   section line up with .ui-page, with the footer and with the
   eighteen sections it will be stacked between, and the file
   cannot see a stacked page — it draws every band in isolation.
   Honouring 90 here would jog this one band inward by 90px on a
   tablet page. So the inset stays --grid-margin and the drawn
   MEASURES (fact 4 and fact 5) are honoured instead, which is
   where the composition actually lives. Consequence, stated: at
   834 this band carries ~180px more trailing air than the frame
   draws. If design confirms the 90, it belongs in §1 as a new
   tablet margin for every section, not here.

   ── COLOUR: THERE IS ALMOST NONE, AND THAT IS THE POINT ──────
   get_variable_defs on the frames returns Primary 100 for the
   H2, Primary 100 for the first paragraph, Primary 60 for the
   rest, and Background Base for the well. No Black ramp on any
   node — including 40004583:68583, whose Primary is a saturated
   orange and whose body copy is that orange at 60%. The brand's
   running text is the brand's colour (divergence 14), so this
   fragment declares NO ink at all: .ui-h2 already paints
   --ui-ink, .ui-copy already paints the copy ramp, and both
   resolve to Primary through §2. Nothing to override, and
   nothing here reaches for a second ink to improve a contrast
   number — that is the mistake divergence 14 records.

   The frames are drawn on Background CORE with a Background BASE
   well, which is divergence 2's artboard convention one more
   time: on a real page the ground IS Base, so the well goes one
   step FORWARD to Core rather than one step back. Same
   correction, same reasoning, as 6.3.14's frame — and like that
   one it survives the two brands that collapse Core onto
   Highlight, because it is the Base/Core pair that separates
   them and not Core/Highlight. No variant here depends on a
   plate lift.

   ── NO MODE AXIS, AND THE FOUR ROWS ARE NOT ONE ──────────────
   Same as the other eighteen (divergence 6). The four frames per
   column look like a Mode axis because the fourth is dark, and
   it is not: 40004583:68561 resolves Primary 100 to the bone
   ink over a deep green Background Base — the dark-ground BRAND
   in its own
   palette, not an inverted anything. The rows are the four
   sub-brands. Everything below reads --ui-* so the section does
   re-skin under .ui-mode--*, and that behaviour is
   extrapolation.

   ⚠ .ui-mode--alternative is the wrong choice for this section
   in particular. §6.2 says so for the footer and this is the
   most text-heavy band in §6.3: Alternative puts the accent on
   the copy ink by design, so on the pale-accent brand three
   paragraphs of body copy land at 1.5:1.

   ── MEASURED AGAINST THE FRAME, AND WHERE IT MISSES ──────────
   Rendered at 1440 with the licensed faces: the well comes out
   353.39 x 450.48 against a drawn 353 x 450, its left edge on the
   content margin, and the prose column 540 wide starting 560.09
   into the content box against a drawn 560. The mirror puts the
   well 746.78 in against a drawn 747. The phone frame's well is
   333 x 364 exactly. Line counts match the drawn frames text node
   for text node: 3/5/4 paragraph lines at tablet and 5/6/5 on the
   phone, and a five-line title on the phone.

   Band heights, rendered against drawn: image left and image
   right 917.88 against 943, text only 803.27 against 803, image
   only 940.11 against 940, quote only 485.58 against 503. Three
   effects, and they are worth separating because two of them
   cancel:

     -20.0  the drawn "Content body" frame carries 20px of padding
            top AND bottom. The top 20 is folded into the
            --ui-section-lead arithmetic below; the bottom 20 is
            dead air between the tallest column and the section's
            own 120px, and it is not reproduced. Deliberate: it
            changes no visible distance, unlike the 20 that 6.3.13
            keeps because a pager sits under it.
      -5.9  the head -> body divergence below.
      +8.4  per paragraph, the other way, because .ui-copy is NOT
            cap-trimmed — see the note below.

   So a band whose height is set by the WELL (both image variants,
   at 450) comes out 25.1 short, which is the 20 plus the 5.9. A
   band whose height is set by the PROSE (text only, three
   paragraphs) comes out at 803.27 against 803 — and that is two
   errors cancelling to a quarter of a pixel, not a bulls-eye:
   +25.1 of untrimmed leading against -25.9 of foot and gap. The
   image-only band has neither head nor prose and is exact.

   ⚠ .ui-copy HAS NO CAP TRIM AND EVERY TEXT NODE IN THESE FRAMES
   DOES. 6.3.3 gives .ui-copy `margin`, `font: inherit`, a colour
   and `text-wrap` and stops there, while .ui-section__lead,
   .ui-section__intro, .ui-list__meta and .ui-section__column-body
   all carry `text-box: trim-both cap alphabetic`. Measured at
   14/1.3: an untrimmed paragraph is 18.2px per line against the
   trimmed 9.84 + 18.2 x (n-1), i.e. 8.36px taller whatever its
   length, and every M gap in a prose column therefore reads 48.4
   cap-to-cap where the file draws 40.
   NOT patched here, and that is a judgement rather than laziness:
   .ui-copy is one object shared by six sections, so trimming it
   for this one would make CONTENT the only section in §6.3 whose
   prose rhythm matches the file, which is a worse inconsistency
   than a uniform 8px. The fix is one declaration in 6.3.3.
   Contrast with the prose-flow fault below, which IS fixed here:
   that one costs the drawn composition three 40px gaps, and this
   one costs it 8px of leading.

   ── STATE ────────────────────────────────────────────────────
   There is none. Nothing in any of the twenty frames has a hover
   or an active cell, and the only interactive thing on the band
   is the Button — whose own states are §5.1's and are not
   touched here. Worth naming because every other component in
   this engine has a State axis and the absence is easy to read
   as an omission.
   ------------------------------------------------------------ */

.ui-section--content {
  /* ── Block padding. Fact 1: 80 drawn on the phone frame, 120
     on both larger ones. Mobile-first, so 80 is the base and the
     640 query raises it — which also means this section no longer
     depends on §1's provisional pair at all. Delete both lines if
     and when §1 adopts the measurement.
     80 is two M and is typed as two M: there is no 80 step on the
     web scale (60 then 120) and adding one for this would be a
     third place to change the same number. */
  --ui-pad-block: calc(var(--s-w-m) * 2);

  /* ── Head -> body. Fact 2, and the arithmetic matters more than
     the number, because §3.2's display headings carry their own
     bottom margin and this gap is added ON TOP of it:

       .ui-h2 margin-bottom = --ui-display-descender + S
                            = 0.22em + 20px
                            = 27.9px at 36px, 30.6 at 48, 34.1 at 64

     so the distance from the title's baseline to the top of the
     body is (that) + --ui-section-lead. Against the drawn 40 / 40
     / 60:

       phone   XS -> 27.9 + 10 = 37.9   (2.1 tighter than drawn)
       tablet  XS -> 30.6 + 10 = 40.6   (0.6 looser than drawn)
       desktop S  -> 34.1 + 20 = 54.1   (5.9 tighter than drawn)

     XS and S are the closest on-scale values at every width and
     the miss is under 6px, which is exactly the trade 6.3.2 makes
     for the title -> lead gap ("a stated divergence rather than a
     magic 25.92px gap"). Same 25.92 turns up here, for the same
     reason, one gap further down.

     ⚠ AND IT IS WHY THIS SECTION'S NUMBERS LOOK SMALL NEXT TO
     ITS SIBLINGS. 6.3.1 sets --ui-section-lead to M/2xM and
     6.3.20 to L, none of which subtract the heading's margin — so
     Long Form, whose frame draws the same 60 this one does,
     renders at 34.1 + 60 = 94. The whole family is ~34px looser
     than the file below its H2. Reported, not fixed here: the fix
     is a --ui-h2 margin token in §3.2 or one subtraction in
     6.3.1, and either way it is not a section's business. If the
     file is corrected there, revert these two lines to M and L. */
  --ui-section-lead: var(--s-w-xs);

  /* ── The title measure. Fact 3: full width on both small
     frames, ten columns only at desktop. 6.3.1 raises it at 640,
     so this puts it back for the tablet band and re-raises it at
     1120. Both values are 6.3.1's own — the breakpoint is the
     only thing that moves.

     Same specificity as the rule it undoes and later in the
     cascade, which is 6.3.9's and §5.9's habit (integration note
     16: match, do not escalate). It wins on order, and order is
     safe here for the same reason it is there — this fragment
     loads after ui-core.css. */
  --ui-section-title-max: 100%;

  /* ── The well. Fact 4: two drawn ratios, and a width cap that
     only exists in the tablet range (the desktop well is a grid
     cell and takes its width from the grid).
     Ratio rather than height for §5.9's reason: this is a fluid
     medium and a fixed 450px well letterboxes its picture the
     moment the column narrows. */
  --ui-content-media-ratio: 333 / 364;
  --ui-content-media-w: 100%;

  /* ── Variant placement, as two tokens rather than four rules.
     Grid placement is the ONLY thing the mirror changes, so the
     axis is one block to read and there is one rule that consumes
     it — instead of four `grid-column` declarations at matching
     specificity racing each other on load order. */
  --ui-content-media-col: 1 / span 4;
  --ui-content-prose-col: 7 / span 6;
}

@media (min-width: 640px) {
  .ui-section--content {
    --ui-pad-block: var(--s-w-xl);            /* 120, drawn */
    --ui-content-media-ratio: 353 / 450;      /* the desktop ratio,
                                                 drawn again at 295
                                                 x 375.77 */
    --ui-content-media-w: 295px;              /* fact 4: drawn, and
                                                 not grid-derived —
                                                 both dimensions are
                                                 the desktop well x
                                                 0.835 */
    /* Fact 5. --ui-measure is a :root token that .ui-prose reads
       for its max-width, so reassigning it on the SECTION caps the
       reading column without a rule reaching into .ui-prose at
       all. 430 is drawn, and it is the one measure in the six
       frames that is inside the readable range. */
    --ui-measure: 430px;
  }
}

@media (min-width: 1120px) {
  .ui-section--content {
    --ui-section-lead: var(--s-w-s);
    --ui-section-title-max: 82.73%;   /* 910 of 1100, 6.3.1's value */
    --ui-content-media-w: 100%;       /* the grid cell owns it now */
    --ui-measure: 540px;              /* back to 6.3.15's, drawn */
  }
}

/* ── The variants ────────────────────────────────────────────
   Co-classed with the base on purpose: `.ui-section--content` and
   `.ui-section--content-image-right` land on the same element, so
   a single-class modifier would be (0,1,0) against the base's
   (0,1,0) and would win only on source order. Two classes is
   (0,2,0) and settles it, and it documents the API — a variant
   modifier does not work without the base class, exactly as
   .ui-card--colorful needs .ui-card. */

/* Image right. The mirror: prose into columns 1-6, well into
   9-12. Written as logical placement (grid-column 1 is the
   inline START), so under RTL the "right" image sits left — the
   class name is the drawing's, the behaviour is the reader's. */
.ui-section--content.ui-section--content-image-right {
  --ui-content-media-col: 9 / span 4;
  --ui-content-prose-col: 1 / span 6;
}

/* Image only. One well across the whole content column, and no
   head — 40004583:68680 is the same H2, drawn hidden="true", so
   the head is genuinely optional here rather than absent.

   ⚠ THE RATIO IS 1100/700 AND NOT 1400/700. The node geometry
   says otherwise at first read: "Content Body" 40004583:68683 is
   1400 wide at x=-150, i.e. bled by exactly the frame's own
   inset. But its parent 40004583:68679 is 1100 wide and
   overflow-clips, so the bleed is invisible — rendering :68679 on
   its own returns a plate that fills 1100x700 edge to edge, which
   is how this was settled rather than by reading the numbers. So
   the drawn well is the content column, 700 tall, and the 1400 is
   dead geometry — somebody dragged the layer wide and the clip
   swallowed it. A full-bleed reading would have been a whole
   different section. */
.ui-section--content.ui-section--content-image-only {
  --ui-content-media-ratio: 1100 / 700;
  --ui-content-media-col: 1 / -1;
  --ui-content-media-w: 100%;   /* no tablet cap: the well is the
                                   column at every width. No small
                                   frame exists for this variant, so
                                   the ratio holding at 373 is an
                                   extrapolation. */
}

/* Quote only. The one centred variant in the family, and centred
   in the node rather than by eye: both "Title h2" and "Content
   body" carry `justify-center` where every other variant carries
   `justify-between` or `justify-end`, so the 910 title lands at
   x=95 in the 1100 box (95 + 910 + 95) and the 540 paragraph at
   x=280 (280 + 540 + 280). Symmetric in both.

   Its body copy is Primary 100, not the 60% the other variants
   drop to after the first paragraph, and it is one line of 44
   characters. Read with the canvas label "Quote Only" that says
   the H2 is the quotation and this line is its attribution. The
   file offers no attribution markup to copy — it is a plain
   "Paragraph body" like every other — so it stays .ui-copy in a
   .ui-prose and the reading is recorded rather than encoded. A
   real quotation wants <blockquote> + <cite>; 6.3.2's reset
   already covers both. */
.ui-section--content.ui-section--content-quote {
  --ui-content-prose-col: 1 / -1;
  text-align: center;
}
.ui-section--content-quote .ui-section__head { align-items: center; }
/* A capped box in a centred flex column is centred by
   align-items; the prose column is a GRID item and needs its own
   auto margins. Same two lines, same reason, as 6.3.21's centred
   CTA. */
.ui-section--content-quote .ui-section__title { margin-inline: auto; }
.ui-section--content-quote .ui-prose { margin-inline: auto; }

/* ── The body ────────────────────────────────────────────────
   A grid, like 6.3.20's and 6.3.22's, and for the same reason:
   the two halves are placed in named columns with a trench
   between them, which a flex row can only fake with
   `justify-content: space-between` and a pair of fixed widths.

   One column below 1120 — both small frames stack, image above
   prose, at a 40px gap (measured: tablet well ends at 375.77, the
   prose starts at 415.77; phone 364 -> 404).

   align-items: start is drawn (`items-start` on every "Content
   body" node) and it is also load-bearing. A grid item stretches
   by default, so a prose column longer than the well would
   stretch the WELL past its aspect ratio and crop the picture
   further than the ratio says. `start` keeps the well at its
   drawn height and lets the prose run past it, which is what
   happens on the tablet frame anyway. */
.ui-section--content .ui-section__body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  justify-items: start;
  row-gap: var(--s-w-m);
  column-gap: var(--grid-gutter);
}
@media (min-width: 1120px) {
  .ui-section--content .ui-section__body {
    grid-template-columns: repeat(var(--grid-cols), minmax(0, 1fr));
  }
  /* ⚠ grid-row: 1 IS NOT DECORATION, and the mirror is why. Both
     items name a column and neither named a row in the first
     version, so auto-placement ran in its default SPARSE mode: it
     placed the well in row 1 columns 9-12, left the cursor past
     column 12, and could not go back for the prose's columns 1-6
     — so the prose landed in row 2 and the mirrored variant
     rendered as a well with a paragraph column UNDER it, 1214px
     tall against a drawn 943. Caught in the render; the numbers
     alone (a 296px height overshoot) said something was wrong but
     not what. Naming the row makes both placements explicit and
     the DOM order stops mattering, which is what lets the two
     variants share one markup shape. */
  .ui-section--content .ui-content__media {
    grid-column: var(--ui-content-media-col);
    grid-row: 1;
  }
  .ui-section--content .ui-prose {
    grid-column: var(--ui-content-prose-col);
    grid-row: 1;
  }
}

/* ── The image well ──────────────────────────────────────────
   The only thing this section paints. --ui-plate for divergence
   2's reason (see the header), --radius-component because that is
   what the placeholder nodes draw — 4px on the near-square
   brands, where the corner resolves over about 3px, the same
   check 6.3.14 ran on its frame.

   width: 100% with a max-width cap rather than `width: var()`, so
   the tablet's drawn 295 never exceeds a narrower column: at a
   640 viewport the content box is 600 (the 20px tablet margin
   twice over) and 295 is 49% of it; at 1119 it is 27%.
   A capped stretch item falls back to start alignment, which is
   how it sits flush left on both small frames without a rule.

   EMPTY IS A REAL STATE. Every one of the twenty frames draws
   this as an unfilled placeholder, and a page that has not
   supplied its picture yet gets the plate at the drawn ratio —
   the same behaviour as §5.9's card well and 6.3.14's frame. It
   is not an error state and it carries no text: a well is
   presentational until an <img> lands in it, and an alt-less
   plate announces nothing, which is correct. */
.ui-content__media {
  position: relative;
  box-sizing: border-box;
  width: 100%;
  max-width: var(--ui-content-media-w);
  aspect-ratio: var(--ui-content-media-ratio);
  overflow: hidden;
  background: var(--ui-plate);
  border-radius: var(--radius-component);
}

/* An empty well needs an edge, on two brands more than the others.
   The plate is one step off the page ground, which is a 1.18:1 step
   on the light brands and 1.12:1 on the dark-ground one — visible
   enough for a filled card and NOT visible enough for an empty
   rectangle, which then reads as a gap in the layout rather than as
   a picture that has not arrived. `:not(:has(img))` scopes it to the
   empty case, so a well with a photograph in it keeps the clean
   plate-free edge the frames draw.

   ⚠ NEVER put a logo in here as a stand-in for a photograph. It was
   tried in the first proof of this section and it looks exactly as
   bad as it sounds: `object-fit: cover` crops a brand mark to a
   353x450 portrait and scales it past its own clear space. The plate
   IS the placeholder — that is what all twenty frames draw. */
.ui-content__media:not(:has(img)) {
  border: var(--rule-w-fine) solid var(--ui-ink-20);
  background: transparent;
}


/* ============================================================
   6.4 RESPONSIVE CORRECTIONS  (read from the small frames)
   ------------------------------------------------------------
   §6.3 was transcribed from DESKTOP frames and its reflow was
   reasoned from the grid. The file has 794-wide tablet and
   373-wide mobile frames for every group; these are the rules
   that changed once they were opened.

   Kept as its own block rather than folded into each subsection,
   on purpose: it is a correction log, and a reader who wants to
   know what was measured versus what was inferred can see the
   boundary. Each rule names the node it came from and the number
   it replaces.

   TWO THINGS THE FRAMES SAID THAT THE GRID DID NOT
   1. 353px is a DESKTOP unit. The tablet unit is 295 — the
      gallery's active frame, the team card, the testimonial
      mount and all three card families. (614 − 20) / 2 = 297 is
      the arithmetic behind it.
   2. Two sections do not wrap below desktop, they SCROLL.
      Testimonials and the aligned card row keep three
      fixed-width items on one line and overflow the content box
      deliberately. Wrapping them, which is what the engine did,
      turns a 392px band into 1176px of stacked column.

   COVERAGE — 9 of 15 groups read: STATS, TEAM, TESTIMONIALS,
   GALLERY, LIST (Condensed), CONTACT FORM, STACKED CARDS,
   ALIGNED CARDS, COLUMNS. Not yet read: COLORFUL CARDS, GENERAL
   INTRO, PILLS, APOLLO FRAMEWORK, LONG TEXT, CTA — those six are
   still reasoned from the grid, and the two findings above are
   the first things to check when they are opened. LIST — Expanded
   has NO small frame in the file at all, so §6.3.18 stays
   extrapolated by necessity rather than by omission.
   ============================================================ */

/* ============================================================
   §6.3 RESPONSIVE CORRECTIONS — from the 794 and 373 frames
   ------------------------------------------------------------
   Paste into §6.3. Every rule below replaces a value that was
   reasoned out from the desktop frame and the grid, and every one
   cites the node it was measured on. Nothing here is a new design
   decision; where a drawn number could not be honoured exactly the
   comment says so and says what was honoured instead.

   Nine of the fifteen groups were read. COLORFUL CARDS, GENERAL
   INTRO, PILLS, APOLLO FRAMEWORK, LONG TEXT and CTA were NOT, and
   neither was List — Expanded, which has no small frame in the
   file at all. See responsive-findings.md for the gap list — the
   rules below must not be read as "§6.3 is now measured".

   Off-scale numbers are written as multiples of the scale (XXS 5,
   XS 10, S 20, M 40, L 60, XL 120, XXL 240, XXXL 320) rather than
   typed, in the same style §1 uses for the drawn 80. Where a drawn
   width is a share of the content column it is written as that
   share, because a fraction survives three grids and 295px does
   not.
   ============================================================ */

/* …and the 1120 declaration of --ui-section-lead comes OUT of the
   existing desktop query. Left as a comment rather than a rule
   because a later `--ui-section-lead: calc(...)` at 1120 would be
   identical and harmless, but the file should not claim a step
   where there is none. */

/* CONTENT keeps the 40 its own tablet frame draws. Measured on the
   CONTENT small frames last night; the desktop value is 40 plus the
   body's own 20 of padding, so this section's head→body never
   reaches two M at any width and the shell's step must not apply.
   One class deeper than .ui-section, so it wins on specificity and
   not on load order. */
.ui-section--content { --ui-section-lead: var(--s-w-m); }

/* ------------------------------------------------------------
   6.3.7 STATS — the mosaic becomes 2 / 1 / 2, not one column
   ------------------------------------------------------------
   Tablet 40004588:5381 · mobile 40004588:5370, screenshots read.

   Both small frames draw FIVE tiles in three bands: two half-width
   tiles, one full-width tile, two half-width tiles. It is the
   desktop mosaic turned ninety degrees — a wide tile interrupting a
   run of narrow ones — and the tile that interrupts is the same one
   that is double-height at desktop, so .ui-stat--tall carries both
   behaviours and needs no second class.

   §6.3.7 reasoned one column below 640 because "a 212px tile is
   40px of padding around a 64px numeral; two of them on a 333px
   content box is 156px each and the label wraps to three lines".
   The file draws 161.5px tiles two up at 373. The reasoning was
   good and the drawing overrules it; the label wrapping to two
   lines is why the tile has to hug rather than sit in a 190 box
   (below).

   Replaces: grid-template-columns: minmax(0,1fr) below 640, and
   repeat(2, …) from 640. Both become the same two tracks, because
   the drawn arrangement is the same at 373 and 794 — only the
   width of the band changes. */
.ui-stats {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
/* The interrupting tile spans the pair below the desktop
   breakpoint and spans two ROWS above it. Two declarations, one
   element, no extra class — and written as 1 / -1 rather than
   span 2 so it stays correct if the track count ever changes. */
.ui-stat--tall { grid-column: 1 / -1; }
@media (min-width: 1120px) {
  .ui-stat--tall { grid-column: auto; grid-row: span 2; }
}

/* The tile HUGS below the desktop breakpoint. Drawn 114 where the
   label is one line and 132 where it is two, at tablet
   (40004590:17988 and :18004); 123 at mobile (40004590:18018).
   None of those is a fixed height — each is 5 + 20 + numeral + 20
   + label + 20 + 5, i.e. the content plus the mount and the
   padding the tile already has. So the correction is to stop
   asserting a floor, not to assert a smaller one: the 190 box is a
   desktop fact.

   `auto` rather than a value, and the rows go back to auto too, so
   a two-line label grows its band and its neighbour stretches to
   match — which is exactly what the file draws in the third band,
   where one tile's text is centred in the taller sibling's height
   (40004590:18010, text container y=14 in a 132 box). The centring
   .ui-stat already sets is what does that. */
.ui-stats { grid-auto-rows: auto; }
.ui-stat  { --ui-card-min-h: auto; }
@media (min-width: 1120px) {
  .ui-stats { grid-auto-rows: minmax(var(--ui-stat-min-h), auto); }
  .ui-stat  { --ui-card-min-h: var(--ui-stat-min-h); }
}

/* The band is capped to the lead measure at tablet and centred.
   40004590:17985 is 430 wide at x=182 in a 614 content column
   whose head starts at x=90 — so 92 inside it, which is
   (614 - 430) / 2. That is the same 430 the centred head already
   uses, so it is --ui-section-lead-max and not a new number: the
   body sits on the head's centre line, which is the whole reason
   this section's head is centred.

   Not applied at mobile: 40004590:18016 is the full 333. */
@media (min-width: 640px) and (max-width: 1119.98px) {
  .ui-section--stats .ui-section__body {
    max-width: var(--ui-section-lead-max);
    margin-inline: auto;
  }
}

/* ------------------------------------------------------------
   6.3.6 TESTIMONIALS + 6.3.11 ALIGNED — the row is a RAIL below
   the desktop breakpoint, not a wrapping grid
   ------------------------------------------------------------
   TESTIMONIALS tablet 40004588:5288 / mobile 40004588:5277
   ALIGNED      tablet 40004588:4730 / mobile 40004588:4719

   At desktop three cards fit the 1100 column exactly (353 x 3 plus
   two 20 gutters is 1099), so the row reads as a grid and was
   transcribed as one. Both small frames keep all three cards on ONE
   LINE at a fixed width and let the line overflow the content box:
   925 in a 614 column at tablet (40004588:4737 is 925 wide), 895 in
   a 333 column at mobile. That is not a grid that wrapped. It is
   the rail TEAM and GALLERY already are, and both sections already
   draw the pager that pages it.

   Cost of getting this wrong, measured at 393: the engine stacks
   three 392px quote cards into 1176px of column where the file
   draws a 392px band. It is the most visible discrepancy found.

   ⚠ ALIGNED's mobile frame goes further and centres the line on the
   MIDDLE card (40004588:4727 at x=-264.85, :4728 at 26.05, :4729 at
   316.95, each 280.9 wide, so the middle card's centre is 166.5 =
   333/2 and the outer two bleed off both edges). §6.3.14 records at
   length why a centred overflowing line drops its leading overflow
   in every browser and makes the first item unreachable. Same
   conclusion here, same reason: this starts on the grid and bleeds
   the trailing edge only. The lost symmetry is a runtime state —
   "card 2 of 3" — that no stylesheet holds.

   Implemented on the same element the markup already carries
   (.ui-grid), by turning the grid off below 1120 rather than by
   changing the markup: --span keeps working at desktop and the
   author writes desktop spans exactly as before. */
@media (max-width: 1119.98px) {
  .ui-section--testimonials .ui-grid,
  .ui-section--cards-aligned .ui-grid {
    display: flex;
    align-items: stretch;
    gap: var(--s-w-s);          /* 20 at BOTH small widths — drawn
                                   (tablet 0/315/630 at 295 wide,
                                   mobile 0/305/610 at 285), so this
                                   is S and not --grid-gutter, which
                                   would give the phone 10. */
    /* Trailing bleed, then paid back inside the scroller, exactly
       as .ui-person-row and .ui-section__rail already do — and
       against --ui-section-inset, so a nested section with a zero
       inset simply gets no bleed. */
    margin-inline-end: calc(-1 * var(--ui-section-inset));
    padding-inline-end: var(--ui-section-inset);
    overflow-x: auto;
    overscroll-behavior-inline: contain;
    scroll-snap-type: x proximity;
  }
  .ui-section--testimonials .ui-grid > *,
  .ui-section--cards-aligned .ui-grid > * {
    /* 295 of a 614 content column at tablet and 285 of 333 at
       mobile. Written as shares, not pixels: half the column less
       half the gutter is 297 at the drawn tablet column (2px off
       the drawn 295, and the drawn number is not on any scale), and
       85% is 283 against a drawn 285. Both are the arrangement the
       file draws — two-and-a-bit cards visible at tablet, one and
       the edge of the next on a phone — which is what has to
       survive, since the engine's content column is 794 where the
       file's is 614 (see the inset note in the reference doc). */
    flex: 0 0 calc((100% - var(--s-w-s)) / 2);
    scroll-snap-align: start;
  }
}
@media (max-width: 639.98px) {
  .ui-section--testimonials .ui-grid > *,
  .ui-section--cards-aligned .ui-grid > * { flex-basis: 85%; }
}

/* ------------------------------------------------------------
   6.3.8 TEAM — the card is half the column at tablet
   ------------------------------------------------------------
   40004588:4916. The card is 295 in a 614 column, i.e. two up;
   §6.3.8's min(353px, 85%) leaves 353 in place at 834 because the
   85% never binds there, so the engine shows two and a third cards
   of the wrong width instead of two of the right one.

   The mobile value needs no change and is worth recording as a
   hit: §6.3.8 invented 85% "so a phone shows one card and the edge
   of the next", and 40004589:13890 draws 285 of a 333 column, which
   is 85.6%. The invention was the drawn number.

   353 stays, at 1120 up, where it is drawn. */
@media (min-width: 640px) and (max-width: 1119.98px) {
  .ui-section--team { --ui-person-w: calc((100% - var(--grid-gutter)) / 2); }
}

/* ------------------------------------------------------------
   6.3.14 GALLERY — 353 is a desktop width, and the mobile clamp
   is off by two margins
   ------------------------------------------------------------
   Tablet 40004588:6497 · mobile 40004588:6486.

   Two separate faults in one declaration.

   1. The active frame is 294.95 x 376 at tablet (40004590:17192) —
      the drawn desktop 353 x 450 scaled by 0.8356 — and 353 only
      from the desktop frame. Same 295 that TEAM, TESTIMONIALS and
      the card families all draw at 794, and the same 295 CONTENT's
      well was measured at: 295 is the tablet unit the way 353 is
      the desktop one, and (614 - 20) / 2 says why. Anywhere §6.3
      hard-codes 353 without a breakpoint, the tablet value is
      wrong.

   2. At 393 the current clamp renders 273, not the 333 its own
      comment claims and the 333 the file draws (40004590:17152 is
      the full content column). `100%` on a rail child resolves
      against the rail's CONTENT box, which the rail's own
      padding-inline-end has already put back on the grid — so
      subtracting two grid margins subtracts them a second time.
      Measured in Chromium at 393, not reasoned about. This half is
      a defect independent of the file: intent, comment and drawing
      all agree on 333.

   Replaces: --ui-gallery-w: min(353px, calc(100% - 2 * --grid-margin)).
   100% is now the whole answer below 1120 — the rail's content box
   IS the content column — and the drawn desktop 353 is restored at
   1120 where the bleed is what the file wants. */
.ui-section--gallery { --ui-gallery-w: 100%; }
@media (min-width: 640px) {
  /* Half the column, less half the gutter: 297 against a drawn
     294.95, which is 2px and not on any scale. */
  .ui-section--gallery { --ui-gallery-w: calc((100% - var(--grid-gutter)) / 2); }
}
@media (min-width: 1120px) {
  /* 353px is the file's own literal, restored unchanged: it is four
     desktop columns, not a spacing step, and §6.3.14 already types
     it and says why. Only its breakpoint moves. */
  .ui-section--gallery { --ui-gallery-w: 353px; }
}

/* ------------------------------------------------------------
   6.3.17 LIST — CONDENSED — the row becomes a card, and the cards
   go two up at tablet
   ------------------------------------------------------------
   Tablet 40004588:5753 · mobile 40004588:5742.

   The largest reflow in the set and two changes at once. §6.3.17
   says of its own single-column fallback "no tablet frame exists,
   so this is judgement". One does, and it says something the
   judgement could not have guessed:

     desktop  head in columns 1-4, list BESIDE it in 6-12; each row
              horizontal — 260 well on the left, 353 text column on
              the right (40004583:67374)
     tablet   head full width at the top, list BELOW; each row
              turned inside out into a CARD — well ABOVE text, 295
              wide — and the cards sit TWO TO A ROW
              (40004590:16648 holds :16649 at x=0 and :16670 at
              x=315, both 295 wide)
     mobile   the same card, 333 wide, one per row
              (40004590:16435, :16456, :16481)

   The head collapse is already right. The row is not: at 834 the
   engine keeps the horizontal row, because 260 + 20 + 353 is 633
   and fits the 794 column without wrapping, so nothing triggers
   the wrap the phone gets for free.

   Column direction below 1120, and a two-track grid from 640 —
   both on the .ui-list stack rather than on the row, so the row
   itself keeps the flex layout the desktop frame needs. */
@media (max-width: 1119.98px) {
  .ui-list--condensed .ui-list__row { flex-direction: column; }
  /* The well stops being a flex basis and becomes the card's full
     width; the text column stops claiming a 353 basis it cannot
     have in a 295 card. */
  .ui-list--condensed .ui-list__well       { flex: 0 0 auto; width: 100%; }
  .ui-list--condensed .ui-list__disclosure { flex: 0 0 auto; width: 100%; }

  /* Two cards to a row at tablet, one at mobile, with the drawn 40
     between rows. A grid rather than a flex wrap, so the second row
     of an odd count keeps the first row's track width — which is
     what 40004590:16695 draws, a lone 295 card in a 614 box. */
  .ui-list--condensed {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    /* 40, drawn at BOTH small widths and against the shared
       --ui-list-gap of 20: tablet row 1 ends 671.17 and row 2
       starts 711.17; mobile card 1 ends 360.08 and card 2 starts
       400.08. ⚠ Set here and not on --ui-list-gap, because that
       token is shared with List — Expanded, which has NO small
       frame in the file — raising the token would change a section
       nobody has read. */
    gap: var(--s-w-m);
  }
}
@media (min-width: 640px) and (max-width: 1119.98px) {
  .ui-list--condensed { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* The column gap between two cards is the tablet gutter, drawn:
     295 + 20 + 295 = 610 in a 614 column. */
  .ui-list--condensed { column-gap: var(--grid-gutter); }
}

/* The well's inline padding is S below the desktop breakpoint.
   Drawn at both small sizes — the illustration sits at x=20 in a
   295 well at tablet (40004590:16651) and at x=20 in a 333 well at
   mobile (40004590:16437) — against the 40 the desktop frame draws
   (40004583:67376, x=40 in a 260 well). Block padding stays M at
   every width, which is why this is padding-inline and not padding.

   And the ratio comes OFF below 1120. The drawn well height is not
   a ratio at any width, it is the illustration plus two M: 118+80
   at desktop, 167.17+80 at tablet, 192.08+80 at mobile. The ratio
   is a desktop-only convenience that happens to hold when the
   illustration is 180 wide, and asserting it on a 333 well
   letterboxes the art it exists to hold. */
@media (max-width: 1119.98px) {
  .ui-list--condensed .ui-list__well {
    padding-inline: var(--s-w-s);
    aspect-ratio: auto;
  }
}

/* ------------------------------------------------------------
   6.3.13 COLUMNS — the stacked gap is S on a phone, not the gutter
   ------------------------------------------------------------
   40004588:4812: four columns at y = 0 / 198 / 396 / 594 at a
   height of 178, so 20 between them. The engine gives 10, because
   .ui-grid's gap is --grid-gutter and the phone gutter is 10.

   Worth stating rather than normalising, because the same file at
   the same width draws 10 between two stacked CARDS
   (40004584:111792/:111793, 377 tall, 10 apart) and 20 between two
   stacked COLUMNS. A card grid tightens to the phone gutter; a run
   of text columns does not — a column is a block of prose and 10px
   between two of them reads as one block. So .ui-grid's gutter gap
   is right for cards and this is the exception, not a fix to the
   grid.

   Tablet needs nothing: 20 is drawn (0 / 216 at height 196) and 20
   is the tablet gutter. */
@media (max-width: 639.98px) {
  .ui-section--columns .ui-grid { row-gap: var(--s-w-s); }
}

/* ------------------------------------------------------------
   6.3.22 CONTACT FORM — the form comes first, and the two blocks
   are XL apart
   ------------------------------------------------------------
   Tablet 40004588:5195 · mobile 40004588:5184.

   Desktop draws the details column left and the form right
   (40004583:68924 at x=0, 373 wide; 40004583:68936 at x=560, 540
   wide). Both small frames STACK them and put the FORM FIRST:
   tablet 40004589:14858 at y=0 with 40004589:14890 at y=690;
   mobile 40004589:14781 at y=0 with 40004589:14813 at y=897.

   `order`, not a markup change. Details-before-form is the right
   reading order for a screen reader and for a keyboard — a form
   whose contact alternatives are announced after the submit button
   is a worse document — so the DOM keeps the aside first and only
   the visual order moves. This is the same split §6.3.7 already
   makes for the stat numeral: markup order is reading order, the
   render is the drawing.

   The gap is XL and it is on the scale: form bottom 570 → aside
   top 690 at tablet, 777 → 897 at mobile. Both 120. Replaces the
   M the body grid currently sets below 1120. */
@media (max-width: 1119.98px) {
  .ui-section--contact .ui-section__body { row-gap: var(--s-w-xl); }
  .ui-section--contact .ui-contact__form  { order: -1; }
}

/* The form's own row gap is S below the desktop breakpoint. Drawn
   at both small sizes on a 64px field row: tablet pitch is 84
   (40004589:14859 at y=0, :14866 at y=84, :14873 at y=168) and
   mobile is the same 20 between groups (:14789 ends 271, :14796
   starts 291). Desktop draws 40 (40004583:68937 at y=0, :68944 at
   y=104), which is where the M belongs and stays.

   The pair rule inside a row needs nothing: .ui-field-row goes two
   up from 640 and one up below, and the frames draw exactly that —
   297 + 20 + 297 = 614 at tablet, one 333 field per line at
   mobile. Confirmed, not changed. */
@media (max-width: 1119.98px) {
  .ui-section--contact .ui-contact__form { gap: var(--s-w-s); }
}

/* ------------------------------------------------------------
   6.4b THE LAST SIX GROUPS
   ------------------------------------------------------------
   COLORFUL CARDS, GENERAL INTRO, PILLS, APOLLO FRAMEWORK, LONG
   TEXT and CTA — the six whose small frames had not been opened
   when 6.4 was written. All six read at both sizes, all four
   brand copies each. §6.3 is now measured rather than reasoned at
   every breakpoint, with one exception the file itself creates:
   LIST — Expanded has no small frame drawn at all.

   THREE THINGS WORTH CARRYING FORWARD
   1. `100vh` was a desktop instruction. The intro's small frames
      drop "(100vh)" from their own names and draw fixed heights.
      Asserting a viewport height everywhere gave 999.75px of art
      band at 834 where the file draws 281.46.
   2. Head→body is 80 at tablet in TEN groups now. It has been
      corrected three times in three directions; CONTENT (60
      desktop → 40) is the only real exception.
   3. `430px` joins `353px` as a desktop-only unit. A number that
      appears in several components deserves checking at every
      breakpoint before it is treated as a constant.

   AND ONE CLOSED ITEM: `Interactive Arches` (40004583:65389) is
   a NAME, not a behaviour. Two children, `Fill` and `Outline`,
   nine identical ellipses on a 133.7 pitch; get_motion_context
   returns nothing, there is no component set and no variants, and
   the small copies are the same tree scaled. Nothing is asked of
   CSS that CSS cannot do, so the engine having no motion layer is
   not a gap here.
   ------------------------------------------------------------ */

/* ============================================================
   §6.3 RESPONSIVE CORRECTIONS, SECOND PASS — the last six groups
   ------------------------------------------------------------
   Paste into §6.4, after the block already there. Same rules of
   engagement: every declaration below replaces a value that was
   reasoned from the desktop frame and the grid, every one cites
   the node it was measured on, and where a drawn number could not
   be honoured exactly the comment says what was honoured instead.

   These six close the set: COLORFUL CARDS, GENERAL INTRO, PILLS,
   SCORECARD, LONG TEXT and CTA. All fifteen groups are now read.
   The scorecard needed NO rule — see the note where its rules
   would have gone, because "§6.3.19 was right without the frame"
   is a finding and deleting the section from this block would
   hide it.

   Off-scale numbers are written as multiples of the scale (XXS 5,
   XS 10, S 20, M 40, L 60, XL 120, XXL 240, XXXL 320), and a drawn
   width that is a share of the content column is written as that
   share, because a fraction survives three grids and a pixel does
   not. Two exceptions are typed and flagged in place.

   THREE THINGS THE FRAMES SAID THAT THE GRID DID NOT
   1. A flex `gap` does not collapse with a margin. §3.2's
      descender note assumes it does. That one sentence is the
      root of the two largest numbers in this block: head→body
      rendering at 160 where 80 is drawn, and title→lead at 50.55
      where 40 is drawn.
   2. `100vh` is a DESKTOP instruction. The intro's eight small
      frames drop "(100vh)" from their own names and draw 684 and
      585, not a viewport.
   3. The centred head's 430 title cap at tablet is drawn nowhere.
      It is the third desktop-only unit found in the same shape as
      353 — a number transcribed off the 1440 frame and left
      without a breakpoint.
   ============================================================ */


/* ------------------------------------------------------------
   THE SHELL — a section that is also a page pays the head→body
   gap twice
   ------------------------------------------------------------
   Found in COLORFUL (tablet 40004590:15611 draws 80, mobile
   40004590:15638 draws 40) and it is not COLORFUL's: every card
   section in the templates is written
   `class="ui-page ui-section …"`, and §6.3.9 sets
   `--ui-flow: var(--ui-section-lead)` so §3.1's flow owl carries
   the same number as the shell's own flex gap. With both classes
   on ONE element both apply, and they add — measured 160 at 834
   (bodyMarginTop 80 + rowGap 80) and 80 at 393.

   §6.3.9's comment says a second owl "would mean two rules
   fighting over the same margin". They are not fighting: a flex
   gap is not a margin, does not collapse with one, and sums.

   The gap goes and the owl keeps the job, because the owl is the
   one §6.3.9 deliberately reassigned. (0,2,0) on the compound, so
   a plain .ui-section is untouched and keeps its gap.

   ⚠ The better fix is in the markup: `.ui-page .ui-section` in
   §6.3.2 shows the class was meant to be an ANCESTOR of a
   section, not a co-class on one. This rule is here because a
   stylesheet cannot edit a template, and it must not become the
   reason the markup is never fixed. */
.ui-page.ui-section { gap: 0; }


/* ------------------------------------------------------------
   6.3.2 THE CENTRED HEAD — the 430 title cap is desktop-only
   ------------------------------------------------------------
   PILLS tablet 40004588:5474 (siblings 40004596:18483, :18956,
   :19255) draws `Title h2` at the full 614 of a 614 content box.
   So does every other tablet frame with a title in it — COLORFUL
   40004590:15784, CTA 40004590:17050, SCORECARD 40004588:5010,
   LONG TEXT 40004588:6312 — and the first pass reports the same
   for its eight groups, STATS among them. Nothing draws 430.

   §6.3.2 gives the centred head
   `--ui-section-title-max: var(--ui-section-lead-max)`, which is
   430px from 640 up, so a centred title wraps a line early at
   tablet. At DESKTOP the cap is real: pills' title text is 540 at
   x=280 in 1100 (40004583:65635) and colorful's is the same
   (40004583:64354), i.e. exactly the lead measure. So only its
   breakpoint moves — the same correction the gallery's 353 needed.

   ⚠ Honest about visibility: the drawn strings are short enough
   that a 430 cap and a 614 box centre identically. This is a
   latent defect, not one you can see in the frame — it appears the
   first time a centred title needs more than 430 at tablet.

   ⚠ STATS shares the selector and was not re-opened in this pass.
   The first pass's own line — "the title going full width at
   tablet is also confirmed correct in all eight" — is the
   evidence for it here.

   Order, not specificity: same (0,2,0) as §6.3.2's rule, so this
   must stay BELOW it in the file. */
@media (max-width: 1119.98px) {
  .ui-section--pills .ui-section__head,
  .ui-section--stats .ui-section__head { --ui-section-title-max: 100%; }
}


/* ------------------------------------------------------------
   6.3.5 PILLS
   ------------------------------------------------------------
   Tablet 40004588:5474 · mobile 40004588:5463, screenshots read.

   The marquee's overflow needed NO change and that is worth a
   line, because the coordinates argue for one and are wrong. At
   mobile the `Content` frame is x=20 w=333 — the content column —
   which reads as "the full bleed comes off at 393". The rows do
   not clip: the render shows chips cut at the FRAME edge, and the
   fade rectangles are drawn from -20.98 to 104 and from 354.5 to
   479.4, deliberately outside the column at both ends
   (40004590:17767 / :17768). `.ui-pill-marquee`'s negative inline
   margin plus `overflow: hidden` is what the file draws at both
   small sizes. Two other things confirmed and not touched: the
   XXS row gap (rows at y=0 and y=45 on a 40px chip) and the 40px
   chip box.

   ONE value is wrong. The note's gap under the rows is M at
   tablet — 85 -> 125, which is what §6.3.5 transcribed — and S at
   mobile: 40004590:17751 is 85 tall and 40004590:17769 starts at
   105. Replaces the M this section declares for every width. */
@media (max-width: 639.98px) {
  .ui-section--pills { --ui-section-body-gap: var(--s-w-s); }
}

/* And the standfirst stops being centred on a phone. The title
   stays centred at both small sizes; the lead is CENTRED at 834
   (40004588:5478 is 430 wide at x=92 of 614, text centred in the
   render) and START-aligned at 393, where the block is the full
   333 and the four-line paragraph sets flush left with a short
   last line (40004588:5467, read in the screenshot). Centred body
   copy needs a measure to be centred in; at mobile there is none.

   On the lead itself rather than on the head, so the title keeps
   the head's centring and only the paragraph moves. */
@media (max-width: 639.98px) {
  .ui-section--pills .ui-section__lead { text-align: start; }
}

/* ⚠ NOT PATCHED, reported: the edge fade. Drawn 163 at tablet and
   ~125 at mobile against `clamp(L, 22.7%, 318px)` resolving to
   ~189 and ~76. 22.7% is the DESKTOP share (318 of 1400) and the
   drawn share grows as the box narrows — 20.5% at tablet, 37.5% at
   mobile — so a single fraction cannot hold it. It is a decorative
   gradient over chips whose own contrast is measured elsewhere,
   and inventing a two-stop clamp for it would cost more than it
   buys. */


/* ------------------------------------------------------------
   6.3.12 COLORFUL CARDS — it WRAPS, and three values move
   ------------------------------------------------------------
   Tablet 40004590:15611 (sibling 40004596:23537) · mobile
   40004590:15638 (sibling 40004596:23563). Screenshot read.

   ⚠ FIRST: the rail suspicion the first pass recorded for this
   section is WRONG and no rule implements it. The tablet frame's
   `Content` (40004590:15627) is 614 wide in a 614 content box —
   four 295 cards in a 2 x 2 block at x = 0 / 315 and y = 0 / 362,
   nothing overflowing. Compare ALIGNED's tablet `Cards` group at
   925 wide in the same 614 box. Aligned rails; Stacked and
   Colorful wrap. §6.4's rail selector list stays as it is.

   The engine already reproduces both arrangements (2-up at 834 via
   --span:4, 1-up at 393). What it gets wrong is the spacing. */

/* The body's own gap — count/filter to cards, cards to pager — is
   M below the desktop breakpoint, not the S the shell declares.
   Drawn at both: tablet filter ends 64 and `Content` starts 104,
   cards end 808 and the pager starts 848; mobile 108 -> 148 and
   1408 -> 1448. Desktop is 20 both times (40004583:64367 at y=84
   after a 64 filter), which is where the S belongs and stays. */
@media (max-width: 1119.98px) {
  .ui-section--cards-colorful { --ui-section-body-gap: var(--s-w-m); }
}

/* The stacked card gap is S on a phone, not the gutter. Cards are
   300 tall at y = 0 / 320 / 640 / 960 (40004590:15656 and its
   three siblings), so 20 between them where `.ui-section__cards`
   gives 10.

   ⚠ This contradicts §6.4's own reading of the same question.
   That block explains 10 between stacked CARDS and 20 between
   stacked COLUMNS as "a card grid tightens to the phone gutter; a
   run of text columns does not". Colorful is a card grid and draws
   20; the scorecard's panels draw 10. Four sections, two values,
   and the split is not cards-versus-prose — it is per section. So
   this is a second exception rather than a fix to the grid, for
   exactly the reason §6.4 gave for the first one.

   Tablet needs nothing: 20 is drawn (342-tall cards at y = 0 /
   362) and 20 is the tablet gutter. */
@media (max-width: 639.98px) {
  .ui-section--cards-colorful .ui-section__cards { row-gap: var(--s-w-s); }
}

/* The filter bar becomes two lines on a phone, and it is not a
   wrap the content forced. 40004590:15647 is 78 tall against the
   tablet 34: the chip set sits at y=0 (181 wide) and the search
   at y=44 AND x=0 — under the chips, left-aligned — with the whole
   Filter block growing 64 -> 108. 181 + 5 + 34 is 220 in a 333
   column, so it FITS on one line and the file breaks it anyway.
   §6.3.12's comment reasons the opposite ("it fits by 2px, and the
   fourth chip wraps"), off a desktop chip set that is wider than
   the drawn phone one.

   Basis 100% on the search is what breaks the line; `space-between`
   then start-aligns the only item on it, which is the drawn x=0.
   The row gap is XS, drawn 44 - 34 = 10.

   Scoped to this section rather than left on the bare
   `.ui-section__filter-row`: the class has one user today, and the
   two list sections that could adopt it have no small frame I have
   read. `flex` on a child of a section, one level deep — the same
   reach §6.3.5 already takes to `.ui-pill`, and no §5 state rule
   sets flex on the search. */
@media (max-width: 639.98px) {
  .ui-section--cards-colorful .ui-section__filter-row {
    row-gap: var(--s-w-xs);
  }
  .ui-section--cards-colorful .ui-section__filter-row > .ui-search {
    flex: 0 0 100%;
  }
}

/* The head is CENTRED, which §6.3.12 never says. Drawn at desktop
   (title text 540 at x=280 of 1100, 40004583:64354; lead 540 at
   x=280) and at tablet (title box the full 614, lead 430 at x=92
   of 614, 40004590:15787) and read in the tablet screenshot as a
   two-line centred title over a centred standfirst. The engine
   start-aligns the title and centres only the intro paragraph,
   which its auto inline margins do by themselves — so a two-line
   title at 834 is left where the file centres it.

   NOT a breakpoint: the file centres it at desktop too. Same two
   declarations §6.3.2 already gives PILLS and STATS, plus the
   title cap at 1120 only — the drawn desktop title is the lead
   measure and the drawn tablet title is the full box, which is the
   corrected form of that cap, not the one this file shipped.

   ⚠ ALIGNED and STACKED draw the same centred head and were not
   re-measured here. The first pass calls their head alignment
   correct on the evidence of the lead's position alone, which is
   the half that auto margins already handle. Worth re-opening. */
.ui-section--cards-colorful .ui-section__head {
  align-items: center;
  text-align: center;
}
@media (min-width: 1120px) {
  .ui-section--cards-colorful .ui-section__head {
    --ui-section-title-max: var(--ui-section-lead-max);
  }
}

/* …and the copy under it does NOT centre on a phone, the same way
   PILLS' standfirst does not. 40004590:15863 read at 333 wide: the
   two-line title is centred and the four-line paragraph sets flush
   left with a short last line. Two sections now draw the same
   thing at 393 — a centred display title over start-aligned body
   copy — which makes it the phone behaviour of the centred head
   rather than a quirk of either.

   On the intro paragraph rather than on the head, so the title
   keeps the centring the head gives it. Colorful's copy block is
   .ui-section__intro where pills' is .ui-section__lead; both are
   patched, and both are one class deep. */
@media (max-width: 639.98px) {
  .ui-section--cards-colorful .ui-section__intro { text-align: start; }
}


/* ------------------------------------------------------------
   6.3.19 SCORECARD — NOTHING TO PATCH, and that is the finding
   ------------------------------------------------------------
   §6.3.19 says "No tablet frame exists: at 834 three equal panels
   are 251px each … checked in the render, not reasoned about."
   FOUR tablet frames exist — 40004588:5009, 40004596:16342,
   :14647, :15426 — and the render check was right in every
   particular:

     panels        3 equal columns at tablet, 191.33 each =
                   (614 - 40) / 3, on a 20 gutter (40004589:14453,
                   :14492, :14531 at x = 0 / 211.33 / 422.67)
     panels        1 column at mobile, 333 (40004589:14319, :14358,
                   :14397 at y = 0 / 269 / 538)
     stack gap     10 at mobile — the phone gutter, drawn
     head -> panels 20 at tablet (68 -> 88), 10 at mobile (63 -> 73)
     padding       S in the head bar and in every panel at both
     head -> body   80 at tablet (562 -> 642), 40 at mobile
                   (603 -> 643) — the shell, unmodified
     lead measure  430 / 333, title full at both

   One file inconsistency, no rule: at tablet the bullet container
   is 430 wide and its four children are 540 (40004589:14434,
   :14437, :14440, :14443), so the file's own bullet text overflows
   by 110. At mobile they are 333 and agree. The engine uses the
   430 and is right to.

   Left in place rather than deleted so that "the judgement held"
   is on the record next to the sections where it did not. */


/* ------------------------------------------------------------
   6.3.20 LONG FORM TEXT — the L is a desktop value, and the well
   is not a ratio below 1120
   ------------------------------------------------------------
   Tablet 40004588:6311 (siblings 40004596:21681, :21755, :21825) ·
   mobile 40004588:6300 (siblings 40004596:21656, :21730, :21800).

   §6.3.20 reasons head→body as "L (60) here, not the 80 the other
   sections use … 40 to the content-body frame plus that frame's
   own 20px of padding. Measured, and on-scale, unlike 80." The
   desktop half is exact. The 20px of inner padding is a DESKTOP
   fact and the conclusion does not survive the small frames:

     desktop  head bottom 173, body top 213, body pad 20  -> 60
     tablet   head bottom 298, body top 378, body pad 0   -> 80
     mobile   head bottom 249, body top 289, body pad 0   -> 40

   So this is the third section to get the step wrong, and it is
   wrong in the opposite direction from CONTENT: CONTENT needed an
   override DOWN to M, this one needs its override confined to
   desktop so the shell's own values apply. The shell is right
   below 1120 in nine of nine groups that have a head and a body;
   only the desktop value was ever a section's own business.

   Two queries rather than three: neither matches at 1120 up, so
   §6.3.20's own L survives there untouched. Mobile-first pair, so
   the second must follow the first. */
@media (max-width: 1119.98px) {
  .ui-section--longform { --ui-section-lead: var(--s-w-m); }
}
@media (min-width: 640px) and (max-width: 1119.98px) {
  .ui-section--longform { --ui-section-lead: calc(var(--s-w-m) * 2); }
}

/* The byline block to the prose column is 2xM at tablet, drawn:
   40004590:16949 ends at 130 and 40004590:16934 starts at 210.
   Mobile draws M (130 -> 170) and already gets it. Same number as
   the head→body step above and for the same reason — at tablet
   this section spaces its two stacked blocks the way the shell
   spaces head from body. */
@media (min-width: 640px) and (max-width: 1119.98px) {
  .ui-section--longform .ui-section__body { row-gap: calc(var(--s-w-m) * 2); }
}

/* The prose measure comes OFF below the desktop breakpoint. The
   column is the full content box at both small sizes — 614 at
   tablet (40004590:16934) and 333 at mobile (40004590:16889) —
   against `--ui-measure`, which steps down to 430 at 640 and caps
   the article 184px short of its own column.

   On the section, never on the token: §6.3.15's 430 was measured
   on the section LEAD in eight tablet frames, which is a different
   job, and the same token is read by both list sections and the
   contact form. At tablet the three text sections in this pass
   draw 430, 540 and 614 for the thing `--ui-measure` caps. One
   token cannot be all three. Same treatment §6.4 gave
   --ui-list-gap, and for the same reason. */
@media (max-width: 1119.98px) {
  .ui-section--longform .ui-longform__prose { max-width: 100%; }
}

/* And the figure's ratio comes off with it. The drawn image is
   300 TALL AT EVERY WIDTH — 540 x 300 at desktop (40004583:68758),
   614 x 300 at tablet (40004590:16942), 333 x 300 at mobile
   (40004590:16897) — so 9/5 is a desktop coincidence, the same one
   §6.4 already unpicked in LIST — Condensed's well. Asserting it
   at 393 letterboxes a 300px band into 185.

   300 is not a step: XXL + L, written as the sum so the intent is
   readable. Height rather than min-height because the drawn value
   does not vary, and the ratio is left in place at 1120 up where
   the two agree to the pixel. */
@media (max-width: 1119.98px) {
  .ui-section--longform .ui-longform__well {
    aspect-ratio: auto;
    height: calc(var(--s-w-xxl) + var(--s-w-l));
  }
}


/* ------------------------------------------------------------
   6.3.21 CTA — every gap in this section is M, at every width
   ------------------------------------------------------------
   Buttons variant: tablet 40004588:6404 (siblings 40004596:21514,
   :21594, :21434) · mobile 40004588:6393 (siblings 40004596:21496,
   :21576, :21416). Newsletter variant: tablet 40004590:17066
   (siblings 40004596:21524, :21604, :21444) · mobile
   40004590:17026 (siblings 40004596:21506, :21586, :21426).

   Eight and eight, as the coverage audit said, and NO structural
   change at any width: one centred block, title over lead over
   controls, at 1440, 794 and 373 alike. It is also the only
   section in the set with no `.ui-section__body` — every child is
   in the head — which is why the shell's head→body step never
   applies here, and why CTA is not a counter-example to the 80.

   The controls sit M under the lead at all three widths: desktop
   113 -> 153 (40004583:68848), tablet 102 -> 142
   (40004590:17055), mobile 129 -> 169 (40004590:17009). The head's
   gap is S and nothing adds to it, because a div carries no
   descender margin — so this is 20 drawn against 40, at every
   width, and it is a value rather than a breakpoint.

   A margin on the row, not a gap on the head: the head's gap also
   sets title -> lead, which is over-large already (see the note
   below). S + S is the drawn M and stays on the scale. */
.ui-section--cta .ui-row { margin-block-start: var(--s-w-s); }

/* The lead keeps the desktop measure at tablet. 40004590:17053 is
   540 wide at x=37 of 614 — symmetric, so drawn rather than
   inherited — against `--ui-measure` stepping down to 430 at 640.
   540px typed, not a share: it is six desktop columns and the
   file's own literal, exactly as §6.4 restores the gallery's 353,
   and only its breakpoint is at issue. */
@media (min-width: 640px) and (max-width: 1119.98px) {
  .ui-section--cta .ui-prose { max-width: 540px; }
}

/* On a phone the whole text block narrows. All eight mobile frames
   draw `Text` 256 wide at x=58.5 in a 373 frame — 58.5 + 256 +
   58.5, symmetric again — and title, lead and button row all take
   that width (40004590:17004, 40004590:17027 and siblings). The
   engine gives the full 333 column.

   77%, because 256 of the drawn 333 content column is 76.88% and a
   share is the only form that survives a 393 frame with a 30px
   margin as well as a 373 frame with a 20px one. On the head
   rather than on each child, since the file narrows the block and
   not the roles: the button row is 258 in that 256 box — 1px over
   each edge, which is the file's own overhang and is why this is a
   max-width and not a hard width. */
@media (max-width: 639.98px) {
  .ui-section--cta .ui-section__head {
    max-width: 77%;
    margin-inline: auto;
  }
}

/* ⚠ NOT PATCHED, reported: the newsletter mount is 353 at desktop,
   295 at tablet (40004590:17082) and 256 at mobile
   (40004590:17042) — 353 is a desktop unit here too, the ninth
   place it appears, and 295 now has ten witnesses. No rule,
   because no template carries a CTA-newsletter instance and the
   engine's rendered width could not be measured. A correction
   nobody has seen both halves of is how this whole exercise
   started. */


/* ------------------------------------------------------------
   6.3.4 GENERAL INTRO — "100vh" is a desktop instruction
   ------------------------------------------------------------
   Tablet 40004590:17273 (siblings 40004590:17408, 40004596:20152,
   :20183, :20592, :20623, :21004, :21035) · mobile 40004590:17478
   (siblings 40004596:20214, :20654, :21066, :21097). Screenshot of
   the tablet frame read.

   ⚠ The small frames are 834 and 393 WIDE, not 794 and 373. This
   is the one group drawn at true viewport width at every size,
   exactly as §6.3.4 says it is at 1440 — which makes it the only
   place in the file where the grid margins can be read directly.
   Two things fall out and both belong in the reference doc rather
   than here: the mobile frame is 393 with a 333 content box, so
   --grid-margin-m: 30 is confirmed by a drawing; and the tablet
   frame is 834 with a 794 content box, so THIS section's tablet
   margin is 20 = --grid-margin-t, while every other group insets
   90 in a 794 frame, i.e. 110 from the same viewport. One section
   obeys §1's tablet grid and fourteen do not.

   ── THE HEIGHT ──────────────────────────────────────────────
   The desktop frames are named "General Intro (100vh)". All
   eight small frames are named "General Intro" — the unit is
   dropped — and they draw 684.46 total at tablet (609.46 below the
   navbar) and 585 at mobile (510 below it), against tablet and
   phone viewports of roughly 1112 and 852. §6.3.4 reads the name
   as "a layout instruction and not a label"; it is, and the file
   withdraws the instruction below desktop.

   Measured cost of keeping it: with a 1200-tall viewport the
   section reports min-height 1200 at both sizes and the art band
   absorbs the difference — 999.75px of band at 834 and 877.03px at
   393, where the file draws 281.46 and 200. Three to four times
   the drawn art, on the first screen.

   The svh/vh pair is restored unchanged at 1120 up, including the
   two-declaration fallback idiom and the offset: everything
   §6.3.4 argues about small-viewport units is right, and only its
   breakpoint moves. */
.ui-section--intro { min-height: 0; }
@media (min-width: 1120px) {
  .ui-section--intro {
    min-height: calc(100vh  - var(--ui-intro-offset));
    min-height: calc(100svh - var(--ui-intro-offset));
  }
}

/* ── THE BAND ────────────────────────────────────────────────
   With the viewport floor gone the band must stop growing, or
   `flex: 1 1 auto` simply eats whatever the section is given.
   The drawn height is the ART's: 491 / 1420 = 0.3458 at desktop
   and 281.46 / 814 = 0.3458 at tablet — the same 2.892 : 1 — while
   mobile breaks the ratio at 393 x 200. The art is a slot the
   markup fills, so the ratio is the markup's; all this has to do
   is stop asserting a height the art does not have.

   The floor drops to 5xM. 200 is what 40004590:17481 draws and it
   is BELOW the 240 §6.3.4 invented "so a short viewport cannot
   flex the band to nothing" — the invention was sound and one step
   too big. Written as a multiple of M rather than as XXL less S,
   because 5 x 40 is how the drawing reads. */
@media (max-width: 1119.98px) {
  .ui-intro__band {
    flex: 0 1 auto;
    min-height: calc(var(--s-w-m) * 5);
  }
}

/* ── THE ROW ─────────────────────────────────────────────────
   It stacks at TABLET, and the engine has no breakpoint for it at
   all: §6.3.4 relies on `flex-wrap` plus a 500px aside basis, and
   at 834 that never wraps — measured as title 254 and aside 500
   side by side on one line, where 40004590:17297 draws the h1
   across the full 794 with the standfirst BELOW it and flush right
   (40004590:17300 at x=454, 454 + 340 = 794).

   The 117px §6.3.4 calls "the residue of justify-between" is a
   desktop residue. Below 1120 there is no residue because there is
   no row.

   `flex: 0 1 auto` on both so the 500px basis stops applying in a
   column, and `flex: 0 0 auto` is wrong on the title for the same
   reason it was right in a row — it must not grow vertically. */
@media (max-width: 1119.98px) {
  .ui-intro__row   { flex-direction: column; }
  .ui-intro__title { flex: 0 0 auto; }
  .ui-intro__aside { flex: 0 1 auto; }
}

/* Stacked at tablet, the standfirst is 340 wide and END-aligned.
   340 is four of nine tablet columns — 4 x (100% - 8 gutters) / 9
   plus the three gutters between them is 341.78 against a drawn
   340, 1.8px on a measure that is not on any scale — so it is
   written as the column count, which survives the grid, rather
   than as 42.8% of a box the engine sizes differently from the
   file. */
@media (min-width: 640px) and (max-width: 1119.98px) {
  .ui-intro__aside {
    align-self: flex-end;
    width: calc((100% - 8 * var(--grid-gutter)) / 9 * 4
                + 3 * var(--grid-gutter));
  }
}

/* On a phone the standfirst is 285 of a 333 column and starts at
   x=0 (40004590:17505), so it stays start-aligned and takes 85% —
   the same mobile unit the team card and the aligned card draw,
   and the same share §6.3.8 invented before a frame confirmed it.

   Two paddings go to zero and one gap opens, all three drawn:
     · the aside's S block padding is desktop-and-tablet only. At
       mobile the standfirst sits at y=0 and the button ends flush
       with the block (40004590:17506 at y=0, :20580 ending at 156
       in a 156 box). Its job at desktop is to drop the standfirst
       below the title's cap in a ROW; stacked, there is no cap to
       clear.
     · the row's own S block padding likewise: 40004590:17502 is at
       y=0 in a 230-tall box 230 tall.
     · and M opens between the band and the row, where desktop and
       tablet abut the hairline directly: 40004590:17481 ends at
       200 and 40004590:17501 starts at 240. The section's gap is
       0 for the whole set, so this is the one width that needs it.
*/
@media (max-width: 639.98px) {
  .ui-section--intro { gap: var(--s-w-m); }
  .ui-intro__row     { padding-block: 0; }
  .ui-intro__aside   { width: 85%; padding-block: 0; }
}

/* ── THE INSET ───────────────────────────────────────────────
   20 at desktop and tablet, 30 at mobile — 393 less a 333 content
   box (40004590:17502 at x=30). --grid-margin is 30 at mobile and
   20 at tablet, so it IS this section's inset below the desktop
   breakpoint, and only desktop needs the literal S that §6.3.4
   declares for every width. One token, two branches, no new
   number. */
.ui-section--intro { --ui-section-inset: var(--grid-margin); }
@media (min-width: 1120px) {
  .ui-section--intro { --ui-section-inset: var(--s-w-s); }
}

/* ⚠ NOT PATCHED, reported: the navbar. The desktop frame draws no
   bar and reserves 75px inside the band; both small frames draw a
   REAL 75px bar as a band ABOVE the section (40004590:17274 at
   y=0, 835 x 75; 40004590:17479 at y=0, 393 x 75, a different
   component). That is the second arrangement §6.3.4 describes, the
   one where --ui-intro-offset would be needed — and it cannot be
   needed below 1120 any more, because 100svh is not asserted
   there. The token keeps its 0 default and its desktop meaning. */


/* ------------------------------------------------------------
   ⚠ NOT PATCHED, and the one to fix next: the head's own gap
   ------------------------------------------------------------
   Shared by every section with a display title over a paragraph,
   found again in all six of these and measured in Chromium.
   §3.2 declares

     .ui-h1, .ui-h2, .ui-h3 {
       margin: 0 0 calc(var(--ui-display-descender) + var(--s-w-s));
     }

   so the margin ALREADY CONTAINS AN S, and `.ui-section__head`'s
   own `gap: S` is a second one:

     1440   14.08 + 20 margin + 20 gap = 54.08   drawn 40
      834   10.56 + 20 margin + 20 gap = 50.55   drawn 40
      393    7.92 + 20 margin + 20 gap = 47.91   drawn 40

   §6.3.2 states this as "gap S -> 34.1px, 6px tighter than drawn",
   and the first responsive pass refines it to 6 / 9 / 12px tighter.
   Both have the sign backwards: those are the numbers for `gap: 0`.
   The engine is 14 / 10.6 / 7.9px LOOSER than the file at the
   three widths.

   Not corrected here for §6.3.2's own reason — the fix is a --ui-h2
   margin token in §3.2, and a third rule in this block arguing over
   the same distance is how the confusion started. But it must stop
   being recorded as a small divergence in the safe direction.

   The root cause is one sentence in §3.2: "it collapses with the
   flow gap in §3.1 and the larger wins". True against §3.1's
   margin; false against a flex gap, which does not collapse and
   simply adds. The same sentence is behind the 160-against-80
   head→body at the top of this block. */

/* ============================================================
   7. PRINT
   ------------------------------------------------------------
   A web page does get printed, and every interactive state is
   meaningless on paper. Controls flatten to outlines so a printed
   form is still a legible form, and the focus ring goes entirely.
   Accent plates print as outlines too: a solid accent fill costs
   ink and, on the pale-accent brands, prints as a grey smudge
   with invisible knockout text on top.
   ============================================================ */
@media print {
  .ui-btn, .ui-tag, .ui-pill,
  .ui-btn:hover, .ui-tag:hover, .ui-pill:hover,
  .ui-signup__submit, .ui-signup__submit:hover {
    background: transparent;
    border-color: var(--c-ink);
    color: var(--c-ink);
  }
  .ui-pager--dots .ui-pager__item[aria-current] {
    background: var(--c-ink);
  }
  .ui-search { width: var(--ui-search-w); }
  .ui-search__input { opacity: 1; }
  :focus-visible { outline: none; box-shadow: none; }

  /* --- INVERTED REGIONS ---------------------------------------------
     Print strips the dark ground but §2 leaves the ink on the inverted
     ramp, so a white display title printed WHITE ON WHITE. Measured on
     a proof page: 1.24:1, in four sections, and invisible on screen
     because on screen the ground is there.

     Fixed by reassigning the mode's own indirection tokens rather than
     by naming components: everything inside reads --ui-*, so one rule
     reaches the lot — titles, copy, borders, plates. Reaching for the
     component list instead is how the other three print bugs in this
     block got missed the first time. */
  .ui-mode--inverted {
    --ui-ground:    transparent;
    --ui-plate:     transparent;
    --ui-plate-up:  transparent;
    --ui-ink:         var(--c-ink);
    --ui-ink-60:      var(--c-ink-1);
    --ui-ink-40:      var(--c-ink-2);
    --ui-ink-20:      var(--c-ink-3);
    --ui-ink-copy:    var(--c-ink);
    --ui-ink-copy-60: var(--c-ink-1);
    --ui-ink-copy-20: var(--c-ink-3);
    --ui-fill-active:   transparent;
    --ui-ink-on-accent: var(--c-ink);
    background: transparent;
    color: var(--c-ink);
  }

  /* --- composed components -------------------------------------
     Each of these re-plates an atom, so each has to re-flatten it:
     a rule in §5/§6 that sets a background at (0,2,0) out-specifies
     the (0,1,0) resets above. Three separate print bugs came from
     exactly this, all found by printing the proof pages. --- */

  .ui-card,
  .ui-card:hover,
  .ui-card[data-state="hover"],
  .ui-card--colorful:hover,
  .ui-card--colorful[data-state="hover"] {
    background: transparent;
    border-color: var(--c-ink);
    color: var(--c-ink);
  }
  .ui-card__media { background: transparent; border: var(--ui-border-w) solid var(--c-ink); }
  .ui-card__title, .ui-card__arrow { color: var(--c-ink); }
  .ui-card__body  { color: var(--c-ink); }
  .ui-card .ui-card__tag,
  .ui-card:hover .ui-card__tag,
  .ui-card[data-state="hover"] .ui-card__tag,
  .ui-card--colorful:hover .ui-card__tag,
  .ui-card--colorful[data-state="hover"] .ui-card__tag {
    background: transparent;
    border-color: var(--c-ink);
    color: var(--c-ink);
  }

  /* The plate goes, the padding stays. Zeroing the inline padding
     was tried and reverted: it puts the logo flush against the
     sheet's own margin while .ui-page keeps its 170px grid margin
     below, so the two disagree more than they did before. */
  .ui-nav { background: transparent; }
  /* The bar re-plates its own controls at (0,2,0), which out-specifies the
     (0,1,0) flattening at the top of this block — so a printed navbar came
     out with a filled CTA on an otherwise flat page. Caught by rendering
     with print media emulated, which is the only way to catch it. */
  .ui-nav .ui-btn,
  .ui-nav .ui-btn:hover,
  .ui-nav .ui-btn[data-state="hover"],
  /* The resting override is written with a :not() pair so §5.1's hover rules
     keep winning — which makes it (0,4,0), higher than anything above. The
     print reset has to carry the same chain or it loses. Confirmed with
     CSS.getMatchedStylesForNode under print emulation rather than guessed. */
  .ui-nav .ui-btn:not(:hover):not([data-state="hover"]) {
    background: transparent;
    border-color: var(--c-ink);
    color: var(--c-ink);
  }
  .ui-nav--sticky { position: static; }
  .ui-nav__toggle { display: none; }
  .ui-nav__menu {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: var(--s-w-m);
    position: static;
    inset: auto;
    max-block-size: none;
    overflow: visible;
    padding: 0;
    background: none;
  }

  .ui-footer__logo { display: none; }
  .ui-footer { background: none; }

  /* And the submit has to be flattened again HERE. §6 already resets
     .ui-signup__submit to an outline, but 6.2.4's resting fill is a
     descendant selector and out-specifies it (0-2-0 against 0-1-0),
     so without this the footer printed a solid accent block — the
     exact thing §6 exists to prevent. Caught by rendering the proof
     under print emulation, which is worth doing for any section that
     re-styles a §5 component. */
  .ui-footer__newsletter .ui-signup__submit,
  .ui-footer__newsletter .ui-signup__submit:hover {
    background: transparent;
    color: var(--c-ink);
  }

  /* --- page sections (§6.3) ------------------------------------
     Same lesson as the composed components above, three more times:
     a section that re-plates an atom out-specifies the resets at the
     top of this block, and a filled CTA band or a solid gallery
     frame costs a page of ink for nothing. --- */

  .ui-section { background: none; }

  .ui-section--intro { min-height: 0; }
  .ui-intro__band {
    /* The rule stays — it is structure, and 1px of ink is cheap.
       The art inside is the brand's and prints as it prints. */
    min-height: 0;
  }

  /* Unroll both overflow layouts so nothing is hidden off the
     sheet. The negative bleed goes with them, or the last card
     prints into the margin. */
  .ui-pill-marquee { overflow: visible; margin-inline: 0; }
  .ui-pill-marquee::before,
  .ui-pill-marquee::after { display: none; }
  .ui-pill-marquee .ui-pill-group { flex-wrap: wrap; }

  .ui-person-row {
    overflow: visible;
    flex-wrap: wrap;
    margin-inline-end: 0;
    padding-inline-end: 0;
  }

  /* Ink, everywhere. Listed one class per role rather than as a
     single grouped selector so that the two-class person rules
     keep their (0,2,0) where they need it. */
  .ui-section__lead,
  .ui-section__lead-dim,
  .ui-quote__text,
  .ui-quote__name,
  .ui-quote__role,
  .ui-stat__value,
  .ui-stat__label,
  .ui-person__name,
  .ui-person__role,
  .ui-person__bio {
    color: var(--c-ink);
  }

  /* The two portrait wells are plates and print as outlines, the
     same treatment §7 gives .ui-card__media. */
  .ui-quote__portrait,
  .ui-person__portrait {
    background: transparent;
    border: var(--ui-border-w) solid var(--c-ink);
  }

  /* The gallery frame is the only plate §6.3 paints. Same
     treatment §7 gives .ui-card__media: the fill goes, an ink
     hairline keeps the frame legible as a frame. */
  .ui-section__frame {
    background: transparent;
    border: var(--ui-border-w) solid var(--c-ink);
  }

  /* A scroll container prints as whatever happened to be in view,
     which for a five-frame rail is two frames and a stump. Unclip
     it, let it wrap onto as many lines as it needs, and put both
     edges back on the margin — the bleed is a screen device and on
     paper it would simply amputate the trailing frames. */
  .ui-section__rail {
    flex-wrap: wrap;
    overflow: visible;
    margin-inline-end: 0;
    padding-inline-end: 0;
    scroll-snap-type: none;
  }

  /* 60% ink prints as a grey wash. §7 already promotes
     .ui-card__body to full ink for the same reason; these are the
     two dimmed roles §6.3 adds. */
  .ui-section__intro,
  .ui-section__count,
  .ui-section__column-body { color: var(--c-ink); }

  /* A column split across a page break loses its tag to the
     previous sheet, and a frame split in half is not a picture.
     Cheap, and neither box is ever tall enough for the avoid to
     cost a page. */
  .ui-section__column,
  .ui-section__frame { break-inside: avoid; }

  /* ⚠ NOT A SECTION RULE. A §5.2/§7 gap that §6.3 is the first
     thing to expose, left here because this block merges into §7
     and that is where it belongs.

     §7 flattens `.ui-tag` at (0,1,0). §5.2's `.ui-tag--filled` is
     also (0,1,0) so §7 wins it on order — but
     `.ui-tag--filled[aria-pressed="true"]` is (0,2,0) and §7 loses
     to it, plate AND transparent border. Nothing before this
     chunk ever put a filled, PRESSED tag on a page: the card's
     tag is filled but never pressed, and §7 re-flattens it by name
     anyway. The Colorful section's filter bar is the first, and it
     printed the selected chip as a pale plate with no edge while
     its two siblings printed as outlines — the selected filter
     read as the unselected one. Measured with computed styles
     under print emulation, not eyeballed: background stayed
     rgb(244,241,239).

     Same specificity as the rule it undoes, per integration note
     16's habit, so it wins on order rather than by escalating. */
  .ui-tag--filled[aria-pressed="true"],
  .ui-tag--filled.ui-tag--active {
    background: transparent;
    border-color: var(--c-ink);
    color: var(--c-ink);
  }

  /* The accent band is the expensive one: a full-bleed saturated
     ground costs a page of ink and prints its knockout type as
     white-on-grey. Same argument §7 already makes for an accent
     button fill, one band larger. */
  .ui-section--cta {
    background: transparent;
    color: var(--c-ink);
  }
  .ui-section--cta .ui-copy,
  .ui-section--cta .ui-copy--dim { color: var(--c-ink); }

  /* Copy ink is a screen decision. On paper there is one ink. */
  .ui-copy, .ui-copy--dim,
  .ui-deflist > dt, .ui-deflist > dd,
  .ui-list__title, .ui-list__meta, .ui-list__arrow, .ui-list__toggle,
  .ui-list-filter__count,
  .ui-scorecard__group-title, .ui-scorecard__label,
  .ui-longform__caption { color: var(--c-ink); }

  /* Wells and plates flatten to outlines, exactly as §7 does for
     .ui-card__media — the well is a placeholder, and a printed
     grey rectangle is ink spent on nothing. */
  .ui-list--expanded .ui-list__media,
  .ui-longform__well,
  .ui-scorecard__head,
  .ui-scorecard__group {
    background: transparent;
    border: var(--ui-border-w) solid var(--c-ink);
  }

  /* The row tag is plated at (0,2,0) — §7's generic .ui-tag reset
     is (0,1,0) and would lose, so a printed list came out with one
     accent chip on an otherwise flat page. The hover pair is
     listed too: the plate comes from a token set on the ROW, so
     naming only the tag leaves the accent behind. */
  .ui-list__row .ui-list__tag,
  .ui-list--expanded .ui-list__row:hover .ui-list__tag,
  .ui-list--expanded .ui-list__row[data-state="hover"] .ui-list__tag {
    background: transparent;
    border-color: var(--c-ink);
    color: var(--c-ink);
  }

  /* Same trap on the checked chip: (0,2,0) against §7's (0,1,0). */
  .ui-choice__input:checked + .ui-choice__option {
    background: transparent;
    border-color: var(--c-ink);
    color: var(--c-ink);
  }

  /* The meter has to keep saying N of four on paper, so a filled
     dot goes solid ink and an empty one becomes a ring — §5.8's
     own distinction, which exists because two rings at different
     opacities are indistinguishable at 14px and a ring and a disc
     are not. Colour is gone, the count survives. */
  .ui-meter__dot {
    box-sizing: border-box;
    background: transparent;
    border: var(--ui-border-w) solid var(--c-ink);
  }
  .ui-meter__dot--red,
  .ui-meter__dot--orange,
  .ui-meter__dot--yellow,
  .ui-meter__dot--green { background: var(--c-ink); }

  /* A closed disclosure is a closed disclosure on screen; on paper
     there is nothing to click, so the copy has to be there. The
     UA hides a closed <details>'s content through
     ::details-content, so that is what has to be reopened —
     `display` on the panel does not reach it in current Chromium.
     Both spellings, because engines that predate
     ::details-content use display: none on the children instead. */
  .ui-list__disclosure::details-content { content-visibility: visible; }
  .ui-list__disclosure:not([open]) > .ui-list__panel { display: block; }
  /* And the plus/cross is meaningless on paper. */
  .ui-list__toggle { display: none; }

  /* A printed form must still be a legible form (§7's own words),
     so the message field keeps its height — someone is going to
     write in it with a pen. */
  .ui-contact__form .ui-field__control--area {
    min-height: var(--ui-contact-area-h);
  }

  /* --- 6.3.23 CONTENT --- */

  /* The well is a plate, so it flattens to an outline — §7's own
     treatment for .ui-card__media, .ui-longform__well and
     .ui-section__frame, and the same argument: an empty well
     prints as a grey rectangle, which is ink spent on nothing,
     and the outline keeps it legible as a frame. (0,1,0) against
     (0,1,0) and later in the file, so it wins on order like every
     other reset in §7.

     break-inside on the same rule rather than a second one: the
     image-only well is 700px tall at the drawn width and a
     picture cut in half by a page break is not a picture. §7
     already makes this call for .ui-section__frame and
     .ui-section__column. */
  .ui-content__media {
    background: transparent;
    border: var(--ui-border-w) solid var(--c-ink);
    break-inside: avoid;
  }

  /* ⚠ NOT A SECTION RULE, and left here for the reason 6.3.22's
     print block gives for its filled-tag reset: this block merges
     into §7 and §7 is where it belongs.

     §7 removes an inverted section's GROUND (`.ui-section {
     background: none }`) and never puts its INK back, so a display
     title in an inverted band prints the inverted ink — white — on
     the paper's bone ground. Measured on ui-core's own verified
     proof page, not on this one: four inverted sections there
     print their titles in the inverted ink — pure white — on the
     contract's paper ground, which measures 1.24:1. §7 already
     resets .ui-section__lead and the dimmed roles by name; the
     display title is the one text role in a section head it does
     not list, and .ui-h1/.ui-h2/.ui-h3 paint --ui-ink on
     themselves, so an inherited colour cannot reach them
     (integration note 18, one more time).

     (0,1,0), matching .ui-h2's own declaration and later in the
     cascade. It does NOT fix the accent-band case, which is
     (0,2,0) and paints --ui-ink-on-accent: measured under the same
     emulation, that title prints the Background Core plate colour
     on the same paper at 1.09:1. Reported — patching another
     section's rule from this file would be the descendant override
     §6's header forbids. */
  .ui-section__title { color: var(--c-ink); }

  /* Nothing else. Deliberately: this section re-plates no atom,
     so §7's (0,1,0) flattening of .ui-btn is not out-specified by
     anything here and the button prints as an outline on its own
     — checked under emulateMedia({media:'print'}) rather than
     assumed, which is the only way three of §7's four bugs were
     found. The prose is .ui-copy and §7 already promotes it and
     .ui-copy--dim to full ink; the H2 is §3.2's and prints as
     ink. */
}
