/* ══════════════════════════════════════════════════════════════════════════
   branded+Flow — brand-truth.css
   THE single stylesheet. One <link> to this file gives a surface the entire
   design system: colour, type, spacing, motion, brand marks, base and every
   ruling. Nothing else needs importing.

   Cut by foundation/brand-truth/cut.sh from tokens/, sources in this order:
     tokens/colors.css · typography.css · spacing.css · motion.css ·
     brand.css · base.css · unified.css · aliases.css
   unified.css carries every ruling and supersedes anything above it.
   aliases.css is the bridge — names no other file declares — and stays last.

   Do not hand-edit this file. Re-cut it: bash foundation/brand-truth/cut.sh

   FONTS ARE THE SURFACE'S JOB, NOT THIS FILE'S (2026-09-13, Jen, in session).
   Until this cut the line under this header was an @import of Poppins and
   JetBrains Mono from Google Fonts. A browser cannot see an @import until
   the whole stylesheet has arrived, so every surface paid three blocking
   hops in a row — this file, then Google's CSS, then Google's font files —
   before drawing a word: 753 ms on Lighthouse's mobile profile against
   brandedandflow.com, the single largest render-blocking item on the page.
   A <link> in the surface's own HTML is discovered at once, in parallel.
   The site bundles the fonts (next/font); the Hub and proposals link them;
   dist/index.html links them. The families and weights the system uses:
     Poppins 200 · 300 · 400 · 500 · 600
     JetBrains Mono 300 · 400 · 500
   https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600&family=JetBrains+Mono:wght@300;400;500&display=swap
   ══════════════════════════════════════════════════════════════════════════ */


/* ══════ colors.css ════════════════════════════════════════════════════════════ */
/* ══════════════════════════════════════════════════════════════════════════
   LOCKED 2026-08-21 — read the LOCKED section of CLAUDE.md before editing.

   Every value in this file was decided in session and measured off the
   rendered page. The ones that get "fixed" by mistake, so they are named here:

     - --fg1..4 are ALL #FFFFFF. That is not an oversight. There is no grey.
     - --fw-body is 200. Headings are 200 too. Weight separates nothing.
     - --ls-body .022em is POSITIVE. Reading text is expanded, not condensed.
     - --lh-body 1.7 belongs to a 200 weight at .022em. It was correctly 1.5
       for a 300 weight; do not revert it without re-judging weight and
       tracking with it.
     - --fs-eyebrow-web 16px is PINNED. It is the one scalar in the ladder.
     - h1 and display are UPPERCASE at +.055em. Caps take positive tracking.
     - .bf-stamp .joint margins are NEGATIVE and must stay negative — they
       match the placed wordmark, whose spacing is in the file, not the CSS.
     - a shimmer is a highlight over a fixed face, or a repeating band that
       travels. A whole gradient sliding makes the element cycle colour.
   ══════════════════════════════════════════════════════════════════════════ */
/* branded+Flow Design System — generated from tokens/unified.css.
   The rulings this encodes are recorded in DESIGN-SPEC.md and CLAUDE.md.
   Do not edit a value here without changing all three. */

:root {
  /* ── the guide · gold, written as INTENT not colour (§10) ────────────────
     Write var(--guide) in new work, never var(--gold): the token name says
     what the mark is doing. Gold is brushed metal (see --gold-metal-*),
     never flat — these flats exist only for a one-colour print fallback. */
  --guide: #C89A46; /* @kind color */   /* flat print fallback only. #DCBB72 retired, ruling 4. */
  --guide-on-light: #9A742C; /* @kind color */
  --guide-soft: rgba(200, 154, 70, 0.32); /* @kind color */
  --guide-glow: rgba(230, 206, 147, 0.5); /* @kind color */
  --guide-wash: rgba(220, 187, 114, 0.07); /* @kind color */
  --guide-border: rgba(220, 187, 114, 0.45); /* @kind color */

  /* borders and dividers */
  --border: rgba(255, 255, 255, 0.10); /* @kind color */
  --border-strong: rgba(255, 255, 255, 0.18); /* @kind color */
  --border-on-light: rgba(20, 24, 28, 0.12); /* @kind color */

  /* ── the tracker. Gold, but never flat paint — see the gold system below. ── */
  --track: #C89A46; /* @kind color */
  --track-dim: rgba(200, 154, 70, 0.32); /* @kind color */
  --track-glow: rgba(230, 206, 147, 0.5); /* @kind color */
  --track-wash: rgba(220, 187, 114, 0.07); /* @kind color */
  --track-border: rgba(220, 187, 114, 0.45); /* @kind color */

  /* the metal itself — light, dark, light. Never a single hex. */
  --gold-metal: linear-gradient(168deg, #FBF0D2 0%, #E4C889 22%, #B98F3E 48%, #EBD6A2 68%, #F8EDCD 100%); /* @kind color */
  --gold-metal-sweep: linear-gradient(112deg, #F4E5C2 0%, #EBD8AC 14%, #E1CB94 28%, #D7BC7C 42%, #CDAD66 56%, #C49F53 70%, #BC9345 84%, #B58B3B 100%); /* @kind color */
  /* the teal metal — deep, rising to a mint highlight, falling back. Same
     construction as the gold sweep and for the same reason: the old face went
     #80F2E8 straight into #21B9B2 across 18% of its width, a ~37-point
     lightness drop that renders as a seam rather than a highlight. Every
     adjacent pair here moves less than ~20, so the shimmer travels and no edge
     appears. All stops are teal - lightness moves, hue does not (ruling 13). */
  --teal-metal-sweep: linear-gradient(112deg, #1FAFA8 0%, #21B9B2 10%, #26C7BE 20%, #2BE9D9 34%, #4FEDE0 46%, #80F2E8 56%, #4FEDE0 66%, #2BE9D9 78%, #24C0B8 88%, #21B9B2 95%, #1FAFA8 100%); /* @kind color */
  --gold-metal-v: linear-gradient(180deg, #F8EDCD, #C89A46 55%, #8E6B28); /* @kind color */
  --gold-emboss: linear-gradient(180deg, #F0DFB0, #C79B45); /* @kind color */
  --gold-emboss-shadow: inset 0 1px 0 rgba(255,255,255,.7), 0 3px 8px -2px rgba(0,0,0,.6); /* @kind shadow */
  /* Ruling 55 (Tony's instruction, 2026-09-08, in session: "move on the scrim and shadow"). The
     sweep of all three surfaces left 17 literals, every one a black overlay or a
     black drop shadow, because the system had no name for either. Values are
     the ones the surfaces already agreed on. */
  --scrim: rgba(0, 0, 0, 0.55); /* @kind color */          /* behind a drawer, a dialog, a lightbox */
  --shadow: 0 20px 44px rgba(0, 0, 0, 0.5); /* @kind shadow */   /* the one lift: drawers, raised cards, floating panels */

  /* ── THE TEAL FAMILY · the default's five approved values, UNCHANGED ──
        Corrected 2026-08-16: an invented #35E0CB was overriding these. The
        default system's teals are approved and are not up for revision —
        this merge moved GOLD and the ground in, plus imagery,
        motion and components. It does not touch the teal. ── */
  --teal-flat: #21B9B2; /* @kind color */    /* eyebrow ticks, structural marks, hovers */
  --teal-hover: #1CA19E; /* @kind color */
  --teal-bright: #2BE9D9; /* @kind color */  /* emphasis words, eyebrow text */
  --teal-light: #80F2E8; /* @kind color */   /* highlight */
  --teal-deep: #094856; /* @kind color */    /* gradient endpoints; the only teal ON gold */
  --teal-mint: #2BE9D9; /* @kind color */
  --accent: #21B9B2; /* @kind color */
  --accent-hover: #1CA19E; /* @kind color */
  --accent-bright: #2BE9D9; /* @kind color */
  --accent-light: #80F2E8; /* @kind color */
  --accent-deep: #094856; /* @kind color */
  --accent-border: rgba(43,233,217,.4); /* @kind color */
  --accent-subtle: rgba(43,233,217,.12); /* @kind color */
  --accent-glow: rgba(43,233,217,.32); /* @kind color */
  --accent-gradient: linear-gradient(315deg,#094856 0%,#147D81 22%,#2BE9D9 50%,#20B4AE 78%,#094856 100%); /* @kind color */

  /* ── ruling 2 · the default's teal family stands. Nothing retired here. ── */
  /* --legacy-amber removed: it held retired amber as a live token. */
  --legacy-charcoal: #141414; /* @kind color */

  /* ── ruling 11 · TEXT ON DARK IS NEUTRAL WHITE ────────────────────────
     Four opacities, nothing else. Any hex with a blue cast is a bug. ── */
  /* RULED 2026-08-21, and this one is absolute: THERE IS NO GREY TEXT AND NO
     CREAM TEXT. All four rungs are #FFFFFF. Hierarchy is carried by size,
     weight, case and tracking - which is the brand's stated principle - so the
     colour scale was doing nothing but dimming the read. The names survive so
     existing markup keeps working; they no longer differ.
     Retired as text colours: #FAFAFA #FFFFFF #FFFFFF #FFFFFF #FFFFFF #FFFFFF
     and every rgba(255,255,255,<1). Transparency belongs on borders, fills and
     shadows - never on type. */
  --fg1: #FFFFFF; /* @kind color */
  --fg2: #FFFFFF; /* @kind color */
  --fg3: #FFFFFF; /* @kind color */
  --fg4: #FFFFFF; /* @kind color */
  --fg-hair: rgba(255,255,255,0.14); /* @kind color */
  /* the v4 token file defines these as blue-greys; ruling 11 neutralises them
     at the source so any markup still reading them lands on white. */
  --fg-navy-body: rgba(255,255,255,0.82); /* @kind color */  /* back-compat alias of --fg2; the name is retired, write --fg2 */
  /* on light: cool neutral ink, no blue cast */
  --ink-1: #14181C; /* @kind color */
  --ink-2: #14181C; /* @kind color */
  --ink-3: #14181C; /* @kind color */
  --ink-hair: rgba(20,24,28,.14); /* @kind color */

  /* ── ruling 14 · THE GROUND IS NEUTRAL DARK ─────────────────────────
     Neutral dark is retired. Neutral dark, and NEVER flat — the drift field is
     what keeps it alive. ── */
  --ground: #141414; /* @kind color */          /* the canvas */
  --ground-elevated: #1C1C1A; /* @kind color */ /* cards and panels on dark */
  --ground-deep: #0E0E0E; /* @kind color */     /* footer, chrome, the far end of the gradient */
  --ground-warm: #191917; /* @kind color */     /* the near end of the gradient */
  --ground-sweep: linear-gradient(168deg, #191917 0%, #0E0E0E 74%); /* @kind color */
  --panel-fill: rgba(28, 28, 26, 0.62); /* @kind color */
  --ink-navy: #141414; /* @kind color */        /* back-compat alias of --ground; the name is retired, write --ground */

  /* the drift field — teal only, plus ONE faint warm ball. 5-15% opacity:
     you should not be able to point at them, only notice the ground lives. */
  /* --drift RETIRED 2026-08-21 — see the ground note below. */

  /* ── ruling 15 · THE JOINT ────────────────────────────────────
     The "+" in the wordmark, the icon, and the casing stamp — one model. */
  --joint-face: linear-gradient(180deg, #F8EDCD, #C89A46 55%, #8E6B28); /* @kind color */
  --joint-cast: #147D81; /* @kind color */      /* darker + less saturated than the face */

}

/* ── ruling 9 · Teal Deep is the ONLY teal that reads on light, so the light
      scope clamps the RAW family too — not just the semantic tokens. Without
      this, any component reaching for var(--teal-bright) directly keeps
      dark-surface cyan on paper (1.53:1 on white). ── */
.bf-light {
  --teal-bright: var(--teal-deep); /* @kind color */
  --teal-light: var(--teal-deep); /* @kind color */
  --teal-mint: var(--teal-deep); /* @kind color */
  --accent-bright: var(--teal-deep); /* @kind color */
  --accent-light: var(--teal-deep); /* @kind color */
  --accent: var(--teal-deep); /* @kind color */
  /* ruling 11, on paper · cool neutral ink, still no blue cast */
  --fg1: var(--ink-1); /* @kind color */
  --fg2: var(--ink-2); /* @kind color */
  --fg3: var(--ink-3); /* @kind color */
  --fg4: #14181C; /* @kind color */
  --fg-hair: var(--ink-hair); /* @kind color */
}
/* ── THE GOLD SYSTEM · ruled 2026-08-16 ───────────────────────────────
   Gold is never a flat fill. It is brushed metal that catches light as it
   drifts (03 + 04), and embossed metal wherever it becomes a solid surface
   you could press (05). Flat #D4A54A is retired — too deep, too orange.

     .gold-metal    large text and numbers — brushed, slowly sweeping
     .gold-surface  chips, buttons, tags — raised and lit from above
     .gold-rule     the rail, dividers, any vertical or horizontal bar
     .gold-token    the tracker dot and any small round mark
*/
/* Brushed gold on text. STATIC by default — see §4.0: gradient-clipped text
   cannot be GPU-composited, so the live sweep is rationed to ONE element per
   page. Add `.is-live` (or `.bf-hero` on a lockup) to that one element. */
.gold-metal {
  background: var(--gold-metal-sweep);
  background-size: 200% 100%;
  background-position: 60% 50%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.gold-metal.is-live {
  animation: bfGoldSweep 6s linear infinite;
}
.gold-surface {
  background: var(--gold-emboss);
  color: #2A1E06;
  box-shadow: var(--gold-emboss-shadow);
  border: 0;
}
.gold-rule {
  background: var(--gold-metal-v);
  box-shadow: inset -1px 0 0 rgba(255,255,255,.45);
}
.gold-token {
  background: radial-gradient(circle at 32% 28%, #FFF6DE, #E4CE95 45%, #9A742C);
  box-shadow: inset 0 -1px 2px rgba(80,58,14,.9), 0 3px 8px rgba(0,0,0,.55);
}
@keyframes bfGoldSweep {
  0% { background-position: 0% 50%; }
  100% { background-position: 200% 50%; }
}


/* ══════ typography.css ════════════════════════════════════════════════════════ */
/* ══════════════════════════════════════════════════════════════════════════
   LOCKED 2026-08-21 — read the LOCKED section of CLAUDE.md before editing.

   Every value in this file was decided in session and measured off the
   rendered page. The ones that get "fixed" by mistake, so they are named here:

     - --fg1..4 are ALL #FFFFFF. That is not an oversight. There is no grey.
     - --fw-body is 200. Headings are 200 too. Weight separates nothing.
     - --ls-body .022em is POSITIVE. Reading text is expanded, not condensed.
     - --lh-body 1.7 belongs to a 200 weight at .022em. It was correctly 1.5
       for a 300 weight; do not revert it without re-judging weight and
       tracking with it.
     - --fs-eyebrow-web 16px is PINNED. It is the one scalar in the ladder.
     - h1 and display are UPPERCASE at +.055em. Caps take positive tracking.
     - .bf-stamp .joint margins are NEGATIVE and must stay negative — they
       match the placed wordmark, whose spacing is in the file, not the CSS.
     - a shimmer is a highlight over a fixed face, or a repeating band that
       travels. A whole gradient sliding makes the element cycle colour.
   ══════════════════════════════════════════════════════════════════════════ */
/* branded+Flow Design System — generated from tokens/unified.css.
   The rulings this encodes are recorded in DESIGN-SPEC.md and CLAUDE.md.
   Do not edit a value here without changing all three. */

:root {
  /* ── the families. Poppins does everything; JetBrains Mono is labels. ── */
  --font-sans: 'Poppins', -apple-system, 'system-ui', 'Segoe UI', sans-serif; /* @kind font */
  --font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', 'Courier New', monospace; /* @kind font */
  --font-display: var(--font-sans); /* @kind font */

  /* ── TYPE · the default's roles, at Jen's modest scale (thin, not big) ──
     FLUID at both ends. DESIGN.md §2's fluid rule: a fluid heading against
     a frozen body breaks the heading-to-body ratio across viewports — the
     page then reads correctly at exactly one width. Every reading size
     carries a clamp; only small chrome is fixed. ── */
  --t-label: 13px; /* @kind font */   /* mono · 500 · .14em · SENTENCE CASE, never upper */
  /* == ONE MODULAR SCALE - ratio 1.200 (minor third) - anchor 15px ======
     RULED 2026-08-18. Six rungs, one ratio, COMPUTED - not tuned. The
     ladder this replaces had eight rungs on a ratio that ACCELERATED:
     1.16 at the bottom, 1.19, 1.22, 1.26, 1.33 at the top. That is why the
     top read oversized while the middle read crowded, and why h4 and lead
     were indistinguishable. A modular scale is the standard here: pick one
     ratio, anchor it at the body size, compound it.

       body 15 - lead 18 - h3 21.5 - h2 26 - h1 31 - display 37

     --fs-h4 is now an ALIAS of --fs-h3. Eight rungs cannot all be used on
     one surface; the pair sitting inside 4px was the proof.

     --fs-body is PINNED at 15px both ends. It is ruling 7's sentence floor,
     and reading size should not scale - everything above it does, so the
     heading-to-body ratio opens as the viewport widens instead of the whole
     page inflating. Chrome (meta, eyebrow, button) sits BELOW the anchor and
     is deliberately off the modular scale: it is not read as prose.

     Floors run on 1.15 rather than 1.2, because the body floor and the body
     ceiling are the same number and the rungs above it need somewhere to go.

     RETIRED: the eight-rung ladder (15.5 / 18.5 / 21.5 / 25.5 / 31 / 39 / 52),
     --fs-h4 as an independent value, and the "clear the rung below by 15%"
     rule - a computed 1.2 ratio makes every step exactly 20%.           */
  --fs-scale-ratio: 1.3; /* @kind other */
  /* REVISED the same day. Body was pinned at 15px both ends, which read as
     tiny on a wide screen: every rung above it scaled and the reading size
     did not. Same ratio, anchor moved to 16px, and chrome came UP off the
     floor - a 12px label in #FFFFFF was unreadable, which is a size AND a
     colour failure at once (see --fg4 below).
       body 16 - lead 19 - h3 23 - h2 27.5 - h1 33 - display 40
     Floors run 1.15 from 15: 15 - 17 - 19.5 - 22.5 - 26 - 30.             */
  --fs-meta: clamp(13px, 1vw, 14px); /* @kind font */
  /* PINNED at 15px, 2026-08-21. The nameplate is read, not scanned, so it
     sits on the body anchor rather than in the chrome band below it. Pinned
     both ends deliberately: a label that shrinks with the window stops being
     a nameplate at the width the page is actually read. */
  --fs-eyebrow-web: 16px; /* @kind font */
  --fs-eyebrow: 16px; /* @kind font */  /* the DS Eyebrow reads this name */
  --fs-btn: clamp(15px, 1.15vw, 16px); /* @kind font */
  --fs-body: clamp(17px, 1.35vw, 18px); /* @kind font */
  --ls-body: 0.022em; /* @kind font */
  /* reading text is ExtraLight - the same weight as the word "branded".
     200 is the floor on a dark ground: at 100 the strokes go thinner than a
     pixel and white composites toward cream. */
  --fw-body: 200; /* @kind font */
  --ls-lead: 0.016em; /* @kind font */
  --fs-lead: clamp(20.4px, 1.7vw, 23.4px); /* @kind font */
  --fs-h3: clamp(24.5px, 2.2vw, 30.4px); /* @kind font */
  --fs-h2: clamp(29.4px, 2.85vw, 39.5px); /* @kind font */
  --fs-h1: clamp(35.3px, 3.7vw, 51.4px); /* @kind font */
  --fs-display: clamp(42.3px, 4.8vw, 66.8px); /* @kind font */
  --fs-h4: var(--fs-h3); /* @kind font */

  /* == THE STAGE LADDER - 1920x1080 slide + video - anchor 28px, 1.2 =====
     A fixed canvas cannot compute off a 15px web body, so it carries its own
     anchor on the same ratio. Ruling 7's 24px floor stays a FLOOR: captions
     and meta sit on it, body sits above it. Fixed px is correct here - rule
     11's clamping requirement exempts a fixed canvas. The slide eyebrow
     stays 22px per ruling 4: a nameplate is a label, not a sentence.    */
  --fs-stage-meta: 24px; /* @kind font */
  --fs-stage-body: 28px; /* @kind font */
  --fs-stage-lead: 36.4px; /* @kind font */
  --fs-stage-h3: 47.3px; /* @kind font */
  --fs-stage-h2: 61.5px; /* @kind font */
  --fs-stage-h1: 80px; /* @kind font */
  --fs-stage-display: 104px; /* @kind font */

  /* == THE PRINT LADDER - points - anchor 12pt, 1.2 =====================
     Ruling 7's 12pt floor IS the print body. A caption or a table cell may
     sit under it at 10pt; a sentence never does.                        */
  --fs-print-caption: 10pt; /* @kind font */
  --fs-print-body: 12pt; /* @kind font */
  --fs-print-lead: 15.6pt; /* @kind font */
  --fs-print-h3: 20.3pt; /* @kind font */
  --fs-print-h2: 26.4pt; /* @kind font */
  --fs-print-h1: 34.3pt; /* @kind font */
  --fs-print-display: 44.6pt; /* @kind font */

  /* ── ONE ladder · the --t-* family aliases --fs-* ─────────────────────
     Two parallel ladders shipped: --t-* drove every h1–h4 element and
     --fs-* drove inline styles. On the --t-* ladder body was 19px, lead
     21px and h4 22px — three rungs inside 3px, which is why "the lead and
     the body" read as the same size and why h1 down to h4 had no
     separation. There is now one ladder; --t-* are aliases, kept so
     existing markup and the generated element rules keep working.

     RETIRED: --t-body 19 / --t-lead 21 / --t-h4 22 / --t-h3 30 /
     --t-h2 38 / --t-h1 48 / --t-display 60 as independent values.

     --t-h4 now resolves to --fs-h3: clamp(24.5px, 2.2vw, 30.4px);
  --t-small: var(--fs-body); /* @kind font */
  --t-body: var(--fs-body); /* @kind font */
  --t-lead: var(--fs-lead); /* @kind font */
  --t-h4: var(--fs-h4); /* @kind font */
  --t-h3: var(--fs-h3); /* @kind font */
  --t-h2: var(--fs-h2); /* @kind font */
  --t-h1: var(--fs-h1); /* @kind font */
  --t-display: var(--fs-display); /* @kind font */

  /* ── ruling 5 · REVISED 2026-08-16 · the default's thin heading wins ── */
  --weight-display: 300; /* @kind font */
  --weight-heading: 300; /* @kind font */
  --weight-subheading: 400; /* @kind font */
  --weight-body: 300; /* @kind font */
  --weight-ui: 500; /* @kind font */
  --track-mono: 0.14em; /* @kind font */
}

/* ── ruling 5, enforced · Light 300 display and headings, 400 at H3/H4 ── */
/* one step lighter than before — h1/h2 were 300, h3/h4 were 400. Tighter
   negative tracking climbs with the size, which is what makes a large light
   line read as a HEADING rather than as big body copy. */
h1, h2, h3, h4, h5, h6 { font-weight: 200; line-height: var(--lh-heading); margin: 0; }
h1 { font-size: var(--t-h1); font-weight: 200; letter-spacing: -0.040em; line-height: var(--lh-display); }
h2 { font-size: var(--t-h2); font-weight: 200; letter-spacing: -0.020em; }
h3 { font-size: var(--t-h3); font-weight: 200; letter-spacing: -0.018em; line-height: 1.2; }
/* h4 shares h3's rung (--fs-h4 aliases --fs-h3), so it shares its tracking
   and leading too - two rungs inside 4px is what the alias retired. */
h4 { font-size: var(--t-h4); font-weight: 200; letter-spacing: -0.018em; line-height: 1.2; }
p { font-size: var(--t-body); font-weight: 200; line-height: var(--lh-body); letter-spacing: -0.005em; }

/* ── heading weight, made unreachable-proof ────────────────────────────────
   Every heading on every page carried an INLINE font-weight, which beats the
   h1–h4 rules above — so the weight ruling reached none of them. The inline
   declarations were stripped from our own pages; this shim catches the two
   remaining sources:

     · the bound design system's components, which render h3 card titles with
       inline font-size:13px; font-weight:600 and live read-only under _ds/
     · any future page that re-declares the weight

   Every heading is 200 — thinner than the 300 body, which is what makes it
   read as a heading. 600 → 500 rather than 200 for the one exception: a Hub
   card title is UI chrome, and the design system's own guide puts UI at 500. Its 13px size is left alone — that is a
   label, not a sentence, so --fs-meta's 12px floor governs, not the 15px one. */
h1[style], h2[style], h3[style], h4[style] { font-weight: 200 !important; }
h3[style*="font-weight: 600"], h4[style*="font-weight: 600"],
h3[style*="font-weight:600"],  h4[style*="font-weight:600"] { font-weight: 500 !important; }


/* emphasis is flat teal — never italic, never a weight bump */
em, .t-em { font-style: normal; font-weight: inherit; color: #2BE9D9; }

/* ── teal emphasis carries a soft halation ────────────────────────────────
   Teal on charcoal is a thin, cool colour; set as a phrase inside prose it
   reads as flat coloured text unless it glows. The glow is what makes it read
   as LIT rather than tinted — the same reason the gold sweeps.

   Scoped to teal inside a paragraph, which is where an emphasis PHRASE lives.
   A standalone teal label (a section name, an eyebrow, a plate heading) gets
   no glow — it is naming a thing, not being emphasised inside a sentence. */
/* Match BOTH notations. React serialises a style object to rgb(), so a
   selector written against the authored hex matches nothing in the rendered
   DOM — which is how the first version of this rule reached 0 of 28 teal
   phrases. Plain-HTML pages keep the hex, so both forms are listed. */
p em, p .t-em, .t-em.is-phrase, em.t-em,
p span[style*="rgb(43, 233, 217)"], p span[style*="rgb(33, 185, 178)"],
p span[style*="rgb(128, 242, 232)"],
p span[style*="#2BE9D9"], p span[style*="#21B9B2"], p span[style*="#80F2E8"],
p span[style*="--teal-bright"], p span[style*="--teal-flat"] {
  text-shadow: 0 0 18px rgba(43,233,217,.34), 0 0 42px rgba(43,233,217,.16);
}
/* Mono is never emphasis. A file path, a hex code or a token name set in teal
   mono is a CODE reference — glowing it makes it look like a highlighted
   phrase. Excluded explicitly, since the selectors above match on colour and
   cannot tell a phrase from a path.

   Going forward, author an emphasis phrase as <em> or class="t-em" rather than
   a bare coloured span: the substring selectors above only exist because 28
   teal phrases on one page were hand-rolled, and a colour-matching selector
   is guesswork. */
p span[style*="--font-mono"], p span[style*="JetBrains"],
p code, p .mn, p .meta, p [class*="mono"] {
  text-shadow: none !important;
}





/* ══════ spacing.css ═══════════════════════════════════════════════════════════ */
/* ══════════════════════════════════════════════════════════════════════════
   LOCKED 2026-08-21 — read the LOCKED section of CLAUDE.md before editing.

   Every value in this file was decided in session and measured off the
   rendered page. The ones that get "fixed" by mistake, so they are named here:

     - --fg1..4 are ALL #FFFFFF. That is not an oversight. There is no grey.
     - --fw-body is 200. Headings are 200 too. Weight separates nothing.
     - --ls-body .022em is POSITIVE. Reading text is expanded, not condensed.
     - --lh-body 1.7 belongs to a 200 weight at .022em. It was correctly 1.5
       for a 300 weight; do not revert it without re-judging weight and
       tracking with it.
     - --fs-eyebrow-web 16px is PINNED. It is the one scalar in the ladder.
     - h1 and display are UPPERCASE at +.055em. Caps take positive tracking.
     - .bf-stamp .joint margins are NEGATIVE and must stay negative — they
       match the placed wordmark, whose spacing is in the file, not the CSS.
     - a shimmer is a highlight over a fixed face, or a repeating band that
       travels. A whole gradient sliding makes the element cycle colour.
   ══════════════════════════════════════════════════════════════════════════ */
/* branded+Flow Design System — generated from tokens/unified.css.
   The rulings this encodes are recorded in DESIGN-SPEC.md and CLAUDE.md.
   Do not edit a value here without changing all three. */

:root {
  /* ── ruling 17 · THE CLAMPED SCALE ───────────────────────────────
     Every space, pad, margin and type size on a WEB surface resolves from
     here, and every one is fluid. A bare pixel value in a web layout is a
     bug. Slides and video frames are the exception: a fixed 1920×1080
     canvas, so fixed px against the 24px floor. ── */
  /* COMPRESSED 2026-08-19. Same 1.4 ratio, but the top four rungs were
     doing far more work than the content needed: --sp-8 at 92px and --sp-7
     at 66px put a screen-height of air between blocks that belong to one
     read. The low rungs are untouched (they are component padding); the
     climb is flattened to 1.32 from --sp-4 up, which takes ~28% off the
     section gaps without touching a single page. */
  --sp-1: clamp(6px, .45vw, 7px); /* @kind spacing */
  --sp-2: clamp(9px, .8vw, 11px); /* @kind spacing */
  --sp-3: clamp(13px, 1.1vw, 15px); /* @kind spacing */
  --sp-4: clamp(18px, 1.5vw, 21px); /* @kind spacing */
  --sp-5: clamp(25px, 2.1vw, 29px); /* @kind spacing */
  --sp-6: clamp(35px, 3vw, 41px); /* @kind spacing */
  --sp-7: clamp(53px, 4.4vw, 62px); /* @kind spacing */
  --sp-8: clamp(82px, 6.6vw, 96px); /* @kind spacing */

  /* ── THE TYPE LADDER IS NOT DECLARED HERE ─────────────────────────────
     It lives in tokens/typography.css and nowhere else. This file carried a
     SECOND full copy of it — nine rungs plus --fs-btn declared twice — and
     styles.css imports spacing.css AFTER typography.css, so this copy won
     every cascade. A change to the real ladder rendered as no change at all.

     Its comment had also drifted out of agreement with its own declarations:
     the prose claimed h4 22.5 / h3 27 / h2 32.5 while the lines below it said
     21.5 / 25.5 / 31. A token defined twice is not defined, and a comment
     that contradicts the declaration under it reads as compliant on
     inspection. Removed 2026-08-18. Do not re-add a --fs-* line to this
     file. ── */
  --tick: clamp(52px, 4.6vw, 64px); /* @kind spacing */
  /* the placed wordmark, in a page head. One token so it cannot drift per
     surface - it was 32-46px on four pages and read as a banner. */
  --mark-h: clamp(21px, 1.9vw, 27px); /* @kind spacing */   /* lengthened 2026-08-21 — a 30px rule
     above a 15px nameplate read as a dash, not as a structural mark */

  /* The gutter that clears the guide rail. This is the rail's EXPANDED extent,
     not its collapsed width — its active label grows to ~187px, so a token set
     to the collapsed 132–176px did NOT clear it (measured encroachment 54.5px).
     Size a clearance token to the widest state of the thing it clears. */
  --rail: clamp(200px, 16vw, 228px); /* @kind spacing */

  --lh-display: 1.06; /* @kind font */
  --lh-heading: 1.12; /* @kind font */
  --lh-body: 1.7; /* @kind font */

}

/* ── ruling 19 · THE RHYTHM ────────────────────────────────────────────────
   RULED 2026-08-17. The vertical rhythm of a text block is not even. Three
   gaps, deliberately unequal:
     eyebrow  →  heading      --sp-6   FAR. The nameplate is its own object.
     heading  →  subheading   --sp-2   TIGHT. They are one thought.
     subhead  →  body         --sp-7   AWAY. The body is a separate read.
   Even spacing reads as a list. Uneven spacing reads as a hierarchy. ── */
.bf-rhythm-eyebrow { margin-bottom: var(--sp-5); }
.bf-rhythm-heading { margin: 0 0 var(--sp-2); }
.bf-rhythm-sub     { margin: 0 0 var(--sp-6); }


/* ══════ motion.css ════════════════════════════════════════════════════════════ */
/* branded+Flow Design System — motion.
   Two eases carry everything. Nothing bounces, nothing pops, nothing scales
   up from zero. Recorded in DESIGN-SPEC §18. */

:root {
  --ease-brand: cubic-bezier(.22, 1, .36, 1); /* @kind other */    /* reveals, cards, buttons */
  --ease-primary: cubic-bezier(.16, 1, .3, 1); /* @kind other */   /* page chrome */

  --dur-press: 80ms; /* @kind other */
  --dur-micro: 200ms; /* @kind other */
  --dur-hover: 320ms; /* @kind other */
  --dur-short: 420ms; /* @kind other */
  --dur-reveal: 900ms; /* @kind other */
  --dur-draw: 1200ms; /* @kind other */
  --dur-long: 1700ms; /* @kind other */
}

/* ── ambient motion. SUPERSEDED by ruling 50 (below): decoration runs 14–46s,
   a status signal runs 1.2–2.4s. This line once read "Slow loops only:
   14–46s" and carried no number, no date and no initials — it was never
   ruled, and most loops broke it. ── */
@keyframes bnfShimmerSweep {
  0% { background-position: 0% 50%; }
  100% { background-position: 200% 50%; }
}
@keyframes bnfMarquee {
  0% { transform: translateX(0); }
  100% { transform: translateX(-50%); }
}
@keyframes bnfMarqueeRev {
  0% { transform: translateX(-50%); }
  100% { transform: translateX(0); }
}

/* Three headless keyframe bodies used to sit here — the drift-field balls
   retired by ruling 34. Their @keyframes lines were deleted and the bodies
   were left behind, so the parser dropped the two rules that followed them.
   Removed 2026-09-03. See "Motion harvest - before and after.html". */
@keyframes bnfGlowPulse {
  0%, 100% { opacity: 0.5; }
  50% { opacity: 0.9; }
}
@keyframes bnfStatusPulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}

/* ── reduced motion CALMS, it never removes content — DESIGN.md §6 ──────
   Animate FROM hidden TO visible and make visible the default, so a failed
   script or an early return still leaves content on screen. A blanket
   `animation:none` strands anything that animates in at opacity:0 — the
   exact trap DESIGN.md names, which once hid content from the owner. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
  [data-reveal], [data-reveal] > * { opacity: 1 !important; transform: none !important; }
}

body {
  margin: 0;
  background: var(--ground);
  /* ruling 11 · neutral white body text. Never a blue-grey. */
  color: var(--fg2);
  font-family: var(--font-sans);
  font-weight: 200;
  text-wrap: pretty;
}
a { color: #2BE9D9; text-decoration: none; }
a:hover { color: #80F2E8; }
::selection { background: #21B9B2; color: var(--ground); }


/* ══════════════════════════════════════════════════════════════════════════
   § MOTION HARVEST — promoted from the marketing site, 2026-09-03.
   Sources, read at brandedflow-site@main:
     foundation/design-system/motion_and_space.css  (nine brand keyframes)
     components/FlowRing.css + FlowRing.tsx         (the following ring)
     app/home-money.css, app/home-editorial.css     (the ~20 page keyframes)
   These lived on ONE page and nothing else could reach them. Promoted so the
   Hub, the proposals app and every template can use the same motion.

   Two things did NOT come up: the live product demos (LeakEstimator, RoiCalc,
   MoneyDemos — product, not brand) and the page-local marquee/glow variants
   that were duplicates of tokens already here.
   ══════════════════════════════════════════════════════════════════════════ */
:root {
  /* The site's --ease-out-soft measured identical to --ease-primary
     (.16,1,.3,1). It is an alias, not a ninth ease. */
  --ease-out-soft: cubic-bezier(.16, 1, .3, 1); /* @kind other */
  --dur-ring: 22s; /* @kind other */          /* the decorative dashed ring */
  --dur-ring-follow: 14s; /* @kind other */   /* the ring that follows the read */
  /* ruling 50 · 6s → 18s. The sheen is decoration, so it sits in the slow
     band. At 6s it swept while you read the label. */
  --dur-shimmer: 18s; /* @kind other */
  --dur-pulse: 2400ms; /* @kind other */
  --dur-wave: 1200ms; /* @kind other */
}

@keyframes bnfRevealUp { from { opacity: 0; transform: translateY(22px); } to { opacity: 1; transform: none; } }
@keyframes bnfRevealLeft { from { opacity: 0; transform: translateX(-26px); } to { opacity: 1; transform: none; } }
@keyframes bnfRevealRight { from { opacity: 0; transform: translateX(26px); } to { opacity: 1; transform: none; } }
@keyframes bnfDrawIn { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
@keyframes bnfRuleDraw { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes bnfRingSpin { to { transform: rotate(360deg); } }
@keyframes bnfGradFlow { 0% { background-position: 0% 50%; } 100% { background-position: 200% 50%; } }
@keyframes bnfPulseDot { 0%, 100% { opacity: .35; transform: scale(.8); } 50% { opacity: 1; transform: scale(1.15); } }
@keyframes bnfTickFlash { 0% { opacity: 0; } 12% { opacity: 1; } 100% { opacity: .55; } }
@keyframes bnfLivePulse { 0% { box-shadow: 0 0 0 0 rgba(43,233,217,.5); } 100% { box-shadow: 0 0 0 8px rgba(43,233,217,0); } }
@keyframes bnfWaveBar { 0%, 100% { height: 25%; } 50% { height: 95%; } }


/* ── ruling 50 · DECORATION IS SLOW, A SIGNAL IS QUICK ──────────────────────
   RULED 2026-09-03 by Jen, after the harvest turned up five loops breaking a
   speed limit that had never actually been ruled.

   What was here before: one undated, unnumbered comment line above the
   keyframes — "ambient motion. Slow loops only: 14–46s." No ruling number, no
   date, no initials. Every real ruling in this file carries all three. It was
   an intention someone typed, and three of the five loops that carry a
   duration broke it.

   The line was half right, and the half it missed is the distinction between
   decoration and signal.

     AMBIENT DECORATION — 14s to 46s.
       Carries no information. The reader is not meant to catch it moving.
       The dashed rings, the button sheen, the marquee.

     STATUS SIGNAL — 1.2s to 2.4s, and ONLY where something is genuinely live.
       Exists to be noticed. Its whole job is to say "this is on, right now".
       The live dot, the audio bars, the status light.

     RESPONSE — 200ms to 2.6s, and ONLY while the thing is being touched.
       AMENDED IN 2026-09-03, the same day, before the ruling had been applied
       to anything. The first draft had two bands and it was wrong: it left
       ruling 40's hover sheen (2.6s) and money face (11s) in the empty gap
       between them, which would have marked your approved buttons
       non-compliant on the day this landed.
       A hover sheen is neither decoration nor a status signal. It runs only
       while the cursor is on the button — it is the object answering you, and
       an answer has to be quick or it is not an answer. It stops the moment
       you leave, so it is not reporting a state either.
       Hover sheens, ring lights, presses. Ruling 40's 2.6s and 2.4s are
       correct AS THEY STAND. Nothing about a button moves.

   A signal that takes fourteen seconds to complete a beat has stopped
   signalling — it reads as frozen, which is to say as a fault. That is why
   the band splits rather than moves.

   What changed on the day:
     --dur-shimmer  6s → 18s. The only value this ruling moved, and it is NOT
                    a button. Its sole consumer is .bf-grad-flow, a utility
                    created the same morning and used by nothing in the
                    project.

                    STRUCK 2026-09-03: this line first read "The sheen is
                    DECORATION. It runs on the button face continuously." That
                    is FALSE. Ruling 40 stopped it running at rest on
                    2026-08-21, and the button sheen has never read
                    --dur-shimmer at all — ruling 40 hard-codes bfSheen at
                    2.6s / 11s / 7s. The sentence was written from a mental
                    model of the system instead of from the file, the same
                    fault as citing a duration that did not exist. Jen caught
                    it: "The shimmer moved. That's the only damn thing I
                    love!" Nothing about the buttons changed, then or now.
     --dur-pulse    2.4s, unchanged — now correct as a signal rather than a
                    violation of a band it was never meant to be in.
     --dur-wave     1.2s, unchanged, same reason.
     --dur-ring     22s, unchanged. Its slowness is the point.

   WHAT THIS RULING DOES NOT TOUCH. Every button, at rest and on hover, in all
   three faces — ruling 40 governs those and is unamended. The gold money face
   at rest is RULED SEPARATELY in ruling 51 (see tokens/unified.css), which
   speeds it up rather than slowing it down: an explicit exception, because the
   one money mark is the one thing on a surface meant to look alive.

   And the two dead keyframes are wired. bnfStatusPulse and bnfLivePulse had
   sat in this file with no consumer anywhere in the project — animating
   nothing, surviving every audit because a value check cannot see that a
   keyframe is never called. They now drive .bf-status-dot and .bf-live-badge
   at 2s and 1.8s, both inside the signal band. A keyframe with no consumer is
   either a component that was never finished or a deletion someone lost their
   nerve on; the fix is to pick one. Jen: "Wire them up." ── */
:root {
  --dur-status: 2s; /* @kind other */   /* the status light — signal band */
  --dur-live: 1.8s; /* @kind other */   /* the live badge's ring — signal band */
}

/* The status light. A dot that breathes while something is running.
   OFF and DONE do not animate: a still dot is a state, not a stalled one. */
.bf-status-dot { display: inline-flex; align-items: center; gap: 9px; font-family: var(--font-sans); font-size: var(--fs-meta); font-weight: 300; letter-spacing: .04em; color: var(--fg2); }
.bf-status-dot::before { content: ""; width: 9px; height: 9px; border-radius: 999px; background: var(--fg4); flex: none; }
.bf-status-dot[data-state="running"]::before { background: var(--teal-bright); animation: bnfStatusPulse var(--dur-status) ease-in-out infinite; }
.bf-status-dot[data-state="waiting"]::before { background: var(--guide); animation: bnfStatusPulse var(--dur-status) ease-in-out infinite; }
.bf-status-dot[data-state="done"]::before { background: var(--teal-bright); }
.bf-status-dot[data-state="off"]::before { background: var(--fg4); }

/* The live badge. A pill that pushes a ring out of itself, once per 1.8s.
   Only for something genuinely live — never as decoration on a static label. */
.bf-live-badge { display: inline-flex; align-items: center; gap: 8px; padding: 5px 13px 5px 11px; border-radius: 999px; border: 1px solid var(--accent-border); background: var(--ground-elevated); font-family: var(--font-mono); font-size: 12px; letter-spacing: .14em; text-transform: uppercase; color: var(--teal-bright); }
.bf-live-badge > .bf-live-dot { position: relative; width: 8px; height: 8px; border-radius: 999px; background: var(--teal-bright); flex: none; }
.bf-live-badge > .bf-live-dot::after { content: ""; position: absolute; inset: 0; border-radius: 999px; animation: bnfLivePulse var(--dur-live) var(--ease-primary) infinite; }

@media (prefers-reduced-motion: reduce) {
  /* Calm stops the beat. It does not take the state away — the dot keeps its
     colour and the badge keeps its ring, both simply still. */
  .bf-status-dot::before, .bf-live-badge > .bf-live-dot::after { animation: none !important; }
  .bf-live-badge > .bf-live-dot::after { box-shadow: 0 0 0 4px rgba(43,233,217,.22); }
}


/* ══════ brand.css ═════════════════════════════════════════════════════════════ */
/* branded+Flow Design System — generated from tokens/unified.css.
   The rulings this encodes are recorded in DESIGN-SPEC.md and CLAUDE.md.
   Do not edit a value here without changing all three. */

:root {
  /* ── the wordmark's three live dimensions ──────────────────────────────
     Every surface reads these, so the mark is identical everywhere. The
     Tweaks panel on the system page overrides them inline per-page; these
     are the ratified defaults. See DESIGN-SPEC §5.1. */
  --wm-tighten: 1.35; /* @kind other */
  --wm-bright: 1.18; /* @kind other */
  --wm-glow: 0.5; /* @kind other */

}

/* ── THE WORDMARK · typeset to the default system's approved lockup ─────
   branded = thin (300) and slightly smaller · + = gold, tucked tight ·
   Flow = thicker (600) and slightly larger. No space around the +, ever.
   Tracking and the negative tucks are the default's ratified values.

   Use: <span class="bf-lockup" style="font-size:64px">
          <span class="bf-branded">branded</span
          ><span class="bf-plus">+</span
          ><span class="bf-flow">Flow</span>
        </span>
*/
.bf-lockup {
  font-family: var(--font-sans);
  display: inline-flex;
  align-items: baseline;
  gap: 0;
  white-space: nowrap;
  line-height: 1;
  /* --wm-tighten scales every tuck and tracking value at once, so the mark
     closes up or opens out as ONE unit. 1 = the ratified §5.1 geometry. */
  letter-spacing: calc(-0.0516em * var(--wm-tighten, 1));
  text-transform: none;
  font-kerning: normal;
  text-rendering: geometricPrecision;
  -webkit-font-smoothing: antialiased;
  color: #fff;
}
/* The joint tucks are the wrapper's negative letter-spacing ONLY. Explicit
   negative margins stacked on top of that trailing letter-space and exceeded
   the glyphs' own side bearings, so the `d`, the `+` and the `F` collided into
   one crossed shape. letter-spacing already applies after each part's final
   glyph — that IS the tuck, and it stays inside the bearings at every size.
   The 13px floor keeps the thin word legible at the bottom of the ladder. */
.bf-lockup .bf-branded {
  font-weight: 200;
  font-size: max(13px, 0.80em);
  letter-spacing: calc(-0.0564em * var(--wm-tighten, 1));
}
/* The `+` carries the brushed-gold SWEEP on every surface — that gradient is
   the approved gold, and a flat swatch is not a substitute for it. Only the
   ANIMATION is rationed (§4.0): one instance per page moves, the rest hold the
   same gradient still. */
.bf-lockup .bf-plus {
  font-weight: 400;
  font-size: max(13px, 0.82em);
  letter-spacing: calc(-0.048em * var(--wm-tighten, 1));
  transform: translateY(-0.01em);
  position: relative;
  z-index: 1;
  background: var(--gold-metal-sweep);
  background-size: 200% 100%;
  background-position: 34% 50%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: brightness(var(--wm-bright, 1)) drop-shadow(0 0 calc(16px * var(--wm-glow, 0)) rgba(240,222,175,calc(0.5 * var(--wm-glow, 0))));
}
/* §4.0 · the one ANIMATED sweep per page */
.bf-lockup.bf-hero .bf-plus,
.bf-hero .bf-lockup .bf-plus {
  animation: bfGoldSweep 6s linear infinite;
  filter: brightness(var(--wm-bright, 1)) drop-shadow(0 0 calc(18px * var(--wm-glow, 0)) rgba(240,222,175,calc(0.55 * var(--wm-glow, 0))));
}
.bf-lockup .bf-flow {
  font-weight: 700;
  font-size: max(15px, 1.06em);
  letter-spacing: calc(-0.0636em * var(--wm-tighten, 1));
}
/* The lowercase `l` overshoots the cap-height of `F` and reads as the tallest
   thing in the mark. Bring it back down to the cap line. */
.bf-lockup .bf-flow-l {
  font-size: 0.9em;
  font-weight: inherit;
  letter-spacing: inherit;
}
.bf-lockup.on-light { color: var(--ink-1); }


/* ── ruling 16 · THE CASING STAMP ───────────────────────────────
   The wordmark's construction, lent to other word pairs: done+Right,
   booked+Solid, no+Apps. ONE per page. The wordmark itself is never set
   this way — it is always the placed file. ── */
.bf-stamp {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-sans);
  line-height: 1;
  white-space: nowrap;
  color: var(--fg1);
}
.bf-stamp .w1 { font-weight: 200; letter-spacing: -0.092em; }
.bf-stamp .w2 { font-weight: 600; letter-spacing: -0.045em; }
.bf-stamp .joint {
  position: relative;
  width: 0.44em;
  height: 0.44em;
  flex-shrink: 0;
  margin: 0 -0.05em 0 -0.02em;
  transform: translateY(0.115em);
}
.bf-stamp .joint i { position: absolute; left: 50%; top: 50%; display: block; }
.bf-stamp .joint .h { width: 0.44em; height: 0.125em; }
.bf-stamp .joint .v { width: 0.125em; height: 0.44em; }
.bf-stamp .joint .cast {
  background: var(--joint-cast);
  transform: translate(calc(-50% + 0.013em), calc(-50% + 0.013em));
}
.bf-stamp .joint .face {
  background: var(--joint-face);
  transform: translate(-50%, -50%);
}


/* ══════ base.css ══════════════════════════════════════════════════════════════ */
/* branded+Flow Design System — generated from tokens/unified.css.
   The rulings this encodes are recorded in DESIGN-SPEC.md and CLAUDE.md.
   Do not edit a value here without changing all three. */

body {
  margin: 0;
  background: var(--ground);
  /* ruling 11 · neutral white body text. Never a blue-grey. */
  color: var(--fg2);
  font-family: var(--font-sans);
  font-weight: 200;
  text-wrap: pretty;
}
a { color: #2BE9D9; text-decoration: none; }
a:hover { color: #80F2E8; }
::selection { background: #21B9B2; color: var(--ground); }

/* ── RETIRED-HEX GUARD ─────────────────────────────────────────────────────
   The bound design system's own colors.css defines --teal-mint: #9FEDE2 and
   may load after this file. #9FEDE2 was retired by ruling 1, so it is pinned
   on-palette here rather than left to import order. A retired value must not
   depend on the cascade to stay retired. ── */
:root { --teal-mint: #80F2E8; /* @kind color */ }
.bf-dark, [data-bnf-page] { --teal-mint: #80F2E8; }

/* ── GOLD-AS-TEXT SHIM ─────────────────────────────────────────────────────
   The bound design system's components consume --gold / --guide-soft /
   --guide-on-light as a TEXT colour: MoneyStat paints the one number flat
   #D4A54A, and its pills and counters take warm ink. Rulings 3 and 4 retire
   flat gold as type anywhere, and ruling 4 makes the one number brushed metal.

   Those component files live under _ds/ and are read-only, so the correction
   is applied here by matching their inline declaration — the same escape the
   eyebrow shim uses. When the repo components are fixed, delete this block.

   The one number keeps its gold, but as the brushed vertical face, which is
   the single sanctioned use of gradient-clipped text (§10). ── */
[style*="color: var(--gold)"],
[style*="color:var(--gold)"] {
  background: var(--joint-face) !important;
  -webkit-background-clip: text !important;
  background-clip: text !important;
  color: transparent !important;
  text-shadow: none !important;
}
[style*="color: var(--guide-soft)"],
[style*="color:var(--guide-soft)"],
[style*="color: var(--guide-on-light)"],
[style*="color:var(--guide-on-light)"],
[style*="color: var(--guide)"],
[style*="color:var(--guide)"] {
  color:#FFFFFF !important;
}

/* ── TINY-TEXT SHIM ────────────────────────────────────────────────────────
   The bound design system's Hub components carry inline font-size: 9px and
   10px on real labels ("Drafts to approve", "Leads gone quiet", "live").
   --fs-meta's floor is 12px, and the design system's own guide says not to set
   meaningful text at 10–11px. Those files live under _ds/ and are read-only,
   so the correction matches their inline declaration — the same escape the
   eyebrow and gold-as-text shims use. Delete when the components are fixed. ── */
[style*="font-size: 9px"],
[style*="font-size:9px"],
[style*="font-size: 10px"],
[style*="font-size:10px"],
[style*="font-size: 11px"],
[style*="font-size:11px"],
[style*="font-size: 11.5px"],
[style*="font-size:11.5px"],
[style*="font-size: 10.5px"],
[style*="font-size:10.5px"] {
  font-size: var(--fs-meta) !important;
}

/* ── ALPHA-TEXT SHIM ──────────────────────────────────────────────────────
   Alpha white takes the hue of whatever is behind it. Over the drift field's
   warm radial it composites to CREAM — which is why every probe of the
   declared colour returned a neutral and the screen still looked warm.

   Our own layers now use opaque neutrals (--fg1..4). The bound design system's
   components hardcode rgba(255,255,255,.x) inline and are read-only, so they
   are pinned here to the opaque equivalents over #141414. ── */
[style*="color:#FFFFFF"], [style*="color:#FFFFFF"] { color: #FFFFFF !important; }
[style*="color:#FFFFFF"],  [style*="color:#FFFFFF"]  { color: #FFFFFF !important; }
[style*="color:#FFFFFF"], [style*="color:#FFFFFF"] { color: #FFFFFF !important; }
[style*="color:#FFFFFF"], [style*="color:#FFFFFF"] { color: #FFFFFF !important; }
[style*="color:#FFFFFF"],  [style*="color:#FFFFFF"]  { color: #FFFFFF !important; }
[style*="color:#FFFFFF"],  [style*="color:#FFFFFF"]  { color: #FFFFFF !important; }
[style*="color:#FFFFFF"],  [style*="color:#FFFFFF"]  { color: #FFFFFF !important; }
[style*="color:#FFFFFF"], [style*="color:#FFFFFF"] { color: #FFFFFF !important; }

/* ══ RETIRED SURFACE TOKENS, PINNED ════════════════════════════════════════
   Ruling 8 retires ink navy as the ground, but the bound design system's
   components do not read our classes - they write inline
   style="background: var(--navy-panel)" and friends, so no selector of ours
   can reach them and !important on a guessed class reaches nothing. The only
   lever is the TOKEN.

   --ink-navy was pinned in an earlier pass and the surface still rendered
   navy, because pinning ONE of a component's inline tokens fixes nothing:
   AuroraSurface paints --gradient-navy, HubCard and StackCard paint
   --navy-panel, six more paint --navy-deep and --navy-soft. Measured on the
   Workspace before this block: #16304A x6 and #0A1626 x6 on screen, reading
   as purple-blue cards on charcoal. Pin the whole family or pin none.     */
:root {
  --ink-navy: #141414; /* @kind color */
  --navy-panel: #1C1C1A; /* @kind color */
  --navy-soft: #1C1C1A; /* @kind color */
  --navy-deep: #0E0E0E; /* @kind color */
  --navy-mid: #1C1C1A; /* @kind color */
  --gradient-navy: var(--ground-sweep); /* @kind color */
  --navy-wash: rgba(28,28,26,.55); /* @kind color */

  /* --fg4 was #FFFFFF: too dark to read at any size, and it was carrying
     12px labels. Chrome only, and now light enough to be chrome. */
  --fg4: #FFFFFF; /* @kind color */
}

/* ══ NO SERIF, NO BLACK TEXT ═══════════════════════════════════════════════
   Seven nodes measured at 16px Times in rgb(0,0,0) on the Workspace - black
   type on a dark ground, invisible, with no console error. Design-system
   components that declare no font-family inherit from the UA once they mount
   outside our styled subtree, and a component that declares no colour
   inherits the UA's black the same way. Guard both.                      */
x-dc, x-dc *, [class^="bnf-"], [class*=" bnf-"], [class^="bf-"], [class*=" bf-"],
article, aside, section, figure, figcaption, blockquote, dl, dd, dt,
table, th, td, li, output, fieldset, legend {
  font-family: var(--font-sans);
}
[class^="bnf-"], [class*=" bnf-"], article, aside, section, figure,
figcaption, blockquote, table, th, td, li, output {
  color: inherit;
}

p[style*="--fs-eyebrow"] {
  display: block !important;   /* DS Eyebrow sets display:flex inline - see components.css */
  letter-spacing: .06em !important;
  font-weight: 400 !important;
  white-space: nowrap;
}
p[style*="--fs-eyebrow"] > span:first-child {
  display: block !important;
  margin-bottom: var(--sp-2) !important;
}


/* ══════ unified.css ═══════════════════════════════════════════════════════════ */
/* ══════════════════════════════════════════════════════════════════════════
   LOCKED 2026-08-21 — read the LOCKED section of CLAUDE.md before editing.

   Every value in this file was decided in session and measured off the
   rendered page. The ones that get "fixed" by mistake, so they are named here:

     - --fg1..4 are ALL #FFFFFF. That is not an oversight. There is no grey.
     - --fw-body is 200. Headings are 200 too. Weight separates nothing.
     - --ls-body .022em is POSITIVE. Reading text is expanded, not condensed.
     - --lh-body 1.7 belongs to a 200 weight at .022em. It was correctly 1.5
       for a 300 weight; do not revert it without re-judging weight and
       tracking with it.
     - --fs-eyebrow-web 16px is PINNED. It is the one scalar in the ladder.
     - h1 and display are UPPERCASE at +.055em. Caps take positive tracking.
     - .bf-stamp .joint margins are NEGATIVE and must stay negative — they
       match the placed wordmark, whose spacing is in the file, not the CSS.
     - a shimmer is a highlight over a fixed face, or a repeating band that
       travels. A whole gradient sliding makes the element cycle colour.
   ══════════════════════════════════════════════════════════════════════════ */
/* branded+Flow — UNIFIED TOKEN LAYER
   Loaded AFTER the v4 token files. This file is the merge: every conflict
   between the v3 (charcoal + #21B9B2 + weight 400) and v4 (neutral dark +
   #12B5A6 + gold + light 300) systems is resolved here, once, in one place.

   THE RULINGS  — ruled by Jen, 2026-08-15
   1. Ground     — neutral dark #141414 with drifting light. SUPERSEDED by
                    ruling 14 below: neutral dark #0E1C2E is retired.
   2. Teal       — the DEFAULT's five approved teals, unchanged:
                   #21B9B2 flat · #1CA19E hover · #2BE9D9 bright ·
                   #80F2E8 highlight · #094856 deep.
   3. Warm        — gold #D4A54A moves IN, as the tracker and the guide.
                   Amber #F4A14A is retired.
   4. Scope      — gold is STRUCTURE plus one money moment per surface.
                   Because gold is back at v4's value and v4's one-per-surface
                   law, the gold token family keeps its real values: warmth is
                   governed by WHERE it is spent, not by remapping it away.
   5. Headings   — REVISED 2026-08-16. The v3 default's type treatment wins:
                   Light 300 display and headings, tight negative tracking,
                   modest sizes. 400 only at H3/H4. Mono at .14em, weight 500.
   6. Depth      — v4 cast-shadow / metallic. Hover lifts up-and-left.
   7. Corners    — near-square structure, pills on CTAs only.
   8. Icons      — the v4 brand set, 24px grid, 1.75px stroke.
   9. Light      — cool paper #F4F8FA; Teal Deep is the only teal on it.
  10. Pricing    — offer-v2 figures publish verbatim.
  11. Text       — RULED 2026-08-16. Text on dark is WHITE at four opacities.
                   The bluish body values (rgba(255,255,255,.82) · rgba(255,255,255,.82) · rgba(255,255,255,.82) ·
                   rgba(255,255,255,.60)) are RETIRED everywhere. Blue-grey text on a navy
                   ground reads muddy and sick; neutral white reads printed.
  12. Buttons    — RULED 2026-08-16. Every button is METALLIC: a lit top edge,
                   a darker lower lip, a hard 45deg cast block, and a lift on
                   hover. Five faces: primary (teal liquid) · bright · mint ·
                   ghost · outline, plus money (gold, one per surface).
                   Flat-fill buttons are retired.
  13. Eyebrow    — RULED 2026-08-16, resized 2026-08-17. 15px web / 22px on
                   a 1920 slide, tracking .22-.26em, 36px / 48px tick.
                   The section NUMBER is a DECK device only: on a slide it
                   leads, in brushed gold, then the tick, then the label, so
                   it aligns flush with the heading. On a scrolling page there
                   is no number — the tick leads and there is no gold, because
                   nobody cites a section number on a page they scroll.
  14. Ground     — RULED 2026-08-17. INK NAVY #0E1C2E is RETIRED as the ground.
                   The ground is NEUTRAL DARK: #141414 charcoal, #1C1C1A
                   elevated, #0E0E0E deep well. It is never flat — the
                   drifting light field is what keeps it alive, now teal
                   only plus one faint warm ball. No navy, no blue cast.
  15. The joint  — RULED 2026-08-17. The wordmark's "+" and the icon carry
                   ONE depth model: a brushed gold face with a #147D81 cast
                   at 45deg down-right. #2BE9D9 as the cast is RETIRED — it
                   was lighter and more saturated than the gold it sat
                   behind, so it read as a sticker outline, not depth.
                   A shadow is always darker than its object.
  16. The stamp  — RULED 2026-08-17. The casing stamp (done+Right,
                   booked+Solid, no+Apps) uses the wordmark's construction
                   exactly. One per page. The wordmark itself is always the
                   placed file, never the live-type stamp.
  19. Ladder     — RULED 2026-08-18. ONE modular scale, ratio 1.2 (minor
                   third), six rungs, anchored at a 15px body: 15 / 18 /
                   21.5 / 26 / 31 / 37 on web. A 1920 stage anchors at 28px
                   on the same ratio (28 / 34 / 41 / 49 / 59 / 71); print
                   anchors at 12pt (12 / 14.5 / 17 / 21 / 25 / 30). --fs-h4
                   aliases --fs-h3. Never tune a rung - recompute the ratio.
                   RETIRED: the eight-rung accelerating ladder and the
                   15%-clearance rule it needed.
  17. Scale      — RULED 2026-08-17. Every space, pad, margin and type size on
                   a WEB surface is clamped and fluid, from --sp-1..8 and the
                   --fs-* ladder. A bare pixel value in a web layout is a bug.
                   Slides and video are a fixed 1920x1080 canvas and stay in
                   fixed px against the 24px floor.
  18. Eyebrow wt — RULED 2026-08-17. JetBrains Mono LIGHT 300 on web, never
                   500 or 600. Note the mono only ships 400/500/700 by default,
                   so 300 must be LOADED or it silently falls back to 400. On a
                   scrolling page the TICK SITS ABOVE the label, so the
                   eyebrow's text starts on the same left edge as the logo,
                   the heading and the body under it.
*/

:root {
  /* The gutter that clears the guide rail — the rail's EXPANDED extent, not
     its collapsed width. Its active label grows to ~187px. */
  --rail: clamp(200px, 16vw, 228px); /* @kind spacing */
  /* ── the wordmark's three live dimensions ──────────────────────────────
     Every surface reads these, so the mark is identical everywhere. The
     Tweaks panel on the system page overrides them inline per-page; these
     are the ratified defaults. See DESIGN-SPEC §5.1. */
  --wm-tighten: 1.35; /* @kind other */
  --wm-bright: 1.18; /* @kind other */
  --wm-glow: 0.5; /* @kind other */

  /* ── the tracker. Gold, but never flat paint — see the gold system below. ── */
  --track: #C89A46; /* @kind color */
  --track-dim: rgba(200, 154, 70, 0.32); /* @kind color */
  --track-glow: rgba(230, 206, 147, 0.5); /* @kind color */
  --track-wash: rgba(220, 187, 114, 0.07); /* @kind color */
  --track-border: rgba(220, 187, 114, 0.45); /* @kind color */

  /* the metal itself — light, dark, light. Never a single hex. */
  --gold-metal: linear-gradient(168deg, #FBF0D2 0%, #E4C889 22%, #B98F3E 48%, #EBD6A2 68%, #F8EDCD 100%); /* @kind color */
  --gold-metal-sweep: linear-gradient(112deg, #F4E5C2 0%, #EBD8AC 14%, #E1CB94 28%, #D7BC7C 42%, #CDAD66 56%, #C49F53 70%, #BC9345 84%, #B58B3B 100%); /* @kind color */
  /* the teal metal — deep, rising to a mint highlight, falling back. Same
     construction as the gold sweep and for the same reason: the old face went
     #80F2E8 straight into #21B9B2 across 18% of its width, a ~37-point
     lightness drop that renders as a seam rather than a highlight. Every
     adjacent pair here moves less than ~20, so the shimmer travels and no edge
     appears. All stops are teal - lightness moves, hue does not (ruling 13). */
  --teal-metal-sweep: linear-gradient(112deg, #1FAFA8 0%, #21B9B2 10%, #26C7BE 20%, #2BE9D9 34%, #4FEDE0 46%, #80F2E8 56%, #4FEDE0 66%, #2BE9D9 78%, #24C0B8 88%, #21B9B2 95%, #1FAFA8 100%); /* @kind color */
  --gold-metal-v: linear-gradient(180deg, #F8EDCD, #C89A46 55%, #8E6B28); /* @kind color */
  /* the tagline's own gold, chosen 2026-08-24. It is --gold-metal-v with the
     deep brown floor lifted out: that floor turned the bottom half of every
     letter muddy, which is worst at meta size where the tagline often sits.
     Only the tagline uses this. Rules, joints and button edges keep the full
     ramp, because a 2px rule wants the dark end and a letter does not. */
  --gold-tagline: linear-gradient(180deg, #FBF3DC, #E9D29A 46%, #C89A46); /* @kind color */
  --gold-emboss: linear-gradient(180deg, #F0DFB0, #C79B45); /* @kind color */
  --gold-emboss-shadow: inset 0 1px 0 rgba(255,255,255,.7), 0 3px 8px -2px rgba(0,0,0,.6); /* @kind shadow */

  /* ── THE TEAL FAMILY · the default's five approved values, UNCHANGED ──
        Corrected 2026-08-16: an invented #35E0CB was overriding these. The
        default system's teals are approved and are not up for revision —
        this merge moved GOLD and the ground in, plus imagery,
        motion and components. It does not touch the teal. ── */
  --teal-flat: #21B9B2; /* @kind color */    /* eyebrow ticks, structural marks, hovers */
  --teal-hover: #1CA19E; /* @kind color */
  --teal-bright: #2BE9D9; /* @kind color */  /* emphasis words, eyebrow text */
  --teal-light: #80F2E8; /* @kind color */   /* highlight */
  --teal-deep: #094856; /* @kind color */    /* gradient endpoints; the only teal ON gold */
  --teal-mint: #2BE9D9; /* @kind color */
  --accent: #21B9B2; /* @kind color */
  --accent-hover: #1CA19E; /* @kind color */
  --accent-bright: #2BE9D9; /* @kind color */
  --accent-light: #80F2E8; /* @kind color */
  --accent-deep: #094856; /* @kind color */
  --accent-border: rgba(43,233,217,.4); /* @kind color */
  --accent-subtle: rgba(43,233,217,.12); /* @kind color */
  --accent-glow: rgba(43,233,217,.32); /* @kind color */
  --accent-gradient: linear-gradient(315deg,#094856 0%,#147D81 22%,#2BE9D9 50%,#20B4AE 78%,#094856 100%); /* @kind color */

  /* ── ruling 2 · the default's teal family stands. Nothing retired here. ── */
  /* --legacy-amber removed: it held retired amber as a live token. */
  --legacy-charcoal: #141414; /* @kind color */

  /* ── TYPE · the default's roles, at Jen's modest scale (thin, not big) ──
     FLUID at both ends. DESIGN.md §2's fluid rule: a fluid heading against
     a frozen body breaks the heading-to-body ratio across viewports — the
     page then reads correctly at exactly one width. Every reading size
     carries a clamp; only small chrome is fixed. ── */
  --t-label: 13px; /* @kind font */   /* mono · 500 · .14em · SENTENCE CASE, never upper */
  /* ── ONE ladder · the --t-* family aliases --fs-* ─────────────────────
     Two parallel ladders shipped: --t-* drove every h1–h4 element and
     --fs-* drove inline styles. On the --t-* ladder body was 19px, lead
     21px and h4 22px — three rungs inside 3px, which is why "the lead and
     the body" read as the same size and why h1 down to h4 had no
     separation. There is now one ladder; --t-* are aliases, kept so
     existing markup and the generated element rules keep working.

     RETIRED: --t-body 19 / --t-lead 21 / --t-h4 22 / --t-h3 30 /
     --t-h2 38 / --t-h1 48 / --t-display 60 as independent values.      */
  --t-caption: var(--fs-meta); /* @kind font */
  --t-small: var(--fs-body); /* @kind font */
  --t-body: var(--fs-body); /* @kind font */
  --t-lead: var(--fs-lead); /* @kind font */
  --t-h4: var(--fs-h4); /* @kind font */
  --t-h3: var(--fs-h3); /* @kind font */
  --t-h2: var(--fs-h2); /* @kind font */
  --t-h1: var(--fs-h1); /* @kind font */
  --t-display: var(--fs-display); /* @kind font */

  /* ── ruling 11 · TEXT ON DARK IS NEUTRAL WHITE ────────────────────────
     Four opacities, nothing else. Any hex with a blue cast is a bug. ── */
  /* RULED 2026-08-21, and this one is absolute: THERE IS NO GREY TEXT AND NO
     CREAM TEXT. All four rungs are #FFFFFF. Hierarchy is carried by size,
     weight, case and tracking - which is the brand's stated principle - so the
     colour scale was doing nothing but dimming the read. The names survive so
     existing markup keeps working; they no longer differ.
     Retired as text colours: #FFFFFF #FFFFFF #FFFFFF #FFFFFF #FFFFFF #FFFFFF
     and every rgba(255,255,255,<1). Transparency belongs on borders, fills and
     shadows - never on type. */
  --fg1: #FFFFFF; /* @kind color */
  --fg2: #FFFFFF; /* @kind color */
  --fg3: #FFFFFF; /* @kind color */
  --fg4: #FFFFFF; /* @kind color */
  --fg-hair: rgba(255,255,255,0.14); /* @kind color */
  /* the v4 token file defines these as blue-greys; ruling 11 neutralises them
     at the source so any markup still reading them lands on white. */
  --fg-navy-body: rgba(255,255,255,0.82); /* @kind color */  /* back-compat alias of --fg2; the name is retired, write --fg2 */
  /* on light: cool neutral ink, no blue cast */
  --ink-1: #14181C; /* @kind color */
  --ink-2: #14181C; /* @kind color */
  --ink-3: #14181C; /* @kind color */
  --ink-hair: rgba(20,24,28,.14); /* @kind color */

  /* ── ruling 14 · THE GROUND IS NEUTRAL DARK ─────────────────────────
     Neutral dark is retired. Neutral dark, and NEVER flat — the drift field is
     what keeps it alive. ── */
  --ground: #141414; /* @kind color */          /* the canvas */
  --ground-elevated: #1C1C1A; /* @kind color */ /* cards and panels on dark */
  --ground-deep: #0E0E0E; /* @kind color */     /* footer, chrome, the far end of the gradient */
  --ground-warm: #191917; /* @kind color */     /* the near end of the gradient */
  --ground-sweep: linear-gradient(168deg, #191917 0%, #0E0E0E 74%); /* @kind color */
  --panel-fill: rgba(28, 28, 26, 0.62); /* @kind color */
  --ink-navy: #141414; /* @kind color */        /* back-compat alias of --ground; the name is retired, write --ground */

  /* the drift field — teal only, plus ONE faint warm ball. 5-15% opacity:
     you should not be able to point at them, only notice the ground lives. */
  /* --drift RETIRED 2026-08-21 — see the ground note below. */

  /* ── ruling 15 · THE JOINT ────────────────────────────────────
     The "+" in the wordmark, the icon, and the casing stamp — one model. */
  --joint-face: linear-gradient(180deg, #F8EDCD, #C89A46 55%, #8E6B28); /* @kind color */
  --joint-cast: #147D81; /* @kind color */      /* darker + less saturated than the face */

  /* ── ruling 17 · THE CLAMPED SCALE ───────────────────────────────
     Every space, pad, margin and type size on a WEB surface resolves from
     here, and every one is fluid. A bare pixel value in a web layout is a
     bug. Slides and video frames are the exception: a fixed 1920×1080
     canvas, so fixed px against the 24px floor. ── */
  /* COMPRESSED 2026-08-19. Same 1.4 ratio, but the top four rungs were
     doing far more work than the content needed: --sp-8 at 92px and --sp-7
     at 66px put a screen-height of air between blocks that belong to one
     read. The low rungs are untouched (they are component padding); the
     climb is flattened to 1.32 from --sp-4 up, which takes ~28% off the
     section gaps without touching a single page. */
  --sp-1: clamp(6px, .45vw, 7px); /* @kind spacing */
  --sp-2: clamp(9px, .8vw, 11px); /* @kind spacing */
  --sp-3: clamp(13px, 1.1vw, 15px); /* @kind spacing */
  --sp-4: clamp(18px, 1.5vw, 21px); /* @kind spacing */
  --sp-5: clamp(25px, 2.1vw, 29px); /* @kind spacing */
  --sp-6: clamp(35px, 3vw, 41px); /* @kind spacing */
  --sp-7: clamp(53px, 4.4vw, 62px); /* @kind spacing */
  --sp-8: clamp(82px, 6.6vw, 96px); /* @kind spacing */

  /* ── the type ladder · ONE ratio, ~1.20, body → display ──────────────
     Every rung is a real step. Ceiling values and the ratio to the rung
     below: body 15.5 · lead 18.5 (1.19) · h4 22.5 (1.22) · h3 27 (1.20) ·
     h2 32.5 (1.20) · h1 39 (1.20) · display 52 (1.33 — the hero is allowed
     one bigger jump). The floors ladder too: 15 · 17 · 19 · 21 · 24 · 27 · 34.

     RETIRED: --fs-lead at 18px against --fs-body 16.5px. A 9% step is not a
     step — lead and body rendered as the same size, which is the defect this
     replaces. SUPERSEDED 2026-08-18: the ladder is a computed 1.2 modular scale, so every step is exactly 20% and no clearance rule is needed.    */
  /* == ONE MODULAR SCALE - ratio 1.200 (minor third) - anchor 15px ======
     RULED 2026-08-18. Six rungs, one ratio, COMPUTED - not tuned. The
     ladder this replaces had eight rungs on a ratio that ACCELERATED:
     1.16 at the bottom, 1.19, 1.22, 1.26, 1.33 at the top. That is why the
     top read oversized while the middle read crowded, and why h4 and lead
     were indistinguishable. A modular scale is the standard here: pick one
     ratio, anchor it at the body size, compound it.

       body 15 - lead 18 - h3 21.5 - h2 26 - h1 31 - display 37

     --fs-h4 is now an ALIAS of --fs-h3. Eight rungs cannot all be used on
     one surface; the pair sitting inside 4px was the proof.

     --fs-body is PINNED at 15px both ends. It is ruling 7's sentence floor,
     and reading size should not scale - everything above it does, so the
     heading-to-body ratio opens as the viewport widens instead of the whole
     page inflating. Chrome (meta, eyebrow, button) sits BELOW the anchor and
     is deliberately off the modular scale: it is not read as prose.

     Floors run on 1.15 rather than 1.2, because the body floor and the body
     ceiling are the same number and the rungs above it need somewhere to go.

     RETIRED: the eight-rung ladder (15.5 / 18.5 / 21.5 / 25.5 / 31 / 39 / 52),
     --fs-h4 as an independent value, and the "clear the rung below by 15%"
     rule - a computed 1.2 ratio makes every step exactly 20%.           */
  --fs-scale-ratio: 1.3; /* @kind other */
  /* REVISED the same day. Body was pinned at 15px both ends, which read as
     tiny on a wide screen: every rung above it scaled and the reading size
     did not. Same ratio, anchor moved to 16px, and chrome came UP off the
     floor - a 12px label in #FFFFFF was unreadable, which is a size AND a
     colour failure at once (see --fg4 below).
       body 16 - lead 19 - h3 23 - h2 27.5 - h1 33 - display 40
     Floors run 1.15 from 15: 15 - 17 - 19.5 - 22.5 - 26 - 30.             */
  --fs-meta: clamp(13px, 1vw, 14px); /* @kind font */
  /* PINNED at 15px, 2026-08-21 — the body anchor, not the chrome band. */
  --fs-eyebrow-web: 16px; /* @kind font */
  --fs-btn: clamp(15px, 1.15vw, 16px); /* @kind font */
  --fs-body: clamp(17px, 1.35vw, 18px); /* @kind font */
  --ls-body: 0.022em; /* @kind font */
  /* reading text is ExtraLight - the same weight as the word "branded".
     200 is the floor on a dark ground: at 100 the strokes go thinner than a
     pixel and white composites toward cream. */
  --fw-body: 200; /* @kind font */
  --ls-lead: 0.016em; /* @kind font */
  --fs-lead: clamp(20.4px, 1.7vw, 23.4px); /* @kind font */
  --fs-h3: clamp(24.5px, 2.2vw, 30.4px); /* @kind font */
  --fs-h2: clamp(29.4px, 2.85vw, 39.5px); /* @kind font */
  --fs-h1: clamp(35.3px, 3.7vw, 51.4px); /* @kind font */
  --fs-display: clamp(42.3px, 4.8vw, 66.8px); /* @kind font */
  --fs-h4: var(--fs-h3); /* @kind font */

  /* == THE STAGE LADDER - 1920x1080 slide + video - anchor 28px, 1.2 =====
     A fixed canvas cannot compute off a 15px web body, so it carries its own
     anchor on the same ratio. Ruling 7's 24px floor stays a FLOOR: captions
     and meta sit on it, body sits above it. Fixed px is correct here - rule
     11's clamping requirement exempts a fixed canvas. The slide eyebrow
     stays 22px per ruling 4: a nameplate is a label, not a sentence.    */
  --fs-stage-meta: 24px; /* @kind font */
  --fs-stage-body: 28px; /* @kind font */
  --fs-stage-lead: 36.4px; /* @kind font */
  --fs-stage-h3: 47.3px; /* @kind font */
  --fs-stage-h2: 61.5px; /* @kind font */
  --fs-stage-h1: 80px; /* @kind font */
  --fs-stage-display: 104px; /* @kind font */

  /* == THE PRINT LADDER - points - anchor 12pt, 1.2 =====================
     Ruling 7's 12pt floor IS the print body. A caption or a table cell may
     sit under it at 10pt; a sentence never does.                        */
  --fs-print-caption: 10pt; /* @kind font */
  --fs-print-body: 12pt; /* @kind font */
  --fs-print-lead: 15.6pt; /* @kind font */
  --fs-print-h3: 20.3pt; /* @kind font */
  --fs-print-h2: 26.4pt; /* @kind font */
  --fs-print-h1: 34.3pt; /* @kind font */
  --fs-print-display: 44.6pt; /* @kind font */
  --tick: clamp(52px, 4.6vw, 64px); /* @kind spacing */
  /* the placed wordmark, in a page head. One token so it cannot drift per
     surface - it was 32-46px on four pages and read as a banner. */
  --mark-h: clamp(21px, 1.9vw, 27px); /* @kind spacing */   /* lengthened 2026-08-21 — a 30px rule
     above a 15px nameplate read as a dash, not as a structural mark */

  --lh-display: 1.06; /* @kind font */
  --lh-heading: 1.12; /* @kind font */
  --lh-body: 1.7; /* @kind font */

  /* ── ruling 5 · REVISED 2026-08-16 · the default's thin heading wins ── */
  --weight-display: 300; /* @kind font */
  --weight-heading: 300; /* @kind font */
  --weight-subheading: 400; /* @kind font */
  --weight-body: 300; /* @kind font */
  --weight-ui: 500; /* @kind font */
  --track-mono: 0.14em; /* @kind font */
}

/* ── ruling 5, enforced · Light 300 display and headings, 400 at H3/H4 ── */
/* one step lighter than before — h1/h2 were 300, h3/h4 were 400. Tighter
   negative tracking climbs with the size, which is what makes a large light
   line read as a HEADING rather than as big body copy. */
h1, h2, h3, h4, h5, h6 { font-weight: 200; line-height: var(--lh-heading); margin: 0; }
h1 { font-size: var(--t-h1); font-weight: 200; letter-spacing: -0.040em; line-height: var(--lh-display); }
/* Tracking below the two caps rungs, corrected 2026-08-23. These element rules
   carried -0.032em h2 and -0.026em h3 while `.bf-sechead > h2` carried -0.02em,
   so a plain <h2> and a section head's <h2> were tracked differently on the same
   page. Resolved to -0.020em / -0.018em on two pieces of evidence that exist:
   the LOCKED — 2026-08-21 section of CLAUDE.md states -.020em h2 / -.018em h3,
   and `.bf-sechead > h2` was already declared at -0.02em.

   NOT evidence, and an earlier version of this comment wrongly claimed it was:
   the frozen specimen. It renders no <h2> at all — its section-head rung is
   demonstrated with a mono label and a body line — so it is silent on this, and
   a reader who checks it will find nothing. Cite what renders, or cite the
   ruling; never cite a page without looking. */
h2 { font-size: var(--t-h2); font-weight: 200; letter-spacing: -0.020em; }
h3 { font-size: var(--t-h3); font-weight: 200; letter-spacing: -0.018em; line-height: 1.2; }
h4 { font-size: var(--t-h4); font-weight: 200; letter-spacing: -0.018em; line-height: 1.2; }
p { font-size: var(--t-body); font-weight: 300; line-height: var(--lh-body); letter-spacing: -0.005em; }

/* ── heading weight, made unreachable-proof ────────────────────────────────
   Every heading on every page carried an INLINE font-weight, which beats the
   h1–h4 rules above — so the weight ruling reached none of them. The inline
   declarations were stripped from our own pages; this shim catches the two
   remaining sources:

     · the bound design system's components, which render h3 card titles with
       inline font-size:13px; font-weight:600 and live read-only under _ds/
     · any future page that re-declares the weight

   Every heading is 200 — thinner than the 300 body, which is what makes it
   read as a heading. 600 → 500 rather than 200 for the one exception: a Hub
   card title is UI chrome, and the design system's own guide puts UI at 500. Its 13px size is left alone — that is a
   label, not a sentence, so --fs-meta's 12px floor governs, not the 15px one. */
h1[style], h2[style], h3[style], h4[style] { font-weight: 200 !important; }
h3[style*="font-weight: 600"], h4[style*="font-weight: 600"],
h3[style*="font-weight:600"],  h4[style*="font-weight:600"] { font-weight: 500 !important; }


/* emphasis is flat teal — never italic, never a weight bump */
em, .t-em { font-style: normal; font-weight: 500; color: var(--teal-bright); }

/* ── teal emphasis carries a soft halation ────────────────────────────────
   Teal on charcoal is a thin, cool colour; set as a phrase inside prose it
   reads as flat coloured text unless it glows. The glow is what makes it read
   as LIT rather than tinted — the same reason the gold sweeps.

   Scoped to teal inside a paragraph, which is where an emphasis PHRASE lives.
   A standalone teal label (a section name, an eyebrow, a plate heading) gets
   no glow — it is naming a thing, not being emphasised inside a sentence. */
/* Match BOTH notations. React serialises a style object to rgb(), so a
   selector written against the authored hex matches nothing in the rendered
   DOM — which is how the first version of this rule reached 0 of 28 teal
   phrases. Plain-HTML pages keep the hex, so both forms are listed. */
/* ANY prose container, not just <p>. This list was written as "p em" and a
   specimen setting its body copy in <span class="bd"> therefore rendered flat
   teal with no halation - the emphasis was correct in colour and dead in light,
   which is the same defect class as gold with no animation-name. An emphasis
   phrase is authored as <em> or .t-em; where it sits is not the rule's business. */
em, .t-em, .t-em.is-phrase, em.t-em,
p em, p .t-em,
p span[style*="rgb(43, 233, 217)"], p span[style*="rgb(33, 185, 178)"],
p span[style*="rgb(128, 242, 232)"],
p span[style*="#2BE9D9"], p span[style*="#21B9B2"], p span[style*="#80F2E8"],
p span[style*="--teal-bright"], p span[style*="--teal-flat"] {
  text-shadow: 0 0 18px rgba(43,233,217,.34), 0 0 42px rgba(43,233,217,.16);
}
/* Mono is never emphasis. A file path, a hex code or a token name set in teal
   mono is a CODE reference — glowing it makes it look like a highlighted
   phrase. Excluded explicitly, since the selectors above match on colour and
   cannot tell a phrase from a path.

   Going forward, author an emphasis phrase as <em> or class="t-em" rather than
   a bare coloured span: the substring selectors above only exist because 28
   teal phrases on one page were hand-rolled, and a colour-matching selector
   is guesswork. */
p span[style*="--font-mono"], p span[style*="JetBrains"],
p code, p .mn, p .meta, p [class*="mono"] {
  text-shadow: none !important;
}


/* ── ruling 9 · Teal Deep is the ONLY teal that reads on light, so the light
      scope clamps the RAW family too — not just the semantic tokens. Without
      this, any component reaching for var(--teal-bright) directly keeps
      dark-surface cyan on paper (1.53:1 on white). ── */
.bf-light {
  --teal-bright: var(--teal-deep); /* @kind color */
  --teal-light: var(--teal-deep); /* @kind color */
  --teal-mint: var(--teal-deep); /* @kind color */
  --accent-bright: var(--teal-deep); /* @kind color */
  --accent-light: var(--teal-deep); /* @kind color */
  --accent: var(--teal-deep); /* @kind color */
  /* ruling 11, on paper · cool neutral ink, still no blue cast */
  --fg1: var(--ink-1); /* @kind color */
  --fg2: var(--ink-2); /* @kind color */
  --fg3: var(--ink-3); /* @kind color */
  --fg4: #14181C; /* @kind color */
  --fg-hair: var(--ink-hair); /* @kind color */
  /* ruling 52 · 2026-09-04 · the light scope remaps BORDERS too. Until now it
     remapped teals and text only, so every hairline stayed white-on-paper and
     vanished — found on the Hub's light theme at install. Ink, not white. */
  --border: var(--border-on-light); /* @kind color */
  --border-strong: rgba(20, 24, 28, 0.22); /* @kind color */
}

/* ── THE GOLD SYSTEM · ruled 2026-08-16 ───────────────────────────────
   Gold is never a flat fill. It is brushed metal that catches light as it
   drifts (03 + 04), and embossed metal wherever it becomes a solid surface
   you could press (05). Flat #D4A54A is retired — too deep, too orange.

     .gold-metal    large text and numbers — brushed, slowly sweeping
     .gold-surface  chips, buttons, tags — raised and lit from above
     .gold-rule     the rail, dividers, any vertical or horizontal bar
     .gold-token    the tracker dot and any small round mark
*/
/* Brushed gold on text. STATIC by default — see §4.0: gradient-clipped text
   cannot be GPU-composited, so the live sweep is rationed to ONE element per
   page. Add `.is-live` (or `.bf-hero` on a lockup) to that one element. */
.gold-metal {
  background: var(--gold-metal-sweep);
  background-size: 200% 100%;
  background-position: 60% 50%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.gold-metal.is-live {
  animation: bfGoldSweep 6s linear infinite;
}
.gold-surface {
  background: var(--gold-emboss);
  color: #2A1E06;
  box-shadow: var(--gold-emboss-shadow);
  border: 0;
}
.gold-rule {
  background: var(--gold-metal-v);
  box-shadow: inset -1px 0 0 rgba(255,255,255,.45);
}
.gold-token {
  background: radial-gradient(circle at 32% 28%, #FFF6DE, #E4CE95 45%, #9A742C);
  box-shadow: inset 0 -1px 2px rgba(80,58,14,.9), 0 3px 8px rgba(0,0,0,.55);
}
@keyframes bfGoldSweep {
  from { background-position: 0 50%; }
  to   { background-position: -288px 50%; }
}

/* ── THE WORDMARK · typeset to the default system's approved lockup ─────
   branded = thin (300) and slightly smaller · + = gold, tucked tight ·
   Flow = thicker (600) and slightly larger. No space around the +, ever.
   Tracking and the negative tucks are the default's ratified values.

   Use: <span class="bf-lockup" style="font-size:64px">
          <span class="bf-branded">branded</span
          ><span class="bf-plus">+</span
          ><span class="bf-flow">Flow</span>
        </span>
*/
.bf-lockup {
  font-family: var(--font-sans);
  display: inline-flex;
  align-items: baseline;
  gap: 0;
  white-space: nowrap;
  line-height: 1;
  /* --wm-tighten scales every tuck and tracking value at once, so the mark
     closes up or opens out as ONE unit. 1 = the ratified §5.1 geometry. */
  letter-spacing: calc(-0.0516em * var(--wm-tighten, 1));
  text-transform: none;
  font-kerning: normal;
  text-rendering: geometricPrecision;
  -webkit-font-smoothing: antialiased;
  color: #fff;
}
/* The joint tucks are the wrapper's negative letter-spacing ONLY. Explicit
   negative margins stacked on top of that trailing letter-space and exceeded
   the glyphs' own side bearings, so the `d`, the `+` and the `F` collided into
   one crossed shape. letter-spacing already applies after each part's final
   glyph — that IS the tuck, and it stays inside the bearings at every size.
   The 13px floor keeps the thin word legible at the bottom of the ladder. */
.bf-lockup .bf-branded {
  font-weight: 200;
  font-size: max(13px, 0.80em);
  letter-spacing: calc(-0.0564em * var(--wm-tighten, 1));
}
/* The `+` carries the brushed-gold SWEEP on every surface — that gradient is
   the approved gold, and a flat swatch is not a substitute for it. Only the
   ANIMATION is rationed (§4.0): one instance per page moves, the rest hold the
   same gradient still. */
.bf-lockup .bf-plus {
  font-weight: 400;
  font-size: max(13px, 0.82em);
  letter-spacing: calc(-0.048em * var(--wm-tighten, 1));
  transform: translateY(-0.01em);
  position: relative;
  z-index: 1;
  background: var(--gold-metal-sweep);
  background-size: 200% 100%;
  background-position: 34% 50%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: brightness(var(--wm-bright, 1)) drop-shadow(0 0 calc(16px * var(--wm-glow, 0)) rgba(240,222,175,calc(0.5 * var(--wm-glow, 0))));
}
/* §4.0 · the one ANIMATED sweep per page */
.bf-lockup.bf-hero .bf-plus,
.bf-hero .bf-lockup .bf-plus {
  animation: bfGoldSweep 6s linear infinite;
  filter: brightness(var(--wm-bright, 1)) drop-shadow(0 0 calc(18px * var(--wm-glow, 0)) rgba(240,222,175,calc(0.55 * var(--wm-glow, 0))));
}
.bf-lockup .bf-flow {
  font-weight: 700;
  font-size: max(15px, 1.06em);
  letter-spacing: calc(-0.0636em * var(--wm-tighten, 1));
}
/* The lowercase `l` overshoots the cap-height of `F` and reads as the tallest
   thing in the mark. Bring it back down to the cap line. */
.bf-lockup .bf-flow-l {
  font-size: 0.9em;
  font-weight: inherit;
  letter-spacing: inherit;
}
.bf-lockup.on-light { color: var(--ink-1); }

/* The DS <Eyebrow> renders font-size: var(--fs-eyebrow) inline, so ruling 13
   reaches every existing instance by redefining the token. Tracking, gap and
   tick width are inline literals, so those need the !important escape. */
:root { --fs-eyebrow: 16px; }
/* white-space: the DS Eyebrow is a flex ROW of tick + label, and with no
   nowrap the label broke mid-phrase - "BRAND & / AUTOMATION SHOWROOM" and
   "01 - MEDIA & / VIDEO STAGE" wrapped even at 1440px, which reads as a
   broken nameplate rather than a label. A nameplate is one line. */
/* display:block is REQUIRED and must be !important: the DS Eyebrow sets
   display:flex INLINE, which laid the tick and the label out as a ROW - so the
   label started 38px right of the page edge while the wordmark, heading and
   body all started at 44px. Ruling 4 puts the tick on its OWN LINE ABOVE the
   label precisely so the eyebrow text shares that left edge; the tick-and-
   label-on-one-row form is a deck/video device only. The `gap` below is dead
   once the row is gone, and is dropped.

   Written against p[style*="--fs-eyebrow"] because .bf-eyebrow matches ZERO
   elements on any page using the component - the third time this session a
   correct ruling turned out to govern nothing. */
p[style*="--fs-eyebrow"] {
  display: block !important;
  font-weight: 300 !important;   /* ruling 13, third amendment — light, not 400 */
  letter-spacing: .06em !important;
  font-weight: 400 !important;
  white-space: nowrap;
}
p[style*="--fs-eyebrow"] > span:first-child {
  display: block !important;
  margin-bottom: var(--sp-2) !important;
}
p[style*="--fs-eyebrow"] > span[aria-hidden="true"] {
  width: var(--tick) !important;
  background: var(--joint-face) !important;   /* gold tick, per ruling 5 */
}

/* ── ruling 13, amended three times · THE EYEBROW ───────────────────
   Weight 300 — JetBrains Mono LIGHT, settled 2026-08-24. This class shipped
   400 while ruling 18's prose said 300, so the file contradicted itself for a
   week and the 400 was about to be ported into the repo. Jen ruled 300.
   The mono only ships 400/500/700 by default, so 300 MUST be loaded or it
   silently falls back to 400 — styles.css loads 300;400;500. The tick sits ABOVE the label on a scrolling page,
   so the eyebrow's text starts on the same left edge as the logo, heading and
   body beneath it. On a slide the gold number leads instead (see .on-stage),
   which puts ink at that same edge. ── */
.bf-eyebrow {
  display: block;          /* NOT a column flex — fit-content collapses and the
                              label wraps mid-phrase. Plain block flow keeps the
                              tick above and the text on one line. */
  font-family: var(--font-mono);
  font-size: var(--fs-eyebrow-web);
  font-weight: 300;
  letter-spacing: 0.06em;
  text-transform: none;
  color: var(--teal-bright);
  margin: 0;
  /* a nameplate is ONE line. The nowrap guard lived only on the DS Eyebrow's
     p[style*="--fs-eyebrow"] spelling, so .bf-eyebrow broke mid-phrase -
     "ANATOMY OF THE / SECTION HEAD". Same miss as ruling 24's :not(). */
  white-space: nowrap;
}
.bf-eyebrow::before {
  content: '';
  display: block;
  width: var(--tick);
  /* 2px, not 1px. A 1px line renders at half a pixel once the preview scales the
     page and all but disappears — this rendered at 0.99 while every .gr rule on
     the same page rendered at 1.99, so the tick read as a faint sliver beside
     solid rules. The guideline cards were swept for exactly this defect; the
     token layer, where the real one lived, was not. */
  height: 2px;
  /* A REPEATING band that travels — the same construction as every other gold
     rule in the system. It was a whole non-repeating gradient sliding across a
     fixed window, which is the retired shimmer: the window sweeps the ramp and
     the element's own colour cycles light-to-dark. Because this pattern repeats,
     facets cross the tick continuously and the average brightness never changes.
     The tick is the surface's one piece of systematic gold (ruling 15), so it
     has to be the correct metal. */
  background-image: repeating-linear-gradient(108deg,
    #C69848 0px,  #CEA054 9px,  #D6A961 18px, #DCB06B 27px,
    #D4A75E 36px, #CA9C4B 45px, #C09240 54px, #BB8D3C 63px,
    #C29642 72px, #CB9E4E 81px, #D3A65B 90px, #D9AD66 99px,
    #D1A45B 108px, #C79A49 117px, #BE8F3E 126px, #C69848 135px);
  background-size: 300% 100%;
  background-repeat: repeat;
  animation: bfGoldSweep 11s linear infinite;
  margin-bottom: var(--sp-2);
}
/* the section number — brushed gold, vertical sweep so every digit carries
   the same metal face. DECK ONLY: a scrolling page carries no number. */
.bf-eyebrow .num {
  font-weight: 400;
  letter-spacing: 0.10em;
  flex-shrink: 0;
  background: var(--joint-face);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.bf-eyebrow .tick {
  display: block;
  width: var(--tick);
  height: 1px;
  background: #21B9B2;
  flex-shrink: 0;
}
/* slide + video nameplate — fixed canvas, fixed px. The number leads and the
   row runs horizontally; no tick above. */
.bf-eyebrow.on-stage {
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  gap: 18px;
  font-size: 22px;
  letter-spacing: 0.26em;
  text-transform: uppercase;
}
.bf-eyebrow.on-stage::before { display: none; }
.bf-eyebrow.on-stage .tick { width: 48px; }

/* ── ruling 16 · THE CASING STAMP ───────────────────────────────
   The wordmark's construction, lent to other word pairs: done+Right,
   booked+Solid, no+Apps. ONE per page. The wordmark itself is never set
   this way — it is always the placed file. ── */
.bf-stamp {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-sans);
  line-height: 1;
  white-space: nowrap;
  color: var(--fg1);
}
/* Tracking eased 2026-08-21 at Jen's request — slightly, not loosened. The
   wordmark's own values are -0.092 / -0.045; these come off by about a third so
   the letters breathe without the stamp drifting away from the placed file. The
   JOINT margins are untouched and stay negative. */
.bf-stamp .w1 { font-weight: 200; letter-spacing: -0.062em; }
.bf-stamp .w2 { font-weight: 600; letter-spacing: -0.028em; }
.bf-stamp .joint {
  position: relative;
  width: 0.44em;
  height: 0.44em;
  flex-shrink: 0;
  /* -0.02em / -0.05em. Restored 2026-08-21 after trying .06, .105/.155,
     .20/.25, .145/.19 and .03/.04: every positive value made the stamp stop
     matching the placed wordmark, whose joint spacing is baked into the file.
     The stamp is the wordmark's construction lent to other pairs, so it has to
     match the artwork - not read well on its own. Do not widen it again
     without re-rendering the wordmark to match. */
  margin: 0 -0.05em 0 -0.02em;
  transform: translateY(0.115em);
}
.bf-stamp .joint i { position: absolute; left: 50%; top: 50%; display: block; }
.bf-stamp .joint .h { width: 0.44em; height: 0.125em; }
.bf-stamp .joint .v { width: 0.125em; height: 0.44em; }
.bf-stamp .joint .cast {
  background: var(--joint-cast);
  transform: translate(calc(-50% + 0.013em), calc(-50% + 0.013em));
}
.bf-stamp .joint .face {
  background: var(--joint-face);
  transform: translate(-50%, -50%);
}

/* ── ruling 12 · THE METALLIC BUTTON SET ──────────────────────────────
   One shading model, six faces. Every face carries, in this order:
     1. a lit top edge          inset 0 1px 0 rgba(255,255,255,.45+)
     2. a darker lower lip      inset 0 -2px 3px rgba(0,0,0,.45)
     3. a hard 45deg cast block 8px 9px 0 <tinted>
     4. a soft settle shadow    16px 17px 28px rgba(0,0,0,.34)
   Hover lifts UP-AND-LEFT into a bigger block; press compresses back down
   onto it. Never a flat fill, never a plain border, on any surface.

   Faces: .primary teal liquid (the default) · .bright · .mint · .ghost ·
          .outline (header CTA) · .money (gold — ONE per surface, and only
          where money is the subject).
*/
.bf-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.65em;
  font-family: var(--font-sans);
  font-size: var(--fs-btn);
  /* 400, lowered from 500 on 2026-08-23. Jen, on the ghost face: "why would
     'see the tiers' be bold". Against 200 body a 500 label reads as bold rather
     than as a label, and the button already has a metal face carrying it. 400
     is the floor that survives the sheen's brightest pass. */
  font-weight: 400;
  letter-spacing: -0.01em;
  line-height: 1;
  padding: var(--sp-3) var(--sp-5);
  border: 0;
  border-radius: 999px;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  color: #EAFFFB;
  /* every stop is teal — only lightness moves, so any visible window reads
     as the same colour. Retired: a gradient running out to #052229 at both
     ends, which made the rest state near-black and the hover state mint. */
  background: linear-gradient(112deg,#0B5F60 0%,#17908E 16%,#21B9B2 34%,#2BE9D9 52%,#80F2E8 62%,#21B9B2 80%,#12787A 100%);
  background-size: 165% 100%;
  background-position: 22% 50%;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.48),
    inset 0 -2px 3px rgba(5,34,41,.72),
    8px 9px 0 rgba(20,125,129,.55),
    16px 17px 28px rgba(0,0,0,.34);
  transition:
    transform .32s var(--ease-brand, cubic-bezier(.22,1,.36,1)),
    box-shadow .32s var(--ease-brand, cubic-bezier(.22,1,.36,1)),
    background-position .6s var(--ease-brand, cubic-bezier(.22,1,.36,1));
}
.bf-btn:hover {
  transform: translate(-4px,-4px);
  background-position: 78% 50%;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.58),
    inset 0 -2px 3px rgba(5,34,41,.7),
    12px 13px 0 rgba(20,125,129,.6),
    20px 21px 34px rgba(0,0,0,.4);
}
.bf-btn:active {
  transform: translate(1px,1px) scale(.99);
  transition-duration: 80ms;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.4),
    inset 0 -2px 3px rgba(5,34,41,.75),
    4px 5px 0 rgba(20,125,129,.5),
    8px 9px 16px rgba(0,0,0,.35);
}
.bf-btn:focus-visible { outline: 2px solid #2BE9D9; outline-offset: 4px; }
.bf-btn.bright {
  color: #052229;
  background: linear-gradient(112deg,#18BFB6 0%,#2BE9D9 28%,#80F2E8 48%,#C8FFF9 62%,#2BE9D9 100%);
  background-size: 240% 100%;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.75), inset 0 -2px 3px rgba(5,34,41,.28), 8px 9px 0 rgba(43,233,217,.42), 16px 17px 28px rgba(0,0,0,.32);
}
.bf-btn.mint {
  color: #052229;
  background: linear-gradient(180deg,rgba(240,255,253,.98),rgba(128,242,232,.9) 44%,rgba(43,233,217,.82));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.8), inset 0 -2px 3px rgba(5,34,41,.22), 8px 9px 0 rgba(43,233,217,.35), 16px 17px 28px rgba(0,0,0,.3);
}
.bf-btn.ghost {
  color: var(--fg1);
  background: linear-gradient(180deg,rgba(255,255,255,.17),rgba(255,255,255,.06));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.28), inset 0 -2px 3px rgba(0,0,0,.5), 8px 9px 0 rgba(255,255,255,.08), 16px 17px 28px rgba(0,0,0,.38);
}
/* RETIRED by ruling 37 and CLAIMED by the glossy guard above, so this block
   never renders - a surface using .outline gets the teal primary. It is the
   §6.2 unfilled button that was wanted; that is .bf-btn.unfilled. Found live on
   the Hub, 2026-09-18, where Send back and Open calendar were rendering as
   glossy teal pills. Kept only so this note sits where someone looks. */
.bf-btn.outline {
  color: #2BE9D9;
  background: linear-gradient(180deg,rgba(43,233,217,.2),rgba(43,233,217,.07));
  box-shadow: inset 0 1px 0 rgba(128,242,232,.34), inset 0 -2px 3px rgba(0,0,0,.45), 8px 9px 0 rgba(43,233,217,.22), 16px 17px 28px rgba(0,0,0,.3);
}
/* the money face — gold, brushed, ONE per surface (ruling 4) */
/* The money face SWEEPS. A gold button with the right stops and no
   animation-name is a brushed surface with the light switched off — which is
   how the one gold button on the page ended up looking flat. A button
   background is not gradient-clipped text, so it composites on the GPU and is
   exempt from §4.0's one-live-sweep rationing. */
.bf-btn.money {
  color: #2A1E06;
  background: var(--gold-metal-sweep);
  background-size: 200% 100%;
  animation: bfGoldSweep 7s linear infinite;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.85), inset 0 -2px 3px rgba(80,58,14,.5), 8px 9px 0 rgba(154,116,44,.5), 16px 17px 28px rgba(0,0,0,.4);
}
.bf-btn .bf-btn-dot { width: 7px; height: 7px; border-radius: 999px; background: currentColor; opacity: .55; flex-shrink: 0; }
.bf-btn svg { width: 17px; height: 17px; }

/* ── the fixed rail needs a gutter of its own; below 720px it stands down ── */
[data-bnf-page] { padding-left: var(--rail); }
@media (max-width: 720px) {
  [data-bnf-page] { padding-left: 0; }
  [data-bnf-rail] { display: none; }
}

/* ── ambient motion. SUPERSEDED by ruling 50 (below): decoration runs 14–46s,
   a status signal runs 1.2–2.4s. This line once read "Slow loops only:
   14–46s" and carried no number, no date and no initials — it was never
   ruled, and most loops broke it. ── */
@keyframes bnfShimmerSweep {
  0% { background-position: 0% 50%; }
  100% { background-position: 200% 50%; }
}
@keyframes bnfMarquee {
  0% { transform: translateX(0); }
  100% { transform: translateX(-50%); }
}
@keyframes bnfMarqueeRev {
  0% { transform: translateX(-50%); }
  100% { transform: translateX(0); }
}

/* Three headless keyframe bodies used to sit here — the drift-field balls
   retired by ruling 34. Their @keyframes lines were deleted and the bodies
   were left behind, so the parser dropped the two rules that followed them.
   Removed 2026-09-03. See "Motion harvest - before and after.html". */
@keyframes bnfGlowPulse {
  0%, 100% { opacity: 0.5; }
  50% { opacity: 0.9; }
}
@keyframes bnfStatusPulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}

/* ── reduced motion CALMS, it never removes content — DESIGN.md §6 ──────
   Animate FROM hidden TO visible and make visible the default, so a failed
   script or an early return still leaves content on screen. A blanket
   `animation:none` strands anything that animates in at opacity:0 — the
   exact trap DESIGN.md names, which once hid content from the owner. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
  [data-reveal], [data-reveal] > * { opacity: 1 !important; transform: none !important; }
}

body {
  margin: 0;
  background: var(--ground);
  /* ruling 11 · neutral white body text. Never a blue-grey. */
  color: var(--fg2);
  font-family: var(--font-sans);
  font-weight: 300;
  text-wrap: pretty;
}
a { color: var(--teal-bright); text-decoration: none; }
a:hover { color: var(--teal-light); }
::selection { background: #21B9B2; color: var(--ground); }

/* ── ruling 19 · THE RHYTHM ────────────────────────────────────────────────
   RULED 2026-08-17. The vertical rhythm of a text block is not even. Three
   gaps, deliberately unequal:
     eyebrow  →  heading      --sp-6   FAR. The nameplate is its own object.
     heading  →  subheading   --sp-2   TIGHT. They are one thought.
     subhead  →  body         --sp-7   AWAY. The body is a separate read.
   Even spacing reads as a list. Uneven spacing reads as a hierarchy. ── */
.bf-rhythm-eyebrow { margin-bottom: var(--sp-3); }
.bf-rhythm-heading { margin: 0 0 var(--sp-2); }
.bf-rhythm-sub     { margin: 0 0 var(--sp-4); }

/* ── RETIRED-HEX GUARD ─────────────────────────────────────────────────────
   The bound design system's own colors.css defines --teal-mint: #9FEDE2 and
   may load after this file. #9FEDE2 was retired by ruling 1, so it is pinned
   on-palette here rather than left to import order. A retired value must not
   depend on the cascade to stay retired. ── */
:root { --teal-mint: #80F2E8; /* @kind color */ }
.bf-dark, [data-bnf-page] { --teal-mint: #80F2E8; }

/* ── GOLD-AS-TEXT SHIM ─────────────────────────────────────────────────────
   The bound design system's components consume --gold / --guide-soft /
   --guide-on-light as a TEXT colour: MoneyStat paints the one number flat
   #D4A54A, and its pills and counters take warm ink. Rulings 3 and 4 retire
   flat gold as type anywhere, and ruling 4 makes the one number brushed metal.

   Those component files live under _ds/ and are read-only, so the correction
   is applied here by matching their inline declaration — the same escape the
   eyebrow shim uses. When the repo components are fixed, delete this block.

   The one number keeps its gold, but as the brushed vertical face, which is
   the single sanctioned use of gradient-clipped text (§10). ── */
[style*="color: var(--gold)"],
[style*="color:var(--gold)"] {
  background: var(--joint-face) !important;
  -webkit-background-clip: text !important;
  background-clip: text !important;
  color: transparent !important;
  text-shadow: none !important;
}
[style*="color: var(--guide-soft)"],
[style*="color:var(--guide-soft)"],
[style*="color: var(--guide-on-light)"],
[style*="color:var(--guide-on-light)"],
[style*="color: var(--guide)"],
[style*="color:var(--guide)"] {
  color:#FFFFFF !important;
}

/* ── TINY-TEXT SHIM ────────────────────────────────────────────────────────
   The bound design system's Hub components carry inline font-size: 9px and
   10px on real labels ("Drafts to approve", "Leads gone quiet", "live").
   --fs-meta's floor is 12px, and the design system's own guide says not to set
   meaningful text at 10–11px. Those files live under _ds/ and are read-only,
   so the correction matches their inline declaration — the same escape the
   eyebrow and gold-as-text shims use. Delete when the components are fixed. ── */
[style*="font-size: 9px"],
[style*="font-size:9px"],
[style*="font-size: 10px"],
[style*="font-size:10px"],
[style*="font-size: 11px"],
[style*="font-size:11px"],
[style*="font-size: 11.5px"],
[style*="font-size:11.5px"],
[style*="font-size: 10.5px"],
[style*="font-size:10.5px"] {
  font-size: var(--fs-meta) !important;
}

/* ── ALPHA-TEXT SHIM ──────────────────────────────────────────────────────
   Alpha white takes the hue of whatever is behind it. Over the drift field's
   warm radial it composites to CREAM — which is why every probe of the
   declared colour returned a neutral and the screen still looked warm.

   Our own layers now use opaque neutrals (--fg1..4). The bound design system's
   components hardcode rgba(255,255,255,.x) inline and are read-only, so they
   are pinned here to the opaque equivalents over #141414. ── */
[style*="color:#FFFFFF"], [style*="color:#FFFFFF"] { color: #FFFFFF !important; }
[style*="color:#FFFFFF"],  [style*="color:#FFFFFF"]  { color: #FFFFFF !important; }
[style*="color:#FFFFFF"], [style*="color:#FFFFFF"] { color: #FFFFFF !important; }
[style*="color:#FFFFFF"], [style*="color:#FFFFFF"] { color: #FFFFFF !important; }
[style*="color:#FFFFFF"],  [style*="color:#FFFFFF"]  { color: #FFFFFF !important; }
[style*="color:#FFFFFF"],  [style*="color:#FFFFFF"]  { color: #FFFFFF !important; }
[style*="color:#FFFFFF"],  [style*="color:#FFFFFF"]  { color: #FFFFFF !important; }
[style*="color:#FFFFFF"], [style*="color:#FFFFFF"] { color: #FFFFFF !important; }

/* ══ RETIRED SURFACE TOKENS, PINNED ════════════════════════════════════════
   Ruling 8 retires ink navy as the ground, but the bound design system's
   components do not read our classes - they write inline
   style="background: var(--navy-panel)" and friends, so no selector of ours
   can reach them and !important on a guessed class reaches nothing. The only
   lever is the TOKEN.

   --ink-navy was pinned in an earlier pass and the surface still rendered
   navy, because pinning ONE of a component's inline tokens fixes nothing:
   AuroraSurface paints --gradient-navy, HubCard and StackCard paint
   --navy-panel, six more paint --navy-deep and --navy-soft. Measured on the
   Workspace before this block: #16304A x6 and #0A1626 x6 on screen, reading
   as purple-blue cards on charcoal. Pin the whole family or pin none.     */
:root {
  --ink-navy: #141414; /* @kind color */
  --navy-panel: #1C1C1A; /* @kind color */
  --navy-soft: #1C1C1A; /* @kind color */
  --navy-deep: #0E0E0E; /* @kind color */
  --navy-mid: #1C1C1A; /* @kind color */
  --gradient-navy: var(--ground-sweep); /* @kind color */
  --navy-wash: rgba(28,28,26,.55); /* @kind color */

  /* --fg4 was #FFFFFF: too dark to read at any size, and it was carrying
     12px labels. Chrome only, and now light enough to be chrome. */
  --fg4: #FFFFFF; /* @kind color */
}

/* ══ NO SERIF, NO BLACK TEXT ═══════════════════════════════════════════════
   Seven nodes measured at 16px Times in rgb(0,0,0) on the Workspace - black
   type on a dark ground, invisible, with no console error. Design-system
   components that declare no font-family inherit from the UA once they mount
   outside our styled subtree, and a component that declares no colour
   inherits the UA's black the same way. Guard both.                      */
x-dc, x-dc *, [class^="bnf-"], [class*=" bnf-"], [class^="bf-"], [class*=" bf-"],
article, aside, section, figure, figcaption, blockquote, dl, dd, dt,
table, th, td, li, output, fieldset, legend {
  font-family: var(--font-sans);
}
[class^="bnf-"], [class*=" bnf-"], article, aside, section, figure,
figcaption, blockquote, table, th, td, li, output {
  color: inherit;
}

/* ── ruling 15 · THE TICK SWEEPS ───────────────────────────────────────────
   The .bf-eyebrow .tick rule below matches nothing on any page that uses the
   design system's own Eyebrow component - it renders <p style="--fs-eyebrow…">
   with an inline <span> tick, no classes at all. So the 11s sweep was
   declared, correct, and unreachable: gold with the light off, which is the
   exact failure ruling 15 names. Addressed through the inline style instead.
   Both selectors are kept; the class one still serves hand-built eyebrows. */
p[style*="--fs-eyebrow"] > span:first-child {
  background: var(--gold-metal-sweep) !important;
  background-size: 250% 100% !important;
  animation: bfGoldSweep 11s linear infinite !important;
}
@media (prefers-reduced-motion: reduce) {
  p[style*="--fs-eyebrow"] > span:first-child { animation: none !important; }
}

/* ── ruling 20 · ONE SECTION HEAD ──────────────────────────────────────────
   RULED 2026-08-19. Every eyebrow/heading/line block on a web surface is this
   one class. It exists because the Workspace had FIVE hand-built versions of
   the same block - the page header on --sp-4 gaps and the lead rung, the four
   sections on a bare 12px gap and the body rung, one of them in mint and four
   in teal - and no amount of tuning individual numbers could make two
   hand-built patterns agree. Size, leading, tracking, colour and both gaps
   resolve here and nowhere else.

   Differentiation is deliberate and structural, not typographic:
     .bf-sechead          a section. Numbered eyebrow, heading, one line.
     .bf-sechead.is-page  the page's own head. Carries the wordmark and the
                          ONE lead-rung line on the surface. Nothing else
                          differs - same eyebrow, same colour, same gaps.
   Ruling 19's uneven rhythm holds: FAR above the heading, TIGHT below it. */
/* ONE resolved column. With justify-items:start the implicit track sized to
   max-content, the child's height resolved against THAT width (one line), and
   then max-width:100% clamped the paint width - so a heading that wraps painted
   its second line outside a box that had already been measured, straight
   through the paragraph below. Nothing errored and the page head hid it,
   because its h1 happens to fit at some widths. */
.bf-sechead { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0; }
.bf-sechead > * { margin: 0; max-width: 100%; justify-self: start; }
.bf-sechead > h1, .bf-sechead > h2, .bf-sechead > p { width: 100%; }
.bf-sechead p[style*="--fs-eyebrow"] { margin-bottom: var(--sp-5) !important; }
.bf-sechead > h1,
.bf-sechead > h2 {
  font-size: var(--fs-h2);
  line-height: var(--lh-heading);
  letter-spacing: -0.02em;
  color: var(--fg1);
  margin-bottom: var(--sp-2);
}
.bf-sechead > p:not([style*="--fs-eyebrow"]):not(.bf-eyebrow):not(.lbl), .bf-sechead > x-import + p {
  font-size: var(--fs-body);
  letter-spacing: var(--ls-body);
  font-weight: var(--fw-body);
  line-height: var(--lh-body);
  color: var(--fg2);
  max-width: 62ch;
}
.bf-sechead.is-page > span { margin-bottom: var(--sp-5); }
.bf-sechead.is-page > p:not([style*="--fs-eyebrow"]):not(.bf-eyebrow):not(.lbl) { font-size: var(--fs-lead); }

/* ── ruling 21 · A BOUNDARY IS A RATIO, NOT A NUMBER ───────────────────────
   RULED 2026-08-19. The gap BETWEEN sections must be at least twice the gap
   INSIDE one. The Workspace had 32px inside and 29px between - inverted - so
   every section head sat nearer the previous section's content than its own,
   and no amount of absolute space fixed it because the relationship was
   backwards. Sections separate on --sp-8, their contents bind on --sp-5, and
   the head's hairline marks the line. ── */

.bf-sechead:not(.is-page) { padding-top: var(--sp-1); }

/* ── ruling 22 · THE STEPPED HEAD ──────────────────────────────────────────
   RULED 2026-08-19. Supersedes the gaps in ruling 20. Four steps, each a rung
   apart, so the eye can tell what belongs to what without reading:
     section  -> section    --sp-8   the break
     eyebrow  -> heading    --sp-4
     heading  -> line       --sp-3   tighter: one thought
     head     -> content    --sp-6   clear of the head, inside the section
   The page read as a mess because only two of these existed (18px and 8px)
   and the between-section gap was barely larger than either. Flow is a
   sequence of DIFFERENT gaps; equal gaps read as a list. ── */
.bf-sechead p[style*="--fs-eyebrow"] { margin-bottom: var(--sp-4) !important; }
.bf-sechead > h1, .bf-sechead > h2 { margin-bottom: var(--sp-3) !important; }
.bf-sechead.is-page > span { margin-bottom: var(--sp-4); }
.bf-sechead:not(.is-page) { padding-bottom: var(--sp-4); width: 100%; }

/* ruling 20, amended 2026-08-21 · the head reaches page-authored eyebrows.
   Seven specimen pages hand-built the same block with .bf-eyebrow or a local
   .lbl, so the stepped gaps governed nothing on them - the selector only knew
   the DS component's inline-style eyebrow. Same rungs, three spellings. */
.bf-sechead > .bf-eyebrow,
.bf-sechead > .lbl { margin-bottom: var(--sp-4) !important; letter-spacing: .06em; }
.bf-sechead > .lbl::before { margin-bottom: var(--sp-2); }
/* the page head is structurally different in ONE way: it carries the lead rung
   and the one h1 on the surface. Every other value resolves above. */
.bf-sechead.is-page > h1 {
  font-size: var(--fs-h1);
  line-height: 1.12;
  /* ruling 25: caps, so POSITIVE. This selector is (0,2,1) and was silently
     beating ruling 25's (0,1,1) — the h1 rendered caps at -.03em. */
  letter-spacing: .055em;
}

/* ── ruling 25 · THE TOP TWO RUNGS ARE CAPS AT THE WORDMARK'S WEIGHT ────────
   RULED 2026-08-21. The hero/display rung and the page title (h1) are set
   UPPERCASE at Poppins 200 - the same thickness as the word "branded" in the
   mark - so the loudest type on a surface is the mark's own weight rather than
   a different voice at a bigger size. H2 and below stay mixed case: tracked
   caps at a section-head size read as a label, which the eyebrow already is.

   Tracking INVERTS with case. The mixed-case rungs tighten as they climb
   (-.040em at display); caps have no descender interlock and need air, so
   these two rungs carry POSITIVE tracking. A negative value on caps welds the
   letterforms together - which is why the retired tracked-caps heading looked
   condensed rather than light. */
h1,
.bf-sechead > h1,
.bf-sechead.is-page > h1,
.bf-display {
  text-transform: uppercase;
  font-weight: 200;
  letter-spacing: .055em;
}
/* a page must not undo it with an inline weight - same shim as ruling 12 */
h1[style], .bf-display[style] { font-weight: 200 !important; text-transform: uppercase !important; }

/* ── ruling 26 · THE PAGE HEAD BREATHES TWO RUNGS WIDER ─────────────────────
   RULED 2026-08-21. A section head and the PAGE head were running the same
   gaps (--sp-4 / --sp-3), so the surface's opening block - eyebrow, the placed
   wordmark, the hero, the lead - read as tight as a section divider. The mark
   is artwork, not a line of type: it needs air on both sides or it reads as a
   graphic wedged between two strings.

   The rhythm stays deliberately uneven (ruling 19); every gap just moves up:
     eyebrow -> mark     --sp-5
     mark    -> hero     --sp-7   the widest gap on the surface
     hero    -> lead     --sp-5
   A SECTION head is unchanged - that contrast is what makes the page head
   read as the page's own opening. */
.bf-sechead.is-page > .bf-eyebrow,
.bf-sechead.is-page > .lbl,
.bf-sechead.is-page p[style*="--fs-eyebrow"] { margin-bottom: var(--sp-5) !important; }
.bf-sechead.is-page > span { margin-bottom: var(--sp-7) !important; }
.bf-sechead.is-page > h1 { margin-bottom: var(--sp-5) !important; }

/* ruling 27 · READING TEXT IS EXTRALIGHT */
body, p, li, dd, dt, blockquote, figcaption {
  font-weight: var(--fw-body);
  letter-spacing: var(--ls-body);
}
p[style*="--fs-eyebrow"], .bf-eyebrow, .bf-btn, [style*="--font-mono"] {
  letter-spacing: inherit;
}

/* ── ruling 28 · A HOVER TRADES FACES WITH ITS PARTNER ──────────────────────
   The teal primary hovers to gold; the gold money button hovers to teal. The
   pair swap places, so hovering one previews the other. The face keeps its own
   shading model - lit top edge, darker lower lip, hard 45 degree cast, soft
   settle - only the hue moves, and the cast shadow moves with it or the button
   appears to change material rather than colour. */
.bf-btn.primary:hover {
  color: #2A1E06;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.95),
    inset 0 -2px 3px rgba(80,58,14,.45),
    12px 13px 0 rgba(154,116,44,.55),
    20px 21px 34px rgba(0,0,0,.45);
}
.bf-btn.money:hover {
  color: #EAFFFB;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.58),
    inset 0 -2px 3px rgba(5,34,41,.7),
    12px 13px 0 rgba(20,125,129,.6),
    20px 21px 34px rgba(0,0,0,.4);
}

/* ── ruling 29 · EACH BUTTON IS EDGED IN ITS PARTNER'S COLOUR ───────────────
   The teal primary carries a gold edge; the gold money face carries a teal
   edge - the hover swap (ruling 28) held still, so the border states what the
   button becomes. The edge is a BORDER, not type and not a fill, so gold on it
   is structure.

   NOT border-image. A gradient border-image ignores border-radius and paints a
   RECTANGLE around a pill, which is what the first version shipped. The edge is
   two background layers instead - the face clipped to padding-box, the edge
   gradient clipped to border-box - because a background follows the radius. */
.bf-btn.primary {
  border: 2px solid transparent;
  border-image: none;
  background-image:
    var(--teal-metal-sweep),
    var(--gold-metal-v);
  background-origin: padding-box, border-box;
  background-clip: padding-box, border-box;
  background-size: 200% 100%, 100% 100%;
  background-repeat: no-repeat, no-repeat;
}
.bf-btn.money {
  border: 2px solid transparent;
  border-image: none;
  background-image:
    var(--gold-metal-sweep),
    linear-gradient(180deg,#80F2E8,#21B9B2 55%,#0B5F60);
  background-origin: padding-box, border-box;
  background-clip: padding-box, border-box;
  background-size: 200% 100%, 100% 100%;
  background-repeat: no-repeat, no-repeat;
}
/* the faces trade on hover, and the edges trade with them */
.bf-btn.primary:hover {
  background-image:
    var(--gold-metal-sweep),
    linear-gradient(180deg,#80F2E8,#21B9B2 55%,#0B5F60);
}
.bf-btn.money:hover {
  background-image:
    var(--teal-metal-sweep),
    var(--gold-metal-v);
}

/* ── ruling 30 · THE TEAL FACE SHIMMERS TOO ─────────────────────────────────
   The gold button swept and the teal one sat still, so the pair read as one
   live button and one dead one. Both move now. 9s on the teal against 7s on the
   gold, deliberately not a multiple, so they never fall into lockstep and the
   surface never pulses as a unit. */
.bf-btn.primary { animation: bfGoldSweep 9s linear infinite; }
.bf-btn.primary:hover { animation-duration: 7s; }
.bf-btn.money:hover { animation-duration: 9s; }

/* ── ruling 31 · THE SHIMMER IS A HIGHLIGHT PASSING OVER A FIXED FACE ───────
   The face was the animation. With background-size 200% and the position
   travelling 0 to 200%, the visible half of the ramp swept the whole gradient,
   so the button's OWN colour cycled continuously - it looked like it was
   changing state with no pointer anywhere near it, and the hover then had
   nothing left to say.

   Now: layer 1 is a narrow moving sheen, layer 2 is the face and never moves,
   layer 3 is the partner-coloured edge in the border box. Only the sheen's
   position is animated; the keyframes restate the other two so the shorthand
   cannot drag them along. The face is a constant, which is what makes a hover
   readable. */
@keyframes bfSheen {
  from { background-position: -70% 50%, 0% 50%, 0% 0%; }
  to   { background-position: 170% 50%, 0% 50%, 0% 0%; }
}
.bf-btn.primary, .bf-btn.money {
  border: 2px solid transparent;
  border-image: none;
  background-origin: padding-box, padding-box, border-box;
  background-clip: padding-box, padding-box, border-box;
  background-size: 55% 100%, 100% 100%, 100% 100%;
  background-repeat: no-repeat, no-repeat, no-repeat;
  background-position: -70% 50%, 0% 50%, 0% 0%;
  animation: bfSheen 9s linear infinite;
}
.bf-btn.primary {
  background-image:
    linear-gradient(100deg, rgba(255,255,255,0) 0%, rgba(224,255,251,.30) 50%, rgba(255,255,255,0) 100%),
    var(--teal-metal-sweep),
    var(--gold-metal-v);
}
.bf-btn.money {
  background-image:
    linear-gradient(100deg, rgba(255,255,255,0) 0%, rgba(255,250,232,.42) 50%, rgba(255,255,255,0) 100%),
    var(--gold-metal-sweep),
    linear-gradient(180deg,#80F2E8,#21B9B2 55%,#0B5F60);
  animation-duration: 7s;
}
/* hover trades the faces - and the edges with them. Only the image list
   changes; the sheen keeps running underneath. */
.bf-btn.primary:hover {
  color: #2A1E06;
  background-image:
    linear-gradient(100deg, rgba(255,255,255,0) 0%, rgba(255,250,232,.42) 50%, rgba(255,255,255,0) 100%),
    var(--gold-metal-sweep),
    linear-gradient(180deg,#80F2E8,#21B9B2 55%,#0B5F60);
}
.bf-btn.money:hover {
  color: #EAFFFB;
  background-image:
    linear-gradient(100deg, rgba(255,255,255,0) 0%, rgba(224,255,251,.30) 50%, rgba(255,255,255,0) 100%),
    var(--teal-metal-sweep),
    var(--gold-metal-v);
}
/* the face is fixed now, so the base hover must not slide it either */
.bf-btn.primary:hover, .bf-btn.money:hover { background-position: -70% 50%, 0% 50%, 0% 0%; }

/* ── ruling 32 · THE SHIMMER BORDER ─────────────────────────────────────────
   A light travels the pill's edge, once around every few seconds - the
   travelling-border effect that lived only on Buttons.dc.html's .bf-star,
   lifted into the shared button so every face gets it.

   Built on a registered angle rather than transform: rotate(). Rotating the
   element would rotate a PILL, which is not rotationally symmetric, so the ring
   would wobble; animating the conic gradient's start angle keeps the shape
   fixed and moves only the light. The ring is masked to the 2px border box, so
   it is an edge and never a fill.

   It sits UNDER the label: the pseudo-element is z-index 0 against the label's
   own stacking, and the button already carries position: relative. */
/* REBUILT 2026-08-23 · back to the React Bits construction it came from.
   This is Star Border (components/effects/react-bits.jsx is the ported catalog;
   the class here has always been .bf-star). Upstream it is TWO radial-gradient
   lights that travel the edge in opposite directions — which is exactly what
   the ruling says the ring is: "two lights, opposite each other".

   The interim build ran an animated registered angle (@property --bf-ring) on a
   conic gradient. It looked right but the compiler cannot tell an animated
   custom property from a malformed token, so the whole system failed its check.
   Two background positions carry the same motion with no custom property.

   Never rebuild this by rotating the pseudo-element: that rotates the pill's
   mask and the ring wobbles. The lights move, the shape never does. */
@keyframes bfRing {
  from { background-position: -62% 100%, 100% 0; }
  to   { background-position: 100% 100%, -62% 0; }
}

.bf-btn.primary, .bf-btn.money { position: relative; isolation: isolate; }
.bf-btn.primary::before, .bf-btn.money::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 2px;
  pointer-events: none;
  z-index: -1;
  /* two lights, opposite each other, so the edge never goes fully dark */
  background-image:
    radial-gradient(circle at 50% 50%, rgba(255,255,255,.92) 0%, rgba(255,255,255,.92) 12%, rgba(255,255,255,0) 62%),
    radial-gradient(circle at 50% 50%, rgba(255,255,255,.55) 0%, rgba(255,255,255,.55) 12%, rgba(255,255,255,0) 62%);
  /* half-height layers anchored to opposite EDGES — one runs the bottom rail
     left to right, the other runs the top rail right to left. Full-height
     layers ride the same track and merge into one double-bright light twice a
     cycle; the ruling is two lights, opposite each other, always. */
  background-size: 62% 50%, 62% 50%;
  background-repeat: no-repeat, no-repeat;
  background-position: -62% 100%, 100% 0;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  animation: bfRing 3.6s linear infinite;
}
.bf-btn.money::before { animation-duration: 4.4s; }
.bf-btn.primary:hover::before, .bf-btn.money:hover::before { animation-duration: 2.4s; }
@media (prefers-reduced-motion: reduce) {
  .bf-btn.primary::before, .bf-btn.money::before { animation: none; }
}

/* ── ruling 33 · A LABEL MUST SURVIVE THE WHOLE FACE ────────────────────────
   The teal face ran #0B5F60 to #80F2E8 and carried a near-white label: 7.2:1
   on the deep end, 1.28:1 on the bright end. The label vanished wherever the
   sheen passed, and no single colour can hold across a range that wide.

   The face is narrowed to the light half of the teal ramp and the label is
   dark ink - which is also the design system's own primary treatment. Ink
   measures 6.7:1 on the darkest stop and 12.13:1 on the lightest, so the label
   is readable at every frame of the sweep rather than on average.

   The rule: pick the LABEL first, then bound the face to the range that label
   survives. A face free to roam the full ramp has no readable label. */
.bf-btn.primary { color: #052229; }
.bf-btn.money:hover { color: #052229; }

/* ── ruling 34 · THE GROUND CARRIES NO GLOWS ────────────────────────────────
   RULED 2026-08-21 by Jen: "Navy and balls are out."

   Navy was already retired as the ground. The DRIFT FIELD is now retired with
   it - the five soft radials, the corner glows, the warm ball, the 15-26s
   animations, all of it. Removed from six pages and from this layer.

   What keeps the ground from being flat is the GRADIENT and nothing else:
     background: linear-gradient(168deg, #191917 0%, #0E0E0E 74%);
   Charcoal #141414 for a surface, #1C1C1A raised, #0E0E0E the deep well.

   So the old rule "a flat dark surface is a bug" no longer holds - a page with
   the gradient and no radials is CORRECT. Do not reintroduce a glow, a blob, a
   breathing corner or an ambient radial on any surface: web, slide, video,
   document, email or social. The movement on a surface is the metal - the
   button sheens, the travelling rules, the one number - not the background. */

/* ── ruling 35 · THE GHOST FACE IS METAL TOO ────────────────────────────────
   RULED 2026-08-21. Rulings 28-32 gave the sheen, the travelling ring light and
   the partner-coloured edge to .primary and .money only, so .ghost stayed a
   flat translucent slab - a plain button on a surface where every other button
   is metal. Ruling 3 says a flat fill is a bug; this was one.

   Ghost is DARK metal: a graphite face on the same shading model, edged in
   teal, with the same sheen and ring. It is the secondary action next to a
   teal primary, so it must not introduce a third hue. */
.bf-btn.ghost {
  position: relative;
  isolation: isolate;
  color: #FFFFFF;
  border: 2px solid transparent;
  border-image: none;
  background-image:
    linear-gradient(100deg, rgba(255,255,255,0) 0%, rgba(224,255,251,.22) 50%, rgba(255,255,255,0) 100%),
    linear-gradient(112deg,#2A2A28 0%,#232321 22%,#2E2E2B 46%,#383834 60%,#2A2A28 78%,#1F1F1D 100%),
    linear-gradient(180deg,#80F2E8,#21B9B2 55%,#0B5F60);
  background-origin: padding-box, padding-box, border-box;
  background-clip: padding-box, padding-box, border-box;
  background-size: 55% 100%, 100% 100%, 100% 100%;
  background-repeat: no-repeat, no-repeat, no-repeat;
  background-position: -70% 50%, 0% 50%, 0% 0%;
  animation: bfSheen 11s linear infinite;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.30),
    inset 0 -2px 3px rgba(0,0,0,.6),
    8px 9px 0 rgba(20,125,129,.30),
    16px 17px 28px rgba(0,0,0,.42);
}
.bf-btn.ghost:hover {
  color: #052229;
  background-image:
    linear-gradient(100deg, rgba(255,255,255,0) 0%, rgba(224,255,251,.30) 50%, rgba(255,255,255,0) 100%),
    var(--teal-metal-sweep),
    linear-gradient(180deg,#F8EDCD,#C89A46 55%,#8E6B28);
  background-position: -70% 50%, 0% 50%, 0% 0%;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.58),
    inset 0 -2px 3px rgba(5,34,41,.7),
    12px 13px 0 rgba(20,125,129,.6),
    20px 21px 34px rgba(0,0,0,.4);
}
.bf-btn.ghost::before {
  content: '';
  position: absolute; inset: 0; border-radius: inherit; padding: 2px;
  pointer-events: none; z-index: -1;
  background-image:
    radial-gradient(circle at 50% 50%, rgba(255,255,255,.72) 0%, rgba(255,255,255,.72) 12%, rgba(255,255,255,0) 62%),
    radial-gradient(circle at 50% 50%, rgba(255,255,255,.42) 0%, rgba(255,255,255,.42) 12%, rgba(255,255,255,0) 62%);
  /* half-height layers anchored to opposite EDGES — one runs the bottom rail
     left to right, the other runs the top rail right to left. Full-height
     layers ride the same track and merge into one double-bright light twice a
     cycle; the ruling is two lights, opposite each other, always. */
  background-size: 62% 50%, 62% 50%;
  background-repeat: no-repeat, no-repeat;
  background-position: -62% 100%, 100% 0;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  animation: bfRing 5.2s linear infinite;
}
.bf-btn.ghost:hover::before { animation-duration: 2.4s; }
@media (prefers-reduced-motion: reduce) { .bf-btn.ghost::before { animation: none; } }

/* ── ruling 36 · THREE BUTTONS, AND LESS SLAB ───────────────────────────────
   RULED 2026-08-21. The set is three and only three:
     .primary  teal metal, gold edge      the default action
     .money    gold metal, teal edge      the one money moment
     .ghost    black metal, teal edge     the secondary action
   Every other face is retired: bright, mint, outline, and the ten hand-rolled
   experiments on Buttons.dc.html.

   And the depth came down. The cast block ran 8px 9px at rest, 12px 13px on
   hover, with a 16px 17px 28px blur behind it — at that size the shadow reads
   as a second object stacked under the button rather than as light. Halved to
   4px 5px / 7px 8px with the blur pulled to 10px 11px 18px. The gesture is
   unchanged: it still lifts up-and-left into its own shadow and compresses back
   down on press. ── */
.bf-btn {
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.42),
    inset 0 -1px 0 rgba(5,34,41,.6),
    4px 5px 0 rgba(20,125,129,.34),
    10px 11px 18px rgba(0,0,0,.32) !important;
}
.bf-btn:hover {
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.52),
    inset 0 -1px 0 rgba(5,34,41,.62),
    7px 8px 0 rgba(20,125,129,.42),
    14px 15px 24px rgba(0,0,0,.34) !important;
}
.bf-btn:active {
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.34),
    inset 0 -1px 0 rgba(5,34,41,.68),
    2px 3px 0 rgba(20,125,129,.3),
    5px 6px 10px rgba(0,0,0,.28) !important;
}
.bf-btn.money {
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.6),
    inset 0 -1px 0 rgba(80,58,14,.42),
    4px 5px 0 rgba(154,116,44,.34),
    10px 11px 18px rgba(0,0,0,.32) !important;
}
.bf-btn.money:hover {
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.7),
    inset 0 -1px 0 rgba(80,58,14,.4),
    7px 8px 0 rgba(154,116,44,.42),
    14px 15px 24px rgba(0,0,0,.34) !important;
}
/* ghost is BLACK metal — a near-black face, not the graphite slab it was, and
   it goes teal on hover. That is the third approved button. */
.bf-btn.ghost {
  color: #FFFFFF;
  background-image:
    linear-gradient(100deg, rgba(255,255,255,0) 0%, rgba(224,255,251,.16) 50%, rgba(255,255,255,0) 100%),
    linear-gradient(112deg,#131312 0%,#0E0E0E 24%,#1A1A18 50%,#232321 66%,#141413 84%,#0C0C0B 100%),
    linear-gradient(180deg,#80F2E8,#21B9B2 55%,#0B5F60);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.24),
    inset 0 -1px 0 rgba(0,0,0,.7),
    4px 5px 0 rgba(20,125,129,.26),
    10px 11px 18px rgba(0,0,0,.38) !important;
}
.bf-btn.ghost:hover {
  color: #052229;
  background-image:
    linear-gradient(100deg, rgba(255,255,255,0) 0%, rgba(224,255,251,.28) 50%, rgba(255,255,255,0) 100%),
    var(--teal-metal-sweep),
    linear-gradient(180deg,#F8EDCD,#C89A46 55%,#8E6B28);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.52),
    inset 0 -1px 0 rgba(5,34,41,.62),
    7px 8px 0 rgba(20,125,129,.42),
    14px 15px 24px rgba(0,0,0,.34) !important;
}
/* retired faces — kept only so old markup does not render unstyled */
.bf-btn.bright, .bf-btn.mint, .bf-btn.outline {
  color: #052229;
  background-image:
    linear-gradient(100deg, rgba(255,255,255,0) 0%, rgba(224,255,251,.22) 50%, rgba(255,255,255,0) 100%),
    var(--teal-metal-sweep),
    var(--gold-metal-v);
}

/* ── ruling 37 · THE FACE IS GLOSSY, NOT BRUSHED ────────────────────────────
   RULED 2026-08-21 by Jen, and it SUPERSEDES ruling 31's "dense facets" clause.
   Comparing the two side by side: the black ghost face read as GLOSS and the
   teal primary read as brushed metal, and she wants the gloss.

   The difference was never the type — both labels are 15px/500. It is the face:
     brushed = many close facets across the width (14 stops)
     glossy  = a smooth VERTICAL ramp, light above a hard mid-line, deeper
               below, with a bright specular edge on top
   Gloss is how a lit curved surface behaves: the top half catches the sky, the
   bottom half catches the ground, and the line between them is sharp. Facets
   are how a sanded flat surface behaves. Both are metal; only one is wet.

   The travelling sheen stays (ruling 31: a highlight over a FIXED face), the
   ring light stays (32), the partner edge stays (29), the faces still trade on
   hover (28), and the depth stays where ruling 36 put it. Hue still never
   moves — only lightness. ── */
.bf-btn.primary {
  color: #052229;
  background-image:
    linear-gradient(100deg, rgba(255,255,255,0) 0%, rgba(233,255,252,.34) 50%, rgba(255,255,255,0) 100%),
    linear-gradient(180deg, #7FEFE4 0%, #5CE3D6 26%, #35CDC2 52%, #21B9B2 74%, #12A19C 100%),
    linear-gradient(180deg,#F8EDCD,#C89A46 55%,#8E6B28);
}
.bf-btn.primary:hover {
  color: #2A1E06;
  background-image:
    linear-gradient(100deg, rgba(255,255,255,0) 0%, rgba(255,250,232,.40) 50%, rgba(255,255,255,0) 100%),
    linear-gradient(180deg, #F7E9C6 0%, #E9D3A2 26%, #D8B673 52%, #C89A46 74%, #B0863A 100%),
    linear-gradient(180deg,#80F2E8,#21B9B2 55%,#0B5F60);
}
.bf-btn.money {
  color: #2A1E06;
  background-image:
    linear-gradient(100deg, rgba(255,255,255,0) 0%, rgba(255,250,232,.40) 50%, rgba(255,255,255,0) 100%),
    linear-gradient(180deg, #F7E9C6 0%, #E9D3A2 26%, #D8B673 52%, #C89A46 74%, #B0863A 100%),
    linear-gradient(180deg,#80F2E8,#21B9B2 55%,#0B5F60);
}
.bf-btn.money:hover {
  color: #052229;
  background-image:
    linear-gradient(100deg, rgba(255,255,255,0) 0%, rgba(233,255,252,.34) 50%, rgba(255,255,255,0) 100%),
    linear-gradient(180deg, #7FEFE4 0%, #5CE3D6 26%, #35CDC2 52%, #21B9B2 74%, #12A19C 100%),
    linear-gradient(180deg,#F8EDCD,#C89A46 55%,#8E6B28);
}
/* the black face was already the glossy one — this is it, stated explicitly so
   the three are built the same way rather than one being an accident */
.bf-btn.ghost {
  color: #FFFFFF;
  background-image:
    linear-gradient(100deg, rgba(255,255,255,0) 0%, rgba(224,255,251,.20) 50%, rgba(255,255,255,0) 100%),
    linear-gradient(180deg, #2E2E2C 0%, #232321 26%, #191917 52%, #111110 74%, #0A0A0A 100%),
    linear-gradient(180deg,#80F2E8,#21B9B2 55%,#0B5F60);
}
.bf-btn.ghost:hover {
  color: #052229;
  background-image:
    linear-gradient(100deg, rgba(255,255,255,0) 0%, rgba(233,255,252,.34) 50%, rgba(255,255,255,0) 100%),
    linear-gradient(180deg, #7FEFE4 0%, #5CE3D6 26%, #35CDC2 52%, #21B9B2 74%, #12A19C 100%),
    linear-gradient(180deg,#F8EDCD,#C89A46 55%,#8E6B28);
}
/* the specular edge is what sells gloss — brighter on top than ruling 36 had it */
.bf-btn.primary, .bf-btn.money, .bf-btn.ghost {
  box-shadow:
    inset 0 2px 0 rgba(255,255,255,.55),
    inset 0 -1px 0 rgba(0,0,0,.5),
    4px 5px 0 rgba(20,125,129,.3),
    10px 11px 18px rgba(0,0,0,.34) !important;
}
.bf-btn.primary:hover, .bf-btn.money:hover, .bf-btn.ghost:hover {
  box-shadow:
    inset 0 2px 0 rgba(255,255,255,.68),
    inset 0 -1px 0 rgba(0,0,0,.5),
    7px 8px 0 rgba(20,125,129,.4),
    14px 15px 24px rgba(0,0,0,.36) !important;
}

/* ── §6.2 · THE UNFILLED (CONTRASTING) BUTTON ───────────────────────────────
   Written in §6.2 since the beginning and never built as a face, so the Hub
   carried its own — the condition §27 exists to catch. It is the quieter
   choice beside a filled action: a gold hairline over a gold wash, with a
   TEAL label. §6.2 on why teal and not a lighter gold: "a gold or cream label
   inside a gold hairline has nothing to push against — the whole control
   recedes and the eye skips it."

   It takes no new names. --track-wash and --track-border ARE §6.2's stated
   fill and border, and --teal-bright / --teal-light are its label and hover
   label. Because .bf-light remaps the teals to --teal-deep, the face reads
   correctly on paper with nothing added — which is what the Hub needs.

   It is flat ON PURPOSE, so it is excluded from the glossy guard above rather
   than inheriting it. That is the one face where a flat fill is not the bug
   Button.jsx warns about: it is unfilled, not a filled button drawn flat. ── */
.bf-btn.unfilled {
  color: var(--teal-bright);
  background: var(--track-wash);
  background-image: none;
  border: 1px solid var(--track-border);
  font-weight: 600;
  padding: 16px 32px;
  /* !important because the base .bf-btn sets the cast with !important (ruling
     36's depth block). The other three faces opt out of it the same way. §6.2
     gives this button no cast: it is the quiet one, and the approved Hub screen
     shows it flat beside the gold action. */
  box-shadow: none !important;
  animation: none;
}
.bf-btn.unfilled:hover {
  color: var(--teal-light);
  background: var(--track-wash);
  background-image: none;
  border-color: rgba(240,222,175,.9);
  box-shadow: none !important;
  animation: none;
}
.bf-btn.unfilled .bf-btn-dot {
  background: var(--teal-bright);
  opacity: 1;
  box-shadow: 0 0 9px rgba(53,224,203,.75);
}
.bf-btn.unfilled:hover .bf-btn-dot { background: var(--teal-light); }

/* ── §6.2b · THE QUIET FACE ─────────────────────────────────────────────────
   RULED 2026-09-18 by Jen, from the approved Hub screen: "Deny" is neither the
   action nor its alternative. It is the refusal, and it must not compete with
   either - so it carries no gold, no teal and no dot, only a neutral hairline.

   §6 named three faces and ruling 37 retired the rest. This is the fourth the
   product actually needs: a feed row offers Approve (money), Send back
   (unfilled) and Deny, and putting Deny on .ghost's black glossy face makes the
   refusal the loudest control on the card.

   Like .unfilled it is flat on purpose, so it takes box-shadow none !important
   to opt out of the base cast (ruling 36), and it is excluded from the glossy
   guard rather than fighting it. ── */
.bf-btn.quiet {
  color: var(--fg2);
  background: var(--bg-elevated);
  background-image: none;
  border: 1px solid var(--border);
  font-weight: 600;
  padding: 16px 32px;
  box-shadow: none !important;
  animation: none;
}
.bf-btn.quiet:hover {
  color: var(--fg1);
  background: var(--bg-elevated);
  background-image: none;
  border-color: var(--border-strong);
  box-shadow: none !important;
  animation: none;
}
/* the refusal carries no dot - §6.3's dot marks a face that acts */
.bf-btn.quiet .bf-btn-dot { display: none; }

/* ── ruling 38 · THE TAGLINE IS GOLD ────────────────────────────────────────
   RULED 2026-08-21. "Brand. Automate. Nurture." is set in brushed gold wherever
   it appears. It is the one line of type gold is allowed to paint, because it
   is the mark's own words — the wordmark file carries no tagline, so the line
   IS part of the identity rather than copy about it. Three beats, periods,
   never reordered. ── */
.bf-tagline {
  font-family: var(--font-sans);
  font-weight: 200;
  letter-spacing: .14em;
  text-transform: uppercase;
  /* background-IMAGE, never the shorthand: the shorthand resets background-clip
     to border-box and the clipped letters vanish into a solid gold bar. */
  background-image: var(--gold-tagline);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* ── ruling 37, amended · NO SEAM THROUGH THE LABEL ─────────────────────────
   The first gloss ramp put a hard stop at 46% into 50% — a visible flat line
   running straight through the letters. Gloss does NOT come from a seam; it
   comes from the specular edge along the top and a smooth fall to the bottom.
   The stops are now evenly spaced with no jump, so the face still reads wet and
   nothing crosses the type. ── */

/* ── ruling 39 · A PRODUCT NAME IS A STAMP IN CHROME, PLAIN IN PROSE ────────
   RULED 2026-08-21. The Leak Check and The Hub are set as the casing stamp
   (thin word, gold joint, heavier word) wherever they appear as a NAME — a
   header link, a footer link, a menu line, a list item, a heading, a label.
   In running prose they stay plain text: a sentence that happens to mention the
   leak check is a sentence, not a nameplate, and stamping it would put the mark
   inside a paragraph.

   This supersedes the earlier "one stamp per page" limit for product names —
   that limit was written for coined phrases like done+Right, which are devices
   rather than names. A name may repeat wherever it is being named. ── */

/* ── ruling 40 · A BUTTON IS STILL UNTIL YOU TOUCH IT ────────────────────────
   RULED 2026-08-21. The sheen and the ring light were running at REST, on every
   button, at three different rates — so a page with three buttons had six things
   moving on it and the buttons looked like they were sliding across the page.
   They were not: the box never moved. The travelling highlight was reading as
   motion of the object.

   At rest the face is a fixed gloss and nothing animates. The sheen and the ring
   run on HOVER only, which is also what makes them mean something: movement is
   the response, not the resting state. The gold money face keeps a slow sweep at
   rest, because it is the one money mark and being alive is its job. ── */
.bf-btn.primary, .bf-btn.ghost { animation: none !important; }
.bf-btn.primary::before, .bf-btn.ghost::before { animation: none !important; }
.bf-btn.primary:hover, .bf-btn.ghost:hover { animation: bfSheen 2.6s linear infinite !important; }
.bf-btn.primary:hover::before, .bf-btn.ghost:hover::before { animation: bfRing 2.4s linear infinite !important; }
/* the money face stays alive at rest — slower than it was, and it is one per surface */
.bf-btn.money { animation: bfSheen 11s linear infinite !important; }
.bf-btn.money::before { animation: bfRing 7s linear infinite !important; }
.bf-btn.money:hover { animation: bfSheen 2.6s linear infinite !important; }
.bf-btn.money:hover::before { animation: bfRing 2.4s linear infinite !important; }
@media (prefers-reduced-motion: reduce) {
  .bf-btn, .bf-btn::before, .bf-btn:hover, .bf-btn:hover::before { animation: none !important; }
}

/* ── ruling 41 · THE GUARD MUST NOT EAT THE EYEBROW ─────────────────────────
   RULED 2026-08-21. The serif guard matches [class^="bf-"] — an attribute
   selector at specificity (0,1,0), identical to .bf-eyebrow, and declared LATER
   in the same file. So it won, and the eyebrow rendered in Poppins on every page
   that loads the token layer while the section labels beside it were mono: the
   same nameplate role in two faces on one screen.

   The guard is still needed — design-system components that declare no family
   inherit the UA serif. So the face is simply re-stated after it, which is the
   only fix that survives the guard being edited again.

   This is the THIRD time this session a later rule quietly beat an earlier
   ruling: the :not() that swallowed the eyebrow in the section head, the inline
   colour that beat .bf-tagline, and now this. The pattern is always the same —
   equal specificity plus later position, no error, no visible failure. The
   assertion that catches it is a computed read of face, weight and tracking per
   branded class, not a source grep. ── */
.bf-eyebrow, p[style*="--fs-eyebrow"] {
  font-family: var(--font-mono) !important;
  font-weight: 400 !important;
  letter-spacing: .06em !important;
}
.bf-tagline {
  font-family: var(--font-sans) !important;
  font-weight: 200 !important;
}

/* ── ruling 42 · THE STEPS BETWEEN TYPE LEVELS GET WIDER ────────────────────
   RULED 2026-08-21. The section head's four gaps were --sp-4 / --sp-3 / --sp-6 /
   --sp-8, which at a real window put eyebrow→heading and heading→line within a
   few pixels of each other — so a heading and the line under it read as one
   block rather than two levels. Each step moves up a rung, and the two inside a
   head are pulled further apart so the sequence is unmistakable:
     eyebrow  -> heading   --sp-6   far. The nameplate is its own object.
     heading  -> line      --sp-4   tight. They are one thought.
     head     -> content   --sp-7
     section  -> section   --sp-8
   The page head runs wider still. ── */
.bf-sechead p[style*="--fs-eyebrow"],
.bf-sechead > .bf-eyebrow,
.bf-sechead > .lbl { margin-bottom: var(--sp-7) !important; }
.bf-sechead > h1, .bf-sechead > h2 { margin-bottom: var(--sp-5) !important; }
.bf-sechead:not(.is-page) { padding-bottom: var(--sp-7) !important; }
.bf-sechead.is-page > .bf-eyebrow,
.bf-sechead.is-page > .lbl,
.bf-sechead.is-page p[style*="--fs-eyebrow"] { margin-bottom: var(--sp-7) !important; }
.bf-sechead.is-page > span { margin-bottom: calc(var(--sp-8) * 1.35) !important; }
.bf-sechead.is-page > h1 { margin-bottom: var(--sp-7) !important; }

/* ── ruling 43 · A BARE CLASS MUST NOT BE THE RETIRED STATE ─────────────────
   RULED 2026-08-23. Rulings 37 and 40 were scoped `.bf-btn.primary` — 22 rules
   — while the base `.bf-btn` still carried the pre-37 horizontal brushed sweep
   and a near-white `#EAFFFB` label. Anything emitting a bare `.bf-btn` (the
   Button component's face map used `primary: ""`, and two specimen cards wrote
   the class by hand) therefore rendered the RETIRED face, with none of the 22
   rules matching and no error anywhere. The design system's own Components card
   was teaching it.

   Two consequences worth naming. The retired face is UNBOUNDED — it reaches
   `#80F2E8` at 62%, so the near-white label measured about 1.1:1 where the
   bright stop sat under the middle of the word. Ruling 37 changed the face and
   the ink together for exactly that reason, and taking half of it is worse than
   taking neither. And the defect is invisible to a value audit: every hex on the
   page was a legal brand hex.

   The rule this generalises to: **the bare class is the default, so the default
   must be the current state.** A ruling scoped only to a modifier leaves the
   unmodified case behind. Scoped here to :not() so bare and .primary are the
   same button, and they cannot drift apart again. ── */
.bf-btn:not(.money):not(.ghost):not(.unfilled):not(.quiet) {
  color: #052229;
  background-image:
    linear-gradient(100deg, rgba(255,255,255,0) 0%, rgba(233,255,252,.34) 50%, rgba(255,255,255,0) 100%),
    linear-gradient(180deg, #7FEFE4 0%, #5CE3D6 26%, #35CDC2 52%, #21B9B2 74%, #12A19C 100%),
    linear-gradient(180deg,#F8EDCD,#C89A46 55%,#8E6B28);
  background-origin: padding-box, padding-box, border-box;
  background-clip: padding-box, padding-box, border-box;
  border: 2px solid transparent;
  box-shadow:
    inset 0 2px 0 rgba(255,255,255,.55),
    inset 0 -1px 0 rgba(0,0,0,.5),
    4px 5px 0 rgba(20,125,129,.3),
    10px 11px 18px rgba(0,0,0,.34);
  animation: none;
}
.bf-btn:not(.money):not(.ghost):not(.unfilled):not(.quiet):hover {
  color: #2A1E06;
  background-image:
    linear-gradient(100deg, rgba(255,255,255,0) 0%, rgba(255,250,232,.40) 50%, rgba(255,255,255,0) 100%),
    linear-gradient(180deg, #F7E9C6 0%, #E9D3A2 26%, #D8B673 52%, #C89A46 74%, #B0863A 100%),
    linear-gradient(180deg,#80F2E8,#21B9B2 55%,#0B5F60);
  box-shadow:
    inset 0 2px 0 rgba(255,255,255,.68),
    inset 0 -1px 0 rgba(0,0,0,.5),
    7px 8px 0 rgba(20,125,129,.4),
    14px 15px 24px rgba(0,0,0,.36);
  animation: bfSheen 2.6s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .bf-btn:not(.money):not(.ghost):not(.unfilled):not(.quiet):hover { animation: none; }
}

/* ── ruling 44 · THE EYEBROW IS SET IN POPPINS ──────────────────────────────
   RULED 2026-08-23. Jen, twice: "I'm not even certain why we use JetBrains Mono
   at 400 for the eyebrow. I'd much rather use Poppins." Then, once the eyebrow
   became the section header on every specimen page: "why is it not the correct
   font?"

   It was mono because it was built as a LABEL — a counter-and-hex-code device
   sitting beside content. Nothing decided it should be mono; it inherited that
   from the rung it was filed under. Once it started doing a header's job the
   mismatch was obvious: it was the only mono in the reading column and the only
   thing on the page above weight 200.

   Poppins 200, UPPERCASE, POSITIVE tracking — the same treatment as the hero
   and the page title (ruling 25), because caps have no descender interlock and
   need air. So the three type levels on a surface are now one family separated
   by size and case alone, which is the system's own stated principle.

   Mono keeps what it is actually for: counters, hex codes, file paths, table
   labels, tabular figures. Not a nameplate. ── */
.bf-eyebrow,
p[style*="--fs-eyebrow"] {
  font-family: var(--font-sans) !important;
  font-weight: 200 !important;
  text-transform: uppercase !important;
  letter-spacing: .16em !important;
  /* 16px was the mono size, and it was pinned there because mono runs large for
     its point size. Poppins at 200 in capitals reads smaller than mono at the
     same number, so holding 16 made the change look like a shrink. 19px. */
  font-size: 19px !important;
}

/* ── ruling 45 · A HEADING BELONGS TO WHAT FOLLOWS IT ───────────────────────
   RULED 2026-08-23. Jen: "it doesn't show clearly to me that that is the heading
   for what's about to follow."

   The section container ran ONE uniform gap, so the eyebrow sat exactly as far
   from the content it introduces as from the section above it. Nothing told the
   eye which side it belonged to — and that is the entire job of a heading.
   Proximity is the only signal doing that work here, because the system has no
   colour step and no weight step left to spend.

   The eyebrow is pulled back to --sp-4 from its own content while the gap
   between sections stays --sp-8. Roughly four times the distance above as
   below, so which side it belongs to is not a judgement call. ── */
.box > .bf-eyebrow,
.box > p[style*="--fs-eyebrow"] {
  margin-bottom: calc(var(--sp-4) - var(--sp-8)) !important;
}

/* ── ruling 46 · A TABLE MUST ANNOUNCE ITSELF ───────────────────────────────
   RULED 2026-08-23. Jen: "there's another header called Element Measured
   Verdict, which looks like it's a table, and I can't even tell that that's a
   table right out of the gate."

   The header row was styled identically to every data row — same size, same
   hairline under it — so a table opened with what looked like one more line of
   content. Striped rows are banned here (they need a second surface value and
   read as two colours), which means the header row and the outer edge are the
   ONLY structure a table gets. Both have to be unmistakable.

   The header row takes a 3px gold rule under it and a wider gap below; the
   whole table takes a 2px rule across its top. Gold on a rule is structure, not
   paint — the same reason a side note's edge may be gold. ── */
/* The rows sit in an unclassed wrapper, so :first-child on the ROW is what
   finds the header — not a selector hung off .box, which reaches nothing.
   And :has(.k) is what makes it a HEADER rather than merely the first row: a
   table whose first row is data was getting the gold rule too, so its first
   line of data was dressed as a column header with a rule floating mid-table.
   Test what a thing IS, never where it sits. */
.row:first-child:has(.k) {
  /* A BACKGROUND LAYER, never border-image — the standing rule, and here is why
     it exists: under one renderer the border-image filled the entire 46px row
     instead of its 3px bottom edge, putting the white header labels on light
     gold at 1.17:1. A rule that can become a fill is not a rule. */
  border-bottom: 0 !important;
  background-image: linear-gradient(90deg,#F8EDCD,#C89A46 55%,#8E6B28) !important;
  background-size: 100% 3px !important;
  background-position: 0 100% !important;
  background-repeat: no-repeat !important;
  padding-bottom: var(--sp-3) !important;
  margin-bottom: var(--sp-2) !important;
}
.row:first-child:has(.k) .k {
  color: #FFFFFF !important;
  font-weight: 500 !important;
}
/* NO extra top edge. A rule under the header row is the announcement; a second
   rule above it makes the table read as a stack of separated strips instead of
   one object. Jen: "the lines make it too separable." */

/* ── ruling 47 · A SECTION GETS A HEADING, NOT A LABEL ──────────────────────
   RULED 2026-08-23. Jen: "it just looks like a shit ton of text on my page."

   She was right, and the cause was mine. When the numbered mono labels came out
   I replaced them with the EYEBROW — but an eyebrow is a nameplate, and at 19px
   it is the same size as the body underneath it. So the page had no size jump
   anywhere: eyebrow 19, body 19, everything weight 200, no colour step and no
   weight step by design. Every hierarchy signal the system owns had been spent
   somewhere else, and nothing was left to say "a new section starts here."

   A section opens with a real heading at --fs-h2 — roughly 30px against 19px
   body, so the jump is unmistakable — and it sits TIGHT to what it introduces:
   --sp-3 below, --sp-8 above. Six times the distance above as below.

   This is the size step the system had been getting away without because every
   page happened to carry a page head at the top. It does not survive a page of
   sections. ── */
h2.sec {
  font-family: var(--font-sans);
  /* h1's rung, not h2's — a section header carrying the stamp needs the joint
     big enough to read as the mark rather than as punctuation. */
  font-size: var(--fs-h1);
  font-weight: 200;
  text-transform: none;
  letter-spacing: -0.02em;
  line-height: var(--lh-heading);
  color: #FFFFFF;
  margin: 0 0 calc(var(--sp-3) - var(--sp-8)) !important;
}

/* ── ruling 48 · HEADERS CARRY THE CADENCE, NOT THE JOINT ───────────────────
   RULED 2026-08-23, after seeing the full stamp on headers and choosing against
   it. The wordmark's construction is thin lowercase word · gold joint · heavier
   Capitalised word. On a section header, two of those three travel and the gold
   one does not.

   Why the joint came off. Four headers put EIGHT gold marks on one page, and
   across sixteen pages the mark's whole power is scarcity — the business card,
   the brand at its most correct, uses no gold at all. And the joint wants two
   words that BELONG together (leak+Check, done+Right); a sentence's first two
   words rarely are, so "readability+Beyond contrast" read as a mangled phrase
   rather than as a mark.

   What is left is the cadence, and it costs nothing: the first word thin and
   lowercase, the second heavier and capitalised, the rest of the line plain.
   The brand's rhythm on every header, gold spent nowhere. The full stamp stays
   where it means something — a product name being named. ── */
h2.sec .sec-cad { display: inline; }
h2.sec .sec-cad .w1 {
  font-weight: 200;
  /* NO text-transform. Forcing lowercase here is half of what produced
     "pick The ink first" — the casing belongs to the sentence, and the cadence
     is the weight split alone. */
  letter-spacing: -0.052em;
}
h2.sec .sec-cad .w2 {
  font-weight: 600;
  letter-spacing: -0.032em;
  /* The space is now a LITERAL space in the markup, not a margin — a margin is
     invisible to copy-paste and to a screen reader, both of which were getting
     "readabilityBeyond contrast". branded+Flow may weld, because the compound is
     the name; a sentence's first two words may not. */
  margin-left: 0;
}

/* ── ruling 49 · THE CADENCE RUNS ALL THREE LEVELS ──────────────────────────
   RULED 2026-08-23. Jen: "how come contrast and readability haven't been changed
   in the same branded+Flow type thing?" — and, of the card labels: "those are
   headers, no?"

   Both true. The cadence had reached only the middle level. The PAGE TITLE was
   plain, and a card's opening line was still a mono LABEL doing a heading's job
   — the same defect as the section labels, one level down and missed because I
   fixed the level she pointed at rather than the pattern.

   Three levels, one rhythm, separated by size alone:
     h1 page title    caps, positive tracking, cadence
     h2.sec section   mixed case, cadence
     h3.card-h card   mixed case, cadence, smaller

   The page title keeps its capitals and positive tracking — that was Jen's own
   ruling, and it is independent of the weight split, so both hold at once.

   A TABLE's column labels stay labels. They are not headings, they are the
   names of columns, and .k still means that — which is why the card rule is
   scoped and the table rule tests for .k rather than for position. ── */
h3.card-h {
  font-family: var(--font-sans);
  font-size: var(--fs-h3);
  font-weight: 200;
  text-transform: none;
  letter-spacing: -0.018em;
  line-height: 1.2;
  color: #FFFFFF;
  margin: 0;
}
h1 .sec-cad .w1 { font-weight: 200; }
h1 .sec-cad .w2 { font-weight: 600; }
h3.card-h .sec-cad .w1 { font-weight: 200; letter-spacing: -0.046em; }
h3.card-h .sec-cad .w2 { font-weight: 600; letter-spacing: -0.028em; }

/* The page title takes the display rung so it outranks a section header, which
   sits on h1's. Three sizes, one per level: 42.3 · 35.3 · 24.5 at a 19px body.
   Raised rather than shrinking the section, because the section size is what
   Jen asked for. */
.bf-sechead.is-page > h1 { font-size: var(--fs-display) !important; }

/* ── ruling 50 · THE STAMP IS THE H2, THE H3 IS PLAIN ───────────────────────
   RULED 2026-08-23 by Jen, overriding rulings 48 and 49 for these two levels.
   The full stamp — thin lowercase, GOLD JOINT, heavier Capitalised — is the
   section header, on the first two words, with no test: a preposition or an
   article is stamped like any other word. The card level is plain type.

   This overrides my own argument that the joint should come off headers. Her
   call, and it makes the section level unmistakable — which was the problem she
   raised in the first place. Gold is spent per section here rather than once per
   surface; that is a deliberate exception, stated so nobody "fixes" it back. */
h2.sec .bf-stamp { font-size: inherit; }
/* h1 too, since 2026-08-24: every page title is a stamp NAME, not a sentence —
   your+Call, icon+Set, says+Back, in+Motion. Twenty-one titles were rewritten in
   one pass. The stamp inherits the h1 rung so the name sits at title size.

   text-transform MUST be cancelled here. h1 and display are UPPERCASE at
   +.055em, and text-transform inherits straight into .w1 / .w2 — which would
   render the stamp as YOUR+CALL and destroy the one thing the stamp is: a thin
   lowercase word against a bold capitalised one. The w1/w2 rules override
   font-weight and letter-spacing but say nothing about case, so it has to be
   said here. The caps tracking is already overridden by w1/w2's own negative
   tracking, so only case needs cancelling. */
h1 .bf-stamp, h1 .bf-stamp .w1, h1 .bf-stamp .w2,
.bf-display .bf-stamp, .bf-display .bf-stamp .w1, .bf-display .bf-stamp .w2,
h1 .bf-stamp .joint, .bf-display .bf-stamp .joint { text-transform: none; }
h1 .bf-stamp { font-size: inherit; }
.bf-display .bf-stamp { font-size: inherit; }
h3.card-h .sec-cad .w1,
h3.card-h .sec-cad .w2 { font-weight: 200; letter-spacing: -0.018em; }

/* ── ruling 51 · THE ICON TILE IS LIT FROM BEHIND ───────────────────────────
   RULED 2026-08-23. Jen asked how to make the plus strike harder in its ICON
   form. The artwork itself is a placed file — the gold ramp and the teal cast
   are baked into the pixels — so nothing about the mark can change without
   re-rendering it. What can change is the ground it sits on.

   The tile carries a contained pool of light behind the mark: #242422 at the
   centre falling to #0E0E0E by 72%. The plus sits in its own light instead of
   flat on a slab, and the deep edge gives the gold more to push against than
   charcoal did.

   Why this does not break ruling 34. That ruling retired ambient radials as a
   BACKGROUND treatment — drifting balls and corner glows on a page. This is a
   container's own lighting, contained inside a 56px tile, static, and it is the
   subject of the surface rather than decoration behind it. The distinction is
   between a page that glows and an object that is lit.

   The mark also grows: it sat at ~40% of the tile and now takes ~54%. More of
   the box is more presence, and it costs no gold. ── */
.bf-plus-tile {
  display: grid;
  place-items: center;
  background: radial-gradient(circle at 50% 42%, #242422, #0E0E0E 72%);
}

/* ── ruling 52 · THE SPLIT MARKS A MODIFIER AND ITS NOUN ────────────────────
   RULED 2026-08-23. Jen: "right now it just looks like a bolded word. I need to
   read the word, and it's super important, but clearly it's not."

   She was right and the cause was that the split was POSITIONAL — always the
   first two words — so it landed on whatever happened to be there: "A pricing
   table" bolded the article. A bold word in an arbitrary place is noise, and the
   eye correctly ignores it.

   The pair is now a MODIFIER + ITS NOUN, wherever it sits in the line: the site
   HEADER, the business CARD, seven SPEEDS, pricing TABLE. That is the
   wordmark's own grammar — "branded" is the thin qualifier and "Flow" is the
   heavy noun — so every heading carries the mark's construction without
   carrying the mark.

   A heading with no natural pair gets NO split. Half of them do not, and that is
   the point: the split means something because it is not everywhere. "Lists",
   "Never", "It worked", "Over time" are plain.

   The joint stays off. A gold plus between two words tells the reader they are
   one term, and in a heading they are not — they are a phrase inside a sentence.
   The full stamp remains reserved for a product name being named. ── */

/* The one line under a section name. The sentence the heading used to be —
   demoted, not invented. Same rung as body, capped at the measure, and it takes
   the heading's old bottom gap so the spacing rhythm is unchanged. */
p.sec-line {
  font-size: var(--fs-body);
  font-weight: var(--fw-body);
  letter-spacing: var(--ls-body);
  line-height: var(--lh-body);
  color: #FFFFFF;
  max-width: 62ch;
  margin: 0 0 calc(var(--sp-3) - var(--sp-8)) !important;
}
h2.sec:has(+ p.sec-line) { margin-bottom: calc(var(--sp-2) - var(--sp-8)) !important; }

/* ── ruling 53 · A SECTION IS NAMED, NOT DESCRIBED ──────────────────────────
   RULED 2026-08-23. Jen: "it looks like a bolded word in an H2 that's supposed
   to mean something that actually doesn't."

   She was right, and rulings 48 and 52 were both wrong about WHERE the motif
   lives. A weight change inside a sentence cannot be the mark's construction,
   because the two words are still a phrase in running prose. The motif is a
   COMPOUND — two words fused into one name by the joint. branded+Flow.
   leak+Check. seven+Speeds.

   So the heading does not get the treatment. The heading BECOMES a name:
   two words, thin lowercase + gold joint + heavier Capitalised, and nothing
   else on the line. The sentence it used to be moves to p.sec-line underneath —
   demoted, never invented.

   This also answers the objection that killed the joint in ruling 48: a plus
   between two words tells the reader they are one term, which is false in a
   sentence and true in a name. Making every section a name makes the joint
   correct rather than confusing.

   62 sections across 15 pages. The full stamp is now the section level AND a
   product name being named; those are the only two places it appears. ── */

/* ══════════════════════════════════════════════════════════════════════════
   § MOTION HARVEST — promoted from the marketing site, 2026-09-03.
   Sources, read at brandedflow-site@main:
     foundation/design-system/motion_and_space.css  (nine brand keyframes)
     components/FlowRing.css + FlowRing.tsx         (the following ring)
     app/home-money.css, app/home-editorial.css     (the ~20 page keyframes)
   These lived on ONE page and nothing else could reach them. Promoted so the
   Hub, the proposals app and every template can use the same motion.

   Two things did NOT come up: the live product demos (LeakEstimator, RoiCalc,
   MoneyDemos — product, not brand) and the page-local marquee/glow variants
   that were duplicates of tokens already here.
   ══════════════════════════════════════════════════════════════════════════ */
:root {
  /* The site's --ease-out-soft measured identical to --ease-primary
     (.16,1,.3,1). It is an alias, not a ninth ease. */
  --ease-out-soft: cubic-bezier(.16, 1, .3, 1); /* @kind other */
  --dur-ring: 22s; /* @kind other */          /* the decorative dashed ring */
  --dur-ring-follow: 14s; /* @kind other */   /* the ring that follows the read */
  /* ruling 50 · 6s → 18s. The sheen is decoration, so it sits in the slow
     band. At 6s it swept while you read the label. */
  --dur-shimmer: 18s; /* @kind other */
  --dur-pulse: 2400ms; /* @kind other */
  --dur-wave: 1200ms; /* @kind other */
}

@keyframes bnfRevealUp { from { opacity: 0; transform: translateY(22px); } to { opacity: 1; transform: none; } }
@keyframes bnfRevealLeft { from { opacity: 0; transform: translateX(-26px); } to { opacity: 1; transform: none; } }
@keyframes bnfRevealRight { from { opacity: 0; transform: translateX(26px); } to { opacity: 1; transform: none; } }
@keyframes bnfDrawIn { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
@keyframes bnfRuleDraw { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes bnfRingSpin { to { transform: rotate(360deg); } }
@keyframes bnfGradFlow { 0% { background-position: 0% 50%; } 100% { background-position: 200% 50%; } }
@keyframes bnfPulseDot { 0%, 100% { opacity: .35; transform: scale(.8); } 50% { opacity: 1; transform: scale(1.15); } }
@keyframes bnfTickFlash { 0% { opacity: 0; } 12% { opacity: 1; } 100% { opacity: .55; } }
@keyframes bnfLivePulse { 0% { box-shadow: 0 0 0 0 rgba(43,233,217,.5); } 100% { box-shadow: 0 0 0 8px rgba(43,233,217,0); } }
@keyframes bnfWaveBar { 0%, 100% { height: 25%; } 50% { height: 95%; } }

/* ── the harvested utilities. Every one animates FROM hidden TO visible, and
      the visible state is the base — a failed script leaves content on
      screen (DESIGN.md §6, the trap that once hid the ring from its owner). ── */
.bf-ring-mark { display: block; border-radius: 50%; border: 1px dashed var(--accent-border); animation: bnfRingSpin var(--dur-ring) linear infinite; }
.bf-grad-flow { background: var(--accent-gradient); background-size: 220% 100%; animation: bnfGradFlow var(--dur-shimmer) linear infinite alternate; }
.bf-draw { stroke-dasharray: 1; stroke-dashoffset: 1; animation: bnfDrawIn var(--dur-draw) var(--ease-out-soft) forwards; }
.bf-pulse { animation: bnfPulseDot var(--dur-pulse) ease-in-out infinite; }
.bf-rule-draw { height: 1px; background: var(--accent); transform-origin: left; animation: bnfRuleDraw var(--dur-reveal) var(--ease-out-soft) forwards; }

/* Reveal — the system's [data-reveal] contract, now with the site's three
   directions and its clip-frame. Hidden only once JS has said it can be. */
[data-reveal] { opacity: 1; }
[data-reveal].is-armed { opacity: 0; will-change: transform, opacity; }
[data-reveal].is-armed.is-in { animation: bnfRevealUp var(--dur-reveal) var(--ease-out-soft) forwards; }
[data-reveal="left"].is-armed.is-in { animation-name: bnfRevealLeft; }
[data-reveal="right"].is-armed.is-in { animation-name: bnfRevealRight; }
[data-reveal="frame"].is-armed { opacity: 1; clip-path: inset(0 0 100% 0); }
[data-reveal="frame"].is-armed.is-in { animation: none; clip-path: inset(0 0 0 0); transition: clip-path 1s var(--ease-out-soft); }

/* Text reveal — words rise out of a mask. Movement only; hierarchy still
   comes from size, case and tracking, never weight. */
.bf-textreveal .w { display: inline-block; overflow: hidden; vertical-align: top; }
.bf-textreveal .w > i { display: inline-block; font-style: inherit; transform: translateY(110%); transition: transform .9s var(--ease-out-soft); }
.bf-textreveal.is-in .w > i { transform: none; }

/* Flip heading — the same device at heading scale, per word or per letter. */
.bf-flip .word { display: inline-block; overflow: hidden; vertical-align: top; }
.bf-flip .word > i, .bf-flip .char > i { display: inline-block; font-style: inherit; transform: translateY(105%); transition: transform .8s var(--ease-out-soft); }
.bf-flip .char { display: inline-block; overflow: hidden; vertical-align: top; }
.bf-flip.is-in .word > i, .bf-flip.is-in .char > i { transform: none; }
.bf-flip i.acc { color: var(--accent-bright); }

/* Rising words — the outcome beat. One line at a time, 180ms apart. */
.bf-rise { display: flex; flex-direction: column; gap: clamp(8px, 1.4vw, 20px); }
.bf-rise-word { opacity: 0; transform: translateY(28px); transition: opacity .9s var(--ease-out-soft), transform .9s var(--ease-out-soft); }
.bf-rise-word.in { opacity: 1; transform: none; }
.bf-rise-word.teal { color: var(--teal-bright); }
.bf-rise-word.gold { color: var(--guide); }

/* The following ring — one pill ring that springs to whatever the reader is
   nearest. Fixed, decorative, never interactive. */
.bf-follow-ring { position: fixed; left: 0; top: 0; z-index: 90; border-radius: 999px; pointer-events: none; border: 2.5px solid rgba(43,233,217,.55); box-shadow: 0 0 0 1px rgba(43,233,217,.08), 0 0 28px rgba(43,233,217,.18); opacity: 0; will-change: transform, width, height; backface-visibility: hidden; transform: translateZ(0); }
.bf-follow-ring.lit { opacity: 1; }
.bf-follow-ring::after { content: ""; position: absolute; inset: -7px; border-radius: 999px; border: 1px dashed rgba(43,233,217,.3); animation: bnfRingSpin var(--dur-ring-follow) linear infinite; }
[data-follow-target] { position: relative; }

/* Process line — a hairline rail with a teal fill that tracks the scroll. */
.bf-procline { position: relative; margin: 0; padding: 0; list-style: none; }
.bf-procline .track { position: absolute; left: 7px; top: 8px; bottom: 0; width: 1px; background: var(--border); }
.bf-procline .fill { position: absolute; left: 7px; top: 8px; width: 1px; background: var(--accent); height: 0; }
.bf-procline > li { position: relative; padding-left: clamp(36px, 4vw, 56px); padding-bottom: clamp(40px, 5vw, 72px); }
.bf-procline > li:last-child { padding-bottom: 0; }
.bf-procline .node { position: absolute; left: 0; top: 4px; width: 15px; height: 15px; border-radius: 999px; border: 1px solid var(--accent-border); background: var(--ground-deep); }
.bf-procline .node::after { content: ""; position: absolute; left: 4px; top: 4px; width: 5px; height: 5px; border-radius: 999px; background: var(--accent-bright); }

/* Image frame — the editorial plate. The placeholder is the correct state
   wherever no real photograph exists; it never fabricates one. */
.bf-imgframe { margin: 0; }
.bf-imgframe .plate { position: relative; border: 1px solid var(--border); border-radius: 2px; overflow: hidden; background: var(--ground-deep); }
.bf-imgframe img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; filter: grayscale(55%) contrast(1.05) saturate(.9); }
.bf-imgframe figcaption { margin-top: 12px; display: flex; align-items: center; gap: 8px; font-family: var(--font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: .14em; color: var(--fg3); }
.bf-imgframe figcaption::before { content: ""; width: 24px; height: 1px; background: var(--border-strong); }

@media (prefers-reduced-motion: reduce) {
  /* Calm, never remove. Everything lands on its end state. */
  [data-reveal].is-armed, [data-reveal].is-armed.is-in { opacity: 1 !important; transform: none !important; animation: none !important; clip-path: none !important; }
  .bf-textreveal .w > i, .bf-flip .word > i, .bf-flip .char > i { transform: none !important; }
  .bf-rise-word { opacity: 1 !important; transform: none !important; }
  .bf-ring-mark, .bf-grad-flow, .bf-pulse, .bf-follow-ring::after { animation: none !important; }
  .bf-draw { stroke-dashoffset: 0 !important; }
  .bf-rule-draw { transform: none !important; animation: none !important; }
}


/* ── ruling 50 · DECORATION IS SLOW, A SIGNAL IS QUICK ──────────────────────
   RULED 2026-09-03 by Jen, after the harvest turned up five loops breaking a
   speed limit that had never actually been ruled.

   What was here before: one undated, unnumbered comment line above the
   keyframes — "ambient motion. Slow loops only: 14–46s." No ruling number, no
   date, no initials. Every real ruling in this file carries all three. It was
   an intention someone typed, and three of the five loops that carry a
   duration broke it.

   The line was half right, and the half it missed is the distinction between
   decoration and signal.

     AMBIENT DECORATION — 14s to 46s.
       Carries no information. The reader is not meant to catch it moving.
       The dashed rings, the button sheen, the marquee.

     STATUS SIGNAL — 1.2s to 2.4s, and ONLY where something is genuinely live.
       Exists to be noticed. Its whole job is to say "this is on, right now".
       The live dot, the audio bars, the status light.

     RESPONSE — 200ms to 2.6s, and ONLY while the thing is being touched.
       AMENDED IN 2026-09-03, the same day, before the ruling had been applied
       to anything. The first draft had two bands and it was wrong: it left
       ruling 40's hover sheen (2.6s) and money face (11s) in the empty gap
       between them, which would have marked your approved buttons
       non-compliant on the day this landed.
       A hover sheen is neither decoration nor a status signal. It runs only
       while the cursor is on the button — it is the object answering you, and
       an answer has to be quick or it is not an answer. It stops the moment
       you leave, so it is not reporting a state either.
       Hover sheens, ring lights, presses. Ruling 40's 2.6s and 2.4s are
       correct AS THEY STAND. Nothing about a button moves.

   A signal that takes fourteen seconds to complete a beat has stopped
   signalling — it reads as frozen, which is to say as a fault. That is why
   the band splits rather than moves.

   What changed on the day:
     --dur-shimmer  6s → 18s. The only value this ruling moved, and it is NOT
                    a button. Its sole consumer is .bf-grad-flow, a utility
                    created the same morning and used by nothing in the
                    project.

                    STRUCK 2026-09-03: this line first read "The sheen is
                    DECORATION. It runs on the button face continuously." That
                    is FALSE. Ruling 40 stopped it running at rest on
                    2026-08-21, and the button sheen has never read
                    --dur-shimmer at all — ruling 40 hard-codes bfSheen at
                    2.6s / 11s / 7s. The sentence was written from a mental
                    model of the system instead of from the file, the same
                    fault as citing a duration that did not exist. Jen caught
                    it: "The shimmer moved. That's the only damn thing I
                    love!" Nothing about the buttons changed, then or now.
     --dur-pulse    2.4s, unchanged — now correct as a signal rather than a
                    violation of a band it was never meant to be in.
     --dur-wave     1.2s, unchanged, same reason.
     --dur-ring     22s, unchanged. Its slowness is the point.

   WHAT THIS RULING DOES NOT TOUCH. Every button, at rest and on hover, in all
   three faces — ruling 40 governs those and is unamended. The gold money face
   at rest is RULED SEPARATELY in ruling 51, which speeds it up rather than
   slowing it down: it is an explicit exception to the decoration band, because
   the one money mark is the one thing on a surface that is meant to look
   alive. Ruling 50 governs motion the reader should never catch. That is not
   the money mark's job.

   And the two dead keyframes are wired. bnfStatusPulse and bnfLivePulse had
   sat in this file with no consumer anywhere in the project — animating
   nothing, surviving every audit because a value check cannot see that a
   keyframe is never called. They now drive .bf-status-dot and .bf-live-badge
   at 2s and 1.8s, both inside the signal band. A keyframe with no consumer is
   either a component that was never finished or a deletion someone lost their
   nerve on; the fix is to pick one. Jen: "Wire them up." ── */
:root {
  --dur-status: 2s; /* @kind other */   /* the status light — signal band */
  --dur-live: 1.8s; /* @kind other */   /* the live badge's ring — signal band */
}

/* The status light. A dot that breathes while something is running.
   OFF and DONE do not animate: a still dot is a state, not a stalled one. */
.bf-status-dot { display: inline-flex; align-items: center; gap: 9px; font-family: var(--font-sans); font-size: var(--fs-meta); font-weight: 300; letter-spacing: .04em; color: var(--fg2); }
.bf-status-dot::before { content: ""; width: 9px; height: 9px; border-radius: 999px; background: var(--fg4); flex: none; }
.bf-status-dot[data-state="running"]::before { background: var(--teal-bright); animation: bnfStatusPulse var(--dur-status) ease-in-out infinite; }
.bf-status-dot[data-state="waiting"]::before { background: var(--guide); animation: bnfStatusPulse var(--dur-status) ease-in-out infinite; }
.bf-status-dot[data-state="done"]::before { background: var(--teal-bright); }
.bf-status-dot[data-state="off"]::before { background: var(--fg4); }

/* The live badge. A pill that pushes a ring out of itself, once per 1.8s.
   Only for something genuinely live — never as decoration on a static label. */
.bf-live-badge { display: inline-flex; align-items: center; gap: 8px; padding: 5px 13px 5px 11px; border-radius: 999px; border: 1px solid var(--accent-border); background: var(--ground-elevated); font-family: var(--font-mono); font-size: 12px; letter-spacing: .14em; text-transform: uppercase; color: var(--teal-bright); }
.bf-live-badge > .bf-live-dot { position: relative; width: 8px; height: 8px; border-radius: 999px; background: var(--teal-bright); flex: none; }
.bf-live-badge > .bf-live-dot::after { content: ""; position: absolute; inset: 0; border-radius: 999px; animation: bnfLivePulse var(--dur-live) var(--ease-primary) infinite; }

@media (prefers-reduced-motion: reduce) {
  /* Calm stops the beat. It does not take the state away — the dot keeps its
     colour and the badge keeps its ring, both simply still. */
  .bf-status-dot::before, .bf-live-badge > .bf-live-dot::after { animation: none !important; }
  .bf-live-badge > .bf-live-dot::after { box-shadow: 0 0 0 4px rgba(43,233,217,.22); }
}


/* ── ruling 51 · THE MONEY MARK IS THE ONE THING THAT IS ALIVE ──────────────
   RULED 2026-09-03 by Jen, after seeing the button magnified six times.
     "The rim light speed: I want it to be the fastest.
      The face sweep speed: I want it to be 5.
      The glass: I would like to keep the wet metal that we have."

   THE LIGHT AROUND THE RIM. 7s → 2.5s at rest. Two lights travelling the whole
   perimeter, opposite sides. At 7s a lap it was invisible on a 43px pill and
   the owner of the brand believed it had been deleted — which is the only
   evidence that matters about whether an effect is working. The rim light is
   the money mark's signature; it is meant to be seen.

   THE SWEEP ACROSS THE FACE. 11s → 5s at rest. Quick enough to read as alive,
   slow enough that the rim light stays the lead.

   THE FACE ITSELF. Unchanged. Wet metal, as ruled in 37 — a bright specular
   edge on top, a smooth fall to a darker base, solid. A frosted see-through
   pane was offered and REJECTED: "I would like to keep the wet metal that we
   have." Ruling 37 stands untouched.

   HOW THIS SITS WITH RULING 50. It does not sit inside the decoration band,
   and it is not meant to. Ruling 40 already carved the money mark out — it is
   the one button allowed to move at rest, because being alive is its whole
   job. Ruling 50's 14–46s decoration band governs things the reader should
   never catch moving; the money mark is the opposite of that. Named here as an
   explicit exception so the two rulings do not read as a contradiction.

   ONE CONSEQUENCE, FLAGGED AND THEN RULED. Ruling 40 set the HOVER rim at
   2.4s. With rest now at 2.5s, hovering changed almost nothing — the response
   had been swallowed by the resting state. Ruled the same day: the hover rim
   goes to 1.4s. See "ruling 51, amended" below. ── */
.bf-btn.money { animation: bfSheen 5s linear infinite !important; }
.bf-btn.money::before { animation: bfRing 2.5s linear infinite !important; }
@media (prefers-reduced-motion: reduce) {
  .bf-btn.money, .bf-btn.money::before { animation: none !important; }
}

/* ── ruling 51, amended · THE HOVER HAS TO OUTRUN THE REST ──────────────────
   RULED 2026-09-03, minutes after ruling 51 itself. Speeding the resting rim
   to 2.5s left ruling 40's 2.4s hover within a tenth of a second of it, so
   the gold button no longer reacted to being touched — the response had been
   swallowed by the resting state. Before ruling 51 the jump was 7s → 2.4s and
   that jump WAS the reaction.

   The hover rim goes to 1.4s. Rest 2.5s → hover 1.4s: a visible change of
   pace, still inside ruling 50's response band (200ms–2.6s).

   This applies to the money face only. The teal and black buttons are still
   at rest, so their 2.4s hover is already a change from nothing, and ruling
   40 governs them unamended. Jen: "Yes." ── */
.bf-btn.money:hover::before { animation: bfRing 1.4s linear infinite !important; }
@media (prefers-reduced-motion: reduce) {
  .bf-btn.money:hover::before { animation: none !important; }
}

/* ── ruling 54 · THE HUB'S CHROME IS BRAND, NOT LOCAL ───────────────────────
   RULED 2026-09-06. Jen: "Into the design system."

   Until today the Hub declared eleven names for itself in
   business-os/hub/app/src/index.css, in both themes: the steel plate
   (--plate-base, --gradient-shimmer-steel), the left rail (--rail-bg,
   --rail-hover), and the four state colours (--danger, --warning,
   --positive, --neutral). The project's sync log of 2026-09-02 says the
   plate pair was added to brand-truth.css then; it was not — neither name
   appears in any cut's token files, which is why the Hub had to carry them.
   A name the system does not own is a value the Claude Design project and
   the repo can disagree about, and that is where drift starts.

   THE STATE COLOURS are not new values. They apply the ruling the system
   already carries in two places — DESIGN-SPEC.md ("a problem is gold, never
   red") and specimens/Messages and states.dc.html ("there is no red and no
   green — teal means it worked or it is working, gold means look at this,
   white is plain information"). The Hub was found on 2026-09-05 carrying nine
   off-palette values against that ruling and was corrected to these four.
   --danger and --warning are the SAME colour on purpose: by the ruling a
   problem and a warning are one thing; two names survive because code uses
   both. Flat text colours only — the full treatment is a left bar plus a
   drawn mark, per the specimen.

   THE RAIL. rgba(14,14,14,·) is --ground-deep at 72% — a carried value, not a
   pointer, because CSS cannot put var() inside rgba() over a hex. Kept at the
   Hub's exact value so dark mode does not move a pixel.

   BOTH THEMES, RE-RESOLVED. A custom property is substituted where it is
   declared, not where it is used. The state names are declared again under
   .bf-light so they resolve against the paper values — --positive goes from
   bright cyan (banned on white, ruling 9) to deep teal, and --danger and
   --warning take --guide-on-light, the darker gold that reads on white.

   AMENDED 2026-09-07 — NEVER AS TYPE. Jen chose A of two: a problem is never
   coloured type, on any theme. Rulings 3 and 4 already retire flat gold as
   type anywhere, and the states specimen already says the treatment is a
   left bar plus a drawn mark with the surface unchanged. So --danger and
   --warning are for MARKS AND FILLS only — a dot, a bar, a border, a chip
   wash. Text stays ink and the mark carries the signal. The Hub's five text
   uses were moved to that shape the same day; the shim at § GOLD-AS-TEXT
   stays as the backstop for anything that slips.

   Consumers: .hub-plate and .hub-section-card (the plate), .hub-rail and
   .hub-rail-link (the rail), the ported sales and marketing pages (the
   states). Three more Hub names — --bg-card, --rule, --dark — are pointers at
   names the system owns and live in tokens/aliases.css, the bridge. ── */
:root {
  --plate-base: #33332f; /* @kind color */
  --gradient-shimmer-steel: linear-gradient(135deg, rgba(244, 247, 248, 0.26) 0%, rgba(196, 208, 214, 0.17) 30%, rgba(139, 154, 163, 0.16) 50%, rgba(200, 212, 218, 0.19) 72%, rgba(244, 247, 248, 0.26) 100%); /* @kind color */
  /* THE RAIL IS DARK TEAL IN BOTH SCOPES (Jen, 2026-09-18: "I want the side
     rail in a dark teal"). --teal-deep at 72% over the dark ground. It was
     --ground-deep at 72%.

     Because the rail is dark on paper too, its own foreground cannot be read
     from --fg1 / --text-secondary: .bf-light remaps those to INK, which would
     put ink on dark teal and lose the whole nav. The rail carries its own two
     names instead, and they hold the SAME value in both scopes. A consuming
     screen uses these and never --fg* inside the rail. */
  --rail-bg: rgba(9, 72, 86, 0.72); /* @kind color */        /* --teal-deep at 72% */
  --rail-hover: rgba(255, 255, 255, 0.06); /* @kind color */
  --rail-fg: rgba(255, 255, 255, 0.98); /* @kind color */    /* the rail's own ink — light in BOTH scopes */
  --rail-fg-dim: rgba(255, 255, 255, 0.72); /* @kind color */
  --danger: var(--guide); /* @kind color */                 /* gold · a problem. NOT red. Marks and fills, never type */
  --warning: var(--guide); /* @kind color */                /* gold · same ruling, same colour */
  --positive: var(--accent-bright); /* @kind color */       /* teal · it worked, or it is working */
  --neutral: var(--fg2); /* @kind color */                  /* white · plain information */
}
.bf-light {
  --plate-base: #f4f7f8; /* @kind color */
  --gradient-shimmer-steel: linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(232, 240, 243, 0.75) 30%, rgba(200, 212, 218, 0.55) 50%, rgba(232, 240, 243, 0.75) 72%, rgba(255, 255, 255, 0.9) 100%); /* @kind color */
  /* Deliberately NOT lighter on paper. The rail is the one dark element in the
     light Hub and it is the same dark teal, carried at a higher alpha because
     it sits over paper rather than over the dark ground. Its two foreground
     names keep their :root values — see the note there. */
  --rail-bg: rgba(9, 72, 86, 0.94); /* @kind color */
  --rail-hover: rgba(255, 255, 255, 0.08); /* @kind color */
  --danger: var(--guide-on-light); /* @kind color */       /* #9A742C · a mark on paper, never type */
  --warning: var(--guide-on-light); /* @kind color */
  --positive: var(--accent-bright); /* @kind color */       /* re-resolves to --teal-deep on paper */
  --neutral: var(--fg2); /* @kind color */                  /* re-resolves to ink on paper */
}

/* ── ruling 58 · A CARD IS EDGED IN TEAL AND CASTS — AND THE PLATFORMS HAVE NAMES ──
   RULED 2026-09-18. Jen, in session, on the Hub's Marketing feed rebuilt on
   tokens: "all cards do need a revision to the design system because they
   need a teal border and a shadow" — and, on the channel marks that had lost
   their colours to the no-literals rule: "the social icons ARE allowed. yes
   to adding the five platform tokens."

   THE CARD. Until tonight the system had a card material (the plate, ruling
   54) but no card EDGE and no card CAST that a surface could name: § 8 says
   "hairline white border" and "0 20px 48px", the Hub's Command Centre says
   "no borders", and the Marketing page shipped a 1.5px blue-grey line of its
   own. One name each, now: --card-border is the accent — the teal hairline
   that says "this is a card" on the dark ground and re-resolves to deep teal
   on paper (ruling 9, the bright teals do not survive on white). --card-shadow
   is --shadow (ruling 55) on dark and the soft paper shadow on light — the
   same value the bridge already carries as --shadow-card, restated here so a
   card can be built from the system's own vocabulary without the bridge.
   .bf-card is the component: the plate under the shimmer, the teal edge, the
   cast, the product radius. A surface that draws a card uses it or these
   three names; it does not draw its own.

   THE PLATFORMS. A channel mark carries the platform's own colour — LinkedIn
   blue, Facebook blue, Instagram magenta, YouTube red, X black. These are not
   brand colours and never leave the mark: § 2.2 stands, the palette is one
   neutral scale, one accent family and gold as the guide. But a surface that
   shows a channel needs the value from somewhere, and "somewhere" is here,
   never a literal in the surface. Values are the platforms' published brand
   colours. Same on both themes — a platform's colour is the platform's.
   Instagram is #C13584, the value the Hub's mark already shipped (Jen,
   2026-09-27: "today's pink"). Facebook is #0866FF, its current blue and
   the value the Hub's logo already drew (Jen, 2026-09-27; #1877F2 was the
   previous one). Merged 2026-09-27 — this block sat
   uncommitted from 09-18 until then. ── */
:root {
  --card-border: var(--accent); /* @kind color */          /* teal · the edge every card carries */
  --card-shadow: var(--shadow); /* @kind shadow */          /* ruling 55's cast, on dark */
  --platform-linkedin: #0A66C2; /* @kind color */
  --platform-facebook: #0866FF; /* @kind color */
  --platform-instagram: #C13584; /* @kind color */
  --platform-youtube: #FF0000; /* @kind color */
  --platform-x: #000000; /* @kind color */
}
.bf-light {
  --card-border: var(--accent); /* @kind color */          /* re-resolves to --teal-deep on paper */
  --card-shadow: 0 12px 32px rgba(23, 22, 18, 0.1); /* @kind shadow */   /* the bridge's paper shadow, restated */
  --platform-linkedin: #0A66C2; /* @kind color */
  --platform-facebook: #0866FF; /* @kind color */
  --platform-instagram: #C13584; /* @kind color */
  --platform-youtube: #FF0000; /* @kind color */
  --platform-x: #000000; /* @kind color */
}
.bf-card {
  background: var(--gradient-shimmer-steel), var(--plate-base);
  border: 1px solid var(--card-border);
  border-radius: 12px;
  box-shadow: var(--card-shadow);
}


/* ══════ aliases.css ═══════════════════════════════════════════════════════════ */
/* ══════════════════════════════════════════════════════════════════════════
   aliases.css — the surface vocabulary layer
   Added 2026-09-04. Re-measured against the 2026-09-04b cut (rulings
   through 52) on 2026-09-05. Part of the token chain, always last.
   2026-09-06: three Hub pointers (--bg-card, --rule, --dark) joined under
   ruling 54 — 47 names. Pushed to the design project the same day.
   2026-09-07: seventeen vocabulary names for the ported pages — 64 names.

   WHY THIS FILE EXISTS
   README.md § 2 of the 09-04 drop states that brand-truth.css "speaks three
   vocabularies at once" — the system's own (--fs-h2, --ground), the Hub's
   (--text-h2, --bg-primary) and retired lineage 1 (--navy, --amber, --gold) —
   and that this is what makes it safe to delete the Hub's three vendored
   copies. Measured against the shipped file, that is not true: it speaks one
   vocabulary, its own. --text-h2, --bg-primary and --amber appear nowhere in
   it at all.

   Without this file, deleting tokens-v4.css, tokens-v4-patch.css, light-v4.css
   and the index.css alias block leaves 41 names the Hub and the proposals app
   ask for with nothing behind them. CSS custom properties fail silently: the
   pages render at browser defaults and nothing errors.

   THE RULE THIS FILE OBEYS
   It declares ONLY names that no earlier file in the chain declares. It may
   never restate or override a ruling. Every line is either a pointer at a name
   the system already owns, or a value carried forward with its source named.
   foundation/test/design-tokens.test.js asserts both properties.

   PROVENANCE
   Values come from the files this drop deletes, read on 2026-09-04:
     business-os/hub/app/src/styles/tokens-v4.css
     business-os/hub/app/src/styles/tokens-v4-patch.css
     business-os/hub/app/src/styles/light-v4.css
     business-os/hub/app/src/index.css  (the :root alias block)

   RE-MEASURED 2026-09-05 against the 09-04b cut. Of the 46 names this file
   carried, that cut now declares five — the ruling-52 border pair and the
   three rgba teal variants — so those five were removed. The remaining 41
   are still declared nowhere else in the chain. --gold is one of them, and
   DESIGN-SPEC.md section 4 now states outright that no --gold token exists,
   so the Hub's 32 var(--gold*) uses stand on this file alone until Jen's
   screen-by-screen pass retires them.

   NOT HERE: --plate-base and --gradient-shimmer-steel. README section 2 said
   they come from brand-truth.css; they do not, and DESIGN-SPEC.md never names
   them at all. Only the Hub consumes them, so they are Hub chrome, not brand,
   and they live in the Hub's own index.css. Moved back 2026-09-05.
   Nothing here was invented. Where a name has an exact twin in the new
   system, it points at the twin rather than repeating the value, so a future
   ruling moves both together.
   ══════════════════════════════════════════════════════════════════════════ */

:root {

  /* ── SURFACES ──────────────────────────────────────────────────────────
     Three of the four are exact twins of the new ground scale, so they
     point rather than repeat. --bg-soft has no twin: the new --ground-warm
     is #191917, a different colour, so its value is carried forward. */
  --bg-primary: var(--ground);           /* #141414 — exact twin */
  --bg-deep: var(--ground-deep);         /* #0E0E0E — exact twin */
  --bg-elevated: var(--ground-elevated); /* #1C1C1A — exact twin */
  --bg-soft: #242422;                    /* no twin; from tokens-v4.css */

  /* ── INK AND RULES ─────────────────────────────────────────────────────
     All four --fg stops are #FFFFFF by ruling — that is not an oversight,
     it is colors.css § 1. These names carry the distinction the Hub's own
     rules still make between them. */
  --ink: var(--fg1);
  --ink-soft: var(--fg2);
  --muted: var(--fg3);
  --text-secondary: var(--fg2);
  --text-muted: var(--fg3);
  --text-faint: var(--fg4);
  --line: var(--border);
  --glass-border: var(--border);

  /* ── GLASS ─────────────────────────────────────────────────────────────
     White at low alpha over the dark ground. No twin in the new system;
     carried from index.css. */
  --glass: rgba(255, 255, 255, 0.04);
  --glass-2: rgba(255, 255, 255, 0.07);
  --glass-hi: rgba(255, 255, 255, 0.025);

  /* ── GOLD IS GOLD ──────────────────────────────────────────────────────
     2026-09-23, Jen's ruling: "Gold should be the gold (with the metal
     finish), never teal." This block used to point --gold / --gold-dim /
     --gold-glow at the teal accent ramp, deliberately, on the theory that
     "there is no gold in this system." That theory is retired with this cut.

     --gold is the true gold #C89A46 (via --guide). Where the metal finish
     belongs, the system already provides it: the --gold-metal-* gradients
     and the .gold-metal / .gold-surface / .gold-rule / .gold-token classes,
     plus the gold-as-text shim that brushes gradient-clipped text.
     --gold-border and --gold-wash were already real gold and are unchanged. */
  --gold: var(--guide);                  /* true gold #C89A46 — Jen 2026-09-23 */
  --gold-dim: rgba(200, 154, 70, 0.32);   /* gold, dimmed — was teal */
  --gold-glow: var(--guide-glow);         /* was teal */
  --gold-border: rgba(200, 154, 70, 0.45); /* actual gold — from tokens-v4.css */
  --gold-wash: rgba(200, 154, 70, 0.12);   /* actual gold — from tokens-v4.css */

  /* ── TYPE SIZES ────────────────────────────────────────────────────────
     Every one is an exact twin.

     --text-body, --text-h1 and --text-h2 were left out on 2026-09-04 because
     nothing under hub/app/src asked for them. ADDED 2026-09-05: the marketing
     site does. It carried its own :root copy of all three, and deleting that
     copy — README section 8, step 3 — leaves app/home.css, home-money.css and
     home-editorial.css asking for names nothing declares. All three values in
     that copy are character-for-character the system's own, so they are
     pointers here, not carried values. One bridge now serves both repos,
     which is the whole reason the site takes dist/ instead of a vendored cut. */
  --text-body: var(--fs-body);     /* clamp(17px, 1.35vw, 18px) */
  --text-h1: var(--fs-h1);         /* clamp(35.3px, 3.7vw, 51.4px) */
  --text-h2: var(--fs-h2);         /* clamp(29.4px, 2.85vw, 39.5px) */
  --text-h3: var(--fs-h3);         /* clamp(24.5px, 2.2vw, 30.4px) */
  --text-h4: var(--fs-h4);         /* = var(--fs-h3) */
  --text-label: var(--fs-meta);    /* clamp(13px, 1vw, 14px) */
  --text-small: var(--fs-meta);    /* clamp(13px, 1vw, 14px) */
  --text-eyebrow: var(--fs-eyebrow); /* 16px, the pinned scalar */

  /* ── TRACKING ──────────────────────────────────────────────────────────
     Two are exact twins of the system's letter-spacing pair. The other six
     have no twin and are carried from tokens-v4.css. --tracking-caps at
     .055em is the same figure the spec states for uppercase display. */
  --tracking-body: var(--ls-body);   /* 0.022em — positive, by ruling */
  --tracking-lead: var(--ls-lead);   /* 0.016em */
  --tracking-caps: 0.055em;
  --tracking-eyebrow: 0.26em;
  --tracking-h2: -0.020em;
  --tracking-mono: 0.14em;
  --tracking-tight: -0.03em;
  --tracking-tighter: -0.045em;

  /* ── WEIGHT ────────────────────────────────────────────────────────────
     The eyebrow is the one place the Hub departs from --fw-body 200. */
  --fw-eyebrow: 400;

  /* ── MOTION ────────────────────────────────────────────────────────────
     The new system's duration ladder has --dur-short 420ms and --dur-long
     1700ms with nothing between, so --dur-medium is carried, not pointed. */
  --dur-medium: 700ms;
  --btn-cast-dur: 180ms;
  --btn-cast-hover: 6px 6px 0 0 rgba(88, 86, 86, 0.45);

  /* ── SHAPE AND MEASURE ─────────────────────────────────────────────── */
  --content-wide: 1320px;
  --radius-pill: 999px;
  --shadow-card: 0 20px 48px rgba(0, 0, 0, 0.4);


  /* ── THE PORTED PAGES' VOCABULARY · added 2026-09-07 ─────────────────
     business-os/hub/app/src/styles/bf-agent-skin.css — the skin for the
     fourteen ported sales, marketing and proposals pages — asks for these
     seventeen names. Its own token block was deleted on 09-04 (lineage six)
     and nothing declared them after, so for two days the pages rendered at
     browser defaults: zero padding, default type, square corners. No error.
     Jen, 2026-09-07: "that is not allowed. the design sync should have
     wiped this away."

     Every line is a pointer at the system's own scale, per the door's one
     rule. SPACING: DESIGN-SPEC § 15.2 names --space-N as a 4px base scale;
     the ruling scale is --sp-1..8 (clamp, floors at 85%), so each step
     points at its rung and the two largest fold onto --sp-7 / --sp-8.
     TYPE: the two smallest sizes both land on --fs-meta, the system's floor
     for a label. RADII: DESIGN-SPEC § 15.1 — structure 6, product card 10,
     nothing rounder than 12 except a button. The retired ramp (--gray-*,
     --black, --white, --border-dark) was NOT bridged: those uses were
     rewritten in the skin by role, because there is no grey. */
  --space-1: var(--sp-1);
  --space-2: var(--sp-2);
  --space-3: var(--sp-3);
  --space-4: var(--sp-4);
  --space-5: var(--sp-5);
  --space-6: var(--sp-6);
  --space-8: var(--sp-7);
  --space-10: var(--sp-8);
  --text-xs: var(--fs-meta);
  --text-sm: var(--fs-meta);
  --text-base: var(--fs-body);
  --text-lg: var(--fs-lead);
  --text-xl: var(--fs-h3);
  --text-3xl: var(--fs-h2);
  --radius-sm: 6px;    /* § 15.1 · structure, near-square */
  --radius-md: 10px;   /* § 15.1 · product card */
  --radius-lg: 12px;   /* § 15.1 · the ceiling; only a button is rounder */

  /* ── LIGHT-ONLY TEAL, declared dark so it always resolves ──────────────
     #147D81 is an existing documented gradient stop, not a new colour.
     Bright cyan is too weak on paper; teal FILLS keep the bright stop. */
  --accent-text-light: #147d81;

  /* ── HUB POINTERS · moved from hub/app/src/index.css 2026-09-06 (ruling 54)
     Three names the Hub declared for itself, each pointing at a name the
     system owns, so they are bridge and not ruling. --bg-card points at
     --bg-elevated, not --ground-elevated: only --bg-elevated is remapped for
     paper. --rule is a hairline. --dark is used both as text on a gold chip
     and as an input background, and the ground reads correctly for both. */
  --bg-card: var(--bg-elevated);
  --rule: var(--border);
  --dark: var(--ground);
}


/* ══════════════════════════════════════════════════════════════════════════
   .bf-light — the paper theme

   brand-truth.css carries its own .bf-light scope. As of ruling 52 that scope
   remaps the teal family, the four --fg stops AND the two border names. So the
   only names left here are the ones it still does not touch: the paper surface
   scale, the card shadow, and the deeper teal for text.

   WHAT LEFT THIS BLOCK ON 2026-09-05, and why it is not a loss:
     --border, --border-strong      ruling 52 · now upstream in the .bf-light
                                    scope of brand-truth.css. Their values
                                    changed with the move — upstream uses
                                    rgba(20,24,28,·), a cooler ink than the
                                    rgba(23,22,18,·) carried here from
                                    light-v4.css. Upstream is the ruling; the
                                    bridge does not get a second opinion.
   Two names, one place instead of two. A bridge that keeps restating a ruling
   after the ruling lands upstream is how lineage seven would start.

   ONLY TWO, and the near-miss is worth recording. The first pass at this also
   dropped --accent-subtle, --accent-border and --accent-glow, on the grounds
   that the 09-04b cut "declares" them. It does — at :root, and nowhere else.
   For a name that exists to INVERT, being declared at :root is not coverage;
   the only question that matters is whether upstream remaps it inside its own
   .bf-light scope. Upstream remaps thirteen names there and those three are
   not among them, so dropping them froze all three at bright cyan on paper.
   Caught by the browser probe, not by the token diff. The three are below.

   Values below are from light-v4.css, the file the 09-04 drop deleted.
   ══════════════════════════════════════════════════════════════════════════ */
.bf-light {

  /* Warm paper scale — the inverse of the dark ground scale. Upstream's
     .bf-light does not repaint surfaces: --ground and its scale stay dark
     under it, so without these four the Hub renders dark on a light page. */
  /* COOL PAPER, NOT CREAM (Jen, 2026-09-18: "fix the background to F4F8FA").
     This scale was warm — #f6f4ef, #efede6, #e8e5dc — and two places in the
     spec said it should not be:
       § 16's table  light ground is `cool paper #F4F8FA`
       § 8's never-list  "... gradient-soup · CREAM OR BEIGE PANELS · ..."
     The canvas takes § 16's value exactly; the hover tint and the inset well
     follow it down the same hue, because a cool canvas under warm tints reads
     worse than either on its own. --bg-elevated was already correct: § 16 says
     a card on light is white. */
  --bg-primary: #F4F8FA;   /* canvas — § 16's "cool paper" */
  --bg-elevated: #ffffff;  /* card */
  --bg-soft: #EDF3F6;      /* hover tint */
  --bg-deep: #E3EBEF;      /* inset well */

  /* Soft shadow for white cards on paper, not the dark-surface drop. */
  --shadow-card: 0 12px 32px rgba(23, 22, 18, 0.1);

  /* The deeper teal for text on paper. Same value as :root — restated here
     only so the pairing with the surfaces above reads in one place. */
  --accent-text-light: #147d81;

  /* ── TEAL READ AS A LITERAL ────────────────────────────────────────────
     Upstream's .bf-light clamps --accent, --accent-bright and --accent-light.
     These three are the rgba variants it does not touch, and they are read as
     literal colours on tinted surfaces, so bright cyan comes through too weak
     on paper. Deeper stop #147D81 at the same alphas. */
  --accent-subtle: rgba(20, 125, 129, 0.1);
  --accent-border: rgba(20, 125, 129, 0.35);
  --accent-glow: rgba(20, 125, 129, 0.18);

  /* NOT carried, all three dead: --footer-bg, --shadow-subtle and
     --shadow-hover. The old files declared them in both themes and nothing
     in the Hub reads any of them. Left dead rather than migrated. */

  /* ── HUB POINTERS, RE-RESOLVED (ruling 54) ─────────────────────────────
     The same two expressions as :root, declared again here so they resolve
     against the paper values — a custom property is substituted where it is
     declared, not where it is used. Without this the light Hub rendered
     white hairlines on paper (found in a real browser, 2026-09-05). */
  --bg-card: var(--bg-elevated);
  --rule: var(--border);

  /* ── THE REST OF THE INDIRECTIONS, RE-RESOLVED ─────────────────────────
     Eight more aliases have the exact shape the two above were fixed for:
     declared at :root as a bare var() onto a name .bf-light remaps, so they
     were substituted against the DARK value and the theme never reached them.
     --bg-card and --rule were caught in 2026-09-05's browser pass because the
     Hub used them; these were not, because until now nothing rendered a light
     surface with them. Found the same way — in a real browser, 2026-09-18 —
     where every muted line came out white on paper.

     Same expression as :root in every case. Nothing new is introduced here
     and no dark value moves: each one only re-resolves against the paper
     value its target already has under this scope. */
  --ink: var(--fg1);
  --ink-soft: var(--fg2);
  --muted: var(--fg3);
  --text-secondary: var(--fg2);
  --text-muted: var(--fg3);
  --text-faint: var(--fg4);
  --line: var(--border);
  --glass-border: var(--border);

  /* --gold / --gold-dim / --gold-glow are not re-resolved here: they do
     not need it. Since the 2026-09-23 cut --gold is var(--guide) and --guide
     is not remapped under .bf-light, so the gold is the true gold #C89A46 in
     both themes — that is the settled state, per Jen's ruling, not a pass. */
}
