/*
 * BrickDb design tokens — Modern Element Palette
 * ----------------------------------------------------------------------------
 * Source of truth for the roles: docs/design/design-system.md (§2, §3).
 * Source of truth for the values: docs/ui-refresh/02-design-system/palette.md.
 * Source of truth for the NAMES: docs/ui-refresh/02-design-system/current-state.md
 * §2.1 — all 125 survive, because TokenPalette.ResolveColour throws on an unknown
 * one. The Modern Element Palette moved token VALUES only; it added no name and
 * removed none.
 *
 * brand/README.md §5 still describes a coral-and-indigo brand palette, and the
 * coral mark still ships. That disagreement is deliberate and temporary: the new
 * mark is its own slice, and brand/README.md moves with it rather than with this
 * file.
 *
 * Every colour resolves to a step of the element scale in palette.md, which is
 * generated from six real LEGO element colours by palette.py. Do not hand-edit a
 * ramp value: change the seed and re-run the generator, or the ladder stops being
 * even and the contrast proofs in token-mapping.md stop holding.
 *
 * Theme resolution order (three states, four resolved palettes):
 *   1. Bare :root            -> complete LIGHT palette (the default/fallback).
 *   2. @media (prefers-color-scheme: dark), guarded :root:not([data-theme="light"])
 *                             -> redefines tokens for OS-level dark, but an explicit
 *                                data-theme="light" on the root beats it.
 *   3. :root[data-theme="dark"]
 *                             -> redefines tokens again so an in-app toggle wins in
 *                                BOTH directions, regardless of OS preference.
 *   4. :root[data-theme="light"]
 *                             -> re-asserts the light values so an explicit light
 *                                choice beats a dark OS preference. Hand-duplicated,
 *                                and it has drifted once; TokenContrastTests resolves
 *                                all four palettes rather than two for that reason.
 *
 * No colour is ever defined only inside a media/attribute block — the bare :root
 * always carries a complete, correct light palette on its own. No var() carries a
 * literal fallback anywhere: TokenSweepTests bans them outright.
 * ----------------------------------------------------------------------------
 */

:root {
  /* ================= Colour — light theme (default) =================
     Neutral ladder (palette.md): 000 #FFFFFF · 050 #F5F7F8 · 100 #EAEEF0 ·
     200 #D8DEE1 · 300 #C0C7CB · 400 #A0A5A9 · 500 #868C90 · 600 #6C6E68 ·
     700 #4A5157 · 800 #28323A · 900 #111E27 · 950 #05131D.
     Four of those are element colours: White, Light Bluish Gray, Dark Bluish
     Gray and Black. The rest are interpolated between them. */

  /* Surfaces — the light theme is a raised/sunken ladder, not a warm/cool one.
     The old card surface was warm (#FFF7F2) because the brand was a coral; with
     an azure brand a warm card has nothing to tie back to, so the card is simply
     the top of the ladder (neutral-000) on a neutral-050 ground, and the tinted
     section surface is the recess below it (neutral-100). */
  --bd-color-surface: #F5F7F8;            /* neutral-050 — app background */
  --bd-color-surface-raised: #FFFFFF;     /* neutral-000 — inputs, raised elements */
  --bd-color-surface-overlay: #FFFFFF;    /* neutral-000 — sheets, popovers — pair with --bd-shadow-3 */
  --bd-color-surface-overlay-2: #FFFFFF;  /* neutral-000 — modals, lightbox chrome — pair with --bd-shadow-4 */

  /* Text / icons on surface */
  --bd-color-on-surface: #05131D;         /* neutral-950 (0 — Black) — 18.79:1 on a card */
  /* neutral-700. The 2026-09-05 deepening of this token is what bought the
     saturated backdrop mesh, and the same argument is re-run here against the
     new stops: the binding pair is muted text on the LIGHTEST light-theme mesh
     stop, which measures 5.41:1 at neutral-700 against azure-200. The mesh floor
     of L 0.62 below is what keeps that true. */
  --bd-color-muted: #4A5157;              /* neutral-700 — secondary text, placeholders, disabled labels, 8.06:1 on a card */
  --bd-color-border: #D8DEE1;             /* neutral-200 — hairlines, input borders, dividers */
  --bd-color-border-strong: #868C90;      /* neutral-500 — focus-adjacent borders, dividers on cards; 3.17:1 on the app background */

  /* Brand / primary — the azure family, seeded by 321 Dark Azure (#078BC9), the
     brand mark's own top rhombus. This replaces the deepened coral ramp entirely.
     The coral had to be scaled down from the brand hex by k = 0.729 to reach
     4.97:1 against white, and the brand hex itself was never white-text-safe
     (2.82:1). Azure needs no such manipulation: the published ramp steps clear AA
     as generated, so resting / hover / pressed are three ladder steps rather than
     three hand-solved hexes. */
  --bd-color-primary: #0077AF;            /* azure-600 — CTAs, active states — white-text-safe, 4.93:1 */
  --bd-color-primary-dark: #004570;       /* azure-800 — pressed/active + link text — white-text-safe, 10.07:1 */
  --bd-color-primary-mid: #005D8F;        /* azure-700 — hover/focus fill on desktop pointers — white-text-safe, 7.09:1 */
  --bd-color-primary-light: #ABF2FF;      /* azure-100 — tints, subtle fills, disabled-primary. The ONLY ramp step that
                                             changes by theme; never a text-bearing fill in the light theme. */
  --bd-color-primary-vivid: #078BC9;      /* 321 — Dark Azure, the brand mark's own azure. Decorative accents only:
                                             3.79:1 against white, so NOT white-text-safe, though it does clear the
                                             3:1 non-text floor there. Pairs at 4.96:1 with --bd-color-on-surface
                                             where text must sit directly on it. */

  /* White on all three of primary / -mid / -dark, in every theme. Those three are
     the white-text-safe steps and the only ones. Do NOT put white text on
     --bd-color-primary-light or --bd-color-primary-vivid. */
  --bd-color-on-primary: #FFFFFF;

  /* ================= Backdrop mesh (light theme) =================
     Three soft radial glows layered over --bd-color-surface, forming a
     low-frequency mesh with no hard edges. Real running text sits directly on it,
     because app.css paints it on body itself. HARD CONSTRAINT, unchanged by the
     redesign: no light-theme stop's relative luminance may drop below 0.62. That
     is conservative on purpose - --bd-color-muted at neutral-700 only needs
     L >= 0.5363 to clear 4.5:1 - and the three stops below sit at L 0.6548-0.7738.
     Recompute design-system.md 2.4.1 if any stop moves. */
  --bd-color-backdrop-warm: #FFD9E1; /* coral-100 — pink glow, top-left, L 0.7632 */
  --bd-color-backdrop-sun: #FFE198;  /* amber-100 — amber glow, top-right, L 0.7738 */
  --bd-color-backdrop-cool: #91DEFF; /* azure-200 — azure glow, bottom-right, L 0.6548 — the worst-case stop */
  --bd-color-backdrop:
    radial-gradient(ellipse 900px 600px at 12% -8%, var(--bd-color-backdrop-warm) 0%, transparent 60%),
    radial-gradient(ellipse 800px 520px at 96% 14%, var(--bd-color-backdrop-sun) 0%, transparent 62%),
    radial-gradient(ellipse 900px 600px at 88% 108%, var(--bd-color-backdrop-cool) 0%, transparent 60%),
    var(--bd-color-surface);

  /* ================= Glass surface tokens (light theme) =================
     Consumed by Components/GlassSurface.razor.css, which sets background / border /
     box-shadow from these UNCONDITIONALLY — only backdrop-filter itself is
     @supports-gated there, so the un-blurred state is the same translucent fill,
     never a separately-verified fallback colour. The light fill stays white; only
     the hairline moves, from the old indigo to neutral-950. */
  --bd-color-glass-bg: rgba(255, 255, 255, 0.62);
  --bd-color-glass-bg-strong: rgba(255, 255, 255, 0.78);
  --bd-color-glass-border: rgba(5, 19, 29, 0.08);
  --bd-color-glass-highlight: inset 0 1px 0 rgba(255, 255, 255, 0.6);
  --bd-glass-blur: 16px; /* capped — declared exactly once, here, on bare :root. No component may raise this. */

  /* Cards are solid, by decision: a content card is never <GlassSurface>, because
     the Collection screen is an unbounded scrolling list and backdrop-filter on
     scrolling content blows the GPU budget. The indirection stays even though the
     decision is made — every card component reads --bd-color-card-surface. */
  --bd-color-card-surface: #FFFFFF;

  /* The ground under a grouped block of content (a <details> disclosure, an empty
     state) as distinct from a content card. Sunken rather than warm: it reads as a
     recess in the page, one neutral step below the ground. */
  --bd-color-section-surface: #EAEEF0;

  /* ================= Photo/camera scrim floor =================
     Mandatory over UNKNOWN content (a user's photo, a live camera feed) — glass
     never sits directly on unknown content; this scrim intervenes first.
     Theme-independent by construction and PALETTE-independent too: it is anchored
     to the photo's own worst case (pure white), not to any surface token, so the
     redesign does not touch it. 85% / 75% are solved minimums with a small margin
     (>=81.7% / >=70% is the bare floor for 4.5:1 / 3:1). */
  --bd-scrim-photo-text: rgba(0, 0, 0, 0.85); /* text/labels over photo or camera content */
  --bd-scrim-photo-icon: rgba(0, 0, 0, 0.75); /* icon-only glass chips over photo or camera content */
  --bd-scrim-photo-fade: 96px;

  /* Semantic — success (the lime family, 27 — Lime). The fill has to stay dark in
     the light theme for two independent reasons: it carries white label text, and
     Toast.razor paints the severity word in this colour as caption-size text on the
     white overlay surface. lime-700 clears both at 6.91:1. */
  --bd-color-success: #456300;            /* lime-700 */
  --bd-color-on-success: #FFFFFF;         /* 6.91:1 on --bd-color-success */
  --bd-color-success-surface: #E4FF9C;    /* lime-050 — success chip fill, 17.08:1 with on-surface */

  /* Semantic — warning (the amber family, 191 — Bright Light Orange) */
  --bd-color-warning: #7A4C00;            /* amber-700 */
  --bd-color-on-warning: #FFFFFF;         /* 7.34:1 on --bd-color-warning */
  --bd-color-warning-surface: #FFF2C6;    /* amber-050 */

  /* Semantic — danger (the coral family, 1050 — Coral) */
  --bd-color-danger: #BB355D;             /* coral-600 */
  --bd-color-on-danger: #FFFFFF;          /* 5.56:1 on --bd-color-danger */
  --bd-color-danger-surface: #FFECF1;     /* coral-050 */

  /* Chrome */
  --bd-color-scrim: rgba(5, 19, 29, 0.48);    /* modal/sheet backdrop — neutral-950 at the old alpha */
  --bd-color-focus-ring: #0077AF;             /* azure-600 — 3px, see app.css focus rules; clears 3:1 on every ground it
                                                 can land on, worst case 3.31:1 over the cool mesh stop */

  /* ================= ConditionBadge grade ramp — light theme =================
     Nine steps, three bands, one element family per band: lime/teal for Like-new,
     azure/lilac for Displayed, amber/coral for Well-used.

     What could NOT change, and has not: a light-theme fill has to stay dark. White
     label text needs 4.5:1, which caps the fill at L <= 0.1833; the chip also has
     to be distinguishable from the card behind it (>=3:1), which on the now-white
     card caps it at L <= 0.30. The label cap is the tighter of the two and still
     binds, so NO LEGO-BRIGHT YELLOW OR ORANGE IS AVAILABLE HERE AT ANY SATURATION
     — on a light ground, brightness and legibility are genuinely exclusive. Only
     the 600-900 steps of any family clear it; every 050-400 step is ruled out. The
     dark theme inverts the label to neutral-950 and therefore CAN go bright; it
     does. Four redundant channels carry the encoding: shape, texture, colour and
     label. */
  /* Band 1 — Like-new (rounded square, solid/solid/hatch) — lime/teal */
  --bd-grade-1: #314A00; /* lime-800  · Sealed    — solid          — label 9.95:1, vs card 9.95:1 */
  --bd-grade-2: #5B7E00; /* lime-600  · Mint      — solid          — label 4.74:1, vs card 4.74:1 */
  --bd-grade-3: #00646E; /* teal-700  · NearMint  — diagonal hatch — label 6.89:1, vs card 6.89:1 */
  /* Band 2 — Displayed (circle, solid/solid/hatch) — azure/lilac/amber */
  --bd-grade-4: #0077AF; /* azure-600 · Excellent — solid          — label 4.93:1, vs card 4.93:1 */
  --bd-grade-5: #885A95; /* lilac-600 · VeryGood  — solid          — label 5.33:1, vs card 5.33:1 */
  --bd-grade-6: #7A4C00; /* amber-700 · Good      — diagonal hatch — label 7.34:1, vs card 7.34:1 */
  /* Band 3 — Well-used (pennant, solid/hatch/cross-hatch) — amber/coral */
  --bd-grade-7: #976300; /* amber-600 · Fair      — solid          — label 5.12:1, vs card 5.12:1 */
  --bd-grade-8: #BB355D; /* coral-600 · Poor      — diagonal hatch — label 5.56:1, vs card 5.56:1 */
  --bd-grade-9: #780632; /* coral-800 · ForParts  — cross hatch    — label 11.23:1, vs card 11.23:1 */
  --bd-color-on-grade: #FFFFFF; /* label/text colour on every grade fill, light theme */

  /* ================= Element accent family — six hues, each with its own ink =====
     Six real element colours for the places identity colour carries meaning: a
     set's theme chip (Star Wars, Technic, City …), the empty-state illustration, a
     section header's accent rule, BrickRow, BrickAvatar, ThemeChip, ThemeTile.

     These are now genuinely THEME-INVARIANT, and that is a consequence of the
     maths rather than a shortcut. The old family had to split — white ink on the
     deep hues, dark ink on the bright ones — because the light theme's ink options
     were bounded by a warm card. With neutral-950 as the ink in both themes every
     element colour clears 4.5:1 on its own fill, and the boundary rule resolves in
     opposite directions per theme: in light the ink carries the edge (18.51:1
     against the white card, while a bright fill cannot); in dark the FILL carries
     it (4.96:1 or better against the neutral-900 card, while the ink cannot).
     Either way the chip has a visible edge. Guard: TokenContrastTests.

     One substitution is deliberate. 321 — Dark Azure is the brand mark's own
     colour and is --bd-color-primary-vivid above, so --bd-accent-blue takes
     322 — Medium Azure instead. This family exists precisely so nobody reaches for
     the brand colour to mean "Technic", and that rule outlives the coral it was
     written for. */
  --bd-accent-red: #FF698F;        /* 1050 — Coral               · ink 6.86:1;  ink vs light card 18.79:1, fill vs dark card 6.19:1 */
  --bd-accent-red-ink: #05131D;
  --bd-accent-orange: #F8BB3D;     /* 191  — Bright Light Orange · ink 10.89:1; ink vs light card 18.79:1, fill vs dark card 9.82:1 */
  --bd-accent-orange-ink: #05131D;
  --bd-accent-yellow: #F2CD37;     /* 14   — Yellow              · ink 12.14:1; ink vs light card 18.79:1, fill vs dark card 10.95:1 */
  --bd-accent-yellow-ink: #05131D;
  --bd-accent-green: #BBE90B;      /* 27   — Lime                · ink 13.22:1; ink vs light card 18.79:1, fill vs dark card 11.93:1 */
  --bd-accent-green-ink: #05131D;
  --bd-accent-blue: #36AEBF;       /* 322  — Medium Azure        · ink 7.13:1;  ink vs light card 18.79:1, fill vs dark card 6.43:1 */
  --bd-accent-blue-ink: #05131D;
  --bd-accent-purple: #AC78BA;     /* 30   — Medium Lavender     · ink 5.50:1;  ink vs light card 18.79:1, fill vs dark card 4.96:1 */
  --bd-accent-purple-ink: #05131D;

  /* ================= Spacing scale (4px base) — unchanged ================= */
  --bd-space-0: 0;
  --bd-space-1: 4px;
  --bd-space-2: 8px;
  --bd-space-3: 12px;
  --bd-space-4: 16px;
  --bd-space-5: 20px;
  --bd-space-6: 24px;
  --bd-space-7: 32px;
  --bd-space-8: 40px;
  --bd-space-9: 48px;
  --bd-space-10: 64px;
  --bd-space-11: 80px;
  --bd-space-12: 96px;

  /* ================= Radii — unchanged ================= */
  --bd-radius-sm: 8px;      /* badges, chips, input fields */
  --bd-radius-md: 12px;     /* buttons, text inputs, small tiles */
  --bd-radius-lg: 20px;     /* SetCard, MinifigCard, PhotoGallery tiles */
  --bd-radius-xl: 28px;     /* bottom sheets, modals (top corners) */
  --bd-radius-pill: 999px;  /* pill buttons, FAB, stepper */

  /* ================= Elevation / shadow =================
     Geometry and alpha unchanged; only the tint moves, from the old indigo
     (28, 34, 71) to neutral-950 (5, 19, 29). */
  --bd-shadow-0: none;
  --bd-shadow-1: 0 1px 2px rgba(5, 19, 29, 0.06), 0 1px 1px rgba(5, 19, 29, 0.04);
  --bd-shadow-2: 0 2px 8px rgba(5, 19, 29, 0.10), 0 1px 2px rgba(5, 19, 29, 0.06);
  --bd-shadow-3: 0 8px 24px rgba(5, 19, 29, 0.14), 0 2px 6px rgba(5, 19, 29, 0.08);
  --bd-shadow-4: 0 16px 48px rgba(5, 19, 29, 0.24);

  /* ================= Typography — see app.css for applied rules ================= */
  --bd-font-display: 'Bricolage Grotesque', 'Space Grotesk', 'Segoe UI', system-ui,
                     sans-serif;
  --bd-font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
                  'Noto Sans', sans-serif;

  /* Type scale — mobile (base 0-599px). >=905px overrides in app.css redefine the
     size/line-height half of each pair only. Unchanged by the redesign. */
  --bd-type-display-lg-size: 2.125rem;   /* 34px */
  --bd-type-display-lg-line: 2.5rem;     /* 40px */
  --bd-type-display-size: 1.75rem;       /* 28px */
  --bd-type-display-line: 2.125rem;      /* 34px */
  --bd-type-headline-size: 1.375rem;     /* 22px */
  --bd-type-headline-line: 1.75rem;      /* 28px */
  --bd-type-title-size: 1.125rem;        /* 18px */
  --bd-type-title-line: 1.5rem;          /* 24px */
  --bd-type-body-lg-size: 1.0625rem;     /* 17px */
  --bd-type-body-lg-line: 1.5rem;        /* 24px */
  --bd-type-body-size: 1rem;             /* 16px — never shrinks below this */
  --bd-type-body-line: 1.375rem;         /* 22px */
  --bd-type-body-sm-size: 0.875rem;      /* 14px */
  --bd-type-body-sm-line: 1.25rem;       /* 20px */
  --bd-type-label-size: 0.75rem;         /* 12px */
  --bd-type-label-line: 1rem;            /* 16px */
  --bd-type-label-tracking: 0.02em;
  --bd-type-caption-size: 0.75rem;       /* 12px */
  --bd-type-caption-line: 1rem;          /* 16px */

  /* ================= Motion — unchanged ================= */
  --bd-duration-instant: 100ms;
  --bd-duration-short: 150ms;
  --bd-duration-medium: 250ms;
  --bd-duration-long: 400ms;
  --bd-ease-standard: cubic-bezier(0.2, 0, 0, 1);
  --bd-ease-decelerate: cubic-bezier(0, 0, 0, 1);
  --bd-ease-accelerate: cubic-bezier(0.3, 0, 1, 1);
  --bd-ease-emphasized: cubic-bezier(0.3, 0, 0.1, 1);

  /* ================= Z-index layers — unchanged ================= */
  --bd-z-base: 0;
  --bd-z-sticky-header: 10;
  --bd-z-sidebar: 20;          /* AppSidebar — the mobile top strip / drawer and the persistent rail/expanded column */
  --bd-z-dropdown: 30;         /* FilterSheet anchored popovers, menus */
  --bd-z-scrim: 40;
  --bd-z-modal: 50;            /* ConfirmSheet, PhotoCapture, modal dialogs */
  --bd-z-toast: 60;
  --bd-z-scanner-overlay: 70;  /* full-screen camera overlay — always on top */

  /* ================= Safe-area insets — unchanged ================= */
  --bd-safe-area-top: env(safe-area-inset-top, 0px);
  --bd-safe-area-right: env(safe-area-inset-right, 0px);
  --bd-safe-area-bottom: env(safe-area-inset-bottom, 0px);
  --bd-safe-area-left: env(safe-area-inset-left, 0px);
}

/* ================= Dark theme via OS preference =================
   Wins unless an explicit light choice is stamped via [data-theme="light"]. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    /* The dark ground is neutral-950 — literally the colour of a black brick
       (0 — Black, #05131D) — and the elevation ladder climbs the neutral scale:
       950 -> 900 -> 850 -> 800. neutral-850 (#1D2831) is the one value here that
       is not in palette.md's twelve-step table: it is the sRGB midpoint of
       neutral-900 and neutral-800, derived by the same interpolation the table
       itself documents, because the surface ladder needs four rungs and the
       published scale offers three in that range. See token-mapping.md. */
    --bd-color-surface: #05131D;            /* neutral-950 */
    --bd-color-surface-raised: #111E27;     /* neutral-900 */
    --bd-color-surface-overlay: #1D2831;    /* neutral-850 (derived) */
    --bd-color-surface-overlay-2: #28323A;  /* neutral-800 */
    --bd-color-on-surface: #EAEEF0;         /* neutral-100 */
    --bd-color-muted: #A0A5A9;              /* neutral-400 (71 — Light Bluish Gray) */
    --bd-color-border: #28323A;             /* neutral-800 */
    --bd-color-border-strong: #6C6E68;      /* neutral-600 (72 — Dark Bluish Gray) — 3.64:1 on the app background */

    --bd-color-primary-light: #6FC5FC; /* azure-300 — link text, focus rings and accents on dark surfaces */
    /* --bd-color-primary / -mid / -dark / -vivid / on-primary are theme-invariant —
       see the note at :root. */

    /* Backdrop mesh — dark theme. HARD CONSTRAINT: no dark-theme stop's relative
       luminance may exceed 0.043. The binding pair is --bd-color-muted reading on
       the LIGHTEST stop, and at neutral-400 (#A0A5A9, L 0.3725) that needs
       L <= 0.0439. The ceiling was 0.045 under the old muted (#A8ACC9, L 0.4205,
       limit 0.0545) and 0.045 is ABOVE the new limit rather than below it, so the
       number is tightened here rather than carried over - nothing moves, because
       the three stops sit at L 0.0217-0.0256, well inside either. */
    --bd-color-backdrop-warm: #58001F; /* coral-900 — L 0.0217 */
    --bd-color-backdrop-sun: #432100;  /* amber-900 — L 0.0228 */
    --bd-color-backdrop-cool: #002E52; /* azure-900 — L 0.0256 — the worst-case stop */

    /* Glass surface — dark theme. The fill is neutral-900 at the same alphas the
       light theme uses for white; the border and highlight tint toward the brand
       accent, which is now azure-300 rather than the old coral. */
    --bd-color-glass-bg: rgba(17, 30, 39, 0.55);
    --bd-color-glass-bg-strong: rgba(17, 30, 39, 0.72);
    --bd-color-glass-border: rgba(111, 197, 252, 0.10);
    --bd-color-glass-highlight: inset 0 1px 0 rgba(111, 197, 252, 0.08);
    /* --bd-glass-blur is theme-invariant (16px), set once in :root */

    --bd-color-card-surface: #111E27;    /* neutral-900 — solid, never glass */
    --bd-color-section-surface: #05131D; /* neutral-950 — the recess reads as the page ground */
    /* --bd-scrim-photo-* tokens are theme-independent */

    --bd-color-success: #A5CC2C;         /* lime-300 */
    --bd-color-on-success: #05131D;
    --bd-color-success-surface: #314A00; /* lime-800 */
    --bd-color-warning: #E4B14F;         /* amber-300 */
    --bd-color-on-warning: #05131D;
    --bd-color-warning-surface: #5E3600; /* amber-800 */
    --bd-color-danger: #FF93AB;          /* coral-300 */
    --bd-color-on-danger: #05131D;
    --bd-color-danger-surface: #780632;  /* coral-800 */

    --bd-color-scrim: rgba(0, 0, 0, 0.6);
    --bd-color-focus-ring: #6FC5FC;      /* azure-300 */

    /* ConditionBadge grades — the same three bands and the same element families
       as the light ramp, lightened so they stay legible on neutral-900. Inverting
       the label to neutral-950 lifts the cap the light theme runs into: the label
       now needs the fill at L >= 0.2052 and the chip needs L >= 0.1408 against the
       card, so the 200-400 steps are what this ramp is made of. This is the theme
       where the app looks most like a box of bricks, and that is a consequence of
       the maths rather than a separate decision. */
    --bd-grade-1: #88B000; /* lime-400  — label 7.39:1,  vs card 6.67:1 */
    --bd-grade-2: #C0E561; /* lime-200  — label 13.09:1, vs card 11.80:1 */
    --bd-grade-3: #4AAFBA; /* teal-400  — label 7.28:1,  vs card 6.56:1 */
    --bd-grade-4: #91DEFF; /* azure-200 — label 12.62:1, vs card 11.38:1 */
    --bd-grade-5: #D5A7E2; /* lilac-300 — label 9.36:1,  vs card 8.44:1 */
    --bd-grade-6: #E4B14F; /* amber-300 — label 9.58:1,  vs card 8.64:1 */
    --bd-grade-7: #CA9411; /* amber-400 — label 6.94:1,  vs card 6.26:1 */
    --bd-grade-8: #FF93AB; /* coral-300 — label 8.96:1,  vs card 8.08:1 */
    --bd-grade-9: #F4698B; /* coral-400 — label 6.48:1,  vs card 5.84:1 */
    --bd-color-on-grade: #05131D;

    /* The element accent family is theme-invariant — see the note at :root. The
       six fills and their inks are NOT redeclared here, deliberately: they resolve
       through the bare :root block in every palette, so there is no hand-duplicated
       copy of them left to drift. */

    /* Dark theme leans on the neutral ladder for elevation, not deeper shadows */
    --bd-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
    --bd-shadow-2: 0 2px 8px rgba(0, 0, 0, 0.45);
    --bd-shadow-3: 0 8px 24px rgba(0, 0, 0, 0.5);
    --bd-shadow-4: 0 16px 48px rgba(0, 0, 0, 0.6);
  }
}

/* ================= Explicit in-app toggle =================
   Wins in BOTH directions over the OS preference. Must stay byte-identical to the
   OS-preference dark block above; see there for how these were solved.
   Guard: TokenContrastTests resolves this palette separately. */
:root[data-theme="dark"] {
  --bd-color-surface: #05131D;
  --bd-color-surface-raised: #111E27;
  --bd-color-surface-overlay: #1D2831;
  --bd-color-surface-overlay-2: #28323A;
  --bd-color-on-surface: #EAEEF0;
  --bd-color-muted: #A0A5A9;
  --bd-color-border: #28323A;
  --bd-color-border-strong: #6C6E68;

  --bd-color-primary-light: #6FC5FC;

  --bd-color-backdrop-warm: #58001F;
  --bd-color-backdrop-sun: #432100;
  --bd-color-backdrop-cool: #002E52;
  --bd-color-glass-bg: rgba(17, 30, 39, 0.55);
  --bd-color-glass-bg-strong: rgba(17, 30, 39, 0.72);
  --bd-color-glass-border: rgba(111, 197, 252, 0.10);
  --bd-color-glass-highlight: inset 0 1px 0 rgba(111, 197, 252, 0.08);
  --bd-color-card-surface: #111E27;
  --bd-color-section-surface: #05131D;

  --bd-color-success: #A5CC2C;
  --bd-color-on-success: #05131D;
  --bd-color-success-surface: #314A00;
  --bd-color-warning: #E4B14F;
  --bd-color-on-warning: #05131D;
  --bd-color-warning-surface: #5E3600;
  --bd-color-danger: #FF93AB;
  --bd-color-on-danger: #05131D;
  --bd-color-danger-surface: #780632;

  --bd-color-scrim: rgba(0, 0, 0, 0.6);
  --bd-color-focus-ring: #6FC5FC;

  --bd-grade-1: #88B000;
  --bd-grade-2: #C0E561;
  --bd-grade-3: #4AAFBA;
  --bd-grade-4: #91DEFF;
  --bd-grade-5: #D5A7E2;
  --bd-grade-6: #E4B14F;
  --bd-grade-7: #CA9411;
  --bd-grade-8: #FF93AB;
  --bd-grade-9: #F4698B;
  --bd-color-on-grade: #05131D;

  --bd-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
  --bd-shadow-2: 0 2px 8px rgba(0, 0, 0, 0.45);
  --bd-shadow-3: 0 8px 24px rgba(0, 0, 0, 0.5);
  --bd-shadow-4: 0 16px 48px rgba(0, 0, 0, 0.6);
}

/* ================= Explicit light choice =================
   Re-asserts the light values so an explicit choice beats a dark OS preference.
   Values intentionally duplicate the bare :root defaults above.

   These MUST stay byte-identical to the bare :root ramps. They drifted once: a
   correction to four ConditionBadge hexes was applied to :root and not here, so
   anyone who actively chose Light kept the pre-correction hexes and read their
   badge labels at 3.40-4.35:1 for a month. Guard: TokenContrastTests, which
   resolves all four palettes rather than two and so cannot miss this class of
   drift again. */
:root[data-theme="light"] {
  --bd-color-surface: #F5F7F8;
  --bd-color-surface-raised: #FFFFFF;
  --bd-color-surface-overlay: #FFFFFF;
  --bd-color-surface-overlay-2: #FFFFFF;
  --bd-color-on-surface: #05131D;
  --bd-color-muted: #4A5157;
  --bd-color-border: #D8DEE1;
  --bd-color-border-strong: #868C90;

  --bd-color-primary-light: #ABF2FF;

  --bd-color-backdrop-warm: #FFD9E1;
  --bd-color-backdrop-sun: #FFE198;
  --bd-color-backdrop-cool: #91DEFF;
  --bd-color-glass-bg: rgba(255, 255, 255, 0.62);
  --bd-color-glass-bg-strong: rgba(255, 255, 255, 0.78);
  --bd-color-glass-border: rgba(5, 19, 29, 0.08);
  --bd-color-glass-highlight: inset 0 1px 0 rgba(255, 255, 255, 0.6);
  --bd-color-card-surface: #FFFFFF;
  --bd-color-section-surface: #EAEEF0;

  --bd-color-success: #456300;
  --bd-color-on-success: #FFFFFF;
  --bd-color-success-surface: #E4FF9C;
  --bd-color-warning: #7A4C00;
  --bd-color-on-warning: #FFFFFF;
  --bd-color-warning-surface: #FFF2C6;
  --bd-color-danger: #BB355D;
  --bd-color-on-danger: #FFFFFF;
  --bd-color-danger-surface: #FFECF1;

  --bd-color-scrim: rgba(5, 19, 29, 0.48);
  --bd-color-focus-ring: #0077AF;

  --bd-grade-1: #314A00;
  --bd-grade-2: #5B7E00;
  --bd-grade-3: #00646E;
  --bd-grade-4: #0077AF;
  --bd-grade-5: #885A95;
  --bd-grade-6: #7A4C00;
  --bd-grade-7: #976300;
  --bd-grade-8: #BB355D;
  --bd-grade-9: #780632;
  --bd-color-on-grade: #FFFFFF;

  --bd-shadow-1: 0 1px 2px rgba(5, 19, 29, 0.06), 0 1px 1px rgba(5, 19, 29, 0.04);
  --bd-shadow-2: 0 2px 8px rgba(5, 19, 29, 0.10), 0 1px 2px rgba(5, 19, 29, 0.06);
  --bd-shadow-3: 0 8px 24px rgba(5, 19, 29, 0.14), 0 2px 6px rgba(5, 19, 29, 0.08);
  --bd-shadow-4: 0 16px 48px rgba(5, 19, 29, 0.24);
}
