/* ==========================================================================
   TIER 1: THE SEED, AND EVERY SCALE DERIVED FROM IT

   One colour decides the app. Everything structural — the brand ramp, the
   greys, every surface and border, and the secondary accent — is derived from
   --cwe-seed-brand in OKLCH. OKLCH is perceptually uniform, so a step of
   lightness reads as the same step at ANY hue: a new seed re-derives the whole
   system without a single value being re-tuned by hand. (The old ramps mixed
   toward white/black in sRGB, which shifts hue and muddies the mid-tones —
   why the greys had to be hand-written as hex, and why they had a hole in them.)
   ========================================================================== */
:root {
    /* THE DEFAULT WHEN NOTHING HAS CHOSEN. `light dark` means "this document renders correctly in
       either", so with no stored preference the browser follows the operating system and every
       light-dark() token below resolves accordingly — with no script involved.

       A host that KNOWS the user's choice (the template's App.razor reads the `cwe-theme` cookie
       server-side) writes `color-scheme` onto <html> as an inline style, which wins over this
       declaration. That is the whole mechanism: this line is the fallback, the cookie is the
       answer, and nothing has to run on the client to pick between them. */
    color-scheme: light dark;

    /* How wide the docked AI assistant is when open. On :root deliberately: the
       chat region and the dialog host are SIBLINGS (CweAppShell closes the shell
       before mounting the provider), so a custom property declared on the chat
       would inherit down and never reach the dialog. Both need the same number
       because the dialog reserves exactly this column rather than centring under
       it (#738), and two copies of it is one copy that drifts. */
    --cwe-ai-chat-width: min(23.75rem, 92vw);

    /* ----- THE SEED. Change this one value to rebrand the entire app. ----- */
    --cwe-seed-brand: #2962ff;

    /* Dials on the derivation — numbers, not colours. */
    --cwe-accent-hue-shift: 19;   /* secondary = the brand, rotated this far round the wheel */
    --cwe-neutral-chroma: 0.018;  /* how much tint the greys carry; 0 = purely achromatic */

    /* WHICH hue the greys carry, in degrees (#1047). Until this existed the neutral ramp was locked
       to the seed's hue, so "more tint" could only ever mean "more BRAND" — with a blue seed the only
       reachable greys were blue ones, and the app read as blue whatever else was done to it.
       75 is a warm grey, chosen by the owner (12-Sep-2026) after looking at the alternatives side by
       side: it is easier to sit in for a working day than a cool grey, and it sets the blue primary
       off by complement instead of competing with it.
       UNSET, the ramp falls back to `h` — the seed's own hue — so this is additive: an app that never
       names it gets exactly the pre-#1047 derivation. NOTE the consequence of us naming it here: the
       greys no longer follow a rebranded seed unless the app moves this dial too. That is the point
       (warm neutrals are the house look, not a by-product of the brand), but it is a real change to
       "one colour rebrands everything" and is stated so nobody rediscovers it. */
    --cwe-neutral-hue: 75;

    /* DARK GETS ITS OWN CHROMA, and that is not a detail. Warmth that reads as paper on a light
       surface reads as BROWN on a dark one — built, looked at, rejected by the owner on sight
       (12-Sep-2026). The same warmth does opposite work in the two schemes, so the amount of it is
       per scheme: dark stays near-achromatic, and on the seed's own hue rather than the warm one. */
    --cwe-neutral-chroma-dark: 0.008;
    /* Dark takes the seed's own hue directly — no dial. There is no use for a warm dark right now,
       and an undeclared token behind a var() fallback is invisible to F4 and to the contrast
       resolver, both of which read declarations rather than evaluating CSS. Add the dial when
       something actually needs it, and declare it when you do. */

    /* ----- STATUS HUES -----
       Deliberately NOT derived from the brand: green/amber/red carry meaning, and
       a "success" rotated off a blue seed stops reading as success. Only the HUE is
       pinned here — lightness and chroma are derived below, which is what keeps
       status harmonious with any brand AND keeps its text legible automatically.

       Filled status surfaces take their text from contrast-color(<bg>), which picks
       whichever of black/white wins on WCAG ratio. That makes LIGHTNESS the contract:
       success/error sit below the ~0.62 OKLCH crossover so white always wins, warning
       sits above it because amber conventionally wants dark text. Those lightnesses are
       now structural constants in the scales — not a hex a rebrand could accidentally
       lighten past the crossover, which is how you used to get muddy dark-on-red. */
    --cwe-hue-success: 150;
    --cwe-hue-warning: 70;
    --cwe-hue-error: 27;

    /* ----- Neutral ramp -----
       The brand's hue at a trace of chroma, on a monotonic lightness ladder, so
       "one step darker" is always well defined. Complete: no 400-700 hole, so a
       mid-tone is always available (the old ramp jumped 300 -> 800, which is why
       nothing in it could make a 3:1 border). The chroma multipliers trace the
       classic bell — near-neutral at both extremes, most tint through the dark
       mid-tones — which is the curve the hand-written greys already had. */
    /* 25 and 150 exist for the LIGHT surfaces (#1047). The light card used to be a literal #ffffff —
       the one hardcoded colour in an otherwise fully derived palette, and maximum luminance for a
       reader who is there all day. It is now a ramp step like everything else, and the page drops to
       100 with a new 150 beneath it for the header band, which also widens the page-to-card
       separation that pure white was providing by being at the very top of the scale. */
    /* ONE anchor colour carries the SCHEME's chroma and hue; every step below derives its lightness
       and its share of that chroma from it. Written this way so the two schemes can disagree about
       warmth without the ramp being spelled out twice — light-dark() resolves where the token is
       finally used, which is inside .cwe-theme and therefore under the right scheme. */
    --cwe-neutral-anchor: light-dark(
        oklch(from var(--cwe-seed-brand) 0.5 var(--cwe-neutral-chroma) var(--cwe-neutral-hue, h)),
        oklch(from var(--cwe-seed-brand) 0.5 var(--cwe-neutral-chroma-dark) h));
    /* 0.20 of the chroma was the mistake here, not the lightness. At 0.985 x0.20 the card rendered
       rgb(251,250,247) — only 4 points of channel spread, i.e. neutral white — while the page around
       it carried 12. Next to warm neighbours a neutral card does not read as "slightly brighter", it
       reads as WHITE, which is exactly what the owner saw after the first pass (12-Sep-2026).
       0.977 x0.55 gives the same warmth as its surroundings while staying a clear step above the
       page's rgb(242,237,230). A surface belongs to the family it sits in.
       The LIGHTNESS is pinned by the contrast gate, not by taste: at 0.970 the brand blue on this
       card measured 4.49:1 against a 4.5:1 floor (the avatar mark in the guaranteed table), and the
       build failed. 0.977 restores the margin. Darkening a surface moves every role drawn ON it, so
       check the gate before trusting the eye. */
    --cwe-primitive-neutral-25:  oklch(from var(--cwe-neutral-anchor) 0.977 calc(c * 0.55) h);
    --cwe-primitive-neutral-50:  oklch(from var(--cwe-neutral-anchor) 0.979 calc(c * 0.30) h);
    --cwe-primitive-neutral-100: oklch(from var(--cwe-neutral-anchor) 0.949 calc(c * 0.60) h);
    --cwe-primitive-neutral-150: oklch(from var(--cwe-neutral-anchor) 0.925 calc(c * 0.70) h);
    --cwe-primitive-neutral-200: oklch(from var(--cwe-neutral-anchor) 0.893 calc(c * 0.80) h);
    --cwe-primitive-neutral-300: oklch(from var(--cwe-neutral-anchor) 0.857 calc(c * 1.05) h);
    --cwe-primitive-neutral-400: oklch(from var(--cwe-neutral-anchor) 0.720 calc(c * 1.15) h);
    --cwe-primitive-neutral-500: oklch(from var(--cwe-neutral-anchor) 0.600 calc(c * 1.20) h);
    --cwe-primitive-neutral-600: oklch(from var(--cwe-neutral-anchor) 0.484 calc(c * 1.25) h);
    --cwe-primitive-neutral-700: oklch(from var(--cwe-neutral-anchor) 0.380 calc(c * 1.35) h);
    --cwe-primitive-neutral-800: oklch(from var(--cwe-neutral-anchor) 0.320 calc(c * 1.40) h);
    --cwe-primitive-neutral-850: oklch(from var(--cwe-neutral-anchor) 0.278 calc(c * 1.05) h);
    /* 875 is the DARK page (#1047). Dark used to run chrome 950 / page 900 / card 850, which put the
       frame at L 0.164 — near-black, and the owner's verdict on seeing it was that the header, rails
       and footer were too dark. The frame moves up to 900 and the page takes this new step, so the
       dark ladder is four even rungs instead of a black frame around a grey app.
       The whole dark end was then LIFTED (0.190/0.212/0.234/0.284 -> 0.225/0.250/0.278/0.320): OKLCH
       lightness is perceptual, and 0.190 lands on rgb(18,20,22) in sRGB — the frame still read as
       black on screen even after it stopped being the darkest step. Judge these in rgb, not in L. 950 is NOT re-lit to achieve that: it is also the light scheme's text ink and its
       divider mix, so changing its lightness would quietly lift light-mode body text. */
    --cwe-primitive-neutral-875: oklch(from var(--cwe-neutral-anchor) 0.250 calc(c * 0.85) h);
    --cwe-primitive-neutral-900: oklch(from var(--cwe-neutral-anchor) 0.225 calc(c * 0.70) h);
    --cwe-primitive-neutral-950: oklch(from var(--cwe-neutral-anchor) 0.164 calc(c * 0.45) h);

    /* ----- Brand ramp -----
       The seed's own hue and chroma, re-lit. Chroma tapers with distance from the
       seed because a tint that keeps full chroma reads neon, and a shade that does
       reads like ink. 400 is the dark-mode tint (lighter than the seed: dark themes
       need the brand to rise off the surface, not sink into it). */
    --cwe-primitive-brand-50:  oklch(from var(--cwe-seed-brand) 0.965 calc(c * 0.12) h);
    --cwe-primitive-brand-100: oklch(from var(--cwe-seed-brand) 0.925 calc(c * 0.24) h);
    --cwe-primitive-brand-400: oklch(from var(--cwe-seed-brand) 0.680 calc(c * 0.85) h);
    --cwe-primitive-brand-500: var(--cwe-seed-brand);
    --cwe-primitive-brand-900: oklch(from var(--cwe-seed-brand) 0.360 calc(c * 0.72) h);

    /* ----- Secondary/accent ramp — the brand, rotated. ----- */
    --cwe-primitive-accent-500: oklch(from var(--cwe-seed-brand) l c calc(h + var(--cwe-accent-hue-shift)));
    --cwe-primitive-accent-400: oklch(from var(--cwe-primitive-accent-500) 0.680 calc(c * 0.85) h);

    /* ----- Status ramps — pinned hue, derived lightness (see the contract above). ----- */
    --cwe-primitive-success-500: oklch(0.527 0.137 var(--cwe-hue-success));
    --cwe-primitive-success-400: oklch(0.680 0.130 var(--cwe-hue-success));
    --cwe-primitive-warning-500: oklch(0.754 0.157 var(--cwe-hue-warning));
    --cwe-primitive-warning-400: oklch(0.845 0.135 var(--cwe-hue-warning));
    --cwe-primitive-error-500: oklch(0.539 0.194 var(--cwe-hue-error));
    --cwe-primitive-error-400: oklch(0.700 0.165 var(--cwe-hue-error));

    /* ----- Status INK ramp — the same hues at text grade. -----
       A fill and a glyph need different lightness, and one value cannot be both. The 500 steps are
       tuned to be filled AREA on a light surface; used as TEXT on that surface, warning measures
       2.26:1 (needs 4.5:1) because yellow is inherently light at fill grade. L is pinned at 0.500
       for all three so a mixed status line reads as one weight; measured on a light surface:
       success 5.64:1, warning 6.14:1, error 6.52:1. Dark mode reuses the 400 steps, which are
       already light enough on a dark surface. */
    --cwe-primitive-success-700: oklch(0.500 0.130 var(--cwe-hue-success));
    --cwe-primitive-warning-700: oklch(0.500 0.120 var(--cwe-hue-warning));
    --cwe-primitive-error-700: oklch(0.500 0.170 var(--cwe-hue-error));
}

/* ==========================================================================
   TIER 2: SEMANTIC THEMATIC LAYERING (Isolated via @scope)
   ========================================================================== */
@scope (.cwe-theme) {
    :scope {
        /* color-scheme is deliberately NOT pinned here. It is inherited from the document root,
           which the HOST decides before a byte reaches the browser: the template's App.razor reads
           the `cwe-theme` cookie server-side and writes color-scheme onto <html>, so a returning
           dark-mode user's very first paint is already dark. There is no pre-paint script any more
           — the server knows the answer, so nothing has to race to correct it.

           With no cookie, `:root { color-scheme: light dark }` above lets the OS preference decide.
           The runtime toggle still flips it explicitly via `:scope.cwe-theme--dark` below, and
           CweThemeProvider keeps <html> in sync on every change so a switch back to light takes
           hold against a server-written dark. */
        /* ----- Palette: Base Surfaces & Text ----- */
        /* The light arm moved one step down the ramp (#1047): page 50 -> 100, card #ffffff -> 25,
           header band 100 -> 150. Pure white cards were glare for a reader who is in the app all day,
           and the literal was the only colour in this palette a rebrand could not reach. Dropping the
           page as well keeps the card clearly lifted off it — that separation used to come for free
           from the card sitting at the very top of the scale. Text is L 0.164, so a darker page RAISES
           text contrast; the move is in the safe direction and ContrastPolicyTests re-measures it. */
        --cwe-palette-background: light-dark(var(--cwe-primitive-neutral-100), var(--cwe-primitive-neutral-875));
        --cwe-palette-surface: light-dark(var(--cwe-primitive-neutral-25), var(--cwe-primitive-neutral-850));
        --cwe-palette-surface-variant: light-dark(var(--cwe-primitive-neutral-150), var(--cwe-primitive-neutral-800));

        /* THE APP FRAME — the header, the footer and the two side rails (#1047). A step DARKER than
           the page it surrounds, in both schemes, so the shell recedes and the content the reader came
           for is the brightest thing on screen. It used to be `surface`, i.e. the same value as a
           card and LIGHTER than the page between them, which inverted that: the chrome advanced and
           the work sat in a well.
           Light takes 200 (below the page's 100); dark takes 950, below its 900. Deliberately not
           surface-variant, which is the band INSIDE a card and has to stay readable against the card
           it sits on — two different jobs that would drift the moment either moved. */
        --cwe-palette-chrome: light-dark(var(--cwe-primitive-neutral-200), var(--cwe-primitive-neutral-900));
        --cwe-palette-text: light-dark(var(--cwe-primitive-neutral-950), var(--cwe-primitive-neutral-50));
        --cwe-palette-text-secondary: light-dark(var(--cwe-primitive-neutral-600), var(--cwe-primitive-neutral-300));
        /* A RECORD'S LABEL, and not body secondary text. The two have different jobs, and one token
           serving both is what coupled them: a micro-cap label must stay a stated distance FROM the
           value beside it, while secondary prose only has to be legible on its surface.

           Sharing text-secondary, the label measured 1.46:1 against text in dark — legible, and
           indistinguishable. #275 accepted the label because it carries four separating signals at
           once (smaller, tracked, uppercase, quieter); in dark the fourth had quietly gone and three
           were doing all the work while every contrast check stayed green (#786).

           The two arms are NOT symmetric, because the ladder is not. Light keeps neutral-600 and its
           3.01:1; dark cannot reach that from one step — neutral-500 matches the separation but drops
           the label to 4.23:1 on the surface and fails AA. neutral-400 is the most the dark ladder
           gives while both hold: 2.33:1 from text, 6.76:1 from surface. ContrastPolicyTests.Separations
           owns that number. Retuning text-secondary instead was tried and rejected: it dropped the
           diagram's decision-shape detail line to 4.46:1, which is the blast radius a shared token
           has and a dedicated one does not. */
        --cwe-palette-text-label: light-dark(var(--cwe-primitive-neutral-600), var(--cwe-primitive-neutral-400));
        /* Light: a border is a DARKER line than its surface. Dark: a LIGHTER one — so the dark
           value must sit ABOVE surface-variant on the ladder, not on it. It used to equal
           surface-variant (both neutral-800) → every edge around a raised chip vanished. */
        --cwe-palette-border: light-dark(var(--cwe-primitive-neutral-200), var(--cwe-primitive-neutral-700));
        /* Translucent so it reads on ANY surface. The dark value used to equal surface
           (neutral-850) → dividers were invisible, forcing per-component border+shadow hacks. */
        --cwe-palette-divider: light-dark(
            color-mix(in srgb, var(--cwe-primitive-neutral-950) 12%, transparent),
            color-mix(in srgb, var(--cwe-primitive-neutral-50) 16%, transparent));
        /* ----- Palette: Brand & Accent Functional Roles ----- */
        --cwe-palette-primary: light-dark(var(--cwe-primitive-brand-500), var(--cwe-primitive-brand-400));
        --cwe-palette-secondary: light-dark(var(--cwe-primitive-accent-500), var(--cwe-primitive-accent-400));
        /* ----- Palette: Status Roles ----- */
        --cwe-palette-success: light-dark(var(--cwe-primitive-success-500), var(--cwe-primitive-success-400));
        --cwe-palette-warning: light-dark(var(--cwe-primitive-warning-500), var(--cwe-primitive-warning-400));
        --cwe-palette-error: light-dark(var(--cwe-primitive-error-500), var(--cwe-primitive-error-400));
        /* ----- Palette: Disabled States ----- */
        --cwe-palette-disabled: light-dark(var(--cwe-primitive-neutral-200), var(--cwe-primitive-neutral-800));
        /* ITS OWN STEP, and one value for both schemes. This was byte-identical to
           --cwe-palette-text-secondary, so a disabled control's text was exactly the colour of text
           that IS live: disabled read as merely quiet, and the one signal left to a control whose
           border and fill have gone flat was already spoken for (#790).

           neutral-500 is the middle of the ladder, which is why it needs no light-dark(): on a light
           surface it is LIGHTER than secondary's neutral-600, and on a dark one it is DARKER than
           secondary's neutral-300 — quieter than live text in both directions from one value. Two
           arms would have been two numbers to keep in step for no gain.

           WCAG exempts a disabled control from the text minimum, so this is deliberately below it;
           what it may not be is indistinguishable from live text, and ContrastPolicyTests.Separations
           holds that. The identical-value coupling is what #786 nearly inverted: retuning secondary
           alone would have left disabled LIGHTER, and therefore more prominent, than the live label
           beside it. */
        --cwe-palette-disabled-text: var(--cwe-primitive-neutral-500);
        /* ----- Palette: Categorical Accents (IDENTITY, not status) -----
           Five roles that carry no judgement, for telling several EQUAL things apart: the five
           perspectives of a scorecard, the departments on a launcher, the streams on a board. The
           status roles could not do this job — Success, Warning and Error each already mean
           something, so a five-member enum ran out of neutral slots at three and its fifth member
           landed on Error. A red Safety chip beside a green Financial one reads as "safety is
           failing", which is the opposite of what it says (#956).

           Every one is a color-mix of two ROLE tokens, never a new hue. That is what keeps them
           inside the theme: a rebrand moves them with the palette, and light-dark() inverts them
           through the roles they are made of, so there is no second pair of numbers to keep in
           step. CweTileAccent's Teal and Orange are the same construction and the same argument.

           Never transition these — they resolve through light-dark(), and a transition on a
           theme-aware colour animates the theme flip itself. */
        --cwe-palette-accent-teal: color-mix(in oklab, var(--cwe-palette-success) 55%, var(--cwe-palette-primary));
        --cwe-palette-accent-violet: color-mix(in oklab, var(--cwe-palette-primary) 60%, var(--cwe-palette-error));
        --cwe-palette-accent-orange: color-mix(in oklab, var(--cwe-palette-warning) 55%, var(--cwe-palette-error));
        --cwe-palette-accent-slate: color-mix(in oklab, var(--cwe-palette-text-secondary) 72%, var(--cwe-palette-primary));
        --cwe-palette-accent-plum: color-mix(in oklab, var(--cwe-palette-error) 55%, var(--cwe-palette-secondary));
        /* ----- Palette: Accessible Text Overrides -----
           The DECLARED value of every one of these is static, and the contrast-color() arm below is
           an @supports-guarded upgrade. That shape is forced, and the obvious alternative is a trap:

           declaring the token twice, static first and contrast-color() second, does NOT give a
           fallback. A custom property accepts almost any token stream at parse time, so the
           contrast-color() line parses fine and WINS THE CASCADE even on an engine that cannot
           compute it; the invalidity only bites later, when `color: var(--cwe-palette-primary-text)`
           substitutes it and goes invalid at computed-value time — at which point `color` INHERITS.
           Every filled button, badge, status chip and sidebar pill then renders body ink on its own
           fill. Framework-wide, silent, and shipped: it reached CI, where Chrome 131 measured the
           sidebar's active pill at 3.93:1 light / 2.92:1 dark while a supporting local browser
           showed 4.90:1. Measured both ways before this was written (probe: two declarations →
           the parent's colour; the same override under a false @supports → the static value stands).

           @supports is evaluated at PARSE time, so a non-supporting engine never sees the override
           and keeps what is declared here.

           The static values are what contrast-color() computes today, measured role by role in a
           browser that supports it — all twenty checked against contrast-color()'s own output, so a
           supporting engine is unchanged and a non-supporting one lands on the same answer. Dark is
           black throughout, because every dark arm is a lighter 400 step and sits above the ~0.62
           crossover; light is white except where the fill is inherently light.

           Because the declared value is now static, CssTokenResolver can read it: the first five
           pairs are enforced by ContrastPolicyTests, which had no way to check any of them before.
           The five categorical accents still are not — they are color-mix(in oklab, …) and the
           resolver models only `in srgb` (#1052). */
        --cwe-palette-primary-text: light-dark(#ffffff, #000000);
        --cwe-palette-secondary-text: light-dark(#ffffff, #000000);
        --cwe-palette-success-text: light-dark(#ffffff, #000000);
        --cwe-palette-warning-text: #000000;
        --cwe-palette-error-text: light-dark(#ffffff, #000000);
        --cwe-palette-accent-teal-text: light-dark(#ffffff, #000000);
        --cwe-palette-accent-violet-text: light-dark(#ffffff, #000000);
        --cwe-palette-accent-orange-text: #000000;
        --cwe-palette-accent-slate-text: light-dark(#ffffff, #000000);
        --cwe-palette-accent-plum-text: light-dark(#ffffff, #000000);
        /* Let the browser compute each one against its own fill where it can. This keeps the pairing
           correct through a rebrand: a reseeded --cwe-seed-brand moves the fills, and these follow
           without anyone re-measuring the ten static values above. */
        @supports (color: contrast-color(red)) {
            --cwe-palette-primary-text: contrast-color(var(--cwe-palette-primary));
            --cwe-palette-secondary-text: contrast-color(var(--cwe-palette-secondary));
            --cwe-palette-success-text: contrast-color(var(--cwe-palette-success));
            --cwe-palette-warning-text: contrast-color(var(--cwe-palette-warning));
            --cwe-palette-error-text: contrast-color(var(--cwe-palette-error));
            --cwe-palette-accent-teal-text: contrast-color(var(--cwe-palette-accent-teal));
            --cwe-palette-accent-violet-text: contrast-color(var(--cwe-palette-accent-violet));
            --cwe-palette-accent-orange-text: contrast-color(var(--cwe-palette-accent-orange));
            --cwe-palette-accent-slate-text: contrast-color(var(--cwe-palette-accent-slate));
            --cwe-palette-accent-plum-text: contrast-color(var(--cwe-palette-accent-plum));
        }
        /* ----- Palette: Status Roles as INK (glyphs ON a surface) -----
           Read the pair carefully, they are opposites:
             *-text  = text drawn ON a role FILL   (white on a warning chip)
             *-ink   = the role drawn AS text on a normal SURFACE (a warning-coloured title)
           Use -ink wherever status colours a glyph rather than an area. The fill-grade role is not
           a substitute: as text on a light surface it fails contrast. */
        --cwe-palette-success-ink: light-dark(var(--cwe-primitive-success-700), var(--cwe-primitive-success-400));
        --cwe-palette-warning-ink: light-dark(var(--cwe-primitive-warning-700), var(--cwe-primitive-warning-400));
        --cwe-palette-error-ink: light-dark(var(--cwe-primitive-error-700), var(--cwe-primitive-error-400));
        /* ----- Spacing Scale (Structural Layout Defaults) -----
           rem, not px, and the comments are the px each resolves to at a default 16px root — so
           nothing about the default rendering changes. What changes is what happens when a reader
           raises their browser's font size: the text grows and now the space around it grows with
           it, instead of the layout staying put and squeezing.

           The name is the step, not the pixel count: -1 through -8 on a 4px grid. Keep it that way.
           A half-step added to legitimise an off-grid value stops the scale being a scale.

           That rule stands, and --cwe-nudge-* below is not an exception to it. A half-step would be
           a SECOND SPACING VALUE, picked because 4px was inconvenient. A nudge is not spacing at
           all: it answers to the geometry of a glyph or a fixed-size control — an inline code span's
           line box, a 34px day circle, the thumb inset in a 20px toggle track — and moving one to
           4px does not tidy it, it visibly misaligns it. Same reasoning that keeps
           --cwe-border-width out of this scale.

           Borders, shadow offsets and the focus ring stay px on purpose — see the note above
           --cwe-border-width. Spacing scales with text because it SURROUNDS text; a hairline does
           not, because it is drawing a line on a screen. */
        --cwe-space-1: 0.25rem;  /*  4px */
        --cwe-space-2: 0.5rem;   /*  8px */
        --cwe-space-3: 0.75rem;  /* 12px */
        --cwe-space-4: 1rem;     /* 16px */
        --cwe-space-6: 1.5rem;   /* 24px */
        --cwe-space-8: 2rem;     /* 32px */
        /* ----- Radius System ----- */
        --cwe-radius-sm: 4px;
        --cwe-radius-md: 8px;
        --cwe-radius-lg: 14px;
        --cwe-radius-pill: 999px;
        /* The corner of every control a pointer hits as a whole: buttons, field controls, the grid's
           search box and a toolbar's own controls. ONE token, so a button and a field of the same
           height read as one family — they did not while buttons took --cwe-radius-sm and fields
           --cwe-radius-md, which put 4px and 8px corners side by side in one toolbar row (#1013).
           The small parts INSIDE a control (a checkbox box, a list option, a picker's nav arrow)
           keep --cwe-radius-sm: they are details of a control, not controls in a row. */
        --cwe-control-radius: var(--cwe-radius-md);
        /* ----- Edges and the focus ring -----
           The hairline was the most duplicated literal in the framework: `1px` appeared in 128
           border declarations across 43 stylesheets, so "make every edge slightly heavier" was a
           128-site edit and therefore never something anyone would do. As one token it is a theme
           decision — which is what makes a denser or higher-contrast variant possible at all.

           px deliberately, not rem. A border is device geometry, not typography: a hairline that
           thickened with the reader's font size would read as a rendering fault rather than as
           larger text. The same is true of the focus ring, which must stay a constant, recognisable
           2px at every text size. See StylesheetUnitPolicyTests for where rem IS required. */
        --cwe-border-width: 1px;
        --cwe-border-width-emphasis: 2px;
        --cwe-focus-ring-width: 2px;
        --cwe-focus-ring-offset: 2px;
        /* ----- Control heights -----
           One tier per CweSize, for every control a finger or a pointer has to hit: buttons and the
           field chrome read the same three values, so "Medium" is one height across the framework
           rather than a 40 that happens to appear in two stylesheets.

           They are here rather than in each component sheet because CweScaffold's Density modes
           override them. That is what makes Touch mean bigger TARGETS and not merely wider gaps —
           a mode that opened the spacing while leaving every control 40px tall would be a shop-floor
           setting that made the screen emptier without making anything easier to press.

           px, like the border tokens: a hit target answers to the size of a fingertip, which does
           not grow when the reader turns up their text size. */
        --cwe-control-h-sm: 32px;
        --cwe-control-h-md: 40px;
        --cwe-control-h-lg: 48px;
        /* ----- Sub-grid: values that answer to glyph or control geometry -----
           Exactly two, and that cap is the point. 1px and 2px are the only sizes that cannot be
           expressed on the 4px grid without moving something visible, so anything >= 3px is rhythm
           and belongs on the scale above — even where it began life as an alignment tweak. Admit a
           --cwe-nudge-3 and the next off-grid value has the same claim, and the argument never gets
           easier to refuse. That is how the residue this family exists to clear got there: 74 values
           across 27 files, of which 2px, 6px and 10px were 57 — a second, finer scale nobody
           decided on.

           px, like the border tokens and for the same reason: an optical correction that grew with
           the reader's font size would stop being a correction.

           This is not permission to leave the grid. It is a RECORD THAT SOMEONE JUDGED THIS VALUE,
           which a raw 5px never was — being indistinguishable from an oversight. */
        --cwe-nudge-1: 1px;
        --cwe-nudge-2: 2px;
        /* ----- The bond: the gap that says "these two things are one thing" -----
           A label and the value it names are one field, and what makes them read as one is that
           the gap INSIDE the pair is much smaller than the gap BETWEEN pairs. The ratio is 1:4 —
           4px inside, 16px between. Let the inside gap drift up toward the outside gap and a
           detail view of twenty fields stops being twenty fields and becomes forty unrelated
           lines, which is a failure of grouping rather than of spacing.

           DENSITY-INVARIANT, and that is the whole reason it is a token of its own rather than
           --cwe-space-1. Density redefines --cwe-space-1 to 2px (compact) and 6px (touch) while
           the row gap moves to 12px and 20px, so a bond written as --cwe-space-1 would be 1:6 in
           compact and 1:3.3 in touch — tightening the grid would silently loosen the very bond
           that holds a field together. Density is about how much room the screen gives; the bond
           is about what belongs to what, and no density mode has an opinion on that.

           rem, unlike --cwe-nudge-* and --cwe-border-width, and the two ideas do not conflict:
           density-invariant and text-invariant are different axes. This gap SURROUNDS TEXT, so it
           tracks the reader's font size exactly as the spacing scale does. What it must never
           track is the density MODE.

           Enforced by StylesheetBondTokenTests — declared once, and never inside a [data-density]
           block. Invariance a future mode can quietly redeclare is a convention, and conventions
           decay silently; that is precisely how --cwe-space-1 came to mean three things. */
        --cwe-bond: 0.25rem;
        /* ----- Elevation. light-dark() must wrap the COLOR (it's a color function); it is
           invalid inside rgb()'s channel list, which silently voids the whole shadow. ----- */
        --cwe-shadow-1: 0 1px 2px light-dark(rgb(16 24 40 / 0.08), rgb(0 0 0 / 0.24)),
                        0 1px 3px light-dark(rgb(16 24 40 / 0.06), rgb(0 0 0 / 0.18));
        --cwe-shadow-2: 0 4px 8px light-dark(rgb(16 24 40 / 0.10), rgb(0 0 0 / 0.30)),
                        0 2px 4px light-dark(rgb(16 24 40 / 0.06), rgb(0 0 0 / 0.22));
        --cwe-shadow-3: 0 12px 24px light-dark(rgb(16 24 40 / 0.14), rgb(0 0 0 / 0.44)),
                        0 4px 8px light-dark(rgb(16 24 40 / 0.08), rgb(0 0 0 / 0.30));
        /* ----- Typography Basics ----- */
        --cwe-font-family: "Segoe UI", system-ui, -apple-system, Roboto, sans-serif;
        /* These two are now ALIASES of the scale, not its source. The direction matters and is not
           cosmetic: --cwe-font-size is the name apps and docs have always used, so it stays, but the
           value lives in the role. Pointing them at each other instead is a circular reference, which
           CSS resolves to nothing at all — every surface reading the role loses its size and inherits.
           Valid syntax, no error anywhere, and no test could see it (StylesheetCycleTests can, now). */
        --cwe-font-size: var(--cwe-text-body);
        --cwe-line-height: var(--cwe-leading-normal);

        /* ----- THE TYPE SCALE (#1048) -----
           Nine ROLES, each naming what the text IS. Not a numeric ladder, and the distinction is the
           whole point: before this, 95 sites carried a literal size in 23 distinct values, because a
           component asked "what size is this?" and answered privately. Eleven components had invented
           their own token (--cwe-grid-font, --cwe-field-font, --cwe-calendar-font-sm …) for a step the
           theme did not offer. None of the 23 was wrong enough to notice alone; the drift was visible
           only when they were listed together, which nothing did.

           Pick by MEANING. Two roles may share a step today — control and caption are both 0.8125rem —
           and that is deliberate: they are different things, so they can move apart later without
           anyone having to work out which of the call sites meant which.

           Values are rem so the reader's own text-size setting scales all of it. Never px: text sized
           in px ignores that setting outright, and StylesheetUnitPolicyTests fails the build on one. */
        --cwe-text-micro:    0.6875rem;  /* 11px — a count inside a badge, a unit suffix. Numerals, mostly. */
        --cwe-text-label:    0.75rem;    /* 12px — field labels, menu group headings, overlines, meta rows. */
        --cwe-text-caption:  0.8125rem;  /* 13px — descriptions and secondary prose under a title. */
        --cwe-text-control:  0.8125rem;  /* 13px — compact interactive text: small buttons, breadcrumbs. */
        --cwe-text-body:     0.875rem;   /* 14px — default reading text, and the source --cwe-font-size aliases. */
        --cwe-text-body-lg:  0.9375rem;  /* 15px — a body passage that carries the surface: empty states, snackbars. */
        --cwe-text-heading:  1.0625rem;  /* 17px — the title of a panel, card or form. */
        --cwe-text-title:    1.25rem;    /* 20px — the title of a page, dialog or focus view. */
        --cwe-text-display:  1.75rem;    /* 28px — the one hero line on a launcher. Used once, on purpose. */

        /* WEIGHT. Four values were already in use and no token named any of them, so a call site said
           `600` and nothing could tell whether that meant "a heading" or "emphasis inside a sentence".
           NOTE what 600 costs on the shipped stack: Segoe UI Semibold ships PROPORTIONAL figures where
           Regular ships tabular ones, which is why the theme root pins font-variant-numeric (#1046).
           Reach for semibold to mark a heading, not to make a number look important. */
        --cwe-font-weight-normal:   400;
        --cwe-font-weight-medium:   500;  /* a label that must separate from its value without shouting */
        --cwe-font-weight-semibold: 600;  /* headings and titles — the default emphasis in this UI */
        --cwe-font-weight-bold:     700;  /* reserve for a numeral in a badge, where size is already tiny */

        /* TRACKING. Every one of these is on an uppercase or small label — widening letterforms is what
           makes a 12px overline legible, and it is meaningless on body text. Named for the job so it
           cannot be sprinkled on a paragraph. */
        --cwe-tracking-sm:       0.02em;  /* initials and micro numerals, where 0.04 reads as a gap */
        --cwe-tracking-label:    0.04em;  /* the standard small label */
        --cwe-tracking-overline: 0.08em;  /* an uppercase section overline, where the width IS the signal */

        /* LEADING, as unitless ratios so it scales with whatever size it lands on. A line-height in px
           does not: it stays put while the text around it grows, which is how three badge pills came to
           centre their digit correctly only at a 16px root. */
        --cwe-leading-none:   1;      /* single-line chrome — a button, a badge, a tab */
        --cwe-leading-tight:  1.25;   /* headings and titles, where a loose line looks accidental */
        --cwe-leading-snug:   1.4;    /* dense multi-line text: grid cells, list rows */
        --cwe-leading-normal: 1.5;    /* reading text, and the source --cwe-line-height aliases. */
        /* INLINE HELP — the sentence that annotates a single control, rendered in the field's meta
           row beneath it. A ROLE, not a size, so every surface that explains a control reaches for
           one value.

           Explanatory text has exactly two tiers, and this is the smaller. Text that introduces a
           GROUP — a panel description, an empty state's hint — sits under a title rather than under
           a label and takes --cwe-font-size instead; it is a peer of body text, not a footnote to a
           control.

           The token exists because the theme carries no type scale, and cwe-calendar.css tells a
           component needing a smaller step to "declare its own". Six surfaces did exactly that and
           landed on 0.72rem, 0.75rem, 0.78rem, 0.8rem, 0.8125rem and 0.875rem. Two of those are the
           two legitimate tiers; the other three are eyeballed. None was wrong enough to notice
           alone — the drift is only visible when the six are listed together, which nothing did. */
        --cwe-help-font: var(--cwe-text-label);
        /* ----- Motion Definitions ----- */
        --cwe-transition: 150ms cubic-bezier(.4, 0, .2, 1);
        /* Mount Root-level Container Properties */
        box-sizing: border-box;
        color: var(--cwe-palette-text);
        background-color: var(--cwe-palette-background);
        font-family: var(--cwe-font-family);
        font-size: var(--cwe-font-size);
        line-height: var(--cwe-line-height);
        /* FIGURES ARE TABULAR, EVERYWHERE, BY CONSTRUCTION (#1046).
           Not a preference — a defect class this removes. Segoe UI Regular ships tabular figures and
           Segoe UI SEMIBOLD ships proportional ones, so emphasising a number silently unaligned its
           digits and nothing in any stylesheet said which surfaces were which. Measured at 15px:
           weight 400 gives 1111 and 8888 both 32.34px; weight 600 gives 24.14px and 33.31px, a 38%
           difference, on the same page in the same family.
           It was OS-dependent too — the stack ends in system-ui/-apple-system/Roboto/sans-serif, so
           which figures a reader got was decided by whichever face their machine supplied.
           Declared here rather than per surface because the trap is invisible at the call site: the
           author of the next bold number cannot see that the weight decided it. font-variant-numeric
           inherits, so one declaration covers every descendant.
           The explicit declarations already on timestamps, score tables, counters, calendar cells,
           the grid's progress label and the charts are now redundant and are deliberately kept: they
           state intent where it is read, and they keep those surfaces tabular if this line is ever
           relaxed for prose. */
        font-variant-numeric: tabular-nums;
    }

        /* Activating dark theme flips the strategy context entirely */
        :scope.cwe-theme--dark {
            color-scheme: dark;
        }

    /* Isolated, safe layout normalization */
    *, *::before, *::after {
        box-sizing: border-box;
    }

    /* ----- Icon Component (Modern Optimized Structure with Nesting) ----- */
    .cwe-icon {
        color: var(--cwe-icon-color, inherit);
        line-height: 0;
        flex: 0 0 auto;

        svg {
            width: 100%;
            height: 100%;
            fill: currentColor;
        }

        /* Native CSS nesting can't concatenate &--primary (that's Sass).
           The modifier is on the SAME element, so use a compound &.class. */
        &.cwe-icon--inherit {
            --cwe-icon-color: inherit;
        }

        &.cwe-icon--primary {
            --cwe-icon-color: var(--cwe-palette-primary);
        }

        &.cwe-icon--secondary {
            --cwe-icon-color: var(--cwe-palette-secondary);
        }

        &.cwe-icon--success {
            --cwe-icon-color: var(--cwe-palette-success);
        }

        &.cwe-icon--warning {
            --cwe-icon-color: var(--cwe-palette-warning);
        }

        &.cwe-icon--error {
            --cwe-icon-color: var(--cwe-palette-error);
        }

        &.cwe-icon--disabled {
            --cwe-icon-color: var(--cwe-palette-disabled);
        }
    }
}
