/**
 * Semantic intent tokens — single source of truth for both light and
 * dark modes via the CSS-native `light-dark()` function.
 *
 * Mode resolution: `:root` declares `color-scheme: light dark` so the
 * UA picks the matching branch from `light-dark()` automatically. The
 * manual toggle classes `:root.light` / `:root.dark` only override the
 * `color-scheme` (no token duplication needed) — `light-dark()` re-
 * resolves on its own.
 *
 * Browser support: Chrome 123+, Firefox 120+, Safari 17.5+ — Baseline
 * Newly Available since 2024-05.
 *
 * ROLE MARKERS. The CSS token reference (`urbicon css-reference`,
 * `get_css_reference`) renders its surface / text / border tables and the
 * intent roles from this file — `bun run tokens:reference` regenerates
 * `packages/design-engine/src/reference/semantic-tokens.gen.ts`, and the
 * `:check` variant gates CI. The role sentences come from two comment markers
 * inside the `@theme` block, so the prose has one home:
 *
 *   `@role <one sentence>`  — binds to the next declaration, one per token
 *                             (by order, not adjacency: an ordinary comment
 *                             between marker and token changes nothing).
 *                             `@role (none)` states an empty role on purpose.
 *   `@absent <core> — <reason>` — a token the family shape implies but this
 *                             file deliberately does not define (today
 *                             `neutral-text`). Stale when the token exists.
 *
 * A marker is ONE sentence of at most 140 characters and the LAST paragraph
 * of its comment: its text runs to the first blank line, a second sentence
 * in that paragraph is an error, and so is anything but another marker
 * after it. Reasoning therefore goes before the marker, separated by a blank
 * line — appended prose cannot travel into the reference as part of a role.
 * Marker text must not contain a `--color-…` name followed by a colon:
 * contrast.test.ts, tokens.test.ts and semantic.test.ts read this file with
 * comments intact and would take it for a declaration (contrast's readDecl
 * takes the LAST one it finds).
 *
 * Which tokens need one: every `--color-surface-*`, `--color-text-*` and
 * `--color-border-*`, plus the six tokens of the FIRST `=== X INTENT ===`
 * section — that intent is the exemplar whose markers define the role of
 * each suffix for every intent; a `@role` on another intent's token is a
 * refinement for that intent only. The generator fails on a tabled token
 * without a marker and on a marker the reference cannot place.
 */

@theme {
  /* === SURFACE ===
     Four-level elevation ladder (L·0 → L·3). Each step sits visually
     above the one below — themes can differentiate them by color (e.g.
     cream → lighter cream → near-white → white) or rely on shadows
     alone when the palette ceiling is reached (as in the neutral default
     where base and overlay are both white).

     LIGHT-MODE SPREAD (2026-07). In light mode `base` sits at the white
     ceiling, so every other rung has to differentiate DOWNWARD — and the
     ramp gave it almost nothing to work with: quiet/elevated were ΔL 0.01
     and 0.02 from base, against 0.03 and 0.08 in dark mode. A tinted
     in-flow surface with no border and no shadow was therefore effectively
     invisible in light mode, which is why components kept reaching for a
     border just to make a surface visible at all (the chat bubble, the
     table-cell progress track). Fixed at the ramp rather than per token —
     `neutral-25` 0.99 → 0.985 and `neutral-50` 0.98 → 0.965 — so quiet,
     elevated and subtle all gain spread without any semantic moving and
     without a new token landing on an occupied value. All five themes share
     those L values (only the hue differs), so the change reaches every
     theme at once. Measured: text-tertiary on the new elevated is 11.32:1,
     far above AA; style/contrast.test.ts is the gate. Guarded below by
     style/semantic.test.ts, which asserts the reading surfaces stay
     mutually distinct in BOTH modes — the failure mode here is silent, a
     collapsed rung looks exactly like a working one in a screenshot. */
  /* @role L·1 — the page itself: content, cards and reading surfaces sit at
     this level */
  --color-surface-base: light-dark(var(--color-neutral-0), var(--color-neutral-900));
  /* @role L·0 — the ground beneath the page; the softest in-page tint, which
     alone signals "own zone" without border or shadow */
  --color-surface-quiet: light-dark(var(--color-neutral-25), var(--color-neutral-850));
  /* @role L·2 — lifted surfaces (Popover, Menu, Select dropdown), above the
     page with shadow and a z-index of their own */
  --color-surface-elevated: light-dark(var(--color-neutral-50), var(--color-neutral-800));
  /* @role L·3 — the highest surfaces (Dialog, Drawer, Sheet, Toast), full
     shadow plus backdrop dimming */
  --color-surface-overlay: light-dark(var(--color-neutral-0), var(--color-neutral-850));
  /* `surface-hover` / `-active` are the hover/press steps for elements resting
     on a READING surface (base/quiet/elevated) — a menu item, a table row.
     `surface-interactive` is a resting FILL one step in from the page (a filled
     Input, a Slider track, the Tab list). Those two ladders start at different
     rungs, so they need their own hover step: `surface-interactive` already sits
     at neutral-100, which is exactly where `surface-hover` lands in light mode —
     `bg-surface-interactive hover:bg-surface-hover` was a silent no-op, and in
     dark mode `surface-active` collapses onto `surface-interactive` the same
     way. `-interactive-hover` continues the fill's own ladder: one step further
     from the page in light, one step toward the light in dark. Guarded by
     style/semantic.test.ts so the pair can never collapse again. */
  /* @role a resting FILL one step in from the page — a filled Input, a Slider
     track, the Tab list; its hover step is surface-interactive-hover */
  --color-surface-interactive: light-dark(var(--color-neutral-100), var(--color-neutral-700));
  /* @role the hover step for an element resting on surface-interactive — it
     continues the fill's own ladder, where surface-hover would be a no-op */
  --color-surface-interactive-hover: light-dark(
    var(--color-neutral-200),
    var(--color-neutral-650)
  );
  /* @role the hover step for an element resting on a READING surface
     (base / quiet / elevated) — a menu item, a table row */
  --color-surface-hover: light-dark(var(--color-neutral-100), var(--color-neutral-750));
  /* @role the pressed step for an element resting on a reading surface; the
     pair of surface-hover */
  --color-surface-active: light-dark(var(--color-neutral-200), var(--color-neutral-700));
  /* @role the fill of a disabled control */
  --color-surface-disabled: light-dark(var(--color-neutral-100), var(--color-neutral-800));
  /* @role the ground of a selected item — the primary intent's tint, not a
     neutral step */
  --color-surface-selected: light-dark(var(--color-primary-50), var(--color-primary-900));
  /* A RESTING tint only — never a hover step. It resolves to exactly
     `surface-elevated`, so `hover:bg-surface-subtle` is invisible on every
     elevated surface (Popover, Menu, Select dropdown, elevated Card). That
     shipped across 8 components until 2026-07-26; the hover idiom moved to
     `surface-hover`, and variants-lint now errors on any hover fill naming a
     reading surface. Its in-page-zone role was superseded by `surface-quiet`
     (see docs/VARIANT-CONTRACT.md "The surface ladder"), which is why it has no rung of its own
     left — prefer `surface-quiet` for a tinted zone and `surface-elevated`
     for a raised one. Kept for the resting uses that read correctly against
     a border (readonly/disabled fields, list rows, code chips). */
  /* @role a RESTING tint only, never a hover step — it equals surface-elevated;
     prefer surface-quiet for a zone, surface-elevated for a lift */
  --color-surface-subtle: light-dark(var(--color-neutral-50), var(--color-neutral-800));
  /* @role the inverted ground (Tooltip) — near-black in light mode, near-white
     in dark; pairs with text-inverted */
  --color-surface-inverted: light-dark(var(--color-neutral-900), var(--color-neutral-100));
  /* Skeleton wave-shimmer highlight (Skeleton animation="wave"). A translucent
     white glint that sweeps across `surface-interactive`; lower alpha in dark
     mode so it reads as a subtle sheen, not a flash. light-dark() keeps it
     mode-aware so the wave needs no `dark:` override. */
  --color-skeleton-shimmer: light-dark(oklch(1 0 0 / 0.5), oklch(1 0 0 / 0.1));

  /* === TEXT ===
     primary/secondary/tertiary are the informative-text ramp: each clears
     WCAG AA 4.5:1 on every reading surface (base/quiet/elevated/overlay/subtle)
     in both modes — measured by style/contrast.test.ts ("informative text on
     reading surfaces"). tertiary was shifted darker (light) for exactly this.

     quaternary is a MARK-ONLY token — placeholders, dense-grid marks (calendar
     day cells, kbd hints), disabled glyphs, decoration. It sits at the AA edge
     on reading surfaces (4.58–4.85:1 light) and drops BELOW AA on interactive/
     pressed surfaces (4.19/3.49) and hard below it under the docs Rooms skin
     (docs-softer, 1.96:1). Never use it for body text — reach for tertiary. The
     contrast test asserts it only against the 3:1 UI-component floor, not 4.5. */
  /* @role body copy — the top of the informative ramp (primary → secondary →
     tertiary); each rung clears AA 4.5:1 on every reading surface */
  --color-text-primary: light-dark(var(--color-neutral-900), var(--color-neutral-100));
  /* @role text on surface-inverted */
  --color-text-inverted: light-dark(var(--color-neutral-0), var(--color-neutral-900));
  /* @role supporting text — the second rung of the informative ramp */
  --color-text-secondary: light-dark(var(--color-neutral-700), var(--color-neutral-300));
  /* @role muted text and metadata — the last rung that is still body-safe
     (AA on every reading surface); reach for it wherever quaternary tempts */
  --color-text-tertiary: light-dark(var(--color-neutral-600), var(--color-neutral-300));
  /* @role MARK-ONLY — placeholders, dense-grid marks, disabled glyphs,
     decoration; never use it for body text (held only to the 3:1 UI floor) */
  --color-text-quaternary: light-dark(var(--color-neutral-500), var(--color-neutral-400));
  /* Disabled text — the same stop in both modes. WCAG 1.4.3 exempts inactive
     controls, so the old value was not a violation, but at neutral-300 / -600 it
     measured roughly 1.5:1 in light and 2:1 in dark: a disabled field's label is
     the one part that still has to say what the field IS, and it was effectively
     invisible. Now held to the 3:1 UI floor by style/contrast.test.ts across
     every theme × mode × reading surface plus the disabled fill — that test is
     the number, not this comment.

     Deliberately still the weakest rung of the text ramp, so "disabled" stays
     legible without stopping being a signal. Two consequences, both accepted:
     it shares its light-mode value with text-quaternary (two roles, not two
     steps of one ladder — but see the Calendar entry in technical-debt, where
     they meet), and the floor covers the token alone, not what a component
     composes on top of it (an `opacity-*` wrapper can undo it). */
  /* @role disabled text — the weakest rung, held to the 3:1 UI floor so a
     disabled field's label still says what the field is */
  --color-text-disabled: light-dark(var(--color-neutral-500), var(--color-neutral-500));
  /* Link ink — the resting colour of an inline text link on a reading
     surface. Aliases the primary intent's `-text` role, not the base intent
     token: the base is tuned as a fill and misses AA as text on calm grounds
     (which is why variants-lint bans `text-primary`), while `-text` is the
     ramp stop measured to clear AA 4.5 as text on every reading surface in
     both modes across all six themes — style/contrast.test.ts ("intent text
     roles on reading surfaces") is the number. Mode- and theme-awareness ride
     along: `primary-text` is light-dark() at its definition and every theme
     re-tunes the primary ramp, so this alias needs neither its own branch nor
     a `dark:` override. A token of its own (rather than spelling
     `text-primary-text` at the call sites) keeps links an independent lever —
     a consumer can restyle links without touching the primary intent, exactly
     the on-primary/on-fill split below. Like every ink in this section it
     answers reading surfaces only; a link sitting on a solid intent fill
     pairs with the on-colors below instead.

     Until 2026-08-18 this token did not exist: auth's five link call sites
     spelled `text-text-link` into a theme-driven namespace with no such key,
     Tailwind emitted no CSS for the class, and every auth link rendered in
     whatever colour it inherited (#86 — the `text-2xs` bug class). Guarded by
     style/semantic.test.ts. */
  /* @role link ink on a reading surface — aliases the primary intent's -text
     role (its AA text step), never the base fill */
  --color-text-link: var(--color-primary-text);
  /* The on-colors below pair text with a *fill* rather than a surface.
     text-on-dark and text-on-fill answer fills that are dark in light
     mode and light in dark mode, so the label has to travel the opposite
     way — white, then near-black. They are deliberately NOT aliased to one
     another (`text-on-fill: var(--color-text-on-dark)`), even though
     they carry the same value today — they answer to different fills and
     must stay independently overridable:

       - text-on-dark is the partner of the avatar identity palette, whose 12
         slots hold ONE lightness per mode (0.45 / 0.72) by construction.
       - text-on-fill is the partner of the intent fills, whose ramps every
         theme re-tunes, and which move again on hover/active.

     Aliasing would couple them asymmetrically: a consumer re-tuning the avatar
     palette's on-color would silently repaint every filled Button/Badge label.
     Their equality is a consequence of both fill families straddling the same
     lightness midpoint — not a definition. */
  /* @role the label on the avatar identity palette (one lightness per mode by
     construction); deliberately not aliased to text-on-fill */
  --color-text-on-dark: light-dark(var(--color-neutral-0), var(--color-neutral-900));
  /* The label colour for ANY solid intent fill — primary, secondary, success,
     danger, info, neutral. Named for the *role* (a fill) rather than for one
     intent, because that is what it does: it is composed onto all of them.

     Until 2026-07-31 this role was carried by `--color-text-on-primary`, whose
     name promised something narrower than its job. That mismatch was not
     cosmetic — it handed consumers the wrong lever. Retheming primary invites
     you to retune "the on-primary colour", and doing so silently repainted the
     labels of every other intent too: on a channel-scoped orange primary, a
     `success` filled Badge rendered dark-orange text on dark green (~1.5:1).

     `--color-text-on-primary` still exists and still works — it now defaults to
     this token and governs the primary fills alone, so overriding it does what
     its name says. Themes that want to move every solid label at once override
     this one instead.

     Dark mode pairs the *lighter* -400/-500 intent fills with dark text (the
     Material-3 `onPrimary` pattern). White here was unconditional until
     2026-07-14, which put 125 of the 126 dark-mode intent fills under WCAG AA
     — down to 1.51:1, and getting *worse* on press. The mode-aware branch
     clears all 108 fills this token governs; `warning` is not one of them —
     its fill is light in BOTH modes, so it carries its own non-mode-aware
     on-color below (`--color-text-on-warning`). */
  /* @role the label on ANY solid intent fill — white in light mode, near-black
     in dark mode; warning is the exception (text-on-warning) */
  --color-text-on-fill: light-dark(var(--color-neutral-0), var(--color-neutral-900));
  /* The primary fill's own label colour. Defaults to the shared on-fill value,
     so nothing moves unless a theme says so; override it to repaint the primary
     fills WITHOUT touching success/danger/neutral/secondary/info. */
  /* @role the label on the primary fill specifically — defaults to
     text-on-fill; override it to repaint the primary fills alone */
  --color-text-on-primary: var(--color-text-on-fill);
  /* Warning's own on-color. Warning is the one intent whose fill stays a
     light amber in BOTH modes (base/hover/active ride L 0.59–0.82), so unlike
     text-on-primary this token is deliberately NOT mode-aware — the label is
     a warm dark in both modes. It rides the warning ramp's own 950 stop
     rather than a neutral, so theme re-hues (forest 60, sunset 92) flow
     through and the label reads as part of the swatch instead of soot on top
     of it. Until 2026-07-20 filled warning paired with `text-on-surface` — a
     *surface* token that flips light in dark mode — which put all 18
     dark-mode warning fills at 1.51–2.80:1. Measured across every theme ×
     mode × state by style/contrast.test.ts. */
  /* @role the label on the warning fill — a warm dark from the ramp's own 950
     stop in BOTH modes, because the fill stays light amber in both */
  --color-text-on-warning: var(--color-warning-950);
  /* @role text on any surface — it tracks the page background, so never on an
     intent fill */
  --color-text-on-surface: light-dark(var(--color-neutral-900), var(--color-neutral-100));

  /* === PRIMARY INTENT ===
     emphasis flips to light shades in dark mode so `text-*-emphasis`
     stays legible against dark surfaces.

     `-text` is the intent in its SECOND job. The base token is tuned as a
     fill — a surface with `text-on-fill` sitting on it — and read as text on
     a calm ground it misses AA: measured across all six themes, the base
     stops bottom out at 4.08:1 (primary, dark, surface-elevated) and 2.05:1
     (warning, light — yellow on white, reachable by no stop of a yellow
     ramp). `-text` is the same hue at the nearest ramp stop that clears AA
     4.5 as text on EVERY reading surface and on the intent's own `-subtle`
     (an Alert's ground), in both modes, in the worst of the six themes —
     chosen by measurement (style/contrast.test.ts is the number, not this
     comment), not taste, so it is the smallest visual step that is safe.

     Two text tiers on purpose, not a duplicate: `-text` still reads as the
     colour (danger text stays red); `-emphasis` is the near-ink tier for
     strong statements and doubles as a fill. Same split as Radix's steps
     11/12. The base token is for fills only — variants-lint errors on
     `text-<intent>` in a tv() config.

     `neutral` has no `-text` on purpose: its base already clears AA as text
     on every ground in every theme (measured 5.29/5.20 at worst) because the
     chassis ramp was text-tuned from the start — a role that would alias the
     base is the kind of duplicate the ramp/alias revert just paid for.

     primary's dark stop lands at exactly 4.50 in the worst case
     (rose/own-subtle) — zero headroom, held by the gate; if a rose retune
     ever trips it, the stop moves to -300, not the bar. */
  /* @role the fill — a surface with text-on-fill sitting on it; never a text
     colour */
  --color-primary: light-dark(var(--color-primary-600), var(--color-primary-500));
  /* @role the hover step of the fill */
  --color-primary-hover: light-dark(var(--color-primary-700), var(--color-primary-400));
  /* @role the pressed step of the fill */
  --color-primary-active: light-dark(var(--color-primary-800), var(--color-primary-300));
  /* @role a soft tinted ground in the intent's hue — an Alert's background, a
     selected row */
  --color-primary-subtle: light-dark(var(--color-primary-50), var(--color-primary-900));
  /* @role the intent as text on a reading surface — the nearest stop that
     clears AA 4.5:1 on every reading surface and the intent's own -subtle */
  --color-primary-text: light-dark(var(--color-primary-700), var(--color-primary-400));
  /* @role the near-ink tier — strong statements as text, and doubles as a
     fill */
  --color-primary-emphasis: light-dark(var(--color-primary-900), var(--color-primary-200));

  /* === SECONDARY INTENT === */
  --color-secondary: light-dark(var(--color-secondary-500), var(--color-secondary-400));
  --color-secondary-hover: light-dark(var(--color-secondary-600), var(--color-secondary-300));
  --color-secondary-active: light-dark(var(--color-secondary-700), var(--color-secondary-200));
  --color-secondary-subtle: light-dark(var(--color-secondary-50), var(--color-secondary-900));
  --color-secondary-text: light-dark(var(--color-secondary-600), var(--color-secondary-400));
  --color-secondary-emphasis: light-dark(var(--color-secondary-800), var(--color-secondary-200));

  /* === NEUTRAL INTENT ===
     Themeable chrome. Takes the warm-neutral ramp's lightness + chroma
     PROFILE — purpose-tuned so that both white-on-fill (bg-neutral) and
     neutral-on-surface (text-neutral) contrast hold across light/dark — and
     re-tints only the HUE per theme via `--neutral-chrome-hue`. Because the
     lightness never moves, every WCAG tradeoff baked into the warm-neutral
     ramp is preserved exactly; only the temperature follows the theme.

     `--neutral-chrome-hue` defaults to 240 (:root, below), so the library
     default reads as a cool grey matching its cool chassis. Each theme
     overrides it to its accent temperature; rooms-docs.css re-pins the neutral
     intent to the warm ramp directly. Uses the same relative-color syntax
     already relied on by `--color-interactive-*` below.

     @absent neutral-text — its base already clears AA as text on every ground
     in every theme, so a -text role would only alias the base */
  --color-neutral: light-dark(
    oklch(from var(--color-warm-neutral-500) l c var(--neutral-chrome-hue)),
    oklch(from var(--color-warm-neutral-400) l c var(--neutral-chrome-hue))
  );
  --color-neutral-hover: light-dark(
    oklch(from var(--color-warm-neutral-600) l c var(--neutral-chrome-hue)),
    oklch(from var(--color-warm-neutral-300) l c var(--neutral-chrome-hue))
  );
  --color-neutral-active: light-dark(
    oklch(from var(--color-warm-neutral-700) l c var(--neutral-chrome-hue)),
    oklch(from var(--color-warm-neutral-200) l c var(--neutral-chrome-hue))
  );
  --color-neutral-subtle: light-dark(
    oklch(from var(--color-warm-neutral-50) l c var(--neutral-chrome-hue)),
    oklch(from var(--color-warm-neutral-900) l c var(--neutral-chrome-hue))
  );
  --color-neutral-emphasis: light-dark(
    oklch(from var(--color-warm-neutral-800) l c var(--neutral-chrome-hue)),
    oklch(from var(--color-warm-neutral-200) l c var(--neutral-chrome-hue))
  );

  /* === SUCCESS INTENT === */
  --color-success: light-dark(var(--color-success-500), var(--color-success-400));
  --color-success-hover: light-dark(var(--color-success-600), var(--color-success-300));
  --color-success-active: light-dark(var(--color-success-700), var(--color-success-200));
  --color-success-subtle: light-dark(var(--color-success-50), var(--color-success-900));
  --color-success-text: light-dark(var(--color-success-600), var(--color-success-400));
  --color-success-emphasis: light-dark(var(--color-success-800), var(--color-success-200));

  /* === WARNING INTENT === */
  --color-warning: light-dark(var(--color-warning-500), var(--color-warning-400));
  --color-warning-hover: light-dark(var(--color-warning-600), var(--color-warning-300));
  --color-warning-active: light-dark(var(--color-warning-700), var(--color-warning-200));
  --color-warning-subtle: light-dark(var(--color-warning-50), var(--color-warning-900));
  /* warning's light text stop is -800: the only rung of a yellow ramp that
     reads on white. It lands amber-brown — that is not a compromise but what
     every AA-clean system does with yellow text (the fill stays yellow).

     @role the light stop is -800, the only rung of a yellow ramp that reads on
     white — it lands amber-brown while the fill stays yellow */
  --color-warning-text: light-dark(var(--color-warning-800), var(--color-warning-400));
  --color-warning-emphasis: light-dark(var(--color-warning-800), var(--color-warning-200));

  /* === DANGER INTENT === */
  --color-danger: light-dark(var(--color-danger-500), var(--color-danger-400));
  --color-danger-hover: light-dark(var(--color-danger-600), var(--color-danger-300));
  --color-danger-active: light-dark(var(--color-danger-700), var(--color-danger-200));
  --color-danger-subtle: light-dark(var(--color-danger-50), var(--color-danger-900));
  /* danger's light stop IS the base stop — red already reads on paper. Dark
     needs -300: sRGB red is so dim that even -400 misses on the lightened
     elevated/subtle panels (4.25 at worst).

     @role the light stop is the fill's stop, red already reads on paper; dark
     needs -300 because sRGB red is too dim on the lightened panels at -400 */
  --color-danger-text: light-dark(var(--color-danger-500), var(--color-danger-300));
  --color-danger-emphasis: light-dark(var(--color-danger-800), var(--color-danger-200));

  /* === INFO INTENT === */
  --color-info: light-dark(var(--color-info-500), var(--color-info-400));
  --color-info-hover: light-dark(var(--color-info-600), var(--color-info-300));
  --color-info-active: light-dark(var(--color-info-700), var(--color-info-200));
  --color-info-subtle: light-dark(var(--color-info-50), var(--color-info-900));
  --color-info-text: light-dark(var(--color-info-600), var(--color-info-400));
  --color-info-emphasis: light-dark(var(--color-info-800), var(--color-info-200));

  /* === CHART CATEGORICAL PALETTE ===
     An *ordered* series palette for data visualization (the charts/ family),
     distinct from the single-role intent tokens above: chart series 0 maps to
     --color-chart-1, series 1 to --color-chart-2, and so on, cycling for more
     series than colors. Built on the foundation hue ramps so light-dark()
     resolves dark mode automatically; ordered for maximal adjacent-hue
     separation (blue → green → amber → violet → cyan → red). Living in the
     --color-* namespace means Tailwind also generates fill-chart-1 /
     stroke-chart-1 / bg-chart-1 utilities for legends and static marks. */
  --color-chart-1: light-dark(var(--color-primary-600), var(--color-primary-400));
  --color-chart-2: light-dark(var(--color-success-600), var(--color-success-400));
  --color-chart-3: light-dark(var(--color-warning-500), var(--color-warning-400));
  --color-chart-4: light-dark(var(--color-secondary-500), var(--color-secondary-400));
  --color-chart-5: light-dark(var(--color-info-500), var(--color-info-400));
  --color-chart-6: light-dark(var(--color-danger-500), var(--color-danger-400));

  /* === AVATAR IDENTITY PALETTE ===
     An *unordered* identity palette: Avatar's `randomColor` hashes a name to a
     fixed slot, so the same person always keeps the same hue. Deliberately NOT
     the chart palette and NOT the intent tokens:

       - chart-* only has 6 slots and derives from the intent ramps, so a brand
         that retunes `secondary` would silently reshuffle people's identities.
         Its dark values are the *-400 tints (L≈0.66) — light enough that white
         initials drop to ~2.6:1, below AA. Charts colour marks; avatars carry
         text on top, which is a different contrast contract.
       - intent tokens are semantic (a person is not "danger").

     Construction: a single lightness per mode, 12 hues at an even 30° spacing.
     Because OKLCH lightness is perceptual, holding L constant means every slot
     shares one guaranteed contrast partner — `--color-text-on-dark`, which
     flips white → neutral-900 with the mode exactly as these fills flip dark →
     light. So legibility is a property of the construction, not 12 separate
     checks. Even spacing also beats the 15 hardcoded hex values this replaced,
     which clustered (red/orange/amber/yellow within ~20°) and blurred when
     avatars sat side by side.

     L=0.45 (light) / L=0.72 (dark) mirror the foundation ramps' *-700 / *-300
     steps, so the palette reads as part of the same chassis. Rebrand by
     overriding these 12 tokens; the text partner follows automatically. */
  --color-avatar-1: light-dark(oklch(0.45 0.13 25), oklch(0.72 0.12 25));
  --color-avatar-2: light-dark(oklch(0.45 0.13 55), oklch(0.72 0.12 55));
  --color-avatar-3: light-dark(oklch(0.45 0.13 85), oklch(0.72 0.12 85));
  --color-avatar-4: light-dark(oklch(0.45 0.13 115), oklch(0.72 0.12 115));
  --color-avatar-5: light-dark(oklch(0.45 0.13 145), oklch(0.72 0.12 145));
  --color-avatar-6: light-dark(oklch(0.45 0.13 175), oklch(0.72 0.12 175));
  --color-avatar-7: light-dark(oklch(0.45 0.13 205), oklch(0.72 0.12 205));
  --color-avatar-8: light-dark(oklch(0.45 0.13 235), oklch(0.72 0.12 235));
  --color-avatar-9: light-dark(oklch(0.45 0.13 265), oklch(0.72 0.12 265));
  --color-avatar-10: light-dark(oklch(0.45 0.13 295), oklch(0.72 0.12 295));
  --color-avatar-11: light-dark(oklch(0.45 0.13 325), oklch(0.72 0.12 325));
  --color-avatar-12: light-dark(oklch(0.45 0.13 355), oklch(0.72 0.12 355));

  /* === LIVE / "NOW" ACCENT ===
     Single-purpose accent for live/now markers — the Calendar current-time
     line and dot, and future live/recording indicators. Red is the
     convention (Google Calendar et al.), but the *purpose* is not `danger`:
     a now-line states "you are here", not "something is wrong", and the two
     must stay independently themeable. Defaults ride the danger foundation
     ramp, so dark mode and per-theme danger re-tunes flow through
     automatically; override `--color-live` alone to restyle live indicators
     without touching error styling. */
  --color-live: light-dark(var(--color-danger-500), var(--color-danger-400));

  /* === BORDERS ===
     border-hairline is the quietest tier — structural
     trennlinien (row dividers, Card header/footer separators, inline
     section dividers). Sits below border-subtle, which is now reserved
     for quiet container outlines (e. g. Popover). */
  /* @role the quietest tier — structural dividers (row dividers, Card
     separators); translucent, so it blends onto any surface */
  --color-border-hairline: light-dark(rgb(0 0 0 / 0.08), rgb(255 255 255 / 0.06));
  /* @role quiet container outlines (Popover) — one tier above hairline */
  --color-border-subtle: light-dark(var(--color-neutral-200), var(--color-neutral-700));
  /* @role the standard border of controls and containers */
  --color-border-default: light-dark(var(--color-neutral-300), var(--color-neutral-600));
  /* @role an emphasised outline, one step stronger than default */
  --color-border-emphasis: light-dark(var(--color-neutral-400), var(--color-neutral-500));
  /* @role the strongest outline the chassis offers */
  --color-border-strong: light-dark(var(--color-neutral-500), var(--color-neutral-400));

  /* === INTERACTIVE STATES === */
  --color-interactive-hover: oklch(from var(--color-primary-500) l c h / 0.1);
  --color-interactive-active: oklch(from var(--color-primary-500) l c h / 0.2);
  --color-interactive-focus: light-dark(var(--color-primary-500), var(--color-primary-400));
  --color-interactive-disabled: light-dark(var(--color-neutral-200), var(--color-neutral-700));

  /* === FEEDBACK ===
     Mapped onto the intent palette so they automatically follow the
     light/dark resolution above. */
  --color-feedback-info: var(--color-info);
  --color-feedback-info-subtle: var(--color-info-subtle);
  --color-feedback-success: var(--color-success);
  --color-feedback-success-subtle: var(--color-success-subtle);
  --color-feedback-warning: var(--color-warning);
  --color-feedback-warning-subtle: var(--color-warning-subtle);
  --color-feedback-error: var(--color-danger);
  --color-feedback-error-subtle: var(--color-danger-subtle);

  /* === SHADOWS ===
     The theme half of the scale. `interaction.css` republishes each step as
     `--blocks-shadow-<step>` on `:root`, and that is the name markup reads
     (`shadow-[var(--blocks-shadow-md)]`). The two halves are NOT a mirror —
     deleting the second one costs a step; see the alias block there.

     Two namespaces are ruled out for this half:

     NOT `--color-*`. Tailwind mints a utility per colour family from every key
     in there, so a step would also be a `bg-`, a `text-` and a `ring-` colour.
     A multi-layer shadow list is invalid at computed-value time as a `color`,
     which on an INHERITED property means `inherit` rather than "ignored": with
     the scale in that namespace `text-primary text-shadow-lg` renders the
     PARENT element's colour.

     NOT `--shadow-*`, Tailwind's own box-shadow namespace: this scale sits one
     rung off it — `base`/`md`/`lg` carry the geometry Tailwind ships as
     `md`/`lg`/`xl` — so a step there redefines `shadow-md` and `shadow-lg` for
     every consumer.

     `--blocks-*` is no Tailwind namespace, so these mint nothing.

     Dark mode uses higher alpha to compensate for low contrast against
     dark surfaces. light-dark() may wrap ONLY the per-layer <color> —
     it is a color function, and layers must stay top-level
     comma-separated. Wrapping the whole multi-layer list makes every
     `box-shadow: var(--blocks-shadow-*)` invalid at computed-value time:
     shadows render as none, library-wide.

     The shadow tint (`--blocks-shadow-tint`) is an oklch L C H triplet
     WITHOUT the alpha channel — declared on `:root` (not inside @theme)
     because it is a raw partial value, not a standalone design token.
     Default is pure black (0 0 0). Themes override it to warm their shadow
     palette — e.g. editorial sets it to a dark brown so shadows blend with
     cream surfaces instead of reading as cool smudges.

     The tint only bites at the root. The steps are substituted where they are
     declared, which is `:root` for both halves, so descendants inherit an
     already-finished value: setting `--blocks-shadow-tint` on a scoped element
     moves nothing. A scope that wants its own tint has to re-declare the five
     `--blocks-shadow-*` steps beside it. */
  --blocks-shadow-scale-xs: 0 1px 2px 0
    light-dark(
      oklch(var(--blocks-shadow-tint) / 0.05),
      oklch(var(--blocks-shadow-tint) / 0.2)
    );
  --blocks-shadow-scale-sm:
    0 1px 3px 0
      light-dark(
        oklch(var(--blocks-shadow-tint) / 0.1),
        oklch(var(--blocks-shadow-tint) / 0.3)
      ),
    0 1px 2px -1px
      light-dark(
        oklch(var(--blocks-shadow-tint) / 0.1),
        oklch(var(--blocks-shadow-tint) / 0.3)
      );
  --blocks-shadow-scale-base:
    0 4px 6px -1px
      light-dark(
        oklch(var(--blocks-shadow-tint) / 0.1),
        oklch(var(--blocks-shadow-tint) / 0.4)
      ),
    0 2px 4px -2px
      light-dark(
        oklch(var(--blocks-shadow-tint) / 0.1),
        oklch(var(--blocks-shadow-tint) / 0.4)
      );
  --blocks-shadow-scale-md:
    0 10px 15px -3px
      light-dark(
        oklch(var(--blocks-shadow-tint) / 0.1),
        oklch(var(--blocks-shadow-tint) / 0.4)
      ),
    0 4px 6px -4px
      light-dark(
        oklch(var(--blocks-shadow-tint) / 0.1),
        oklch(var(--blocks-shadow-tint) / 0.4)
      );
  --blocks-shadow-scale-lg:
    0 20px 25px -5px
      light-dark(
        oklch(var(--blocks-shadow-tint) / 0.1),
        oklch(var(--blocks-shadow-tint) / 0.5)
      ),
    0 8px 10px -6px
      light-dark(
        oklch(var(--blocks-shadow-tint) / 0.1),
        oklch(var(--blocks-shadow-tint) / 0.5)
      );
}

/* Shadow tint — declared on :root rather than inside @theme because
   it is a raw oklch L C H partial, not a standalone design token.
   Themes override this to warm their shadow palette. */
:root {
  --blocks-shadow-tint: 0 0 0;

  /* Hue of the neutral intent chrome (bg-neutral / text-neutral / borders).
     The neutral intent (above) keeps the warm-neutral ramp's lightness but
     takes its hue from here. Default 240 = cool grey, matching the default
     cool chassis. Each theme overrides this to its accent temperature;
     rooms-docs.css re-pins the neutral intent to the warm ramp instead. */
  --neutral-chrome-hue: 240;
}

/* Body styling — `color-scheme` activates the UA's auto pick between
   light and dark; the toggle classes only override the scheme. */
body {
  color: var(--color-text-primary);
  background-color: var(--color-surface-base);
}

:root {
  color-scheme: light dark;
}

:root.light {
  color-scheme: light;
}

:root.dark {
  color-scheme: dark;
}

/* High-contrast media adjustments — runs *in addition* to the
   light-dark() resolution above. light-dark() within the overrides
   keeps the high-contrast values mode-aware. */
@media (prefers-contrast: more) {
  :root {
    --color-text-primary: light-dark(var(--color-neutral-900), var(--color-neutral-0));
    --color-text-secondary: light-dark(var(--color-neutral-800), var(--color-neutral-200));
    /* hairline becomes a real border in high-contrast — otherwise structural
       trennlinien (table rows, Card dividers) disappear entirely. */
    --color-border-hairline: light-dark(var(--color-neutral-700), var(--color-neutral-300));
    --color-border-default: light-dark(var(--color-neutral-900), var(--color-neutral-900));
    --color-interactive-focus: light-dark(var(--color-neutral-900), var(--color-neutral-900));
    --color-secondary: light-dark(var(--color-secondary-700), var(--color-secondary-700));
    /* The dark branch was -700 like the light one, which is backwards: on the
       dark surfaces a DARKER neutral means LESS contrast — text-neutral
       measured 1.49:1 in the mode that asked for more (found by adversarial
       review of the intent-text-role change; the normal-cascade gate strips
       @media blocks by design and could not see it). -200 steps one rung
       lighter than the normal dark value's -400-derived resting tone, the
       same direction every other override in this block takes. */
    --color-neutral: light-dark(
      oklch(from var(--color-warm-neutral-700) l c var(--neutral-chrome-hue)),
      oklch(from var(--color-warm-neutral-200) l c var(--neutral-chrome-hue))
    );
  }
}
