/* ============================================================================
   The Split System — token foundation (D-1 rails, E-1 values)
   Canonical spec: DESIGN_SYSTEM.md at the workspace root. Values here are
   transcribed from that file; if the two ever disagree, the .md wins.

   THIS FILE IS THE ONLY PLACE A THEMED COLOUR IS DEFINED IN THE PORTAL.
   A hardcoded themed hex in a page or component is a review-blocker (§1b/§1c).
   Pages reference tokens; they never restate values.

   Two themes, one language. The portal defaults to LIGHT — "Harbor Light":
   cool paper ground, white surfaces, a solid Harbor-petrol brand mass for the
   sidebar and featured panels. The phone app defaults to dark — "Night Watch":
   graphite ground, steel surfaces, deep-ice nav, ice-cyan as the one
   instrument colour. Every viewer can switch on either surface.

   THE INVARIANT — never violate, never theme:
     --fb-alert    orange = A DECISION IS PENDING. Nothing else is ever orange.
     --fb-verified green  = approved / protected. Nothing else is ever green.
   Both keep the same meaning in both themes; only surface/ink/line/nav tokens
   change. Semantic tokens may be referenced anywhere, but only for meaning.

   WHAT E-1 DID (2026-08-29). The token NAMES and the theme mechanics are
   Vault's, unchanged — this is a re-value, not a re-plumb:
     · every neutral, brand and nav value moved to the Split table;
     · the --fb-nav* set is NEW (sidebar / tab-bar mass, its active row, its
       text, and the Spine hairline) and replaces the old --fb-sidebar* names;
     · --fb-navy is RETIRED. There is no navy in this system. Structure colour
       is --fb-nav / --fb-nav-active, and scripts/check-contrast.mjs fails the
       build if the name comes back at any call site;
     · the semantic hexes (alert / verified / denied families) did NOT move —
       they were audited to AA in D-11 and their meanings are the law. Only the
       neutral wash re-tints off its old navy cast;
     · the on-tokens were RE-DERIVED against those unchanged fills and the new
       --fb-raised, and the radii tightened (14/10/20 -> 8/6/999).

   WHAT E-1b ADDED (2026-08-29), both reviewer-directed spec amendments:
     · --fb-brand-hover — the hover/pressed state of a solid brand fill, so a
       button's hover stops borrowing the nav's token (Q-19);
     · --fb-on-raised is RETIRED into --fb-on-solid (Q-21). It derived to the
       same value in both themes and D-9 allows one name per colour;
       --fb-on-raised-muted stays and is now a spec-table token.
   ========================================================================= */

/* ── Fonts: vendored, never fetched at runtime ───────────────────────────
   Q-6 (2026-08-27, restated by the Split spec §Type): no fonts.googleapis.com
   / fonts.gstatic.com request on any product surface. Self-hosting keeps
   WP-3.2's CSP free of a third-party allow-list entry and keeps every
   visitor's IP off a third party on the login page. Provenance, licence and
   update steps: fonts/split/README.md.

   E-2 (2026-08-29) swapped the families. Vault's Spline Sans + IBM Plex Mono
   are gone with fonts/vault/; Split's three are here, and the role split is
   the spec's:

     Schibsted Grotesk  --fb-font-display  display + headings (the brand voice)
     Public Sans        --fb-font-ui       all UI and body text
     Geist Mono         --fb-font-mono     EVERY number that matters

   All three are VARIABLE — one file per family covers every weight the spec
   asks for (500/700, 400/600, 400/500), so three families cost three requests
   and 96 KB, the same budget the two Vault families cost.

   The declared font-weight range is each FILE's full axis range, not the two
   weights §Type names. A narrower declaration does not reject an out-of-range
   weight, it CLAMPS it: `font-weight: 400` under a `500 700` declaration
   renders at 500 and looks correct-but-wrong. The range is a fact about the
   file (see README.md), so it is stated as one.

   font-display: swap so text is readable immediately and never invisible
   while a face loads. */
@font-face {
  font-family: "Schibsted Grotesk";
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url("../fonts/split/schibsted-grotesk-latin-var.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Public Sans";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/split/public-sans-latin-var.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Geist Mono";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/split/geist-mono-latin-var.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ── Harbor Light (default) ──────────────────────────────────────────────
   Defined on :root, not inside a [data-theme] block, so the portal renders
   correctly even if the theme bootstrap fails to run or JS is unavailable. */
:root {
  --fb-page:           #f4f5f4;   /* page ground — cool paper                 */
  --fb-surface:        #ffffff;   /* cards, tables, modals                    */
  --fb-raised:         #17607e;   /* featured / account card — the brand mass */
  --fb-ink:            #23262a;   /* primary text            13.90 on page    */
  --fb-muted:          #5c6672;   /* secondary text  5.84 surface / 5.34 page
                                     / 5.13 neutral-wash                      */
  --fb-line:           #e1e4e3;   /* borders, dividers                        */
  --fb-row-line:       #eceeed;   /* table row dividers                       */

  /* The alternating (zebra) table row — UI-5, 2026-09-01, Matt's reversal of
     the "no striping" rule. Portal-only: the app has no tables (its chassis
     is cards and grouped lists), so this token is NOT in the shared canonical
     palette, exactly as --fb-ground and --fb-on-band-wash are not.

     DERIVED, not picked, by the Q-19 / Q-20 method: it is the geometric
     half-step from --fb-surface toward this theme's own --fb-page ground —
     a uniform per-channel scale k such that applying it TWICE lands exactly
     on the page ground. Light: #ffffff x (.9782,.9802,.9782) -> #f9faf9,
     again -> #f4f5f4. The table's alternation is therefore the table's own
     two neutrals, and nothing new is introduced into the palette.

     Measured (L* is CIE lightness; the ratios are WCAG):
       vs --fb-surface   1.0463:1, dL* 1.83  — the stripe itself
       vs --fb-neutral-wash (the row hover) 1.0863:1, dL* 3.27 — hover is a
         further, larger step in the same direction, so a hovered plain row
         can never be read as a resting alternate row
       --fb-ink 14.52:1, --fb-muted 5.58:1  — both clear AA
     L* 98.17 is, to two decimals, the lightness of the #F9F9F9 FlatLab and
     Bootstrap struck their stripe with: this IS "the old white / light-grey
     look", re-cast into the Split neutrals' green-leaning grey. */
  --fb-row-alt:        #f9faf9;   /* alternating table row  14.52 with ink    */
  --fb-brand:          #17607e;   /* interactive FILL, icons, focus — Harbor  */
  --fb-brand-wash:     #e3eef2;   /* brand tint bg (chips, icons)             */

  /* Hover/pressed state of a solid brand fill (E-1b, Q-19 ruling). A hover
     may change a button's STATE, never its identity, so this is Harbor one
     step deeper — the value the button already rendered, now under its own
     name instead of borrowing --fb-nav-active, which is the nav's token and
     not the button's. --fb-on-brand is the ink on it (9.21:1 here). */
  --fb-brand-hover:    #124d66;

  /* Brand as TEXT (Q-13, amended by the Split spec). The token STAYS — call
     sites keep the FILL/TEXT distinction — but the values converge: Harbor
     clears AA as running text on every ground it sits on (6.98 surface /
     6.38 page / 5.91 wash), so no separate text hue is needed any more. */
  --fb-brand-ink:      #17607e;

  /* Semantic — meaning is fixed, see THE INVARIANT above. These hexes did NOT
     move at E-1; they are the Vault values, audited to AA in D-11. */
  --fb-alert:          #fe6901;   /* PENDING DECISION ONLY                    */
  --fb-alert-ink:      #b5480a;   /* 4.70 on --fb-alert-wash                  */
  --fb-alert-wash:     #ffece0;
  --fb-verified:       #03a74b;   /* approved / protected ONLY                */
  --fb-verified-ink:   #01703c;   /* 5.54 on --fb-verified-wash               */
  --fb-verified-wash:  #e6f6ec;
  --fb-denied:         #b3261e;   /* denied / destructive                     */
  --fb-denied-wash:    #fdeceb;
  --fb-neutral-wash:   #eef1f0;   /* auto-rule / neutral chip bg              */

  /* ── Navigation: the brand mass (E-1, NEW) ─────────────────────────────
     Replaces the whole --fb-sidebar* family. One unbroken mass — Harbor in
     light, deep ice in dark — carrying its own text scale. The portal
     sidebar and the app's bottom tab bar consume the SAME four tokens; only
     the chassis differs (spec §Navigation). */
  --fb-nav:              #17607e;   /* sidebar / tab-bar mass                 */
  --fb-nav-active:       #124d66;   /* active nav item bg — one step deeper   */
  --fb-nav-text:         #c2dae3;   /* resting nav label  4.79 on --fb-nav    */
  --fb-nav-text-active:  #f4f5f4;   /* active nav label   8.43 on nav-active  */
  /* The Spine: the 1px connector the sub-nav hangs from. Translucent on
     purpose — it has to track whichever nav value is under it rather than
     being a third opaque colour to keep in sync. Same in both themes. */
  --fb-nav-spine:        rgba(255,255,255,.28);
  /* The rule that marks "you are here": a 3px left rule on the active
     top-level row, and the 2px segment that replaces the Spine hairline
     beside an active sub item. DESIGN_SYSTEM.md §Navigation names this
     value literally, twice, and the app's tab-bar indicator is the same
     #58C1E8 — so it is the ice in BOTH themes, like --fb-alert. It is a
     graphic, not text: 3.39:1 on the Harbor mass and 3.92:1 on its hover
     composite, 5.85:1 on the deep ice — all clear of the 3:1 non-text
     minimum, in every state it is drawn on. */
  --fb-nav-rule:         #58c1e8;
  /* Row tint for hover/focus inside the mass — Q-20's ruling (E-4).
     DESIGN_SYSTEM.md §Navigation says "one step darker than the nav"; what
     shipped through D-2/E-1 was rgba(255,255,255,.06/.07), a LIGHTENING
     overlay inherited from the old ink sidebar. The spec is the approved
     artefact, so it wins.
     DERIVED, not picked, by the Q-19 method. The nav's own approved "one
     step" is --fb-nav -> --fb-nav-active: in light, #17607e -> #124d66 is
     reproduced by a black overlay at alpha 0.1937 (least-squares over the
     three channels; the composite is #134d66, one 8-bit unit of red off the
     approved hex). A hover that took the whole step would be indistinguish-
     able from the active row, so hover takes HALF of it, in the composition
     sense: 1 - sqrt(1 - 0.1937) = 0.1021 -> .10, an overlay that applied
     twice lands back on --fb-nav-active. Dark's own half-step is .0883; the
     single value .10 serves both, because the token is theme-invariant (like
     --fb-nav-spine) and an over-quiet hover is the worse failure.
     COMPOSITES, computed by hand — the checker cannot composite translucents:
       light  #17607e x .90 -> #155671 : --fb-nav-text 5.55:1 (was 4.79 on
              the resting mass), --fb-nav-text-active 7.39:1
       dark   #1e3a44 x .90 -> #1b343d : --fb-nav-text 7.74:1,
              --fb-nav-text-active 11.54:1
     and over an ACTIVE row, which the layered hover also darkens:
       light  #124d66 x .90 -> #10455c : --fb-nav-text-active 9.48:1
       dark   #16303a x .90 -> #142b34 : --fb-nav-text-active 13.00:1
     Sub rows take the SAME tint — §Navigation says so outright ("hover =
     same row tint"). The two names stay because the call sites keep the
     top-level/sub distinction; only the values converge, exactly as Q-13
     did for --fb-brand-ink. */
  --fb-nav-hover:        rgba(0,0,0,.10);
  --fb-nav-hover-sub:    rgba(0,0,0,.10);

  /* Full-bleed ground for the standalone card pages that have no chrome —
     login, password-reset, the error pages. It is NOT --fb-page: those pages
     are one card floating on the product's own mass, and a pale card on a
     pale field throws away the one moment the product states what it is.
     The rule is unchanged from D-5, only its referents moved: light takes
     the nav mass; dark drops to the page ground so the surface lifts off it
     (in dark the nav and a card are close enough that a card would sink). */
  --fb-ground:         var(--fb-nav);

  /* ── On-colours for FILLED surfaces (Q-10, re-derived at E-1) ──────────
     Text on a solid fill takes the matching on-token, NEVER a blanket white:
     white is 3.17:1 on verified and 2.90:1 on alert in this theme. White
     where white passes, ink where the fill is bright. Every value is proven
     by scripts/check-contrast.mjs, which both repos run in CI against their
     own token file — the fills did not move at E-1, but --fb-raised did, so
     all six were re-measured rather than assumed. */
  --fb-on-verified:    #10181c;   /* 5.67:1 on #03a74b                        */
  --fb-on-denied:      #ffffff;   /* 6.54:1 on #b3261e — white passes here    */
  --fb-on-brand:       #ffffff;   /* 6.98:1 on #17607e (Q-11 is moot: the
                                     4.4988:1 pairing was on the retired blue)*/
  --fb-on-alert:       #10181c;   /* 6.19:1 on #fe6901                        */

  /* Muted ink on --fb-raised, the featured/account card. Its full-strength
     partner --fb-on-raised is RETIRED at E-1b (Q-21): it derived to exactly
     --fb-on-solid in both themes, and D-9's rule is one name per colour, so
     the call sites moved to --fb-on-solid and check-contrast.mjs now fails
     the build if the name comes back. The muted one survives — it has no
     --fb-on-solid equivalent, and "quiet ink on the brand mass" is a real
     role the spec table now lists. */
  --fb-on-raised-muted:  #c2dae3;   /* 4.79:1 on #17607e                      */

  /* Text on the nav / raised mass generally (spec table). White in light,
     ice in dark — the same switch the interface makes. */
  --fb-on-solid:       #ffffff;   /* 6.98 on nav, 9.21 on nav-active          */

  /* A translucent ground for a chip sitting ON the filled brand band (D-4's
     modal header). It has to be the band's own colour lightened, not an
     opaque token, or it stops tracking the band in the other theme — the
     same reasoning as --fb-nav-spine. */
  --fb-on-band-wash:   rgba(255, 255, 255, .14);

  /* Shape — radii are shared by both themes. Split's geometry is tighter
     than Vault's 14/10/20 (spec §Shape): 8 cards, 6 controls, 999 pills. */
  --fb-radius-card:    8px;
  --fb-radius-control: 6px;
  --fb-radius-pill:    999px;

  /* Elevation. Light uses a 1px --fb-line for structure; shadow is for
     overlays only, so the page reads flat and institutional, not floaty. */
  --fb-shadow-overlay: 0 24px 64px rgba(10, 14, 18, .35);

  /* Type (spec §Type, swapped at E-2). Three roles, three faces, one ramp,
     identical to the app's — the §Cross-platform contract's Type row.
       --fb-font-display  Schibsted Grotesk 500/700 — page titles, stat and
                          hero numerals' headings. The brand's voice face.
       --fb-font-ui       Public Sans 400/600 — all UI and body text.
       --fb-font-mono     Geist Mono 400/500 — every number that matters,
                          always with font-variant-numeric: tabular-nums.
     Fallback stacks are the spec's, verbatim: sans falls to "Helvetica Neue",
     Arial, sans-serif; mono to Menlo, monospace. They are what renders in the
     swap window, so they are two grotesques and a mono, not defaults. */
  --fb-font-display: "Schibsted Grotesk", "Helvetica Neue", Arial, sans-serif;
  --fb-font-ui:      "Public Sans", "Helvetica Neue", Arial, sans-serif;
  --fb-font-mono:    "Geist Mono", Menlo, monospace;

  color-scheme: light;
}

/* ── Night Watch Dark ────────────────────────────────────────────────────
   Only surface / ink / line / nav tokens change. The two semantic hues keep
   their meaning; --fb-alert is deliberately the SAME hex in both themes. */
:root[data-theme="dark"] {
  --fb-page:           #15171a;
  --fb-surface:        #1d2025;
  --fb-raised:         #16303a;
  --fb-ink:            #e6e9ec;
  --fb-muted:          #9aa3ab;
  --fb-line:           #2a2e34;
  --fb-row-line:       #23262b;

  /* The zebra row, dark — same derivation, this theme's own two neutrals:
     #1d2025 x (.8510,.8478,.8383) -> #191b1f, again -> #15171a (--fb-page).
     It steps DOWN toward the graphite ground because that is where Night
     Watch keeps its room: --fb-neutral-wash (the hover) sits only dL* 2.91
     ABOVE --fb-surface, so a lift of any useful size would have collided
     with the hover. Stepping toward the ground instead makes the hover the
     one thing that lifts in this theme, which is the dark-mode idiom anyway.
       vs --fb-surface       1.0556:1, dL* 2.43  — the stripe
       vs --fb-neutral-wash  1.1361:1, dL* 5.34  — the hover, unmistakable
       --fb-ink 14.15:1, --fb-muted 6.73:1       — both clear AA
     Subtlety matches Harbor Light's (dL* 2.43 vs 1.83) rather than being
     eyeballed dark-first. */
  --fb-row-alt:        #191b1f;   /* alternating table row  14.15 with ink    */
  --fb-brand:          #58c1e8;   /* ice — the one instrument colour          */
  --fb-brand-wash:     #16303a;

  /* Q-19's actual finding lives here. Before E-1b the primary button's hover
     was --fb-nav-active, which in dark is #16303a — a DEEP ice carrying light
     ink — so hovering flipped a bright button to a dark one and its text from
     dark to light. Every pairing passed AA; the button still appeared to
     change identity rather than state.
     DERIVED, not eyeballed: the approved light pair #17607e -> #124d66 is a
     relative-luminance step of 1.3211:1, so the same step is applied to the
     ice by scaling its sRGB channels uniformly (k = 0.8679, which also
     reproduces the light pair's own 0.78/0.80/0.81 channel ratios). The
     nearest 8-bit hex is #4ca8c9 at a 1.3169:1 step, and it holds
     --fb-on-brand #10181c at 6.63:1 — well clear of AA, and still a bright
     fill with dark ink, so the state changes and the identity does not. */
  --fb-brand-hover:    #4ca8c9;

  /* Q-13, dark: ice clears AA as text on every ground too (7.94 surface /
     8.73 page / 6.73 wash), so brand-ink converges on brand here as well. */
  --fb-brand-ink:      #58c1e8;

  --fb-alert:          #fe6901;   /* same hex on purpose — see the invariant  */
  --fb-alert-ink:      #ff9550;
  --fb-alert-wash:     #3a2008;
  --fb-verified:       #2fbf71;
  --fb-verified-ink:   #4fd18b;
  --fb-verified-wash:  #0f2e1e;
  --fb-denied:         #f27d72;
  --fb-denied-wash:    #38120f;
  --fb-neutral-wash:   #23262b;

  /* Nav: deep ice. Restated in full rather than inherited so a grep for any
     one of these names finds both themes at once. */
  --fb-nav:              #1e3a44;
  --fb-nav-active:       #16303a;
  --fb-nav-text:         #a9cdda;   /* 7.13 on --fb-nav                       */
  --fb-nav-text-active:  #e6f3f7;   /* 12.21 on --fb-nav-active               */
  --fb-nav-spine:        rgba(255,255,255,.28);
  --fb-nav-rule:         #58c1e8;   /* same ice in both themes — see light  */
  --fb-nav-hover:        rgba(0,0,0,.10);
  --fb-nav-hover-sub:    rgba(0,0,0,.10);

  /* Dark drops the chrome-less pages to the page ground — see the light
     value's note; in dark the nav and a card sit too close for a card to
     lift off the nav mass. */
  --fb-ground:         var(--fb-page);

  /* Restated in dark for the same reason the nav tokens are. */
  --fb-on-band-wash:   rgba(255, 255, 255, .14);

  /* Q-10 on-colours, dark. Every semantic fill is BRIGHTER here than in
     light, so all three take ink — bright fill plus dark text is the standard
     dark-mode idiom, and it is what the numbers demand (white is 2.38:1 on
     verified, 2.64 on denied). --fb-raised inverts: it is the DARK member of
     the pair here, so its inks are the light ones. */
  --fb-on-verified:    #10181c;   /* 7.54:1 on #2fbf71                        */
  --fb-on-denied:      #10181c;   /* 6.80:1 on #f27d72                        */
  --fb-on-brand:       #10181c;   /* 8.73:1 on #58c1e8                        */
  --fb-on-alert:       #10181c;   /* 6.19:1 on #fe6901                        */

  --fb-on-raised-muted:  #a9cdda;   /* 8.19:1 on #16303a                      */

  --fb-on-solid:       #e6f3f7;   /* 10.62 on nav, 12.21 on nav-active        */

  /* Dark uses border plus a deeper shadow, and never a pure-black fill. */
  --fb-shadow-overlay: 0 24px 64px rgba(0, 0, 0, .6);

  color-scheme: dark;
}

/* ── Typography primitives ───────────────────────────────────────────────
   Every number that matters is mono with tabular figures, so digits do not
   jitter as a countdown ticks or an amount changes width. Applied by class
   rather than element so pages opt in explicitly. */
.fb-mono,
.fb-amount,
.fb-countdown {
  font-family: var(--fb-font-mono);
  font-variant-numeric: tabular-nums;
}

/* Label-caps from the ramp: 11px, wide tracking, muted. */
.fb-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--fb-muted);
}

/* ── Theme-transition suppression ────────────────────────────────────────
   Without this, flipping the theme animates every colour on the page at once
   if any transition rule is in play. Set by the bootstrap around the switch. */
:root.fb-theme-switching,
:root.fb-theme-switching * {
  transition: none !important;
}
