/* ============================================================
   Semantic design tokens for the RBI Private Lending brand. LAYER 2.
   Authored under GL-003, the design-system SSOT. Stage D, 2026-07-29.

   WHAT STAGE D DID HERE, IN ONE PLACE
   -----------------------------------
   1. THE ORPHAN RULE, stated for this layer for the first time. A
      token declared here with zero consumers in the component layer
      is a DEFECT, with exactly three exceptions:
        (a) the second half of a mandated pair whose first half is
            consumed — a fill's -ink, a size's -lh. The pairing rule
            REQUIRES it to exist;
        (b) a declared rung of a declared SCALE whose family is
            consumed (an unused categorical position, the ordered
            rung a five-stage progression skips, the top of the
            radius ladder). Deleting a rung invites the next
            component to invent a raw value;
        (c) a token wired by an open, numbered task in this stage.
      Everything else is a LEAF and is deleted. Layer 1 has carried
      the equivalent rule (with its own two exceptions) since Stage
      C; this layer had none, which is why leaves accumulated here.
   2. FOUR LEAVES DELETED: --border-on-brand (the -strong rung is
      the consumed one), --surface-brand-hover (both themes; the
      task describing a defect in it survived three revisions
      because nobody re-checked the consumer), --viz-axis (both
      themes) and the five --viz-div-* lines.
      --viz-axis is the instructive one: its only consumers were the
      chart primitives deleted at Stage C, so a COMPONENT-layer
      deletion orphaned a TOKEN. The seam rule was written one-way
      ("grep the component layer when you delete a token"); it now
      binds both ways.
      The diverging ramp was kept at Stage C as "free to keep". It
      is deleted not because it rotted but because an orphan rule
      with a bespoke fourth exception on its first application is a
      dead rule — and this file has watched two rules die exactly
      that way. Its derivation recipe survives in the Guideline and
      is one minute of work at the moment a consumer exists.
   3. THE CATEGORICAL SET'S PROMISE IS NARROWED, and the arithmetic
      is in brand.css beside the rungs: six-way deficiency
      separability is unattainable under this constraint set, so the
      set now guarantees THREE robust positions (1, 5, 6) and treats
      2, 3 and 4 as legitimate-but-unguaranteed. Position 4's deep
      rung moved once, to the max-min point. Any consumer rendering
      more than the robust triple at once must carry a NON-COLOUR
      key — that is a component contract, not a value.
   4. FORM: a six-rung radius ladder replaces the four-value scale,
      plus --eyebrow-tracking and --status-dot-size. The radius
      change is a WALK, not a rename: --radius-sm doubles to 8px, so
      seven of its twenty existing declarations must demote to
      --radius-xs or micro-marks become lozenges.

   THE RATIO RULE, ADDED AT STAGE E+ AFTER A LIVE AA FAILURE
   ---------------------------------------------------------
   A contrast figure is a property of a (foreground, background,
   theme) triple. It is never a property of a token. A comment naming
   a peer token is an equality, not a measurement, and the next reader
   will read it as one.

   --on-surface-brand-subtle shipped exactly that mistake and ran at
   3.90:1 on every section band of every tab. Its block below carries
   the account. The fix was to derive the ink from its own fill's ink
   rather than borrow a canvas neutral.

   WHAT THIS RULE DOES NOT DO, stated so nobody trusts it further than
   it reaches. An inline ratio in this file is a convenience copy and
   it rots. Five figures recorded here have already been corrected
   against exact arithmetic: 7.41, 1.97, 4.77, 5.96 and "~2.5". The
   record of a pair is the coverage index in GL-003 §8.1a, one row per
   (ink, fill, theme). A blank cell there is an open defect, and a
   wrong number is caught only by measuring a rendered element.

   LOAD ORDER: brand.css, then tokens.css, then the product's own
   layer-3 stylesheet. Layer 3's filename is a product decision.

   Both files are served same-origin, never inline, so they survive
   the strict `style-src 'self'` CSP. JS never holds a colour literal
   — it passes token NAME strings to setVar() (public/js/dom.js).

   THE TWO-LAYER RULE
   ------------------
   Layer 1 (brand/brand.css) holds every brand colour value, as
   `--rbi-*` primitives, with no theme selector.
   Layer 2 (this file) holds every semantic ROLE. A colour here is
   either a var(--rbi-*) reference or a color-mix() of one. NO BRAND
   HEX LITERAL MAY APPEAR IN THIS FILE. A literal that survives here
   is a value the swap file cannot reach.

   The exception, and it is deliberate: the PRODUCT NEUTRALS
   (--color-bg / -surface / -surface-2 / -border / -border-strong /
   -text / -text-muted) stay literal here. The brand owner's real
   applications use unmodified framework greys, so this app's
   cool-grey neutrals are a PRODUCT decision, not brand identity.
   Keeping them out of layer 1 is precisely what lets them survive a
   brand swap untouched.

   WHAT CHANGED AT STAGE C, AND WHY THE STATUS EXCEPTION NARROWED
   -------------------------------------------------------------
   Stage A wrote that the whole status palette was product, not
   brand, and kept all four families literal here. That is still true
   of warning, danger and info: the brand owner supplies no orange,
   no red and no notice-blue, so inventing one and then calling it
   brand would be a lie in the swap file.

   It is NOT true of green. The brand owner does supply a green, and
   in this product green has exactly one job — the on-or-above-target
   flag. Had the brand green been admitted to layer 1 and the flag
   green left literal here, the app would carry TWO greens one hue
   step apart, in the same viewport, meaning almost the same thing.
   That is a worse defect than the coupling it avoids. So the green
   family is brand, and the success role reads it from layer 1.

   The coupling this creates is real and is stated so it is not
   discovered later: a rebrand now moves the on-target flag. The
   guard is that the -700 and -800 rungs of any replacement green
   must be re-derived to clear 4.5:1 on this app's light canvas
   before the swap ships. The brand green itself never can — see the
   warning on --rbi-green-500.

   GREEN CARRIES TWO MEANINGS, AND *WHERE* IT SITS DECIDES WHICH
   -------------------------------------------------------------
   Stage C+ gave the primary control the brand owner's own confirm
   green, unmodified, because their live platforms use it. Stage C+2
   extended green into the chrome: the active period control, and the
   brand marks on the masthead band.

   An earlier revision drew this boundary SEMANTICALLY — green on
   things that act, brand navy on things that report state — and
   ruled the active period control out on that basis. The product
   owner overrode that ruling. It is re-authored rather than softened,
   because a rule the code contradicts is worse than no rule at all.
   The boundary is now SPATIAL, and this is the line that is actually
   true of the values below:

     GREEN REPORTS A MEASUREMENT ONLY WHERE IT IS A STATIC MARK
     INSIDE A PANEL BODY. IN EVERY OTHER POSITION — the chrome band,
     the nav, and any control the reader can operate, wherever it
     sits — GREEN IS THE BRAND. THERE IS EXACTLY ONE ENUMERATED
     EXCEPTION INSIDE A PANEL BODY, AND IT IS NOT A MEASUREMENT: the
     peer-comparison reference figure (--color-reference-ink below).

   Inside a panel body a reader has learned that green means "at or
   above target", and nothing else may compete with that except the
   one enumerated exception: no green heading, no green divider, no
   green hairline for interest, no green rung in an ordered scale.
   Outside it, green is simply the brand owner's colour doing what a
   brand colour does.

   The separator stays hue-independent, which is why it survives both
   canvases and every colour-vision deficiency: a control is still a
   control and a masthead is still a masthead when you cannot see the
   colour, and a flag never encodes by colour alone.

   FIVE GREEN ROLES, ONE PER POSITION. They are separate families on
   purpose — each was sized against a different constraint, and each
   has to be able to move without dragging the others:

     --color-success*         the measurement. Derived rungs, because
                              it must carry text on both canvases.
     --surface-cta*           the action. A SPECIFIED value (-500
                              verbatim) that knowingly accepts 3.10:1.
     --surface-chrome-active* the current state. Derived (-700), and
                              it PASSES at 5.42:1 — see that block for
                              why it does not inherit the CTA's trade.
     --mark-on-brand          the brand mark on the chrome band.
                              Specified (-500), but it never carries
                              text, so the value costs nothing.
     --color-reference-ink    the peer-comparison figure. Derived ink
                              rungs, and the ONLY green permitted
                              inside a panel body that is not a
                              measurement. Read its block: it is the
                              one green in this file that carries no
                              information.

   WHY THE DARK AFFIRMATIVE FILL MOVED A RUNG
   ------------------------------------------
   Before this revision --color-success on dark WAS --rbi-green-500,
   which is now the CTA fill. That made two roles the identical hex
   on one canvas — the degenerate form of the very "two greens
   meaning almost the same thing" defect the section above exists to
   prevent — and the two DO co-occur: a modal with a primary button
   opens over a view whose gauges render the affirmative fill.

   The CTA kept -500 and the affirmative fill moved, because -500 is
   a SPECIFICATION for the CTA (a derived rung is not a match) and
   was only a CONVENIENCE for the flag (it measured well; nothing
   required the raw value — the light canvas has always used derived
   rungs for this role). The move was free: both dark consumers of
   --color-success are TEXTLESS saturated fills, so no ink was
   stranded, and both gained object contrast (5.14:1 -> 8.93:1
   against --color-surface).

   The invariant this produces, and the reason it is not a
   coincidence to be tidied away later: THE CTA IS ALWAYS THE GREEN
   NEARER THE CANVAS; THE AFFIRMATIVE FILL IS ALWAYS THE GREEN
   PUSHED FURTHER FROM IT. Light: CTA -500 (Y .289) / fill -700
   (Y .144), 1.75:1 apart. Dark: CTA -500 (Y .289) / fill -300
   (Y .538), 1.74:1 apart. Same shape, both canvases, by
   construction.
   (CORRECTED at Stage C+2: the light figure was recorded as 1.97:1
   and measures 1.75:1 — (.2889+.05)/(.1436+.05) = 1.750, from the
   luminances stated on this very line. The wrong number sat inside
   the paragraph warning against tidying the polarity away, which is
   exactly where a wrong number invites the correction the paragraph
   exists to prevent. The two canvases are in fact near-identical in
   separation, 1.75 vs 1.74, which strengthens the claim rather than
   weakening it.)

   ROLE STABILITY
   --------------
   A role never swaps meaning across themes; only its value changes.
   --surface-brand is a FILL in both themes. --on-surface-brand is
   INK in both themes. The retired brand-primary alias did both jobs
   and inverted (a fill on light, an ink on dark), which is why
   the component layer had to hardcode a dark fill four times and a
   light ink thirteen times. Never reintroduce a token that changes
   role between themes.

   Several roles below now resolve to the SAME primitive within one
   theme (the accent fill and the accent ink both take --rbi-blue-500
   on light, because that one value is legible in both jobs). They
   stay SEPARATE declarations. Aliasing one to the other would fuse
   two roles that the dark block still has to pull apart.

   Dark mode: theme-init.js always stamps an explicit
   data-theme="light|dark" on <html> before first paint, so there is
   no prefers-color-scheme duplicate — one source of truth per hex.
   ============================================================ */

:root {

  /* ==========================================================
     BRAND SURFACES — role-stable pairs.
     ========================================================== */

  /* The solid brand fill: section bands and table heads. Always a
     FILL, in both themes. White ink on this fill is 13.22:1.

     --surface-brand-border AND -border-hover WERE DELETED AT STAGE
     C+3, and the way they died is worth four lines because it is a
     process defect, not a token defect. -border-hover was already
     orphaned at the previous HEAD. -border lost its last consumer
     when the active pill went green and repointed to
     --surface-chrome-active-border — i.e. THIS FILE orphaned it and
     this file did not notice, because the deletion audit ran over the
     token layer while the consumer lives in the component layer.
     Neither author was reading the SEAM between the two files. That
     is the same blind spot that shipped a stranded --color-emphasis
     reference and left the confidential dot computing rgba(0,0,0,0)
     at 1.00:1 against the band.
     THE RULE THIS GRADUATED INTO, AND STAGE D MADE IT BIDIRECTIONAL:
     a token deletion or repoint is not audited in the layer it is
     written in. Grep the component layer for the OLD name and the
     NEW name both, every time — AND grep this layer whenever a
     component is deleted, because --viz-axis was orphaned by a
     component deletion and went unnoticed for exactly the same
     reason, one revision later, in the opposite direction.

     --surface-brand-hover IS DELETED AT STAGE D. It never had a
     consumer in either theme. The task tracking it described a
     missing hover state and proposed a rung to fix it; the fix would
     have moved no pixel, because nothing read the token. It survived
     three revisions of being carried forward — which is the sharpest
     evidence in this file that "carried forward again" is not
     evidence an item is real. Re-checking the consumer is. */
  --surface-brand:              var(--rbi-navy-700);
  /* Ink ON the solid brand fill. Always INK, in both themes. */
  --on-surface-brand:           var(--rbi-white);
  /* De-emphasised ink on the solid brand fill (band hints).

     ONE DECLARATION, CORRECT IN BOTH THEMES. It is deliberately not
     restated in the dark block. That absence is a correctness claim,
     the same standing every other absence in this file carries.

     WHAT THIS REPLACED. The dark block pinned a literal, #8CA0B3,
     under the note "matches --color-text-muted". The note was true.
     The inference every reader drew from it was not.

     --color-text-muted is measured against --color-surface. There it
     reads 5.91:1 and passes. Nobody measured it against
     --surface-brand, which is the fill this ink actually sits on.
     There it reads 3.90:1 and fails SC 1.4.3 at 13px. That failure
     shipped, and it ran on every section band of every tab.

     THE RULE IT EARNED, and it is GL-003 §10.1 test 6: a contrast
     figure is a property of a (foreground, background, theme) triple.
     It is never a property of a token. A figure does not travel with
     a value. A token does not inherit a figure from a peer that
     happens to share its value.

     SO THIS INK IS DERIVED FROM ITS OWN FILL'S INK, never from a
     canvas neutral. It is 78% of --on-surface-brand over
     --surface-brand, in whichever theme is live. The pair is measured
     by construction: the only two values in it are the fill and the
     fill's own ink.

       light  8.66:1  Unchanged, and byte-identical to what shipped.
                      --on-surface-brand IS --rbi-white on this
                      canvas, so this is the old declaration in a
                      different spelling. No light pixel moves.
       dark   6.61:1  Was 3.90:1.

     The de-emphasis holds its shape on both canvases. That is what
     says the construction is right rather than lucky. The subtle ink
     carries 0.66 of the full ink's ratio on light and 0.68 on dark.
     The deleted literal carried 0.40.

     IF IT READS TOO LOUD ON DARK the answer is 65%: 6.51:1 light and
     5.13:1 dark. Both are still AA, still derived, still one
     declaration. DO NOT answer "too loud" by re-pinning a literal.
     Re-pinning a literal is the move that produced the 3.90. */
  --on-surface-brand-subtle:    color-mix(in srgb, var(--on-surface-brand) 78%, transparent);

  /* The hero band: the one gradient in the app. Its own surface
     because its ink stays pure white in BOTH themes — the gradient
     is dark at its origin on either canvas. Only the stops move.

     Both stops are now navy-to-blue, which retires the last accent
     that was never confirmed by the brand owner. It also makes the
     band provably safe rather than safe-at-the-two-ends: the band is
     monotonic in luminance, so bounding its two ends bounds every
     point on it and no interpolated midpoint needs its own
     measurement.

     THE TWO ENDS, STATED EXACTLY, BECAUSE EVERY MARK PLACED ON THIS
     BAND IS MEASURED AGAINST THEM. The far stop sits at 165%, so the
     VISIBLE band never reaches the blue: at x=100% the interpolation
     is 60.6% of the way, which is #2A3B75 on light and #26366D on
     dark. Visible luminance endpoints:
       light   Y .01354 (origin) .. Y .04871 (right edge)
       dark    Y .00659 (origin) .. Y .04198 (right edge)
     White ink therefore runs 16.52:1 .. 10.64:1 light and
     18.56:1 .. 11.42:1 dark ACROSS THE VISIBLE BAND.
     (An earlier revision of this comment said "between 16.52:1 and
     7.70:1 everywhere along it". The 7.70 is the ratio at the
     off-screen 165% stop, so it is a valid lower BOUND and is not a
     value the band attains anywhere. Kept as a correction rather than
     silently replaced, because a bound quoted as an endpoint is how a
     mark gets sized against a canvas that does not exist — and the
     two published green-on-band figures, 3.43 light and 3.68 dark at
     the right edge, are reproducible from the endpoints above and are
     NOT reproducible from .086.) */
  --surface-brand-hero:    linear-gradient(100deg, var(--rbi-navy-800) 0%, var(--rbi-blue-500) 165%);
  --on-surface-brand-hero: var(--rbi-white);

  /* Ink ladder on the hero band. Theme-invariant: the hero is dark
     on both canvases, so these are never restated in the dark block.
     CONSOLIDATION DEBT: 92% and 82% are two steps where one would
     almost certainly do; they are kept apart here only because
     collapsing them would move pixels. */
  --on-surface-brand-secondary: color-mix(in srgb, var(--rbi-white) 92%, transparent);
  --on-surface-brand-muted:     color-mix(in srgb, var(--rbi-white) 82%, transparent);

  /* Veils and hairlines ON the hero band. Theme-invariant.
     CONSOLIDATION DEBT: 10/12 is still a near-duplicate pair.

     THE 25/28 PAIR IS RESOLVED BY DELETION, NOT BY COLLAPSE.
     --border-on-brand (25%) had no consumer in any layer; the -strong
     rung (28%) is the one every border on the band actually reads. The
     consolidation task proposed merging two near-identical values and
     warned that "collapsing them moves pixels" — but only one of them
     was ever painted, so deleting the unpainted one moves nothing and
     ends the debt. Check which half of a near-duplicate pair is real
     before pricing the merge. */
  --surface-brand-veil-subtle: color-mix(in srgb, var(--rbi-white) 10%, transparent);
  --surface-brand-veil:        color-mix(in srgb, var(--rbi-white) 12%, transparent);
  --surface-brand-veil-hover:  color-mix(in srgb, var(--rbi-white) 18%, transparent);
  --rule-on-brand:             color-mix(in srgb, var(--rbi-white) 16%, transparent);
  --border-on-brand-strong:    color-mix(in srgb, var(--rbi-white) 28%, transparent);

  /* THE BRAND MARK ON A BRAND SURFACE. The one green that appears in
     the chrome band, and the only place in the system where the raw
     brand green is free: it never carries text here, so the 3.10:1
     constraint that governs every other use of -500 simply does not
     apply to it.

     MARKS ONLY, and the "-on-brand" in the name is a CONSTRAINT, not
     a description. Against the light app canvas this value measures
     2.89:1 and FAILS SC 1.4.11. It is safe only because a brand
     surface is dark on both canvases. A green accent that has to sit
     on the light canvas is not this token — it is the chrome-active
     family below (-700, 5.05:1 on the app canvas).

     THE NUMBER OF RECORD IS THE BAND RANGE, NOT A POINT, and at
     Stage C+3 that stopped being a stylistic preference and became
     the only defensible reading. The surface is a gradient, so
     contrast differs at every x — AND the x each mark occupies is
     VIEWPORT-DEPENDENT. A responsive sweep put the dot anywhere from
     5.7% to 78.2% across the band. Quoting a point figure for it was
     quoting one breakpoint.
       light  5.30:1 (origin) .. 3.43:1 (right edge)
       dark   5.99:1 (origin) .. 3.68:1 (right edge)
     Both ends clear 1.4.11 with headroom, so no position along the
     band needs its own ruling. That is the whole point of a range.

     POINT FIGURES, kept as illustration and explicitly NOT as the
     record:
       dot, 76.76% across   3.84 light / 4.21 dark  (reproducible)
       chip rim vs the veiled chip fill, which is the tighter of that
       mark's two boundaries — the veil lightens the navy beneath it:
         at 1280px  3.72 light / 4.34 dark
         at  375px  3.47 light / 3.99 dark  <-- TRUE WORST CASE of
           every green-on-band figure in this system. Still a 1.4.11
           pass, 0.47 of headroom. It was unrecorded until Stage C+3
           because the sweep had only ever been run at desktop width.
     A 3.88 / 4.26 pair circulates in hand-off notes. It is NOT
     reproducible and must not be adopted.

     As TEXT on the band it is 3.84:1 at the dot's desktop position
     and fails 4.5:1 everywhere along the band. There is deliberately
     no -ink counterpart.

     Theme-invariant: the surface it marks is dark on both canvases,
     so restating it would create a second place to get it wrong. */
  --mark-on-brand:             var(--rbi-green-500);

  /* ==========================================================
     CTA SURFACE — a first-class role, NOT a borrowed brand fill.

     WHY THIS EXISTS AS ITS OWN FAMILY. Before this revision the
     primary button and the section band both read --surface-brand,
     so a button could not be recoloured without dragging every
     section band, table head and active pill with it. That is one
     token doing two jobs, which is the same defect the retired
     brand-primary alias made (see ROLE STABILITY above) — only in
     the other axis. A CTA is a distinct role: it is the surface that
     ACTS. It gets its own pair.

     THE FILL IS THEME-INVARIANT ON PURPOSE. It is a specified value,
     not a derivation: the brand owner's live platforms use exactly
     this green on their confirm controls, and matching them is the
     whole point. It is mid-luminance, so it reads on both canvases
     without moving (3.10:1 against a white surface, 5.14:1 against
     the dark one). Its absence from the dark block is therefore a
     correctness claim, not an omission.

     ACCEPTED SC 1.4.3 FAILURE, RECORDED WITH ITS NUMBER SO NOBODY
     "FIXES" IT: white ink on this fill measures 3.10:1 against a
     4.5:1 requirement. It is accepted deliberately, in exchange for
     matching the brand owner's live platforms. DO NOT silently
     substitute a passing green — doing so destroys the consistency
     that was bought with this number. If the decision is ever
     reversed, the escape hatch is ONE line: point --surface-cta at
     --rbi-green-700, which carries white at 5.42:1. Nothing else in
     this family needs to change.

     Note the hover state PASSES at 5.42:1. The button is most
     legible under the pointer and least legible at rest; that is a
     consequence of the accepted trade-off, not an oversight.

     THE BORDER IS LOAD-BEARING, NOT DECORATION. The fill clears 3:1
     as a graphical object against --color-surface (3.10:1 light,
     5.14:1 dark) but NOT against the lighter product neutrals
     (2.75:1 on --color-surface-2, 2.89:1 on --color-bg). The border
     rung carries SC 1.4.11 on any light canvas — 5.42:1 on white,
     4.82:1 on surface-2, 5.05:1 on the app canvas — at zero cost to
     the specified fill. Same mechanism the funnel already uses on
     dark: where a fill cannot carry the object rule, a hairline
     derived from the same family does.
     ========================================================== */
  --surface-cta:              var(--rbi-green-500); /* 3.10:1 obj vs surface (light),
                                                       5.14:1 vs surface (dark) */
  --surface-cta-hover:        var(--rbi-green-700); /* 5.42:1 obj vs white surface */
  --surface-cta-border:       var(--rbi-green-700); /* carries 1.4.11: 5.42 white /
                                                       4.82 surface-2 / 5.05 app bg */
  --surface-cta-border-hover: var(--rbi-green-800); /* 6.71 white / 5.97 surface-2
                                                       (was recorded 5.96 here and
                                                       5.97 on --color-success-ink;
                                                       same pair, exact value 5.966) */
  /* Ink ON the CTA fill. Always INK, in both themes.
     3.10:1 at rest — the accepted failure above. 5.42:1 on hover. */
  --on-surface-cta:           var(--rbi-white);
  /* Focus ring drawn INSIDE the CTA, the same two-tone mechanism the
     brand fill uses. It is a separate token from --focus-ring-on-brand
     because the surface underneath it is a different colour: a brand
     surface is near-black on both canvases and takes white, a CTA
     surface is mid-luminance green and does not. White here would be
     3.10:1 — a knife-edge 1.4.11 pass AND the same value as the
     button's own label, so the indicator would have no independent
     presence. The deepest navy rung gives 5.99:1 against the resting
     fill and 3.42:1 against the hover fill.
     Theme-invariant, because the fill it lands on is. */
  --focus-ring-on-cta:        var(--rbi-navy-900);

  /* ==========================================================
     CHROME-ACTIVE SURFACE — "you are here", in green.

     ADDED AT STAGE C+2 by product-owner decision. The active period
     control was navy and is now green.

     WHY IT IS ITS OWN FAMILY AND NOT --surface-cta. Sharing that
     token would re-make the one-token-two-jobs defect this file has
     now corrected twice: the CTA carries a reversible trade-off with
     a one-line escape hatch, and if that hatch is ever pulled the
     current-state marker must NOT follow it. Different decision,
     different owner, different token.

     WHY IT IS -700 WHEN THE CTA IS -500, WHICH IS THE ONE THING A
     READER WILL ASK. The CTA's value is a SPECIFICATION: it exists
     to match the brand owner's own confirm controls, so a derived
     rung defeats the decision and the 3.10:1 failure is the price of
     something bought. This surface has nothing to match — the brand
     owner ships no equivalent control — so the raw value would buy
     nothing and inherit the failure for free. It also carries a
     SMALLER label than a button does, on a surface read from across
     a room. So it takes the rung that passes: white on -700 is
     5.42:1, clean AA. The decision was about the button's colour,
     not about every green in the product.

     THE BORDER IS DECORATIVE ON LIGHT AND LOAD-BEARING ON DARK, and
     that asymmetry is measured, not assumed. The fill clears 1.4.11
     on its own against every light neutral (5.42 white / 4.82
     surface-2 / 5.05 app canvas — the surface-2 figure was recorded
     4.77 here and 4.82 for the identical pair on --surface-cta-border
     two blocks up; exact value 4.819). On dark it does NOT — 2.94:1
     against --color-surface, which is the surface this control
     actually sits on, and 2.49:1 against --color-surface-2. The dark
     border therefore inverts to -500 and carries the object rule at
     5.14:1. Identical mechanism, identical numbers, to the CTA's dark
     border two blocks up: -700 measures 2.94:1 on the dark surface in
     both roles, because it is the same rung on the same canvas.

     THE FOCUS RING IS NOT A CONTRAST FIX, IT IS AN INDEPENDENCE FIX,
     and the distinction matters because the obvious reading is wrong.
     White on -700 is 5.42:1 and would PASS. It is rejected anyway:
     white is also this surface's own label, so a white ring would
     have no independent presence — the same objection raised against
     white on the CTA, arriving here for a different reason. The
     deepest navy rung gives 3.42:1 against the fill and is
     unmistakably not the label. Theme-invariant, because the fill it
     lands on is.
     ========================================================== */
  --surface-chrome-active:        var(--rbi-green-700); /* 5.42 obj vs surface (light);
                                                           2.94 dark -> see border */
  --surface-chrome-active-border: var(--rbi-green-700); /* = the fill on light; the
                                                           fill carries 1.4.11 alone */
  --on-surface-chrome-active:     var(--rbi-white);     /* 5.42:1 — AA, both themes */
  --focus-ring-on-chrome-active:  var(--rbi-navy-900);  /* 3.42:1 vs the fill */

  /* ==========================================================
     FOCUS
     ========================================================== */
  /* The brand blue is dark enough to be its own focus ring on a
     light canvas: 7.70:1 on white, 7.18:1 on the app canvas, 6.85:1
     on a zebra row. The previous accent needed a separate darker
     ink rung to clear 1.4.11 here; this one does not. */
  --focus-ring: var(--rbi-blue-500);
  /* STILL NOT WIRED BY ANY COMPONENT — and Stage C made wiring it
     MORE urgent, not less. The default ring is measured against a
     light surface; on the brand band it is measured against navy,
     and now that the ring and the band are the same hue family it
     lands at 1.72:1 there (it was 2.09:1 before this palette). That
     is a live WCAG 1.4.11 failure on every sortable header sitting
     on a brand band. White clears it at 13.22:1 on the light band
     and 9.69:1 on the dark one, which is why this token is
     theme-invariant and correctly absent from the dark block. */
  --focus-ring-on-brand: var(--rbi-white);
  /* Selected-tab underline. Shares a value with the focus ring in
     this theme, different role — do not alias them. */
  --border-selected: var(--rbi-blue-500);

  /* ==========================================================
     ACCENTS
     ========================================================== */
  --color-accent:     var(--rbi-blue-500); /* fills, active, highlights */
  --color-accent-ink: var(--rbi-blue-500); /* text/links on light — 7.70:1 */

  /* `--color-emphasis` IS RETIRED AT STAGE C+2, and `--color-gold` /
     `--color-gold-ink` before it. The chain is worth one comment because
     it is the same role failing to find a home three times.

     The gold was a punctuation accent the brand owner does not own.
     --color-emphasis replaced it with the one warm value they DO own,
     and it had exactly one consumer: the confidential mark on the
     chrome band. That mark is now green (--mark-on-brand above), which
     leaves this token with zero consumers — so it is DELETED rather
     than left declared to acquire new ones, on the same wire-up-or-
     delete rule this file applies everywhere else.

     The honest reading, recorded so the role is not re-invented: the
     emphasis role never existed on its own merits. It existed because
     one mark needed a colour after the gold was retired, and that mark
     has now found the colour it should always have had. A future
     punctuation accent is a new decision, not a revival of this one.

     Consequence in layer 1: --rbi-warm-500 now has no direct consumer.
     It is not orphaned — it is the brand-truth core its two derived
     rungs (-400 / -700, both consumed by categorical position 5) are
     derived FROM, which is a different standing from an unconsumed
     leaf. brand.css states that distinction as a rule. */

  /* ==========================================================
     THE STATUS LABEL PAIR — a fill and an ink that hold still
     across themes. THEME-INVARIANT BY CONSTRUCTION, GL-003
     section 2.2b.

     A status label names a state in words on a panel, and it
     wears one appearance on both canvases. The word carries the
     state and the colour carries the family, so a reader
     scanning a half-finished list reads one visual language
     whatever theme they are in.

     DECLARED ONCE, AND ABSENT FROM THE DARK BLOCK. That absence
     is a correctness claim (section 7), and here it is the
     product decision itself rather than a consequence of one.
     Adding a --surface-status-* line to the dark block silently
     unpins the whole family.

     THE DIRECTION IS THE MECHANISM AND IT IS NOT DECORATIVE.
     These eight hold the values; the four light status pairs
     below read FROM them. Written the other way round —
     --surface-status-info: var(--color-info-bg) — it would NOT
     pin: custom-property substitution resolves the cascade-
     winning value of the reference ON THAT SAME ELEMENT, the
     theme attribute is stamped on the root element, so the dark
     block would win and the alias would resolve to the dark
     tint. The token would swap with the theme while reading as
     though it could not.

     Because the value moved rather than being retyped, no
     literal is restated and enforcement test 6 has nothing to
     flag. A later light retune moves the label with the tint,
     on both canvases, by construction.

     THE FAMILIES ARE ASYMMETRIC AND THAT IS INHERITED, NOT NEW.
     Success reads layer 1, so a rebrand reaches the pinned
     success label. The other three are product literals with no
     primitive behind them (see the header's deliberate
     exception), so a rebrand does not reach them and is not
     meant to.
     ========================================================== */
  --surface-status-info:       #E7F0FA;
  --on-surface-status-info:    #215390; /* 6.74:1 on the fill above */
  --surface-status-warning:    #FBEEDD;
  --on-surface-status-warning: #8F4E10; /* 5.63:1 on the fill above */
  --surface-status-success:    var(--rbi-green-050);
  --on-surface-status-success: var(--rbi-green-800); /* 5.94:1 on the fill above */
  --surface-status-danger:     #FBE7E4;
  --on-surface-status-danger:  #B32D24; /* 5.32:1 on the fill above */

  /* ==========================================================
     STATUS — the framework flag language.
     warning / danger / info are PRODUCT and stay literal.
     success is BRAND and reads layer 1 — see the header.

     THE FOUR TINT PAIRS READ THE PINNED PAIR ABOVE and keep
     every consumer they had. They still swap with the theme,
     because the dark block restates them and does not restate
     the pinned pair. A tint and a label are two roles; only one
     of them holds still.
     ========================================================== */
  --color-success:     var(--rbi-green-700); /* on/above target — 5.42:1 on white */
  --color-success-ink: var(--on-surface-status-success); /* 5.97:1 on zebra */
  --color-success-bg:  var(--surface-status-success);
  --color-warning:     #D9822B; /* watch + target-line */
  --color-warning-ink: var(--on-surface-status-warning); /* AA on white (6.43), zebra (5.19+) */
  --color-warning-bg:  var(--surface-status-warning);
  --color-danger:      #CC3A31; /* red flag — below band */
  --color-danger-ink:  var(--on-surface-status-danger);
  --color-danger-bg:   var(--surface-status-danger);
  --color-info:        #2D6CB5;
  --color-info-ink:    var(--on-surface-status-info);
  --color-info-bg:     var(--surface-status-info);
  /* Ink on a solid status fill. Role-stable, same reasoning as
     --on-surface-brand: a status fill must never invert its ink.

     TWO OF THE FOUR FAMILIES CARRY THIS ROLE. warning and info do
     not, and that is a measured absence rather than an omission.
     Neither has a consumer painting text on its solid fill in any
     product today. Grep before adding a third, and report the count
     even when it is zero.

     --on-surface-success WAS ADDED 2026-08-10, and it closes a
     missing role rather than a wrong token. A quiz option marker in a
     second product painted --on-surface-cta on this fill. That reads
     5.42:1 on light by coincidence, because the light success fill
     and --surface-cta-hover are the same rung. On dark the same
     declaration measures 1.79:1.

     Both rungs come from GL-003 §2.3a-i and neither is a judgement.
     The light fill is Y .1436, under the .1833 white-ink ceiling, so
     white carries it at 5.42:1. The dark fill is Y .5382, far over
     the .2046 deepest-ink floor, so the deepest navy rung carries it
     at 10.40:1. Same table and same two thresholds as the danger ink
     one line below. */
  --on-surface-success: var(--rbi-white);
  --on-surface-danger:  var(--rbi-white);

  /* --- THE WATCH MARK — one alias, and it exists to close a hole in a
     closed list, not to start a family.

     The status-dot pattern takes its colour as a --dot custom property
     written from JS, i.e. JS passes a TOKEN NAME. That is only safe while
     every entry on the permitted list is theme-stable, because a name
     written once must stay correct when the theme toggle flips underneath
     it without a re-render.

     Four of the five permitted values are theme-stable in exactly that
     sense: --color-success / -danger / -info / --color-text-muted are all
     restated in the dark block, so the NAME keeps resolving correctly. The
     watch state is the one that is not — on a light canvas the mark has to
     take the INK rung (the fill measures 2.93:1 and fails SC 1.4.11 there),
     and on a dark canvas it takes the FILL rung. One role, two rungs, and
     no single existing name expresses it.

     So this is the alias that makes that asymmetry addressable. It is one
     token rather than the five semantic --mark-* tokens the status-dot
     block explicitly rejected, and the test that separates it from those
     five is whether a rule elsewhere REQUIRES it to exist: the closed list
     does, because a list passed by name cannot contain an entry that has no
     name.

     DO NOT "tidy" the light/dark asymmetry away by pointing both at the
     fill. That is the 2.93:1 failure the exclusion was measured to prevent.
     Measured as a graphical object against surface / surface-2 / bg:
       light  (-ink rung)  6.43 / 5.72 / 5.99
       dark   (fill rung)  6.75 / 5.72 / 7.59  --- */
  --mark-watch: var(--color-warning-ink);

  /* ==========================================================
     REFERENCE INK — the peer-comparison figure.

     ADDED AT STAGE C+3 by product-owner decision. The comparison
     figure rendered in the muted neutral and got lost against the
     subtitle it sits beside; he asked for it in the brand green.

     IT IS DELIBERATELY ADJACENT TO THE STATUS BLOCK AND DELIBERATELY
     NOT IN IT. This is a NEUTRAL comparison — "here is the team's
     number beside yours" — not a verdict. It must never be
     --color-success-ink, and the reason is not stylistic: reusing the
     success ink would fuse a neutral read-out into the flag language,
     which is the same semantic borrow the CTA split (--surface-cta)
     was created to undo. Two meanings that can move independently
     need two tokens even when they resolve to the same value.

     AND ON BOTH CANVASES TODAY THEY DO RESOLVE TO THE SAME VALUE.
     Stated here rather than discovered later, because it is the
     sharpest thing about this token: light -800 = --color-success-ink
     light; dark -300 = --color-success-ink dark. Byte-identical, both
     themes. So a reader gets NO COLOUR SEPARATION between "the team
     did 322" and "you are at or above target". The separation is
     CHASSIS, and it is hue-independent, which is why it survives
     every colour-vision deficiency:
       - a flag is ALWAYS a pill (--color-success-bg behind it) or a
         caret glyph plus text. Never bare ink.
       - a reference figure is ALWAYS bare ink and ALWAYS prefixed
         with the literal word "Team".
     That is the same "never colour alone" guarantee the flag language
     already carries, doing double duty. IF A FUTURE CHANGE STRIPS THE
     CARET OR THE PILL FROM A FLAG, THIS TOKEN'S SEPARATION GOES WITH
     IT — and the cheap fix at that point is to move this role to a
     rung of its own, not to re-borrow the success ink.

     WHY -800 / -300 AND NOT A NEW RUNG. No new rung was authorised
     and none is needed: brand.css already designates exactly these
     two as "the ink rung for this canvas" (-800 for light at 6.71:1,
     -300 for dark), and the raw brand -500 is ruled out for text on a
     light canvas in every role, in every theme. Measured, and note
     this is a legibility GAIN over the muted neutral it replaces, not
     a trade:
       light  -800  6.71 vs surface / 5.97 vs surface-2 (the zebra
              row) / 6.25 vs app canvas / 5.87 vs the warning tint a
              callout uses.   Muted neutral was 5.77 / 5.13.
       dark   -300  8.93 vs surface / 7.56 vs surface-2 / 10.04 vs app
              canvas / 7.72 vs the dark warning tint.
              Muted neutral was 5.91 / 5.01.
     All four backdrops are measured because this ink appears in tile
     subs, in funnel stage lines beside a table, and inside a callout.
     Every figure clears AA at the 13px this text renders at.

     THE TENSION, RECORDED BECAUSE IT WAS SEEN AND OVERRULED RATHER
     THAN MISSED. Green on a comparison figure can read as a verdict,
     and the compare control's own on-screen copy says "Context, not a
     scoreboard." This is the one green in the system that carries no
     information — it is here for PROMINENCE. The product owner was
     shown that and chose it. Do not "restore" the neutral on the
     grounds that it is more correct; it was considered.
     ========================================================== */
  --color-reference-ink: var(--rbi-green-800); /* 6.71 surface / 5.97 zebra */

  /* ==========================================================
     NEUTRALS — PRODUCT, not brand. Literal by design.
     ========================================================== */
  --color-bg:            #F5F7FA; /* app canvas */
  --color-surface:       #FFFFFF; /* tiles, cards, table */
  --color-surface-2:     #EEF2F6; /* zebra, hover, inset */
  --color-border:        #DDE3EA; /* hairlines, gridlines */
  --color-border-strong: #C4CDD6;
  --color-text:          #2A3742; /* body */
  --color-text-muted:    #5B6774; /* labels, captions */
  /* The one neutral that is genuinely brand-bound: heading and
     numeral ink IS the authoritative navy on a light canvas.
     13.22:1 on white. */
  --color-text-strong:   var(--rbi-navy-700);

  /* Modal scrim. Theme-invariant (it darkens whatever is behind it). */
  --scrim-backdrop: color-mix(in srgb, var(--rbi-navy-900) 45%, transparent);

  /* ==========================================================
     FUNNEL RAMP — six rungs, ordered, role-stable.

     REBUILT AT STAGE C, and this is the rung set that finally
     satisfies both accessibility rules on fill alone.

     What was wrong before: the ramp walked navy -> slate -> teal ->
     GREEN, so a sequential progression terminated in a status
     colour. A reader who has learned that green means on-target in
     this product cannot un-learn it for one chart. Terminating an
     ordered scale in a semantic colour is a category error, and it
     is now gone: every rung is the brand navy family.

     What that buys, measured:
       (a) the ramp is MONOTONIC in luminance for the first time
           (.007 .014 .029 .050 .101 .231 — previously it dipped
           twice and the "monotonic" claim was false);
       (b) every rung clears 4.5:1 with its paired ink (1.4.3);
       (c) every rung clears 3:1 against the white surface (1.4.11)
           ON FILL ALONE. The light canvas needs no border trick.
           The two light failures carried since Stage A — rung 5 at
           2.91:1 as text AND as an object, rung 6 at 4.14:1 — are
           closed by construction, not patched.

     The ink flips to navy at rung 6 because rung 6 is the only rung
     above the .1833 white-ink ceiling. That flip is the whole reason
     each rung ships its own ink.

     A five-stage funnel samples rungs 1,2,3,4,6 and drops rung 5.
     Do not renumber to close the gap.

     KNOWN LIMITATION, accepted and stated: adjacent separation at
     the dark end is 1.12:1 (rungs 1-2). A ramp every rung of which
     must also stay under 3:1 against white has only so much room.
     Acceptable because the funnel never encodes stage by colour
     alone — every bar carries a text label and a count.
     ========================================================== */
  --brand-ramp-1: var(--rbi-navy-900);
  --brand-ramp-2: var(--rbi-navy-800);
  --brand-ramp-3: var(--rbi-navy-700);
  --brand-ramp-4: var(--rbi-navy-600);
  --brand-ramp-5: var(--rbi-navy-500);
  --brand-ramp-6: var(--rbi-navy-400);
  /* ink-on-fill (1.4.3) / fill-vs-white-surface (1.4.11) */
  --brand-ramp-1-ink: var(--rbi-white);     /* 18.56 / 18.56 */
  --brand-ramp-2-ink: var(--rbi-white);     /* 16.52 / 16.52 */
  --brand-ramp-3-ink: var(--rbi-white);     /* 13.22 / 13.22 */
  --brand-ramp-4-ink: var(--rbi-white);     /* 10.52 / 10.52 */
  --brand-ramp-5-ink: var(--rbi-white);     /*  6.98 /  6.98 */
  --brand-ramp-6-ink: var(--rbi-navy-900);  /*  4.97 /  3.73 */

  /* ==========================================================
     DATA-VIZ — categorical (reps, share bars).

     Six NOMINAL positions. Rebuilt at Stage C against four rules;
     TWO OF THE FOUR WERE UNATTAINABLE AS WRITTEN and are narrowed at
     Stage D rather than left as promises the palette cannot keep:
       1. >= 3:1 against both --color-surface and --color-bg, in
          both themes. Met; unchanged;
       2. no member within perceptual reach of success / warning /
          danger / info — NARROWED to "…where the two can co-occur
          as graphical objects a reader must tell apart." Three of
          the original six failed the old rule for real (a gold one
          hue step off warning, a rust off danger, an olive off
          success, all three used as chart fills); all three are
          gone. What remains is position 3 sitting dE00 1.6 from the
          notice-blue on the dark canvas, and that is NOT the same
          kind of finding: the notice-blue is never a chart area on
          either canvas — it is a hairline border, a tint, an ink and
          a checkbox accent — so the pair is a chart fill against a
          badge, two things no reader ever compares. The narrowed
          rule is checkable; the old one was not satisfiable, because
          the dark canvas has to hold six categorical members AND
          four status colours inside a 3.15:1 luminance window;
       3. six-way separable for deuteranopia and protanopia —
          NARROWED to THREE guaranteed positions (1, 5, 6) plus three
          legitimate-but-unguaranteed ones (2, 3, 4). The arithmetic
          is in brand.css beside the rungs and it is not close: the
          dark luminance budget allows 1.26:1 per gap across six
          positions, and simulation shows 1.20:1 collapses while
          1.35:1 holds. A CONSUMER RENDERING MORE THAN THE ROBUST
          TRIPLE AT ONCE MUST CARRY A NON-COLOUR KEY. That is a
          component contract; no value in this file can supply it;
       4. position 1 anchors to the brand — and as of Stage D it
          actually DOES. The anchor rung was a duplicated literal of
          the brand navy, not a reference to it, so a rebrand would
          have moved the brand and left the anchor behind. Six of the
          twelve layer-1 categorical rungs had that defect.

     On the light canvas five of six take white ink, and that is not
     a coincidence: the .1833 luminance ceiling that makes white ink
     clear 4.5:1 is the SAME ceiling that makes a fill clear 3:1
     against white. Sizing the deep rungs for one rule satisfied the
     other. Position 6 sits above that ceiling on purpose — it is
     the luminance-separated brand position — so it takes navy ink.

     The previous set's binding constraint was a pair of 4.53:1
     margins with no headroom. The thinnest margin here is 3.48:1 on
     an OBJECT rule (position 6 against the app canvas) and 4.97:1
     on any TEXT rule. The knife-edge is gone.

     WHAT STAGE D DID AND DID NOT DO ABOUT THE MEASURED DEFECTS:

       (a) Position 4 was the weak MEMBER, not the 3/4 pair the old
           design note named. Measured: 4-vs-5 = dE00 3.8 under light
           deuteranopia and 1-vs-4 = dE00 2.2 under dark protanopia,
           while 3-vs-4 measured 21.0. The structural argument that a
           near-neutral (position 5) "collides with nothing" runs
           BACKWARDS: a near-neutral is only safe against colours
           that STAY saturated, and position 4 desaturates toward
           grey under a deficiency and lands on it.
           FIXED ON LIGHT, to the max-min point and not to the point
           that repairs one pair: the deep rung moved from Y .1071 to
           Y .0718, so 4-vs-1 and 4-vs-5 now sit at 1.53:1 and
           1.52:1 in luminance where they sat at 1.98:1 and 1.18:1.
           The minimum over the set is the objective — targeting a
           named pair is what produced the wrong diagnosis the first
           time. UNVERIFIED IN dE00: this is a luminance-budget
           decision, computed exactly; whether it lands where the
           arithmetic predicts is a simulation question and must be
           re-measured before it is believed. THE KNOWN COST: 3-vs-4
           luminance separation tightens from 1.23:1 to 1.05:1, so
           light 3-vs-4 WILL degrade from its measured 21.0 / 8.8.
           That trade is only legitimate under the narrowed rule 3 —
           neither 3 nor 4 is a guaranteed position, so their pair is
           not a promise this file makes. Under the old rule 3 it
           would have been an illegitimate trade.
           NOT FIXED ON DARK, deliberately, because it cannot be: the
           anchor's bright rung sits at Y .231, barely above the
           .2046 ink floor, and every direction position 4 could
           travel from Y .2881 buys one separation and costs two.
           This is what the non-colour key is for.
       (b) Position 3 vs the notice-blue on dark, dE00 1.6: CLOSED as
           NOT A DEFECT under the narrowed rule 2 above, on a
           co-occurrence audit rather than on a re-measurement. The
           two values sit 1.006:1 apart in luminance, so the 1.6 is
           exactly what arithmetic predicts and re-measuring it would
           tell us nothing new. What settles it is that they never
           render as comparable objects.

     The one structural claim that MEASURED OUT is position 6's:
     separating it from position 1 by luminance rather than hue makes
     1-vs-6 the strongest pair in all four conditions (dE00
     29.9-30.3). Luminance is the only channel a deficiency never
     degrades. The trade was sound; it was spent on the wrong slot,
     and Stage D spent it on the right one.
     ========================================================== */
  --viz-cat-1: var(--rbi-cat-1-deep);   --viz-cat-1-ink: var(--rbi-white);     /* 13.22 */
  --viz-cat-2: var(--rbi-cat-2-deep);   --viz-cat-2-ink: var(--rbi-white);     /*  5.96 */
  --viz-cat-3: var(--rbi-cat-3-deep);   --viz-cat-3-ink: var(--rbi-white);     /*  8.22 */
  --viz-cat-4: var(--rbi-cat-4-deep);   --viz-cat-4-ink: var(--rbi-white);     /*  8.62 — was
                                                                                  6.69 at the
                                                                                  lighter rung */
  --viz-cat-5: var(--rbi-cat-5-deep);   --viz-cat-5-ink: var(--rbi-white);     /*  5.66 */
  --viz-cat-6: var(--rbi-cat-6-deep);   --viz-cat-6-ink: var(--rbi-navy-900);  /*  4.97 */
  --viz-grid: #E3E8EE;  /* neutral — product */
  /* --viz-axis DELETED at Stage D. Its only consumers were the chart
     axis classes removed at Stage C with the three unreachable SVG
     primitives, so a component deletion orphaned a token and the
     Stage C audit — which had just written the seam rule — did not
     look in this direction. --viz-grid stays: the heatmap cell border
     reads it. */

  /* ==========================================================
     DATA-VIZ — sequential (heatmap).

     Now samples the brand NAVY family directly. The separate
     twelve-rung monochrome ladder that Stage A declared is DELETED:
     once the brand core became a full ten-rung single-hue family,
     the ladder was a second monochrome scale sitting beside the
     first, and two scales that must agree are one scale too many.

     The Stage A guarantee is preserved exactly — both themes draw
     from ONE ladder, and theme-awareness lives in which rungs are
     sampled, never in the rungs themselves. It is just that the
     ladder is now the navy family.

     Light walks lightest -> darkest. Rung --rbi-navy-400 is skipped
     on purpose: it is the rung nearest the ink dead band, so the
     step is spent where the ink crossover happens.
     ========================================================== */
  --viz-seq-0: var(--rbi-navy-050);  --viz-seq-0-ink: var(--rbi-navy-900);  /* 16.30 */
  --viz-seq-1: var(--rbi-navy-100);  --viz-seq-1-ink: var(--rbi-navy-900);  /* 14.19 */
  --viz-seq-2: var(--rbi-navy-200);  --viz-seq-2-ink: var(--rbi-navy-900);  /* 10.92 */
  --viz-seq-3: var(--rbi-navy-300);  --viz-seq-3-ink: var(--rbi-navy-900);  /*  7.17 */
  --viz-seq-4: var(--rbi-navy-500);  --viz-seq-4-ink: var(--rbi-white);     /*  6.98 */
  --viz-seq-5: var(--rbi-navy-700);  --viz-seq-5-ink: var(--rbi-white);     /* 13.22 */

  /* ==========================================================
     DATA-VIZ — diverging (vs-target delta): DELETED AT STAGE D.

     Five lines, zero consumers, and Stage C kept them on the
     grounds that they were "free to keep" — they had just been
     re-pointed from literals onto the status tokens the flag logic
     reads, so they could no longer go stale.

     That reasoning was sound and it is not why they are gone. They
     are gone because Stage D writes an orphan rule for this layer
     with three structural exceptions, and a complete unconsumed
     scale fits none of them. Granting it a bespoke fourth exception
     on the rule's FIRST application is how the two previous rules in
     this file died: the seam rule was violated by the same revision
     that wrote it, and the chrome enumeration shipped with the wrong
     count. A rule that starts with a carve-out is not a rule.

     The recipe is in the Guideline (§2.6) and it is one minute of
     work when a consumer exists: the endpoints ARE --color-danger
     and --color-success, the mild steps are a color-mix() of each
     45% toward --color-surface, and the midpoint is
     --color-surface-2. Declare it WITH its first consumer, and
     measure the two mild steps as objects on both canvases at that
     point — which was never done, because nothing rendered them.
     ========================================================== */

  /* ==========================================================
     TYPOGRAPHY.
     ========================================================== */
  --font-sans: var(--rbi-font-sans);
  /* CODE, not "mono". The rename is the point: this token no longer sets
     numerals. Every number a reader compares is now --font-sans with
     `font-variant-numeric: tabular-nums` — the sans face carries tabular
     figures, so digits align in a column without changing typeface. What is
     left for this token is text that is genuinely code-shaped and benefits
     from a fixed advance: schema field names, inline `code`, chart axis
     labels. Retiring mono for numerals is right; retiring it entirely would
     flatten a real semantic distinction the empty-state design depends on.
     DO NOT reach for this token for a numeral. */
  --font-code: var(--rbi-font-code);
  /* RETIRED at Stage B: --font-mono-nudge. It corrected an x-height
     mismatch between two typefaces that no longer coexist in a numeral.
     Both call sites are gone. Do not reintroduce it. */
  --text-metric:    34px; --text-metric-lh: 1.1;
  --text-metric-sm: 22px; --text-metric-sm-lh: 1.15;
  --text-h1:        22px; --text-h1-lh: 1.25;
  --text-h2:        16px; --text-h2-lh: 1.3;
  --text-h3:        13px; --text-h3-lh: 1.35;
  --text-body:      14px; --text-body-lh: 1.5;
  --text-caption:   13px; --text-caption-lh: 1.4;
  /* --text-micro-lh had no consumer until Stage D and was NOT an
     orphan: it is the second half of a mandated pair (exception (a)
     in the header), the same standing an unconsumed -ink has. Every
     other size in this scale is paired with its line-height at the
     call site; this one was simply never picked up. Stage D's target
     annotation is its first consumer, which closes the pattern
     rather than breaking it. Deleting it would have let the next
     consumer of --text-micro guess a line-height. */
  --text-micro:     13px; --text-micro-lh: 1.3;

  /* --- Eyebrow tracking ---
     ONE token for the uppercase small-caps label that appears above
     or beside a value — tile labels, stat labels, table heads, form
     labels, subheads, group heads, the persona label, the empty-state
     title, the confidential marker.

     WHY IT IS A TOKEN AND NOT NINE DECLARATIONS: those nine sites
     shipped TWO different values (.03em and .04em) with no rule
     distinguishing them, which is drift rather than a distinction.
     .04em wins because six of the nine already used it AND because
     wider tracking on uppercase is the correct optical correction —
     which matters here, since this text is read from across a room
     and every one of these sites renders at the 13px floor.

     WHAT IT DOES NOT UNIFY, on purpose: size, weight and ink. A
     subhead is heading ink at 700 and a field label is muted at 600;
     those differences are real. Only the transform and the tracking
     were arbitrary, so only they are bound here. */
  --eyebrow-tracking: .04em;

  /* --- Spacing (4px base) — unchanged --- */
  --space-xs: 4px; --space-sm: 8px; --space-md: 12px;
  --space-lg: 16px; --space-xl: 24px; --space-2xl: 32px; --space-3xl: 48px;

  /* --- Radius — REBUILT AT STAGE D as a six-rung ladder.
     Was 4 / 8 / 12 / pill. The old scale had one value for
     everything from a 2px-wide bar cap to a 40px chip, so a
     container could not be made rounder without rounding a
     micro-mark into a lozenge. Six rungs separate those cases.

     THE NAMES SHIFTED MEANING, WHICH IS THE ONE DANGEROUS THING
     ABOUT THIS CHANGE. --radius-sm was 4px and is now 8px. A
     find-and-replace across the component layer is WRONG: seven of
     its twenty existing declarations are micro-marks that must
     DEMOTE to --radius-xs and hold at 4px, and two of those sit on
     objects with a 2px min-width. This lands as a walk, site by
     site, from the demotion list in the Guideline (§5.1).

     --radius-xl and -2xl have no consumer today. They are declared
     under exception (b) in the header — rungs of a declared scale —
     because the alternative is the next author of a large container
     writing `border-radius: 24px` in a component file. Which
     container takes them is a visual call and is not made here.

     ONE SANCTIONED LITERAL SURVIVES in the component layer: the 4px
     -wide rep chip keeps `border-radius: 2px`, because an object 4px
     across cannot take a 4px radius without becoming a capsule.
     Recorded so it is not "consistency-fixed". --- */
  --radius-xs:   4px;   /* micro-marks: bar caps and tracks, heatmap cells,
                           legend swatches, skeletons, clipped scroll edges */
  --radius-sm:   8px;   /* small controls and inline chrome: inputs, buttons,
                           tabs, callouts, toast, chips-that-are-not-pills */
  --radius-md:  12px;   /* contained surfaces: tiles, section band/body, nav,
                           dialog, empty state */
  --radius-lg:  16px;   /* the outermost panel of a view */
  --radius-xl:  22px;   /* declared, unconsumed — see above */
  --radius-2xl: 28px;   /* declared, unconsumed — see above */
  --radius-pill: 999px;

  /* --- Status-dot geometry ---
     The size of a state MARK that sits beside its own label. 7px is
     the optical match for the 13px text it accompanies; it is a token
     rather than a literal because the mark is about to appear on the
     gate screens and on a landing page, and a second author choosing
     8px is precisely the drift this file exists to prevent.

     THE MARK'S COLOUR IS NOT A TOKEN, DELIBERATELY. It arrives as a
     --dot custom property from the call site, the same CSSOM idiom
     the bar fills and heatmap cells already use, so the closed list
     of permitted values lives in the Guideline (§6.2) with its
     measured contrasts rather than as five semantic tokens with one
     consumer each. Two of the obvious candidates FAIL the 3:1 object
     rule on a light canvas and are excluded there — see §6.2. --- */
  --status-dot-size: 7px;

  /* --- Shadow ---
     Navy-tinted to the authoritative brand navy. Expressed as a
     color-mix() of the primitive rather than a baked rgba() triplet,
     so the tint follows a rebrand. */
  --shadow-sm: 0 1px 2px color-mix(in srgb, var(--rbi-navy-700)  6%, transparent);
  --shadow-md: 0 2px 8px color-mix(in srgb, var(--rbi-navy-700)  8%, transparent);
  --shadow-lg: 0 8px 24px color-mix(in srgb, var(--rbi-navy-700) 12%, transparent);
}

/* ============================================================
   DARK THEME.
   Restates VALUES only. Every role above keeps its meaning here:
   a fill stays a fill, an ink stays an ink. If you find yourself
   wanting to flip a role in this block, you need a new token.

   Note what is ABSENT: the hero band's ink, veil, rule and border
   tokens, --mark-on-brand, --scrim-backdrop, --focus-ring-on-brand,
   the CTA fill / hover / ink / focus-ring, and the chrome-active
   fill / ink / focus-ring are theme-invariant and deliberately not
   repeated. Absence here is a correctness claim.
   (The diverging ramp used to be listed in that sentence as "derived
   from tokens that are already themed". It is DELETED at Stage D, so
   it is out of the sentence too — a note explaining why a token that
   no longer exists is absent is exactly the shape of comment that
   revived a deleted role once already.)
   ============================================================ */
:root[data-theme="dark"] {

  /* --- Brand surfaces ---
     --surface-brand-border / -border-hover were DELETED at Stage C+3
     and --surface-brand-hover at Stage D, in both themes. Six dead
     lines in total, no consumer in any layer at any point. See the
     light block for why the orphaning went unnoticed and for the
     bidirectional seam rule it graduated into. --- */
  --surface-brand:              var(--rbi-navy-600);
  --on-surface-brand:           #F2F6FA; /* neutral — today's --color-text-strong(dark),
                                            pinned literally so a neutral-ramp retune
                                            cannot leak onto a brand surface. 9.69:1 */
  /* --on-surface-brand-subtle is NOT restated here. Deleting its dark
     literal is the whole of the fix. It is declared once in :root, as
     a derivation of --on-surface-brand. It therefore follows the ink
     restated one line above, and re-resolves on this canvas by itself.

     It was a pinned #8CA0B3 measuring 3.90:1 on this band, a live SC
     1.4.3 failure. It now measures 6.61:1. Read the :root block before
     re-pinning anything here. */

  --surface-brand-hero: linear-gradient(100deg, var(--rbi-navy-900) 0%, var(--rbi-blue-500) 165%);

  /* --- CTA surface ---
     Only the two BORDER rungs are restated here, and they invert
     relative to the fill rather than following it. On light the
     border is DARKER than the fill because the canvas is light; on
     dark it must be LIGHTER for the same reason. This is a value
     restatement of one role, not a role flip.

     What makes it necessary: the resting fill clears 3:1 against the
     dark surface on its own (5.14:1), but the HOVER fill does not
     (--rbi-green-700 measures 2.94:1 there). Holding the border at
     the resting green means the button darkens under the pointer
     while keeping a bright rim — a "pressed" read that also carries
     SC 1.4.11 for the hover state at 5.14:1.

     --surface-cta, -hover, --on-surface-cta and --focus-ring-on-cta
     are THEME-INVARIANT and deliberately absent from this block. The
     fill is a specified brand value that reads on both canvases;
     restating it would create a second place to get it wrong. */
  --surface-cta-border:       var(--rbi-green-500); /* = the resting fill; 5.14:1 vs surface */
  --surface-cta-border-hover: var(--rbi-green-500); /* holds while the fill darkens */

  /* --- Chrome-active surface ---
     Only the BORDER is restated, and for the same reason and with the
     same numbers as the CTA border directly above: -700 is the fill in
     both themes, and on this canvas -700 measures 2.94:1 against
     --color-surface (the surface this control sits on) and 2.49:1
     against --color-surface-2. On light the fill carries SC 1.4.11 by
     itself and this border is decoration; here it is the only thing
     carrying it, at 5.14:1 / 4.36:1 / 5.79:1 against surface /
     surface-2 / app canvas.

     The label does not move: white on -700 is 5.42:1 on both canvases,
     because the fill is theme-invariant. --surface-chrome-active,
     --on-surface-chrome-active and --focus-ring-on-chrome-active are
     therefore deliberately absent from this block. --- */
  --surface-chrome-active-border: var(--rbi-green-500);

  /* --- Focus ---
     The ring is one rung LIGHTER than the accent fill on this canvas,
     which is a Stage C correction rather than a preference. With the
     ring and the brand band now in the same hue family, the accent
     fill rung would have landed at 2.87:1 against the dark brand
     band and failed 1.4.11 — a regression this palette would
     otherwise have introduced. The lighter rung clears it at 4.83:1
     while still clearing the dark surface at 7.33:1.
     (CORRECTED at Stage C+3: recorded 7.41 here and in GL-003 §8.3.
     Exact arithmetic on blue-300 (Y .4327) against --color-surface
     (Y .0159) gives 7.326. 7.41 is not this pair's figure at all —
     see --brand-ramp-5-ink below, which carried the same slip.) */
  --focus-ring:      var(--rbi-blue-300);
  --border-selected: var(--rbi-blue-400);
  /* --focus-ring-on-brand is theme-invariant — not restated. */

  /* --- Accents --- */
  --color-accent:     var(--rbi-blue-400);
  --color-accent-ink: var(--rbi-blue-300);
  /* --color-emphasis was retired at Stage C+2 with zero consumers.
     This block carried a "theme-invariant, not restated" note for it
     for one revision after the deletion — a comment describing a
     token that no longer exists, which is how a deleted role gets
     revived. Removed at Stage C+3. */

  /* --- Status — raised luminance for the dark canvas.

     THE AFFIRMATIVE FILL MOVED FROM -500 TO -300 AT THIS REVISION.
     -500 is now the CTA fill (see the header), and leaving the flag
     fill there would have made two roles the identical hex on one
     canvas. The full reasoning is in the header; the measurements
     are here:

       object vs --color-surface   8.93:1  (was 5.14:1 at -500)
       object vs --color-surface-2 7.56:1
       object vs --color-bg       10.04:1
       separation from --surface-cta  1.74:1 luminance, dL* 17.7

     This costs no text contrast, and that is checkable rather than
     asserted: --color-success has exactly two consumers on either
     canvas — a gauge band and a progress fill — and BOTH are
     textless. The flag pill a reader actually sees is -ink on -bg
     and does not move.

     It also stays inside the projected-screen glare budget: Y .538
     sits below --rbi-navy-200 (Y .568), which the dark funnel ramp
     already uses.

     --color-success and --color-success-ink now resolve to the same
     rung on this canvas. They stay SEPARATE declarations, for the
     same reason the accent fill and accent ink do on light: one
     value serving two roles today is not two roles merged. --- */
  --color-success: var(--rbi-green-300);  --color-success-ink: var(--rbi-green-300);  --color-success-bg: var(--rbi-green-950);
  --color-warning: #E8964A;  --color-warning-ink: #F0AC6A;  --color-warning-bg: #3A2A17;
  --color-danger:  #E5695C;  --color-danger-ink:  #F08579;  --color-danger-bg:  #3A1E1B;
  --color-info:    #4B93E0;  --color-info-ink:    #78AEE8;  --color-info-bg:    #16283D;
  /* The watch mark takes the FILL rung on this canvas and the INK rung on
     light. See the light block: this is the one entry on the status-dot's
     permitted list that is not theme-stable, which is the whole reason the
     alias exists. 6.75 / 5.72 / 7.59 as an object vs surface / surface-2 / bg. */
  --mark-watch: var(--color-warning);
  /* --- Reference ink — the ink rung this canvas designates, exactly
     as the light block takes the light one. 8.93 vs surface / 7.56 vs
     surface-2 (zebra) / 10.04 vs app canvas / 7.72 vs the dark
     warning tint. It resolves to the SAME rung as --color-success and
     --color-success-ink here, which is the third role on one value on
     this canvas; they stay three separate declarations for the reason
     the light block gives at length. --- */
  --color-reference-ink: var(--rbi-green-300);
  /* The deepest navy rung on the dark affirmative fill: 10.40:1. The
     light rung is white and is declared once in :root. Read that
     block before moving either. Both are rulings of GL-003 §2.3a-i's
     two thresholds, and neither was picked by eye. */
  --on-surface-success: var(--rbi-navy-900);

  /* CLOSED 2026-08-10 by a threshold table, not by a re-pick.
     The dark danger fill sits at Y .2753. GL-003 §2.3a-i puts the
     white-ink ceiling at Y .1833. It puts the deepest-ink floor at
     Y .2046. This fill is over both, so it is outside the dead band
     and one row gives one answer.

     WHITE HERE MEASURED 3.2275:1 and failed SC 1.4.3. Four sites
     published that pair as 3.24, and the exact figure from the two
     hexes is the one above. It cleared 3:1, so the defect was a text
     failure and never an object one.

     THE DIRECTION WAS NEVER A CHOICE. A lighter ink needs Y at or
     above 1.414 on this fill to reach 4.5:1. White is Y 1.000, so no
     lighter value exists and the ceiling with white is 3.2275:1.

     The deepest navy rung measures 5.75:1 here. IF IT READS TOO HEAVY
     the fallback is pre-decided: --rbi-navy-800 at 5.12:1, still AA,
     still a var(), still one line. DO NOT answer "too heavy" by
     lightening back toward white. That direction has no passing value
     in it. */
  --on-surface-danger: var(--rbi-navy-900);

  /* --- Neutrals (navy-based, elevation by lightness step) --- */
  --color-bg:            #0A1826;
  --color-surface:       #0F2338;
  --color-surface-2:     #16304A;
  --color-border:        #234156;
  --color-border-strong: #315875;
  --color-text:          #D6E0EA;
  --color-text-muted:    #8CA0B3;
  --color-text-strong:   #F2F6FA;

  /* --scrim-backdrop is theme-invariant — not restated. */

  /* --- Funnel ramp — the same navy family, walked ascending so the
     ramp rises against the dark canvas. Monotonic in luminance
     (.050 .101 .231 .356 .433 .568).

     Rungs 1 and 2 do NOT clear 3:1 against the dark surface on fill
     alone (1.53:1 and 2.31:1); they clear it through the
     ink-derived hairline already in the component layer. That
     mechanism is unchanged from Stage A and still applies to exactly
     the same two rungs — deliberately, because the alternative was
     pushing the whole dark ramp up into near-white, which is glare on
     a projected screen. Retune a rung and re-measure BOTH rules. --- */
  --brand-ramp-1: var(--rbi-navy-600);
  --brand-ramp-2: var(--rbi-navy-500);
  --brand-ramp-3: var(--rbi-navy-400);
  --brand-ramp-4: var(--rbi-navy-300);
  --brand-ramp-5: var(--rbi-blue-300);
  --brand-ramp-6: var(--rbi-navy-200);
  /* ink-on-fill (1.4.3) / fill-vs-dark-surface (1.4.11) */
  --brand-ramp-1-ink: var(--rbi-white);     /* 10.52 / 1.53 -> hairline */
  --brand-ramp-2-ink: var(--rbi-white);     /*  6.98 / 2.31 -> hairline */
  --brand-ramp-3-ink: var(--rbi-navy-900);  /*  4.97 / 4.32 */
  --brand-ramp-4-ink: var(--rbi-navy-900);  /*  7.17 / 6.23 */
  --brand-ramp-5-ink: var(--rbi-navy-900);  /*  8.53 / 7.33 (fill-vs-surface was
                                                recorded 7.41; same blue-300-on-dark
                                                -surface pair as --focus-ring above,
                                                same slip, corrected at Stage C+3) */
  --brand-ramp-6-ink: var(--rbi-navy-900);  /* 10.92 / 9.47 */

  /* --- Data-viz: categorical — the bright variant of each of the
     six positions. Every bright fill takes the navy ink; white fails
     on all six, which is exactly what the paired-ink rule is for.

     STAGE D RULED ON BOTH OPEN ITEMS AND CHANGED NEITHER VALUE:
     --viz-cat-3 vs --color-info at dE00 1.6 is closed as NOT A
     DEFECT on a co-occurrence audit (the notice-blue is never a chart
     area), and position 4 at dE00 2.2 from position 1 under
     protanopia is DECLARED UNSOLVABLE ON THIS CANVAS. The reason is
     arithmetic: position 1 sits at Y .231, barely above the .2046
     deepest-ink floor, and position 4 at Y .2881 has 3 (Y .2753) just
     below it and 2 (Y .4048) above, so every direction it can move
     trades one collapse for two. The dark canvas's whole usable
     window is 3.15:1 across five gaps. It is handled by the
     non-colour key, not by a rung. --- */
  --viz-cat-1: var(--rbi-cat-1-bright);  --viz-cat-1-ink: var(--rbi-navy-900); /* 4.97 */
  --viz-cat-2: var(--rbi-cat-2-bright);  --viz-cat-2-ink: var(--rbi-navy-900); /* 8.04 */
  --viz-cat-3: var(--rbi-cat-3-bright);  --viz-cat-3-ink: var(--rbi-navy-900); /* 5.75 */
  --viz-cat-4: var(--rbi-cat-4-bright);  --viz-cat-4-ink: var(--rbi-navy-900); /* 5.97 */
  --viz-cat-5: var(--rbi-cat-5-bright);  --viz-cat-5-ink: var(--rbi-navy-900); /* 8.16 */
  --viz-cat-6: var(--rbi-cat-6-bright);  --viz-cat-6-ink: var(--rbi-navy-900); /* 14.19 */
  --viz-grid: #21384F;
  /* --viz-axis deleted at Stage D in both themes — see the light block. */

  /* --- Data-viz: sequential — the same navy family, walked from the
     dark end so the ramp ascends against the dark canvas. --- */
  --viz-seq-0: var(--rbi-navy-700);  --viz-seq-0-ink: var(--rbi-white);     /* 13.22 */
  --viz-seq-1: var(--rbi-navy-600);  --viz-seq-1-ink: var(--rbi-white);     /* 10.52 */
  --viz-seq-2: var(--rbi-navy-500);  --viz-seq-2-ink: var(--rbi-white);     /*  6.98 */
  --viz-seq-3: var(--rbi-navy-400);  --viz-seq-3-ink: var(--rbi-navy-900);  /*  4.97 */
  --viz-seq-4: var(--rbi-navy-300);  --viz-seq-4-ink: var(--rbi-navy-900);  /*  7.17 */
  --viz-seq-5: var(--rbi-navy-200);  --viz-seq-5-ink: var(--rbi-navy-900);  /* 10.92 */

  /* --- Elevation via border, not shadow, on dark --- */
  --shadow-sm: 0 0 0 1px color-mix(in srgb, var(--rbi-white)  3%, transparent);
  --shadow-md: 0 2px 8px  color-mix(in srgb, var(--rbi-black) 35%, transparent);
  --shadow-lg: 0 8px 24px color-mix(in srgb, var(--rbi-black) 45%, transparent);
}
