/* ============================================================
   Training Management – design tokens
   ------------------------------------------------------------
   Single source of truth for every colour, size, radius, shadow,
   font and duration in the application.
   See docs/08-design-system-v2.md §2–§4 (visual system v2).

   No colour, radius, shadow, font-size or spacing literal may
   appear in any other stylesheet — ThemeTokenTests exists to catch
   that bypass. Token *names* are load-bearing: the E2E suite reads
   --tm-primary, --tm-sidebar-item-active and friends by name, so
   names are kept even when values move.

   Every contrast ratio quoted below is WCAG 2.x, computed against
   the ground named on the same line. AA wants 4.5:1 for normal
   text (1.4.3) and 3:1 for non-text graphics that carry meaning
   on their own (1.4.11).
   ============================================================ */

:root {
    /* ── Typography families ──────────────────────────────────
       Inter is gone (§2). It is a competent typeface and that was the problem:
       it is also the default of every dashboard template of the last five years,
       so it read as "no decision was made". Schibsted Grotesk is a grotesque with
       the same small-size legibility and noticeably more character in the
       numerals, which this application is mostly made of.

       No serif face anywhere in the system. Hierarchy is carried by size and
       weight, never by a second family.

       --tm-font-mono carries identifiers and dates — personal numbers (BRN-018),
       codes, timestamps. That is a functional choice, not decoration: tabular
       figures let a column of numbers be compared down the page. It is applied
       through the .tm-mono utility in base.css, never by writing font-family by
       hand in a component sheet. */
    --tm-font-sans: 'Schibsted Grotesk', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    --tm-font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;

    /* ── Brand — petrol ───────────────────────────────────────
       Was indigo-600 (#4f46e5). Indigo is the most worn accent of the decade and
       it sits close enough to a "recommended / mandatory" blue that it competed
       with the status palette. Petrol is far from both the green of "done" and
       the red of "overdue", so the brand can never be mistaken for a state — in
       an application whose whole job is flagging statutory training that is a
       functional requirement, not taste (§3.2).

       Measured, so the choice is checkable rather than asserted:
         white on --tm-primary        5.91:1  ✔ AA — every solid primary button
         white on --tm-primary-hover  8.20:1  ✔
         --tm-primary on --tm-surface 5.91:1  ✔ — links, active pagination
         --tm-primary on --tm-bg      5.57:1  ✔
         --tm-primary on --tm-primary-light  5.17:1  ✔ — text on the tint
         --tm-primary-hover on --tm-primary-light  7.17:1  ✔ — active nav row
       --tm-primary-light is a tint surface, not a foreground: 1.14:1 on
       --tm-surface, which is exactly what a quiet selected-state fill should be.
       --tm-primary-border is the hairline that goes with it (1.44:1); it is a
       decorative edge on an already-tinted surface, never the sole affordance.

       Where the accent may appear (§3.2): active navigation item, primary button,
       links, focus ring, current page in the pager, one accented KPI tile.
       Nowhere else.

       --tm-primary-on-dark IS GONE. It existed for one reason: the sidebar was
       near-black (#0b1120), --tm-primary only reached 2.84:1 against it, and a
       lighter step was needed to clear the 3:1 floor. The sidebar is white now
       (§3.4), so the token has nothing to mean — the sidebar's accent is simply
       --tm-primary-hover, via --tm-sidebar-item-active. Anything still reading
       --tm-primary-on-dark is reading a token that no longer exists and must move
       to --tm-sidebar-item-active.

       --tm-primary-glow was already retired in the previous round and stays gone:
       the coloured drop-shadow under every primary button was one of the most
       dated details in the UI. Consumers derive their shadow from --tm-primary
       with color-mix, which is also how the focus rings are built. */
    --tm-primary: #0B6E7F;
    --tm-primary-hover: #085765;
    --tm-primary-light: #E6F2F4;
    --tm-primary-border: #BFDCE1;

    /* --tm-on-accent is the label written ON a saturated accent fill — the solid
       primary button, the solid destructive button in a confirmation modal, a
       filled badge where §5.3 permits one. It is white here and it is the colour
       those controls already paint; the token exists because they paint it as a
       `#fff` LITERAL, and buttons.css says why in as many words: "`#fff` is a
       literal because there is no white token: --tm-surface is #FFFFFF only in
       the light theme".

         --tm-on-accent on --tm-primary  5.91:1 ✔ AA
         --tm-on-accent on --tm-success  5.34:1 ✔
         --tm-on-accent on --tm-warning  5.09:1 ✔
         --tm-on-accent on --tm-danger   6.69:1 ✔

       buttons.css reads it now — TM-143 step 2 moved the solid buttons off their
       `#fff` literals and onto this token, which is what makes them survive the
       dark theme: on dark accents white is NOT the answer (see the "white label"
       note in the dark block), and a literal cannot flip. */
    --tm-on-accent: #FFFFFF;

    /* ── Success / Warning / Danger ───────────────────────────
       Each hue ships as a pair: a foreground and the light tint it is written on.
       §5.3 makes soft tint + dot the shape of a status badge — a saturated fill is
       reserved for the genuinely exceptional — so every foreground has to clear AA
       on BOTH --tm-surface and its own tint, which is the constraint that picked
       these values.

         --tm-success #17795E   on --tm-surface 5.34:1 ✔   on tint 4.67:1 ✔
         --tm-warning #9A6206   on --tm-surface 5.09:1 ✔   on tint 4.56:1 ✔
         --tm-danger  #B02525   on --tm-surface 6.69:1 ✔   on tint 5.81:1 ✔

       The warning value is the one that moved most: it was amber-500 (#f59e0b),
       which measures 2.16:1 on white — unusable as text and, as the old base.css
       comment recorded, unable to reach even the 3:1 non-text floor at any usable
       saturation. Darkening it to #9A6206 is what lets a warning badge carry its
       meaning in the text colour like the other two, instead of being the one
       state that needed a workaround.

       White on any of the three also clears AA (5.34 / 5.09 / 6.69), so a
       saturated fill stays available where §5.3 permits one. */
    --tm-success: #17795E;
    --tm-success-light: #E4F3EE;
    --tm-warning: #9A6206;
    --tm-warning-light: #FDF1DF;
    --tm-danger: #B02525;
    --tm-danger-light: #FCEBEB;

    /* ── Neutrals — clean, faintly cool ───────────────────────
       Not parchment (that reads as book typesetting), not Tailwind slate (that
       reads as a template — #f8fafc / #0f172a is the exact pair every generic
       dashboard ships with). The ramp below is lower-chroma than slate and
       slightly cooler than neutral grey (§3.1).

         --tm-text on --tm-surface   18.32:1
         --tm-text on --tm-bg        17.24:1
         --tm-text on --tm-surface-2 16.48:1
         --tm-text-secondary on --tm-surface   6.18:1 ✔ AA
         --tm-text-secondary on --tm-bg        5.82:1 ✔
         --tm-text-secondary on --tm-surface-2 5.56:1 ✔

       --tm-text-secondary is a real text colour and clears AA on all three
       grounds, which is what makes it safe for column headers, field labels and
       secondary rows. */
    --tm-bg: #F7F8FA;
    --tm-surface: #FFFFFF;
    --tm-surface-2: #F1F3F6;       /* table headers, row hover, inset surfaces */

    /* --tm-text-muted IS NOT A TEXT COLOUR. 2.55:1 on --tm-surface, 2.40:1 on
       --tm-bg — under AA for text and under the 1.4.11 floor for a graphic that
       carries meaning by itself. It is permitted only for decoration that sits
       next to a label already carrying the meaning: the chevron in a select, an
       icon beside its own caption, an em-dash standing in for an empty value.
       If it is the only thing saying something, it is the wrong token. */
    --tm-text: #10151C;
    --tm-text-secondary: #59626E;
    --tm-text-muted: #9AA3AF;

    /* ── Rules and edges ──────────────────────────────────────
       --tm-border is the ordinary edge of a panel or a card (1.24:1 on
       --tm-surface); --tm-border-light is the hairline between table rows
       (1.14:1), deliberately quieter so a long list does not read as a grid.

       --tm-border-strong is the edge of an input (§6) and measures 3.11:1 on
       --tm-surface. That number is not decoration — it is the WCAG 1.4.11 floor.

       READ THIS BEFORE LIGHTENING IT. A text input here is --tm-surface sitting
       on --tm-bg, ~1.02:1 between them, so the fill tells the reader nothing.
       The border is therefore the whole of the "visual information required to
       identify a user interface component", which puts it under the 3:1 rule.

       The v2 spec first proposed #C9D0D9 because it looked lighter in the signed
       off mock-up. It measures 1.55:1 and does not clear the floor. The argument
       for shipping it anyway was that height, padding, placeholder and the focus
       ring carry the affordance instead — but that argument only holds when the
       control is identifiable at rest, and at 1.02:1 fill contrast it is not.
       A focus ring cannot rescue it either: focus is a state, 1.4.11 is about
       identifying the component before you interact with it.

       #8A93A0 is the compromise actually taken: it clears 3:1 with the least
       weight, one notch lighter than the previous round's #858f9e (3.27:1) so
       forms stay light against this paler ramp without failing the criterion.
       If this ever has to move, it moves here — never as a literal in a
       component sheet. */
    --tm-border: #E3E7EC;
    --tm-border-light: #EDF0F4;
    --tm-border-strong: #8A93A0;

    /* ── Application frame ────────────────────────────────────
       THE SIDEBAR IS LIGHT (§3.4). The near-black rail was the single most dated
       element left in the application — a heavy slab straight out of a 2015 admin
       template — and it forced a whole parallel palette to exist just to be
       legible on it (--tm-primary-on-dark, plus a set of white-alpha overlays
       derived with color-mix). All of that goes away: the sidebar is a surface
       like any other, so it uses the ordinary ramp.

       The active item is a tint plus a saturated label — no vertical indicator
       bar, no gradient, no decorative glow:
         --tm-sidebar-item-active on --tm-sidebar-item-active-bg   7.17:1 ✔ AA
         --tm-sidebar-item-active on --tm-sidebar-bg               8.20:1 ✔
         --tm-sidebar-item        on --tm-sidebar-bg               6.18:1 ✔
       The tint itself is 1.14:1 against the rail, which is intended: the label
       weight and colour carry "you are here", the fill only groups them.

       --tm-sidebar-item-active replaces --tm-primary-on-dark as the name anything
       drawn on the rail reaches for. */
    --tm-sidebar-bg: #FFFFFF;
    --tm-sidebar-border: #E3E7EC;
    --tm-sidebar-item: #59626E;
    --tm-sidebar-item-active-bg: #E6F2F4;
    --tm-sidebar-item-active: #085765;

    /* ── Typography scale ─────────────────────────────────────
       §2. One scale; each size has a matching --tm-leading-*. Weights 700 and 800
       are deliberately absent — semibold is the ceiling of the system.

       THE ROOT IS 14px, NOT 16px. Fomantic ships `html { font-size: 14px }` in
       semantic.min.css and nothing here overrides it, so `1rem` renders as 14px.
       The rem values below are calibrated against that 14px root — the px comment
       on each line is what actually renders and is the number §2 asks for.
       Writing the "obvious" 0.875rem for 14px would render 12.25px and quietly
       shrink the whole application by 12.5%.

       Moving the root to 16px is deliberately NOT done here: Fomantic sizes its
       own components in rem and em, so it would scale every untouched component
       up by 14% mid-redesign. Tracked separately as TM-148. If the root ever
       moves, these seven values are the only place that has to change.

       THE STEPS SHIFTED IN V2 — READ THIS IF YOU CONSUME THEM. The old scale ran
       12/13/14/16/18/22/28 and its "base" was the body default; the new one runs
       11/12/13/14/16/26/32 and the body default is --tm-text-md. Concretely:
         --tm-text-base  14px → 13px   now dense table content, not body text
         --tm-text-md    16px → 14px   now the default: forms, body, nav
         --tm-text-lg    18px → 16px   now card headers
         --tm-text-xl    22px → 26px   page titles, with real separation from body
         --tm-text-2xl   28px → 32px   KPI values
       A rule that said --tm-text-base meaning "body" now renders a step small and
       must move to --tm-text-md. The gap between a page title and its body text
       was 22px vs 14px, which is not a typographic difference; 26px vs 14px is. */
    --tm-text-xs: 0.786rem;     --tm-leading-xs: 1.143rem;    /* 11px / 16px — column labels, meta */
    --tm-text-sm: 0.857rem;     --tm-leading-sm: 1.286rem;    /* 12px / 18px — badges, secondary */
    --tm-text-base: 0.929rem;   --tm-leading-base: 1.429rem;  /* 13px / 20px — dense table content */
    --tm-text-md: 1rem;         --tm-leading-md: 1.5rem;      /* 14px / 21px — form text, DEFAULT */
    --tm-text-lg: 1.143rem;     --tm-leading-lg: 1.571rem;    /* 16px / 22px — card headers */
    --tm-text-xl: 1.857rem;     --tm-leading-xl: 2.143rem;    /* 26px / 30px — page title */
    --tm-text-2xl: 2.286rem;    --tm-leading-2xl: 2.571rem;   /* 32px / 36px — KPI values */

    --tm-weight-normal: 400;
    --tm-weight-medium: 500;
    --tm-weight-semibold: 600;

    /* Page titles are set with optical tracking; at 26px the default spacing
       reads loose. §2 fixes the value so it is not re-guessed per page. */
    --tm-tracking-title: -0.022em;

    /* ── Spacing ──────────────────────────────────────────────── */
    --tm-space-1: 4px;
    --tm-space-2: 8px;
    --tm-space-3: 12px;
    --tm-space-4: 16px;
    --tm-space-5: 24px;
    --tm-space-6: 32px;
    --tm-space-7: 48px;
    --tm-space-8: 64px;

    /* ── Radii ────────────────────────────────────────────────
       §4: 4px reads as print, 12px and up reads as a toy. 6–8px is the shape of
       an application. --tm-radius-lg (12px) is the ceiling and belongs to large
       surfaces only. --tm-radius-xl (16px) is gone — it had no consumers left. */
    --tm-radius-sm: 6px;
    --tm-radius-md: 8px;
    --tm-radius-lg: 12px;

    /* ── Shadows ──────────────────────────────────────────────
       §4. All four are cast in --tm-text (16,21,28) rather than slate, so the
       shade sits in the same hue family as the rest of the ramp instead of going
       blue. Elevation is deliberately shallow: a card is a card, not a floating
       panel. --tm-shadow-xl is not in §4 but has live consumers (the mobile nav
       drawer, the overlay sheets); it is the same ink taken one step further. */
    --tm-shadow-sm: 0 1px 2px rgba(16, 21, 28, 0.05);
    --tm-shadow-md: 0 1px 3px rgba(16, 21, 28, 0.08);
    --tm-shadow-lg: 0 6px 20px -4px rgba(16, 21, 28, 0.12), 0 2px 6px rgba(16, 21, 28, 0.05);
    --tm-shadow-xl: 0 16px 40px -8px rgba(16, 21, 28, 0.18), 0 4px 12px rgba(16, 21, 28, 0.08);

    /* ── Layout ───────────────────────────────────────────────
       --tm-row-height IS 46px, AND THAT IS A DECISION OF THIS ROUND, not an
       inherited constant. It was 44px. Tests that pinned 44 are rewritten (§8).

       --tm-content-max came down from 1440px: past ~1320px a table row gets wide
       enough that the eye loses the line it is reading on the way back from the
       right edge.

       --tm-control-height-sm is new — the height of a row-level action button, so
       the ⋮ menu and a "Detail" button agree with each other without either
       inventing a size. */
    --tm-content-max: 1320px;
    --tm-sidebar-width: 240px;
    --tm-header-height: 56px;
    --tm-row-height: 46px;
    --tm-control-height: 36px;
    --tm-control-height-sm: 28px;

    /* ── Motion ───────────────────────────────────────────────────
       --tm-transition-fast is the row hover (§5.2). It is deliberately
       quicker than --tm-transition: a table repaints under a moving
       pointer constantly, and at 150ms the highlight visibly lags the
       cursor across a long list. 120ms reads as instant while still
       softening the edge. */
    --tm-transition-fast: 120ms cubic-bezier(0.4, 0, 0.2, 1);
    --tm-transition: 150ms cubic-bezier(0.4, 0, 0.2, 1);
    --tm-transition-slow: 250ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* ── Dark theme ───────────────────────────────────────────────
   The neutral, surface, rail AND accent tokens are redefined here. The accent
   ramp used to be left at its light-theme values with a note that it needed its
   own contrast pass against dark surfaces; TM-143 step 1 is that pass, and every
   ratio quoted below is measured, not asserted — the grounds are the dark
   surfaces defined in this same block, and DarkThemeContrastTests re-measures
   them in a real browser on real screens.

   WIRED TO prefers-color-scheme AS OF TM-143 STEP 3 — AND IT WAS NOT, FOR TWO
   ROUNDS, ON PURPOSE.
   ---------------------------------------------------------------------------
   This is worth reading before touching either selector, because the reason the
   media query was absent is not the reason people assume, and it will apply
   again the next time a theme is half-built.

   The third selector — `@media (prefers-color-scheme: dark) {
   :root:not([data-theme="light"]) { … } }` — was written in round one and
   REMOVED, because on its own it shipped a half-finished dark theme to every
   reader whose OS is in dark mode. Swapping the neutral tokens repaints OUR
   surfaces, but it cannot touch the colours Fomantic paints from its own
   literals: `.ui.list .header` was `rgba(0,0,0,.87)` and `.description`
   `rgba(0,0,0,.7)`, straight out of semantic.min.css. On the profile page that
   put near-black text on the new near-black card at 1.10:1 — text that had been
   perfectly readable before, because there was no dark theme at all and those
   readers simply got the light one (TM-152). A media query hands that to people
   who never asked for it; an explicit opt-in only reaches people who did.

   The condition attached to putting it back was therefore a condition about the
   sweep, not about the switch: it goes in the same change that finishes
   auditing every screen and overrides the Fomantic literals. Step 2 did that —
   the vendored semantic.min.css now reads `--tm-fm-ink` and `--tm-fm-paper`
   (see base.css), 197 ink declarations and 86 white grounds follow the theme,
   and the screen sweep in DarkThemeContrastTests measures every text-painting
   element on a page rather than the ones somebody thought to name. So the
   condition is met and the query is below.

   WHY THE SELECTOR IS `:root:not([data-theme="light"])` AND NOT BARE `:root`.
   The switch is three-valued — light / dark / system — and `system` is the state
   that sets NO attribute, which is what lets this query decide. `:not(...)` is
   what makes an explicit choice win in BOTH directions: a reader on a dark OS
   who picks light gets light, which a bare `:root` inside the query could not
   deliver at any specificity, because the two blocks would tie and source order
   would decide instead of intent.

   THE VALUES BELOW ARE DUPLICATED INTO THAT QUERY, AND THE DUPLICATION IS
   TESTED. CSS has no way to give one declaration block two activation
   conditions, so the palette appears twice; a divergence between the copies
   would be invisible until a user with a dark OS saw a colour nobody chose. In
   DarkThemeContrastTests, the test
   `PrefersColorSchemeDark_AndDataThemeDark_MustResolveIdentically` reads EVERY
   --tm-* custom property in both states and compares them, so the two blocks
   cannot drift apart without a red test.

   The rail follows --tm-surface here, which is the v2 model: the sidebar is a
   surface like any other, so it is dark in the dark theme for the same reason
   the cards are, not because it is a sidebar. */
:root[data-theme="dark"] {
    --tm-bg: #0D1116;
    --tm-surface: #151A21;
    --tm-surface-2: #1C222B;
    --tm-border: #262D37;
    --tm-border-light: #1C222B;
    --tm-border-strong: #39424E;

    --tm-text: #E6E9ED;
    --tm-text-secondary: #A0A8B4;
    --tm-text-muted: #6B7482;

    --tm-sidebar-bg: #151A21;
    --tm-sidebar-border: #262D37;
    --tm-sidebar-item: #A0A8B4;

    /* ── Accent ramp, re-derived for dark grounds (TM-143 step 1) ──────────
       The light ramp cannot simply be reused: #0B6E7F measures 2.96:1 on
       --tm-surface here, and #B02525 measures 2.61:1 — a link and an overdue
       badge would both be under the AA floor, in the two colours the whole
       application uses to say "act on this".

       Each hue keeps its light-theme identity (petrol 189°, green-teal 164°,
       amber 37°, red 0°) and moves up the lightness axis until it clears AA on
       the DARKEST-ON-LIGHTEST pairing that actually occurs. --tm-surface-2 is
       the binding ground, not --tm-surface: it is the lightest thing an accent
       is ever written on (table headers, row hover, inset panels), so a value
       that clears it clears --tm-surface and --tm-bg with room to spare.

         --tm-primary on --tm-surface-2  5.48:1 ✔ AA   --tm-surface 5.99:1 ✔   --tm-bg 6.49:1 ✔
         --tm-success on --tm-surface-2  5.51:1 ✔      --tm-surface 6.02:1 ✔   --tm-bg 6.53:1 ✔
         --tm-warning on --tm-surface-2  5.51:1 ✔      --tm-surface 6.02:1 ✔   --tm-bg 6.52:1 ✔
         --tm-danger  on --tm-surface-2  5.51:1 ✔      --tm-surface 6.02:1 ✔   --tm-bg 6.52:1 ✔

       The four are deliberately levelled to the same luminance. In the light
       ramp they are not (5.09 to 6.69), and on a dark ground that spread is far
       more visible: an unequal accent reads as an unequal priority, and a
       "warning" that glows harder than a "danger" is a lie told in colour.

       THE -light TOKENS INVERT. In the light ramp they are pale tints written
       ON; here they are dark tints, and they have to be, or a status badge
       becomes a lit panel on a dark card. Their job is unchanged — group the
       badge without shouting — and so is the ratio that proves they do it:

         --tm-primary-light vs --tm-surface  1.20:1   (light theme: 1.14:1)
         --tm-success-light vs --tm-surface  1.20:1
         --tm-warning-light vs --tm-surface  1.19:1
         --tm-danger-light  vs --tm-surface  1.20:1

       and the pair that carries the text on that tint clears AA in both themes:

         --tm-primary on --tm-primary-light  5.00:1 ✔ AA   (light theme: 5.17:1)
         --tm-success on --tm-success-light  5.03:1 ✔      (light theme: 4.67:1)
         --tm-warning on --tm-warning-light  5.04:1 ✔      (light theme: 4.56:1)
         --tm-danger  on --tm-danger-light   5.02:1 ✔      (light theme: 5.81:1)
         --tm-primary-hover on --tm-primary-light  6.84:1 ✔ — active nav row
                                                            (light theme: 7.17:1)

       --tm-primary-border is the hairline that goes with the tint, held at the
       same weight the light theme gives it: 1.45:1 on --tm-surface (light:
       1.44:1), 1.21:1 on --tm-primary-light. Decorative edge, never the sole
       affordance — --tm-border-strong is still what identifies a control.

       THE HOVER STEP GOES UP, NOT DOWN. In the light ramp --tm-primary-hover is
       darker than --tm-primary (8.20:1 vs 5.91:1 on white). Here it is lighter,
       for the same reason and with almost the same number: hover has to move
       AWAY from the ground, and the ground is now the dark end.
         --tm-primary-hover on --tm-surface  8.19:1 ✔   (light theme: 8.20:1)

       WHITE IS NOT THE LABEL ON THESE FILLS. --tm-on-accent flips to the darkest
       neutral in the ramp, and that is forced, not stylistic: an accent bright
       enough to be readable AS text on --tm-surface-2 needs relative luminance
       ≥ 0.245, while a white label on it needs ≤ 0.183. No single colour is
       both, so the fill and the label cannot both stay light. Measured:
         white           on --tm-primary  2.92:1  ✘ — under AA, and unfixable at this lightness
         --tm-on-accent  on --tm-primary  6.49:1  ✔ AA
         --tm-on-accent  on --tm-success  6.53:1  ✔
         --tm-on-accent  on --tm-warning  6.52:1  ✔
         --tm-on-accent  on --tm-danger   6.52:1  ✔
         --tm-on-accent  on --tm-primary-hover  8.87:1  ✔

       components/buttons.css reads this token now. It used to carry `#fff`
       literals — Fomantic's own for .primary, an explicit `color: #fff` for the
       modal .negative button — and solid buttons therefore measured ~2.9:1 in
       the dark theme; step 2 moved all eight of them onto var(--tm-on-accent),
       and DarkTheme_SolidPrimaryButton_LabelShouldClearWcagAaOnItsOwnFill
       measures the real control at 6.49:1 instead of measuring this token
       against itself. That gap was the last thing the media query was waiting
       on, which is why it is now above.

       --tm-sidebar-item-active-bg and --tm-sidebar-item-active follow the tint
       and the hover step, exactly as the light theme has them follow
       --tm-primary-light and --tm-primary-hover. Left at their light values they
       were the worst offender in the whole block — a near-white pill glowing on
       a dark rail:
         --tm-sidebar-item-active on --tm-sidebar-item-active-bg  6.84:1 ✔ AA
         --tm-sidebar-item-active on --tm-sidebar-bg              8.19:1 ✔
         --tm-sidebar-item        on --tm-sidebar-bg              7.29:1 ✔ */
    --tm-primary: #2CA5BA;
    --tm-primary-hover: #5EBFD0;
    --tm-primary-light: #172C30;
    --tm-primary-border: #193B41;

    --tm-success: #31AA88;
    --tm-success-light: #162D27;
    --tm-warning: #CA8B24;
    --tm-warning-light: #312718;
    --tm-danger: #EB7373;
    --tm-danger-light: #411F1F;

    --tm-on-accent: #0D1116;

    --tm-sidebar-item-active-bg: #172C30;
    --tm-sidebar-item-active: #5EBFD0;
}

/* ── Dark theme, activated by the operating system (TM-143 step 3) ─────
   The `system` state of the switcher, which is the default and sets no
   attribute. Read the long note above the block before this one: this query is
   half of a pair, it is DELIBERATELY a copy of that block's values, and the
   copy is held to it by
   DarkThemeContrastTests.PrefersColorSchemeDark_AndDataThemeDark_MustResolveIdentically,
   which reads every --tm-* property in both states and compares them.

   `:not([data-theme="light"])` is the whole reason an explicit choice survives:
   a reader whose OS is dark and who picked light keeps light. Only the values
   belong here — every word of reasoning for WHY each colour is what it is lives
   with the declarations above, so there is one place to read and one place to
   change. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --tm-bg: #0D1116;
        --tm-surface: #151A21;
        --tm-surface-2: #1C222B;
        --tm-border: #262D37;
        --tm-border-light: #1C222B;
        --tm-border-strong: #39424E;
        --tm-text: #E6E9ED;
        --tm-text-secondary: #A0A8B4;
        --tm-text-muted: #6B7482;
        --tm-sidebar-bg: #151A21;
        --tm-sidebar-border: #262D37;
        --tm-sidebar-item: #A0A8B4;
        --tm-primary: #2CA5BA;
        --tm-primary-hover: #5EBFD0;
        --tm-primary-light: #172C30;
        --tm-primary-border: #193B41;
        --tm-success: #31AA88;
        --tm-success-light: #162D27;
        --tm-warning: #CA8B24;
        --tm-warning-light: #312718;
        --tm-danger: #EB7373;
        --tm-danger-light: #411F1F;
        --tm-on-accent: #0D1116;
        --tm-sidebar-item-active-bg: #172C30;
        --tm-sidebar-item-active: #5EBFD0;
    }
}

/* ── Reduced motion ───────────────────────────────────────────
   One of only two places in the system where !important is legitimate: it has
   to beat every author-level transition and animation, wherever declared. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}
