/* ========================================
 * Theme: dark
 * DO NOT EDIT — regenerate with: node transform-tokens.js
 * Version: 2.5.0
 * ======================================== */

[data-theme="dark"] {

  /* ── Colors ── */
  --stm-color-primary: #96ADF8; /* Primary brand color - main interactive color. Do NOT use for error/danger states — use error tokens instead */
  --stm-color-link: #96ADF8; /* Link/breadcrumb text color — defaults to primary per brand, but brand-overridable (RETUR keeps links at the AA-safe mid-green while primary is the deep #024225, Q9/A11Y-01) */
  --stm-color-primaryHover: #B5C6FA; /* Primary color hover state - slightly darker */
  --stm-color-primaryActive: #D1DBFC; /* Primary color active/pressed state - darkest */
  --stm-color-primarySubtle: #122666; /* Subtle primary background - light brand tint */
  --stm-color-primarySubtleHover: #1A3791; /* Subtle primary hover - slightly more saturated */
  --stm-color-primarySubtleActive: #2145B8; /* Subtle primary active - most saturated subtle state */
  --stm-color-primaryEmphasis: #7694F7; /* Primary emphasis - for borders and icons on primary surfaces */
  --stm-color-overlaySurface: #2C5BF2; /* The surface a FLOATING overlay paints itself with (issue #601/#602/TOK-146). Defaults to the same value as core.color.primary, so nothing moves until a context re-points it. A host that paints its own background with a primary fill (the canvas modal, StmBulkBar, StmAppBar's inset band) re-points this ONE role and every overlay rendered inside it follows - instead of each host enumerating a dozen component slots and the next one having to be found first. Overlay components bind it in their OWN CSS rule, never at :root: a var() inside a custom-property declaration substitutes at the DECLARING element, so a component token declared on :root can never see a descendant's re-point. That is the whole finding of #602, measured and reverted. */
  --stm-color-onOverlaySurface: #FFFFFF; /* Ink on core.color.overlaySurface. Defaults to the same value as core.color.onPrimary - the pair moves together or contrast breaks, which is exactly how the toolbar reached 1.00:1 against a canvas in #601. */
  --stm-color-overlaySurfaceHover: #2750D5; /* Hover fill for controls sitting ON an overlay surface. Defaults to core.color.primaryHover. */
  --stm-color-overlaySurfaceEmphasis: #5279F4; /* Borders and dividers INSIDE an overlay surface — control outlines, group dividers. Sits on the overlay's own fill, so it is deliberately a same-tone step rather than a separating line. NOT the overlay's outer edge: that is core.color.overlayBorder, and the two were the same slot until #618 measured what happens when one value tries to do both jobs. */
  --stm-color-overlayBorder: #5279F4; /* The overlay's OWN outer boundary — the line that separates a floating surface from whatever it is floating on (#618). Value-identical to overlaySurfaceEmphasis by default, so nothing moves for anyone who does not re-point it; a SEPARATE role because the two have different contrast obligations, which is only visible once a host re-points them. Measured on the canvas modal: the pill and the panel behind it are both #FFFFFF — 1.00:1, the reported "the toolbar has no edge" — and overlaySurfaceEmphasis re-points there to outlineVariant, which lands at 2.41:1 under retur and 1.23:1 under starter. Both fail WCAG 1.4.11's 3:1 for a boundary that conveys where a component is. This role re-points to core.color.outline instead: 7.34 / 4.76 / 4.76 / 4.76:1 across retur/starter/settl/systemate. */
  --stm-color-primaryContainer: #1A3791; /* Primary container background */
  --stm-color-onPrimary: #0F172A; /* Text/icons on primary color - ensures contrast */
  --stm-color-onPrimaryContainer: #B5C6FA; /* Text/icons on primary container */
  --stm-color-onPrimarySubtle: #B5C6FA; /* Text/icons on subtle primary backgrounds */
  --stm-color-primarySurface: #122666; /* Very light primary tint - page backgrounds, large surfaces */
  --stm-color-primarySurfaceVariant: #1A3791; /* Near-white primary wash - lightest brand background */
  --stm-color-supportSurface: #1E293B; /* Warm support surface — the brand's warm accent surface (retur: beige header bar #FEF9EF). Brands without a support ramp resolve to their lightest neutral */
  --stm-color-supportSurfaceVariant: #334155; /* Stronger warm support surface — canvas weight (retur: medium beige #F7F2E8) */
  --stm-color-supportSurfaceHover: #334155; /* Hover state of supportSurface (BF-07, owner 2026-07-09): interactive items resting on the warm support surface hover one step deeper — retur: support.300 is the hover of support.200, so hovers on beige are never invisible. Resolves to the same value as supportSurfaceVariant by design; the separate semantic slot lets a brand split them later */
  --stm-color-neutral: #CBD5E1; /* The NEUTRAL family — the unbranded, grey action tier (rung 4 of the emphasis ladder). Renamed from `secondary*`: that name promised a second BRAND HUE and resolved to {brand.neutral.600}, i.e. grey, and every consumer in the repo was already using it as grey (all 7 component references were disabled or neutral fills). The values did not move — only the name now describes them. The word `secondary` is deliberately left UNDECLARED so it is available for a real second brand family when the brands have one; re-pointing a live `secondary` from grey to a hue later would have silently changed what existing code renders. */
  --stm-color-neutralHover: #E2E8F0; /* Neutral hover state (rung 4 of the emphasis ladder) */
  --stm-color-neutralActive: #F1F5F9; /* Neutral active/pressed state (rung 4 of the emphasis ladder) */
  --stm-color-neutralSubtle: #1E293B; /* Subtle neutral background — the default FILL for a rung-4 (grey) action. Note the brands do not agree on how light this lands: RETUR resolves it to a mid grey (#C3C3C3) where the other three resolve to a near-white tint, which is why components binding a grey action tier default to surfaceContainer and let a brand re-point */
  --stm-color-neutralSubtleHover: #334155; /* Subtle neutral hover */
  --stm-color-neutralSubtleActive: #475569; /* Subtle neutral active */
  --stm-color-neutralEmphasis: #CBD5E1; /* Neutral emphasis — borders and icons on neutral surfaces */
  --stm-color-neutralContainer: #334155; /* Neutral container background */
  --stm-color-onNeutral: #0F172A; /* Text/icons on core.color.neutral */
  --stm-color-onNeutralContainer: #F8FAFC; /* Text/icons on core.color.neutralContainer */
  --stm-color-onNeutralSubtle: #F8FAFC; /* Text/icons on core.color.neutralSubtle — the ink of a rung-4 (grey) action */
  --stm-color-surface: #1E293B; /* Main surface color — primary background for cards and content areas. Do NOT use brand colors directly as backgrounds — use primarySurface for branded backgrounds */
  --stm-color-surfaceVariant: #1E293B; /* Variant surface — brand-overridable, defaults to neutral.050 */
  --stm-color-surfaceContainer: #1E293B; /* Container on surface — brand-overridable, defaults to neutral.100 */
  --stm-color-surfaceContainerHover: #334155; /* Container hover state */
  --stm-color-surfaceContainerActive: #475569; /* Container active/pressed state — one step beyond hover */
  --stm-color-onSurface: #F8FAFC; /* Primary text on surface — highest contrast text color. Do NOT use on colored/primary backgrounds — use onPrimary or onSecondary instead */
  --stm-color-heading: #F8FAFC; /* Semantic heading color (GDK-43) — StmTypography heading/display variants default here; brands re-point it (retur → primary green, others → their high-emphasis neutral, i.e. identical to onSurface). An explicit color prop on the component still wins */
  --stm-color-onSurfaceVariant: #E2E8F0; /* Secondary text on surface */
  --stm-color-onSurfaceSubtle: #94A3B8; /* Tertiary text on surface — soft body copy and tertiary labels. Bumped from neutral.400 to neutral.500 on 2026-04-27 so 'StmTypography color=subtle' meets WCAG AA 4.5:1 on white surfaces (was 2.56:1 at #94A3B8, now ~5.0:1 at #64748B). Placeholder duty unaffected — input placeholders remain visibly lighter than typed text via the onSurface ↔ onSurfaceSubtle delta. */
  --stm-color-errorSurface: #4C0519; /* Near-white error tint - light error backgrounds */
  --stm-color-infoSurface: #172554; /* Near-white info tint - light info backgrounds */
  --stm-color-successSurface: #052e16; /* Near-white success tint - light success backgrounds */
  --stm-color-warningSurface: #4A3600; /* Near-white warning tint - light warning backgrounds */
  --stm-color-outline: #94A3B8; /* Standard border and outline color — default dividers, input borders. Uses neutral.500 for WCAG 3:1 contrast on white surfaces. Do NOT use for interactive focus rings — use focusRing token instead */
  --stm-color-outlineVariant: #334155; /* Subtle outline for minimal borders */
  --stm-color-outlineStrong: #CBD5E1; /* Strong outline for emphasis — darker than `outline` (.500) in light mode so it reads as stronger */
  --stm-color-accent: #96ADF8; /* Brand accent highlight (TOK-43, owner 2026-08-14) — RETUR's pistachio, other brands default to their light 200 step. A decorative highlight family, NOT the interactive primary. No consumer yet — forward slot for accent chips/marketing surfaces. */
  --stm-color-onAccent: #0F172A; /* Ink on the accent highlight (TOK-43) — dark by design, the accent family is light. */
  --stm-color-selectionBackground: #1A3791; /* Text-selection highlight (::selection) — subtle brand tint (TOK-44, owner 2026-08-14: global rule lives in stm-baseline.css). Light enough that selectionText stays AA. */
  --stm-color-selectionText: #F8FAFC; /* Text color inside a selection highlight (TOK-44) — aliases onSurface so it mode-flips automatically. */
  --stm-color-outlineSubtle: #1E293B; /* Subtlest border — one step lighter than outlineVariant, for hairline row separators that should read as texture, not lines (TOK-33: promoted from dataGrid.cellBorderSubtle's pinned brand.neutral.100 so the value can flip in dark mode) */
  --stm-color-disabledText: #94A3B8; /* Disabled-state text/icon color (TOK-13) — the single core answer components and bridges point at instead of hand-rolling. Mirrors MUI action.disabled / Mud ActionDisabled / Kendo --kendo-disabled-text. buttonInverse keeps its own raw-ramp value deliberately (sits on dark surfaces) */
  --stm-color-disabledBackground: #1E293B; /* Disabled-state fill (TOK-13) — inputs and vendor-bridged controls. Mirrors MUI action.disabledBackground / Mud ActionDisabledBackground / Kendo --kendo-disabled-bg. button.containerDisabled stays on secondarySubtle (one step darker) deliberately — a disabled BUTTON must still read as a control, not a field */
  --stm-color-disabledBorder: #334155; /* Disabled-state border (TOK-13) — the faded outline every disabled control shares. Mirrors Kendo --kendo-disabled-border */
  --stm-color-textPrimary: #F8FAFC; /* Text-role alias (TOK-14, owner 2026-07-09) — 1:1 target for vendor maps that model text roles explicitly (MUI text.primary, Mud TextPrimary, Kendo on-app-surface). Pure alias of onSurface; internal components keep using onSurface directly */
  --stm-color-textSecondary: #E2E8F0; /* Text-role alias (TOK-14) — 1:1 target for MUI text.secondary / Mud TextSecondary. Pure alias of onSurfaceVariant */
  --stm-color-textDisabled: #94A3B8; /* Text-role alias (TOK-14) — 1:1 target for MUI text.disabled / Mud TextDisabled. Pure alias of the TOK-13 disabledText slot */
  --stm-color-error: #FB7185; /* Error/danger state color — destructive actions, validation errors. Do NOT use for warnings — use warning tokens instead */
  --stm-color-errorHover: #FDA4AF; /* Error hover state */
  --stm-color-errorActive: #FECDD3; /* Error active/pressed state */
  --stm-color-errorSubtle: #4C0519; /* Subtle error background */
  --stm-color-errorSubtleHover: #881337; /* Subtle error hover */
  --stm-color-errorSubtleActive: #9F1239; /* Subtle error active */
  --stm-color-errorEmphasis: #FDA4AF; /* Error emphasis for borders */
  --stm-color-errorContainer: #4C0519; /* Error container background */
  --stm-color-onError: #0F172A; /* Text/icons on error color */
  --stm-color-onErrorContainer: #FECDD3; /* Text/icons on error container */
  --stm-color-onErrorSubtle: #FECDD3; /* Text/icons on subtle error backgrounds */
  --stm-color-success: #4ADE80; /* Success state color — confirmations, completed states. Always green regardless of brand. Do NOT use for informational states — use info tokens instead */
  --stm-color-successHover: #86EFAC; /* Success hover state */
  --stm-color-successActive: #BBF7D0; /* Success active/pressed state */
  --stm-color-successSubtle: #052e16; /* Subtle success background */
  --stm-color-successSubtleHover: #14532D; /* Subtle success hover */
  --stm-color-successSubtleActive: #166534; /* Subtle success active */
  --stm-color-successEmphasis: #86EFAC; /* Success emphasis for borders */
  --stm-color-successStrong: #BBF7D0; /* Dark success for text on white — WCAG AA (7:1). Use for green text labels, brand marks, or links where success.500 fails contrast. */
  --stm-color-infoStrong: #BFDBFE; /* Dark info for text on light info tints — WCAG AA (8.0:1 on infoSurface). Completes the *Strong family (P2 a11y 2026-07-06: tag labels failed 4.5:1). */
  --stm-color-errorStrong: #FECDD3; /* Dark error for text on light error tints — WCAG AA (7.3:1 on errorSurface). Completes the *Strong family. */
  --stm-color-warningStrong: #FFEFAD; /* Dark warning for text on light warning tints — WCAG AA (6.0:1 on warningSurface; yellow.700 fails). Completes the *Strong family. */
  --stm-color-indigoStrong: #E1D7EA; /* Dark warm-violet for TEXT on the subtle fill — WCAG AA (7.1:1 on indigoSubtle; indigoEmphasis is the 3:1 BORDER step, not a text color). Completes the *Strong family. */
  --stm-color-successContainer: #052e16; /* Success container background */
  --stm-color-onSuccess: #0F172A; /* Text/icons on success color — dark green for WCAG AA contrast on green-600 */
  --stm-color-onSuccessContainer: #BBF7D0; /* Text/icons on success container */
  --stm-color-onSuccessSubtle: #BBF7D0; /* Text/icons on subtle success backgrounds */
  --stm-color-warning: #FFD84A; /* Warning state color — caution states, approaching limits. Do NOT use for errors — use error tokens instead */
  --stm-color-warningHover: #FFE47A; /* Warning hover state */
  --stm-color-warningActive: #FFEFAD; /* Warning active/pressed state */
  --stm-color-warningSubtle: #4A3600; /* Subtle warning background */
  --stm-color-warningSubtleHover: #7D5D00; /* Subtle warning hover */
  --stm-color-warningSubtleActive: #A87D00; /* Subtle warning active */
  --stm-color-warningEmphasis: #FFE47A; /* Warning emphasis for borders */
  --stm-color-warningContainer: #4A3600; /* Warning container background */
  --stm-color-onWarning: #0F172A; /* Text/icons on warning color - dark for readability */
  --stm-color-onWarningContainer: #FFEFAD; /* Text/icons on warning container */
  --stm-color-onWarningSubtle: #FFEFAD; /* Text/icons on subtle warning backgrounds */
  --stm-color-info: #60A5FA; /* Info state color */
  --stm-color-infoHover: #93C5FD; /* Info hover state */
  --stm-color-infoActive: #BFDBFE; /* Info active/pressed state */
  --stm-color-infoSubtle: #172554; /* Subtle info background */
  --stm-color-infoSubtleHover: #1E3A8A; /* Subtle info hover */
  --stm-color-infoSubtleActive: #1E40AF; /* Subtle info active */
  --stm-color-infoEmphasis: #93C5FD; /* Info emphasis for borders */
  --stm-color-infoContainer: #172554; /* Info container background */
  --stm-color-onInfo: #0F172A; /* Text/icons on info color */
  --stm-color-onInfoContainer: #BFDBFE; /* Text/icons on info container */
  --stm-color-onInfoSubtle: #BFDBFE; /* Text/icons on subtle info backgrounds */
  --stm-color-indigo: #818CF8; /* Warm-violet 'undecided' / 'Ikke besluttet' accent (Q6/BF-12). Non-semantic — re-pointed from blue indigo to warm violet #7E5A9B so it reads as a distinct hue (not a status family) and the border step clears 3:1. Token names kept as 'indigo*' for back-compat. */
  --stm-color-indigoHover: #694785; /* Warm-violet hover state */
  --stm-color-indigoSubtle: #1E1B4B; /* Subtle warm-violet background */
  --stm-color-indigoEmphasis: #A5B4FC; /* Warm-violet emphasis for borders and icons — 3.29:1 on the subtle fill (was 2.99:1 indigo, Q6 border fix) */
  --stm-color-indigoContainer: #E1D7EA; /* Warm-violet container background */
  --stm-color-indigoSurface: #2A1A38; /* Near-white warm-violet tint — used for relevance toggle area background */
  --stm-color-onIndigo: #1E1B4B; /* Text/icons on warm-violet color */
  --stm-color-onIndigoSubtle: #C7D2FE; /* Text/icons on subtle warm-violet backgrounds */
  --stm-color-focusRing: #7694F7; /* Focus indicator color - matches brand primary */
  --stm-color-focusRingInverse: #FFFFFF; /* Focus indicator on dark backgrounds */
  --stm-color-iconDefault: #F8FAFC; /* Default icon color — matches primary text. Moved from core/icon.json for type consistency */
  --stm-color-iconInteractive: #96ADF8; /* Interactive icon color — matches brand primary. Moved from core/icon.json for type consistency */
  --stm-color-iconMuted: #94A3B8; /* Muted icon color — secondary / placeholder icons. Moved from core/icon.json for type consistency */
  --stm-color-pageBackground: #1E293B; /* Page-level background — subtle tint behind cards and content. Moved from core/layout.json for type consistency */

  /* ── Chart series ── */
  --stm-chart-series1: #2563EB; /* Categorical series 1 — the brand-family lead hue */
  --stm-chart-series2: #0D9488; /* Categorical series 2 */
  --stm-chart-series3: #EA580C; /* Categorical series 3 */
  --stm-chart-series4: #9333EA; /* Categorical series 4 */
  --stm-chart-series5: #16A34A; /* Categorical series 5 */
  --stm-chart-series6: #DB2777; /* Categorical series 6 */
  --stm-chart-series7: #0891B2; /* Categorical series 7 */
  --stm-chart-series8: #FFC000; /* Categorical series 8 */
  --stm-chart-sequential1: #D1DBFC; /* Sequential ramp step 1 (lightest) — magnitude encoding on the brand's own scale */
  --stm-chart-sequential2: #B5C6FA; /* Sequential ramp step 2 */
  --stm-chart-sequential3: #96ADF8; /* Sequential ramp step 3 */
  --stm-chart-sequential4: #7694F7; /* Sequential ramp step 4 */
  --stm-chart-sequential5: #5279F4; /* Sequential ramp step 5 */
  --stm-chart-sequential6: #2C5BF2; /* Sequential ramp step 6 */
  --stm-chart-sequential7: #2145B8; /* Sequential ramp step 7 (darkest) — skips 600 for a clearer end-of-ramp jump */

  /* ── Overlays ── */
  --stm-overlay-hover: rgba(255, 255, 255, 0.08); /* Standard hover overlay - 4% black in light mode */
  --stm-overlay-hoverStrong: rgba(255, 255, 255, 0.12); /* Strong hover overlay - 8% black in light mode */
  --stm-overlay-active: rgba(255, 255, 255, 0.16); /* Active/pressed overlay - 12% black in light mode */
  --stm-overlay-focus: rgba(255, 255, 255, 0.16); /* Focus state overlay - 12% black in light mode */
  --stm-overlay-selected: rgba(255, 255, 255, 0.12); /* Selected state overlay - 8% black in light mode */
  --stm-overlay-disabled: rgba(255, 255, 255, 0.38); /* Disabled state overlay - 38% black in light mode */
  --stm-overlay-surfaceTranslucent: rgba(15, 23, 42, 0.90); /* Translucent surface veil - 90% white. RENAMED from surfaceGlass (TOK-44): the name promised a frosted-glass effect and the value is a flat alpha wash — there is no blur anywhere in the system, so every consumer reading the name got the wrong mental model of what it does. Renaming was chosen over adding overlay.glassBlur because a backdrop-filter is a real visual and performance decision, and a token declared ahead of the behaviour would be dead on arrival. When glass is genuinely wanted, glassBlur lands WITH the backdrop-filter that makes it true. Its 25% sibling was RETIRED in the same change (TOK-46): nothing has ever bound it, and a second veil strength nobody uses is exactly the kind of slot the dead-token ratchet exists to remove. */
  --stm-overlay-scrim: rgba(0, 0, 0, 0.6); /* Modal backdrop/scrim - 50% black */
  --stm-overlay-hoverInverse: rgba(255, 255, 255, 0.05); /* White hover overlay - for dark backgrounds */
  --stm-overlay-shimmer: rgba(255, 255, 255, 0.08); /* Skeleton shimmer sweep — the highlight that travels across a loading placeholder (TOK-97). 30% white reads as a sheen over the light-mode neutral.200 fill; the dark override drops it to 8% because the same alpha over a neutral.700 fill is a blown-out white streak, which is exactly what the coverage scan flagged as wrong in dark. Lives at core, not on the component, so any future shimmering surface inherits the mode flip. */

  /* ── Elevation ── */
  --stm-elevation-level-000: none; /* No elevation */
  --stm-elevation-level-001: inset 0px 0px 0px 9999px rgba(255,255,255,0.05), 0px 1px 3px 0px rgba(255,255,255,0.10); /* Level 1 elevation - subtle shadow for cards */
  --stm-elevation-level-002: inset 0px 0px 0px 9999px rgba(255,255,255,0.07), 0px 4px 6px 0px rgba(255,255,255,0.08); /* Level 2 elevation - medium shadow for raised elements */
  --stm-elevation-level-003: inset 0px 0px 0px 9999px rgba(255,255,255,0.08), 0px 8px 16px 0px rgba(255,255,255,0.08); /* Level 3 elevation - prominent shadow for modals */
  --stm-elevation-level-004: inset 0px 0px 0px 9999px rgba(255,255,255,0.09), 0px 16px 32px 0px rgba(255,255,255,0.12); /* Level 4 elevation - strong shadow for floating elements */
  --stm-elevation-sm: inset 0px 0px 0px 9999px rgba(255,255,255,0.05), 0px 1px 3px 0px rgba(255,255,255,0.10); /* Semantic alias for level.001 (TOK-34). Four CSS files consumed --stm-elevation-md before it existed — StmBulkBar shipped with NO shadow (var resolved to nothing); sm/md/lg make the intuitive names real. */
  --stm-elevation-md: inset 0px 0px 0px 9999px rgba(255,255,255,0.07), 0px 4px 6px 0px rgba(255,255,255,0.08); /* Semantic alias for level.002 (TOK-34) - hover lift for interactive cards (contactCard/statCard elevationHover chains) */
  --stm-elevation-lg: inset 0px 0px 0px 9999px rgba(255,255,255,0.08), 0px 8px 16px 0px rgba(255,255,255,0.08); /* Semantic alias for level.003 (TOK-34) */

  /* ── Typography ── */
  --stm-typography-display-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-typography-display-font-size: 48px;
  --stm-typography-display-font-weight: 700;
  --stm-typography-display-line-height: 1.1;
  --stm-typography-heading1-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-typography-heading1-font-size: 56px;
  --stm-typography-heading1-font-weight: 700;
  --stm-typography-heading1-line-height: 1.1;
  --stm-typography-heading2-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-typography-heading2-font-size: 44px;
  --stm-typography-heading2-font-weight: 700;
  --stm-typography-heading2-line-height: 1.1;
  --stm-typography-heading3-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-typography-heading3-font-size: 36px;
  --stm-typography-heading3-font-weight: 700;
  --stm-typography-heading3-line-height: 1.1;
  --stm-typography-heading4-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-typography-heading4-font-size: 28px;
  --stm-typography-heading4-font-weight: 700;
  --stm-typography-heading4-line-height: 1.1;
  --stm-typography-heading5-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-typography-heading5-font-size: 20px;
  --stm-typography-heading5-font-weight: 700;
  --stm-typography-heading5-line-height: 1.375;
  --stm-typography-heading6-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-typography-heading6-font-size: 16px;
  --stm-typography-heading6-font-weight: 700;
  --stm-typography-heading6-line-height: 1.5;
  --stm-typography-body-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-typography-body-font-size: 16px;
  --stm-typography-body-font-weight: 400;
  --stm-typography-body-line-height: 1.5;
  --stm-typography-bodySmall-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-typography-bodySmall-font-size: 14px;
  --stm-typography-bodySmall-font-weight: 400;
  --stm-typography-bodySmall-line-height: 1.5;
  --stm-typography-bodyLarge-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-typography-bodyLarge-font-size: 18px;
  --stm-typography-bodyLarge-font-weight: 400;
  --stm-typography-bodyLarge-line-height: 1.625;
  --stm-typography-label-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-typography-label-font-size: 14px;
  --stm-typography-label-font-weight: 700;
  --stm-typography-label-line-height: 1.5;
  --stm-typography-action-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-typography-action-font-size: 16px;
  --stm-typography-action-font-weight: 700;
  --stm-typography-action-line-height: 1.5;
  --stm-typography-action-letter-spacing: 0em;
  --stm-typography-labelSmall-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-typography-labelSmall-font-size: 12px;
  --stm-typography-labelSmall-font-weight: 700;
  --stm-typography-labelSmall-line-height: 1.5;
  --stm-typography-caption-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-typography-caption-font-size: 12px;
  --stm-typography-caption-font-weight: 400;
  --stm-typography-caption-line-height: 1.5;
  --stm-typography-overline-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-typography-overline-font-size: 10px;
  --stm-typography-overline-font-weight: 700;
  --stm-typography-overline-line-height: 1.5;
  --stm-typography-overline-letter-spacing: 0.08em;
  --stm-typography-overline-text-transform: uppercase;
  --stm-typography-code-font-family: Roboto Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
  --stm-typography-code-font-size: 14px;
  --stm-typography-code-font-weight: 400;
  --stm-typography-code-line-height: 1.5;
  --stm-typography-letterSpacing-tight: -0.01em; /* Slight negative tracking for display-size headings — -0.01em */
  --stm-typography-letterSpacing-normal: 0em; /* Font-default tracking (explicit 0em) — the brand-override seam on caption/action */
  --stm-typography-letterSpacing-wide: 0.05em; /* Uppercase small-label tracking — 0.05em (statCard/navigation convention) */
  --stm-typography-letterSpacing-wider: 0.08em; /* Overline/eyebrow tracking — 0.08em */
  --stm-typography-weight-regular: 400; /* Regular body weight — emits --stm-typography-weight-regular */
  --stm-typography-weight-medium: 500; /* Medium weight — labels, nav items — emits --stm-typography-weight-medium */
  --stm-typography-weight-bold: 700; /* Bold/emphasis weight — emits --stm-typography-weight-bold (600 under retur, 700 elsewhere) */
  --stm-typography-numeric-tabular: "tnum" 1, "lnum" 1; /* Tabular lining numerals for tables, amount/currency fields, and any column of figures that must align — emits --stm-typography-numeric-tabular / $typography-numeric-tabular */
  --stm-typography-numeric-normal: normal; /* Reset numerals to the font default (proportional) — pairs with the tabular token */
  --stm-typography-underlineOffset: 2px; /* Link/text underline offset (issue #53) — the standard text-underline-offset. Use var(--stm-typography-underlineOffset) instead of a hardcoded 2px so underline air is adjustable in one place. BLAZOR-ONLY (TOK-46c) because of where it is bound, not because React disagrees: this pair exists for RAW-MARKUP contexts and is bound by the `.stm-u-underline` utility in wwwroot/css/utilities/stm-utilities.css. The React package ships no utility stylesheet at all — its styling is CSS modules, scoped per component — so there is no file on that leg for a utility class to live in. Components on both legs use the link.* pair instead. */
  --stm-typography-underlineThickness: 1px; /* Link/text underline stroke (issue #53) — the standard text-decoration-thickness. Pairs with underlineOffset. BLAZOR-ONLY (TOK-46c) because of where it is bound, not because React disagrees: this pair exists for RAW-MARKUP contexts and is bound by the `.stm-u-underline` utility in wwwroot/css/utilities/stm-utilities.css. The React package ships no utility stylesheet at all — its styling is CSS modules, scoped per component — so there is no file on that leg for a utility class to live in. Components on both legs use the link.* pair instead. */

  /* ── Font Sizes ── */
  --stm-fontSize-xs: 10px; /* Type size xs */
  --stm-fontSize-sm: 12px; /* Type size sm */
  --stm-fontSize-md: 14px; /* Type size md */
  --stm-fontSize-base: 16px; /* Type size base */
  --stm-fontSize-lg: 18px; /* Type size lg */
  --stm-fontSize-xl: 20px; /* Type size xl */
  --stm-fontSize-2xl: 24px; /* Type size 2xl — RETUR 28px / shared 24px */
  --stm-fontSize-3xl: 30px; /* Type size 3xl — RETUR 32px / shared 30px */
  --stm-fontSize-4xl: 36px; /* Type size 4xl — RETUR 40px / shared 36px */
  --stm-fontSize-5xl: 48px; /* Type size 5xl */
  --stm-fontSize-6xl: 60px; /* Type size 6xl */
  --stm-fontSize-7xl: 72px; /* Type size 7xl */
  --stm-fontSize-8xl: 96px; /* Type size 8xl */

  /* ── Radius ── */
  --stm-radius-none: 0px; /* No radius */
  --stm-radius-xs: 1px; /* Extra small radius */
  --stm-radius-sm: 2px; /* Small radius */
  --stm-radius-md: 8px; /* Medium radius */
  --stm-radius-lg: 10px; /* Large radius */
  --stm-radius-xl: 12px; /* Extra large radius */
  --stm-radius-2xl: 14px; /* 2X large radius */
  --stm-radius-3xl: 16px; /* 3X large radius */
  --stm-radius-full: 9999px; /* Full radius - pill shape */

  /* ── Spacing ── */
  --stm-space-none: 0px; /* No spacing */
  --stm-space-hairline: 1px; /* Hairline spacing - 1px */
  --stm-space-xs: 4px; /* Extra small spacing - 4px */
  --stm-space-sm: 8px; /* Small spacing - 8px */
  --stm-space-md: 16px; /* Medium spacing - 16px */
  --stm-space-lg: 24px; /* Large spacing - 24px */
  --stm-space-xl: 32px; /* Extra large spacing - 32px */
  --stm-space-2xl: 48px; /* 2X large spacing - 48px */
  --stm-space-3xl: 64px; /* 3X large spacing - 64px */
  --stm-space-4xl: 96px; /* 4X large spacing - 96px */
  --stm-space-paddingCompact: 10px; /* Compact control padding - 10px. Off the coarse xs/sm/md layout rhythm; for pill/button/chip interiors. Mirrors brand.space.paddingCompact — component tokens (--stm-button-paddingY etc.) are the primary surface; this exposes the value for hand-built controls. */
  --stm-space-paddingStandard: 12px; /* Standard control padding - 12px. Mirrors brand.space.paddingStandard. */
  --stm-space-paddingWide: 14px; /* Wide control padding - 14px (chips). Mirrors brand.space.paddingWide. */
  --stm-space-paddingExtraWide: 18px; /* Extra-wide control padding - 18px (tabs); the widest control padding role. Mirrors brand.space.paddingExtraWide. */

  /* ── Sizes ── */
  --stm-size-popupMaxWidth: 640px; /* Maximum width for modals and large popups - 640px */
  --stm-size-popupMaxWidthSmall: 320px; /* Maximum width for tooltips, dropdowns, and small popups - 320px */
  --stm-size-popupMaxHeight: 320px; /* Maximum height for scrollable popup content - 320px */
  --stm-size-controlSm: 32px; /* Control size axis (TOK-20, owner 2026-07-09) — SMALL control min-height (28px). Consumed by the size='small' variants of button/input/select/numberInput; avatar already rides the brand slots */
  --stm-size-controlMd: 44px; /* Control size axis (TOK-20) — MEDIUM control min-height (40px) */
  --stm-size-controlLg: 52px; /* Control size axis (TOK-20) — LARGE control min-height (44px, the current form-control default) */
  --stm-size-controlPaddingXSm: 8px; /* TOK-20 padding pair, small — 8px inline */
  --stm-size-controlPaddingYSm: 4px; /* TOK-20 padding pair, small — 4px block TOK-42: reads the BRAND knob now. The axis has been size-aware since TOK-20 and was not brand-aware, so every brand's floor was the same 32/40/40 (line-height 24px + 2x this) and a compact brand's control ladder could not land no matter what its control{Sm,Md,Lg} said. */
  --stm-size-controlPaddingXMd: 12px; /* TOK-20 padding pair, medium — 12px inline */
  --stm-size-controlPaddingYMd: 8px; /* TOK-20 padding pair, medium — 8px block TOK-42: reads the BRAND knob now. The axis has been size-aware since TOK-20 and was not brand-aware, so every brand's floor was the same 32/40/40 (line-height 24px + 2x this) and a compact brand's control ladder could not land no matter what its control{Sm,Md,Lg} said. */
  --stm-size-controlPaddingXLg: 16px; /* TOK-20 padding pair, large — 16px inline (matches the current button/input default) */
  --stm-size-controlPaddingYLg: 8px; /* TOK-20 padding pair, large — 8px block (min-height carries the rest of the 44px) TOK-42: reads the BRAND knob now. The axis has been size-aware since TOK-20 and was not brand-aware, so every brand's floor was the same 32/40/40 (line-height 24px + 2x this) and a compact brand's control ladder could not land no matter what its control{Sm,Md,Lg} said. */
  --stm-size-dotSm: 0.625rem; /* Small status/fraction dot (issue #54) — 10px. The recurring color-dot glyph in editors and legends (GP fraction dots). Use instead of a raw 0.625rem. */
  --stm-size-dotMd: 0.75rem; /* Medium status/fraction dot (issue #54) — 12px. Pairs with dotSm. */
  --stm-size-labelSlot: 2rem; /* Fixed inline label slot (issue #54) — 32px. The aligned short-label column in dense editors (GP .gp-frlabel). Use instead of a raw 2rem. */
  --stm-size-scrollbarWidth: 10px; /* WebKit scrollbar rail width for the opt-in .stm-scrollbar recipe (TOK-121: was a raw 10px in stm-baseline.css, outside every lint scan set). */
  --stm-size-fieldLg: 320px; /* Field width ramp, LARGE (#619 rec 3). Discovered by hand: StmTextField's stories write 320 / 220 / 140 across BOTH sets, which is a size ramp nobody wrote down. Naming it lets a call site pick a rung instead of inventing a number — the same argument that produced the spacing and radius ladders. NOTE StmTextField already HAS a `width` prop on both sets and the Blazor stories still hand-roll style="width:320px" six times; that half is an API nobody found, i.e. docs and lint rather than design. ADOPTION STATUS: bound today by the Blazor StmTextField stories (6 sites), which the token-usage scanner does not read — it scans the library and product surfaces, not stories, deliberately, because a story is a demo rather than a consumer. So this rung sits in the deadScaleRungs baseline, which is the sanctioned home for "a rung nothing stands on yet". The rung StmTextField itself should stand on is a separate decision: it already HAS a `width` prop on both sets, and giving it a DEFAULT width would be a visual change to every consumer, which is the exact trap the corrected rec 2 fell into. */
  --stm-size-fieldMd: 220px; /* Field width ramp, MEDIUM (#619 rec 3). See fieldLg. ADOPTION STATUS: bound today by the Blazor StmTextField stories (6 sites), which the token-usage scanner does not read — it scans the library and product surfaces, not stories, deliberately, because a story is a demo rather than a consumer. So this rung sits in the deadScaleRungs baseline, which is the sanctioned home for "a rung nothing stands on yet". The rung StmTextField itself should stand on is a separate decision: it already HAS a `width` prop on both sets, and giving it a DEFAULT width would be a visual change to every consumer, which is the exact trap the corrected rec 2 fell into. */
  --stm-size-fieldSm: 140px; /* Field width ramp, SMALL (#619 rec 3). See fieldLg. ADOPTION STATUS: bound today by the Blazor StmTextField stories (6 sites), which the token-usage scanner does not read — it scans the library and product surfaces, not stories, deliberately, because a story is a demo rather than a consumer. So this rung sits in the deadScaleRungs baseline, which is the sanctioned home for "a rung nothing stands on yet". The rung StmTextField itself should stand on is a separate decision: it already HAS a `width` prop on both sets, and giving it a DEFAULT width would be a visual change to every consumer, which is the exact trap the corrected rec 2 fell into. */
  --stm-size-panelSm: 15rem; /* Menu/panel width ramp, SMALL (#619 rec 3). StmDropDownButton's stories write 15rem and 18rem across 8 hand-rolls — two values, i.e. a small-vs-large rung that was never declared. In rem deliberately, because a menu's width should follow the text it lists. */
  --stm-size-panelMd: 18rem; /* Menu/panel width ramp, MEDIUM (#619 rec 3). See panelSm. */
  --stm-size-measureSm: 360px; /* Demo/content MEASURE ladder, SMALL (#619). This is the affordance the audit's 'five undeclared defaults' finding actually pointed at, once it was checked: those numbers were max-width on a story WRAPPER, never a width on a component, and all five components already fill their container. So the real gap was that 81 story authors had no sanctioned way to say 'this demo needs a measure' and hand-rolled a raw <div style="max-width:Npx"> — which CLAUDE.md classifies as a gap in its own right. A ladder, not the observed values: 360 / 480 / 640 is a real ratio, where 360/390/400/480/500 is just a list of what people happened to type. Story-only by intent; nothing a consumer renders changes. */
  --stm-size-measureMd: 480px; /* Demo/content MEASURE ladder, MEDIUM (#619). See measureSm. */
  --stm-size-measureLg: 640px; /* Demo/content MEASURE ladder, LARGE (#619). See measureSm. */

  /* ── Icon Sizes ── */
  --stm-icon-strokeWidth: 2; /* Lucide glyph stroke weight (TOK-43, owner 2026-08-14) — brand-tunable; consumed as --stm-icon-strokeWidth by both legs' StmIcon glyph rule. 2 everywhere today (the vendored default), so this is value-preserving until a brand turns it. */
  --stm-icon-size-xs: 12px; /* Extra-small fixed icon glyph - 12px. The compact-density step below sm: checklist rows, dense table affordances, and any glyph paired with caption-sized text (TOK-59/TOK-95). Do NOT use for icons that should scale with text - use icon.size.inline instead. */
  --stm-icon-size-sm: 16px; /* Small fixed icon glyph — 16px, per board annotations. Dense rows, chip/inline-button glyphs. Do NOT use for icons that should scale with text — use icon.size.inline instead */
  --stm-icon-size-md: 20px; /* Medium fixed icon glyph — 20px, per board annotations. Default icon-button and list-action glyph size */
  --stm-icon-size-lg: 24px; /* Large fixed icon glyph — 24px, per board annotations. App-bar actions, prominent affordances */
  --stm-icon-size-xl: 32px; /* Extra-large fixed icon glyph - 32px. The step above lg for oversized affordances: the large FAB's glyph and the large star in StmRating, both of which used magic multipliers or literals before (TOK-72/TOK-95). */
  --stm-icon-size-inline: 1em; /* Icons inline with text - same size as text */
  --stm-icon-size-button: 1.25em; /* Icons in buttons - slightly larger for visibility */
  --stm-icon-size-heading: 1.5em; /* Icons in headings - proportionally larger */
  --stm-icon-size-badge: 8px; /* Icon size inside a status badge or chip dot — 8px */
  --stm-icon-size-avatar: 20px; /* Icon size for avatar-sized circles (stepper step, chip avatar) — 20px */
  --stm-icon-size-circle: 44px; /* Icon/label size for 40px circle containers (stepper, numbered step cards) */
  --stm-icon-size-hero: 52px; /* Icon size for hero/success illustrations — 44px */

  /* ── Opacity ── */
  --stm-opacity-disabled: 0.38; /* Disabled-state opacity for whole controls — 0.38, the Material disabled-content convention. Applied to .stm-is-disabled / :disabled across STM components. THE RECIPE (owner decision 2026-08-14, TOK-39): the opacity wash is the standard whole-control disabled treatment in both legs; the core.color.disabled* trio is NOT an alternative recipe — it exists solely as the semantic source for component-level disabled slots (button/input/menu TOK-13 pattern) where a surface must keep explicit colors. */
  --stm-opacity-muted: 0.6; /* Muted / medium-emphasis content opacity — 0.6 (Material medium-emphasis). For de-emphasized but still-active content. */
  --stm-opacity-hover: 0.04; /* Hover-state overlay opacity — 0.04 (TOK-12, MUI hoverOpacity convention). Numeric twin of core.overlay.hover; the lockstep is pinned by test — change both together. */
  --stm-opacity-selected: 0.08; /* Selected-state overlay opacity — 0.08 (TOK-12, MUI selectedOpacity). Numeric twin of core.overlay.selected (and hoverStrong); lockstep pinned by test. */
  --stm-opacity-focus: 0.12; /* Focus-state overlay opacity — 0.12 (TOK-12, MUI focusOpacity). Numeric twin of core.overlay.focus; lockstep pinned by test. */
  --stm-opacity-activated: 0.12; /* Activated/pressed-state overlay opacity — 0.12 (TOK-12, MUI activatedOpacity). Numeric twin of core.overlay.active; lockstep pinned by test. */
  --stm-opacity-scrim: 0.5; /* Modal/backdrop scrim opacity — 0.5. The dark overlay behind modals and drawers. */
  --stm-opacity-ghost: 0.5; /* Drag-ghost opacity — 0.5 (TOK-121: tileLayout’s drag preview had no core value between dimmed .55 and pulseFloor .4). */
  --stm-opacity-dimmed: 0.55; /* Dimmed-but-legible content — 0.55 (TOK-39). For de-emphasized cards/tiles that must stay readable (moduleCard irrelevance dim, tileLayout drag ghost). Stronger presence than `disabled` (0.38). */
  --stm-opacity-pulseFloor: 0.4; /* Loading-pulse keyframe floor — 0.4 (TOK-39). The dimmest point of skeleton/overlay pulse animations; was a raw literal in StmSkeleton/StmLoadingOverlay keyframes. */

  /* ── Density ── */
  --stm-density-controlPaddingYComfortable: 10px; /* Default control vertical padding — 10px, anchored on the button's paddingY */
  --stm-density-controlPaddingYCompact: 6px; /* Compact control vertical padding — 6px, anchored on dataGrid's compact cell padding */
  --stm-density-rowHeightComfortable: 44px; /* Default row/control height floor — the brand's own minHeight. KNOWN COINCIDENCE (TOK-116/TOK-42): under RETUR this equals rowHeightCompact (both 28px), making the density axis a no-op there — inherited from the unreconciled brand.size.minHeight ↔ core.size.control* systems; resolves when the TOK-42 per-brand ladders land. */
  --stm-density-rowHeightCompact: 32px; /* Compact row/control height floor — 28px (brand-invariant today; see rowHeightComfortable's RETUR note) */

  /* ── Motion ── */
  --stm-motion-duration-fast: 120ms; /* Fast — micro-interactions (hover, color/background, small state changes). The most common transition speed across STM. */
  --stm-motion-duration-base: 150ms; /* Base — standard control transitions (inputs, buttons, toggles). */
  --stm-motion-duration-slow: 200ms; /* Slow — larger surfaces and composite movements. */
  --stm-motion-duration-slower: 300ms; /* Slower — panels, drawers, overlays entering/leaving. */
  --stm-motion-duration-enter: 225ms; /* Enter — surfaces arriving on screen (drawers, dialogs, popovers). MUI enteringScreen; pairs with easing.decelerate (TOK-15). */
  --stm-motion-duration-exit: 195ms; /* Exit — surfaces leaving the screen; slightly faster than enter so dismissal feels immediate. MUI leavingScreen; pairs with easing.accelerate (TOK-15). */
  --stm-motion-duration-loop: 1400ms; /* Loop — one cycle of a continuous, repeating animation (indeterminate progress sweep, busy spinners). Deliberately longer than the transition durations: a repeating cycle must read as calm/ongoing, not frantic. Use with animation-iteration-count: infinite. */
  --stm-motion-duration-loopSlow: 1500ms; /* Slow loop — skeleton pulse cycle (TOK-36; was a raw 1.5s literal in StmSkeleton both legs). One step calmer than `loop`. */
  --stm-motion-duration-loopSweep: 1600ms; /* Sweep loop — skeleton wave/shimmer traversal (TOK-36; was a raw 1.6s literal in StmSkeleton both legs). The longest cycle: a full-width sweep needs more time to read as calm. */
  --stm-motion-easing-standard: cubic-bezier(0.4, 0, 0.2, 1); /* Standard easing (Material standard curve) — most transitions that both enter and leave. */
  --stm-motion-easing-decelerate: cubic-bezier(0, 0, 0.2, 1); /* Decelerate (Material decelerate curve) — elements entering the screen. */
  --stm-motion-easing-accelerate: cubic-bezier(0.4, 0, 1, 1); /* Accelerate (Material accelerate curve) — elements LEAVING the screen; starts slow, exits fast (TOK-15; Kendo ships the same curve as --kendo-easing-accelerate). */
  --stm-motion-easing-sharp: cubic-bezier(0.4, 0, 0.6, 1); /* Sharp — objects that may return at any time (temporary drawers, toggling panels). MUI sharp curve (TOK-15). */
  --stm-motion-easing-easeInOut: cubic-bezier(0.42, 0, 0.58, 1); /* Symmetric in-out — the CSS ease-in-out keyword as a token (TOK-119: the loop tokenization left skeleton-pulse and spinner-dash on the literal keyword because no symmetric curve existed; standard is asymmetric). */
  --stm-motion-easing-linear: cubic-bezier(0, 0, 1, 1); /* Linear — continuous looping animations only (spinner rotation, skeleton sweep), where any easing would make the loop pulse (TOK-36; was a raw `linear` keyword in StmCircularProgress/StmSkeleton both legs). Never for enter/exit transitions. */

  /* ── Z-Index ── */
  --stm-zIndex-base: 0; /* Default flow — no stacking raise. */
  --stm-zIndex-raised: 1; /* Slightly lifted within local content. (Most z-index: 1..5 inside a single component is local stacking and is NOT this token.) */
  --stm-zIndex-dropdown: 1000; /* Component-anchored overlays: dropdown-button menus, split-button menus, color-picker popups. */
  --stm-zIndex-sticky: 1100; /* Sticky / pinned chrome (app bar, sticky header). MUI appBar level. */
  --stm-zIndex-overlay: 1200; /* Scrims / backdrops and slide-in drawers — the overlay base band. MUI drawer level. */
  --stm-zIndex-modal: 1250; /* Full-screen modals (StmFullScreenModal). */
  --stm-zIndex-popover: 1300; /* Popovers and modal dialogs that float above the scrim. MUI modal level. */
  --stm-zIndex-toast: 1400; /* Snackbars / toasts. MUI snackbar level. */
  --stm-zIndex-tooltip: 10000; /* Tooltips — the topmost STM layer, and deliberately ABOVE max. An explainer must outrank every surface that can host its target, and max (9999) is a surface that hosts targets: a tooltip on a control inside StmFloatingToolbar was painted over by the toolbar itself (#647). It was 1500 (MUI's tooltip level) until 2026-08-31; the number matched MUI and contradicted this scale's own top rung. A Telerik body-portaled popup carries the vendor's own ~10002 and may still out-stack this — vendor-controlled, and adjustable in stm-telerik-popups.css if a real case appears. */
  --stm-zIndex-max: 9999; /* FloatingToolbar — the spec requires it above all CONTENT including modals. Use only for that escape-hatch case. No longer the numeric maximum of this scale, and the name is kept because renaming a token is a breaking change: `tooltip` sits above it on purpose (#647), because a transient explainer has to outrank the surface its target lives on. */

  /* ── Border Widths ── */
  --stm-border-width-thin: 1px; /* Default border / divider weight — 1px. The overwhelming majority of STM borders. */
  --stm-border-width-thick: 2px; /* Emphasis border — 2px. Focus rings, selected/active outlines, heavier dividers. */
  --stm-border-width-medium: 3px; /* Statement border, restrained — 3px (#618). The rung between thick and heavy, restored: 3px was `heavy` itself until GDK-55 widened it to 5px, leaving nothing between an emphasis outline and full chrome. Use it where a surface needs a VISIBLE edge rather than a hairline — an inverted overlay floating over a same-tone panel is the case that asked for it. Still not a focus ring: those stay on `thick`. */
  --stm-border-width-heavy: 5px; /* Statement border — 5px (GDK-55, owner 2026-07-14; widened 3px→5px 2026-07-15). Brand-accent frames that REPLACE a filled band (the outlined top-menu chrome when the dark-canvas flag is off; revision-group boxes stay on thick). Not for focus rings or dividers — this is chrome, not state. */
  --stm-border-overlayWidth: 1px; /* Weight of an OVERLAY's own outer edge (#618) — the companion to core.color.overlayBorder, and a role for the same reason: how strongly a floating surface must announce its boundary depends on what it is floating on, which only the host knows. Defaults to thin, so a pill on a page is unchanged. StmFullScreenModal's canvas surface re-points it to medium (3px): there the overlay and the panel behind it are both #FFFFFF, a hairline is doing the whole job alone, and 3px is the weight the #618 reporter actually asked for. */
  --stm-border-focusRingOffset: 2px; /* Gap between a control's edge and its focus ring — the standard outline-offset (TOK-06). Emits --stm-border-focusRingOffset; use var(--stm-border-focusRingOffset) instead of a hardcoded outline-offset: 2px so focus spacing is adjustable in one place. */
  --stm-border-focusRingOffsetInset: -2px; /* INSET focus ring offset — the ring drawn just inside a clipped/overflow-hidden container where an outer ring would be cropped (accordion headers, tab items, menu items, pagination buttons). A distinct recipe from focusRingOffset, not a sign flip: inset rings pair with border-radius-clipped parents (TOK-35; was a literal -2px at 14 sites across both legs). */
  --stm-border-focusRingWidth: 2px; /* Focus ring stroke width (TOK-31) — 2px via width.thick. Completes the ring trio (color.focusRing + focusRingOffset + THIS). Two-mode recipe: FLAT controls draw `outline: var(--stm-border-focusRingWidth) solid var(--stm-color-focusRing)` + offset; FILLED controls on tinted surfaces draw the shadow ring `box-shadow: 0 0 0 var(--stm-border-focusRingWidth) var(--stm-color-focusRing)` (no offset — the shadow hugs the fill). Kendo's --kendo-focus-outline-width precedent. */
  --stm-border-focusRingRadius: 4px; /* Focus ring corner rounding (issue #53) — the border-radius an outline follows on otherwise-sharp text targets (links, inline actions). 4px sits deliberately between radius.sm (2px) and radius.md (8px); completes the ring quartet (color.focusRing + focusRingWidth + focusRingOffset + THIS). */

  /* ── Layout ── */
  --stm-layout-sidebarWidth: 240px; /* Default sidebar width - collapsed or minimal navigation - 240px */
  --stm-layout-sidebarWidthExpanded: 320px; /* Expanded sidebar width - full navigation with labels - 320px */
  --stm-layout-contentMaxWidth: 1024px; /* Standard page content max-width - 1024px */
  --stm-layout-contentMaxWidthWide: 1280px; /* Wide page content max-width - dashboards, data tables - 1280px */
  --stm-layout-contentMaxWidthNarrow: 768px; /* Narrow content max-width - forms, single-column layouts - 768px */
  --stm-layout-toolbarClearance: 96px; /* Bottom clearance a page keeps free for the StmFloatingToolbar pill — 96px (issue #78: was a raw padding-bottom literal repeated across the Hub/signup shells, logged as spec/app-shell-gaps.md #8; StmPageFrame's toolbarClearance prop consumes it) */
  --stm-layout-containerMaxWidth: 1440px; /* Outer page-shell / stepper-band container max-width — 1440px. The widest centered canvas (the contentMaxWidth* tokens cap the inner content region inside it). BF-14. */
  --stm-layout-gridColumns: 12; /* Layout grid column count at desktop (>=1440). Confirmed against the Figma Grid foundation (Desktop 838:89418 = 12 columns). BF-14 / #20. */
  --stm-layout-gridColumnsMobile: 6; /* Layout grid column count at mobile (390). Confirmed against the Figma Grid foundation (Mobile 838:89419 = 6 columns). BF-14 / #20. */
  --stm-layout-gridGutter: 24px; /* Gap between layout grid columns at desktop — 24px. Confirmed against the Figma Grid foundation (Desktop 838:89418 gutter = 24). BF-14 / #20. */
  --stm-layout-gridGutterMobile: 12px; /* Grid column gap at mobile (390) — 12px. Confirmed against the Figma Grid foundation (Mobile 838:89419 gutter = 12). BF-14 / #20. */
  --stm-layout-gridMargin: 24px; /* Outer margin of the layout grid at desktop — 24px. CORRECTED from the 32px pagePaddingX default-from-scale: the Figma Grid offset is 24 (Desktop 838:89418). BF-14 / #20. */
  --stm-layout-gridMarginMobile: 12px; /* Outer margin of the layout grid at mobile (390) — 12px. Confirmed against the Figma Grid foundation (Mobile 838:89419 offset = 12). BF-14 / #20. */
  --stm-layout-pagePaddingX: 32px; /* Horizontal page padding — 32px */
  --stm-layout-pagePaddingY: 48px; /* Vertical page padding — 48px (TOK-121: canonicalized by StmPageFrame, which shipped hardcoding space-2xl while this token sat at 24px with ZERO consumers; the frame now consumes it, so the page rhythm is retunable in one place). */
  --stm-layout-cardTrackMin: 180px; /* Minimum track width for an auto-filling card grid (TOK-121). The verbatim-length APIs — StmGrid's minItemWidth, StmGrid/StmDataRows' columns — take a CSS string on purpose, because a grid-template track list is layout STRUCTURE that no token set can enumerate. But a HARD LENGTH inside one is a design value wearing structure's clothes, and it was invisible to every guard until stm/no-raw-dimension learned those two prop names. This is the value those call sites reach for. */
  --stm-layout-railTrackMin: 280px; /* Minimum track width for a side rail inside a two-column page grid (TOK-121) — the companion to cardTrackMin, for the `minmax(rail, …)` half of a content+sidebar template. */
  --stm-layout-railTrackMax: 320px; /* Maximum track width for that side rail (TOK-121). A band rather than a fixed width: the rail should stop growing before it competes with the content column. */
  --stm-layout-sectionGap: 24px; /* Gap between major page sections — 24px */

  /* ── Components ── */

  /* accordion */
  --stm-accordion-container: #1E293B; /* Panel background */
  --stm-accordion-headerBg: #1E293B; /* Header background — closed state */
  --stm-accordion-headerBgHover: #334155; /* Header background on hover */
  --stm-accordion-headerBgActive: #334155; /* Header background — expanded state */
  --stm-accordion-headerText: #F8FAFC; /* Header text color */
  --stm-accordion-contentText: #E2E8F0; /* Content body text */
  --stm-accordion-border: #334155; /* Panel border and separator */
  --stm-accordion-headerBorder: #334155; /* Seam drawn under an EXPANDED header, separating it from the content region (TOK-48). One `border` slot used to describe two different edges — the item's outer outline and this seam — so they could never differ. */
  --stm-accordion-headerBorderWidth: 0px; /* Weight of the expanded-header seam — 0 by default, so this is value-preserving (TOK-48): today the header and content share a surface with no rule between them, and that stays true until a consumer turns it up. */
  --stm-accordion-icon: #E2E8F0; /* Expand/collapse chevron */
  --stm-accordion-iconSize: 1em; /* Chevron glyph box — 1em (TOK-48). The chevron is a text glyph and simply inherited the header's font-size, so it could not be scaled independently of the heading; 1em reproduces that inheritance exactly while making it a knob. */
  --stm-accordion-disabledOpacity: 0.38; /* Opacity of a disabled accordion item — 0.38, unchanged (TOK-48). Was a raw core reach-in. */
  --stm-accordion-borderWidth: 1px; /* Border thickness — 1px */
  --stm-accordion-radius: 14px; /* Panel corner radius */
  --stm-accordion-paddingX: 14px; /* Horizontal padding */
  --stm-accordion-paddingY: 16px; /* Vertical padding */
  --stm-accordion-gap: 4px; /* Gap between accordion items in a group */
  --stm-accordion-metaGap: 8px; /* Gap between items inside the header meta slot (counts, totals, a badge) — 8px, unchanged (TOK-48). */
  --stm-accordion-metaMarginX: 16px; /* Space separating the header meta slot from the title and the chevron — 16px, unchanged (TOK-48). */
  --stm-accordion-headerTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-accordion-headerTypography-font-size: 16px;
  --stm-accordion-headerTypography-font-weight: 700;
  --stm-accordion-headerTypography-line-height: 1.5;
  --stm-accordion-contentTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-accordion-contentTypography-font-size: 16px;
  --stm-accordion-contentTypography-font-weight: 400;
  --stm-accordion-contentTypography-line-height: 1.5;
  --stm-accordion-motionDuration: 200ms; /* Chevron rotation length — 300ms (TOK-48), previously raw core with a literal easing beside it. */
  --stm-accordion-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Chevron rotation curve (TOK-48). */
  --stm-accordion-onContainer: #F8FAFC; /* Ink for content a CONSUMER slots into this surface (CMP-65 / #621). The component's own parts each set their own colour and look perfect, so nothing here was missing until a HOST re-inked: an AuthLayout setting `color: onPrimary` for a green canvas left everything slotted in inheriting white, on white — 1.00:1. Defaults to core.color.onSurface, which is EXACTLY what slotted content already resolved to on an ordinary page, so this is value-preserving by construction and nothing moves except where it was already broken. */

  /* alert */
  --stm-alert-containerInfo: #172554; /* Info alert background — light blue */
  --stm-alert-onContainerInfo: #BFDBFE; /* Info alert text */
  --stm-alert-borderInfo: #93C5FD; /* Info alert left border accent */
  --stm-alert-iconInfo: #60A5FA; /* Info alert icon color */
  --stm-alert-containerSuccess: #052e16; /* Success alert background — light green */
  --stm-alert-onContainerSuccess: #BBF7D0; /* Success alert text */
  --stm-alert-borderSuccess: #86EFAC; /* Success alert left border accent */
  --stm-alert-iconSuccess: #4ADE80; /* Success alert icon color */
  --stm-alert-containerWarning: #4A3600; /* Warning alert background — light yellow */
  --stm-alert-onContainerWarning: #FFEFAD; /* Warning alert text */
  --stm-alert-borderWarning: #FFE47A; /* Warning alert left border accent */
  --stm-alert-iconWarning: #FFD84A; /* Warning alert icon color */
  --stm-alert-containerError: #4C0519; /* Error alert background — light red */
  --stm-alert-onContainerError: #FECDD3; /* Error alert text */
  --stm-alert-borderError: #FDA4AF; /* Error alert left border accent */
  --stm-alert-iconError: #FB7185; /* Error alert icon color */
  --stm-alert-containerNeutral: #1E293B; /* Fill of the neutral alert (TOK-49) — an advisory that is NOT one of the four severities. StmInfoBanner already has this shape; the alert family forced every message to claim a severity it may not have. */
  --stm-alert-onContainerNeutral: #F8FAFC; /* Text of the neutral alert (TOK-49). */
  --stm-alert-borderNeutral: #94A3B8; /* Leading bar of the neutral alert (TOK-49). */
  --stm-alert-iconNeutral: #E2E8F0; /* Icon of the neutral alert (TOK-49). */
  --stm-alert-containerTip: #122666; /* Fill of the Tip alert (#628) — helpful guidance rather than a severity. Same treatment infoBanner.containerTip already defines, so nothing re-themes: deliberately the primary-subtle family and NOT warningSubtle, because containerWarning already resolves to warningSubtle and a warning-tinted tip would render identically to a warning alert. Tip was the ONLY thing StmAlert could not express that StmInfoBanner could; with it, the two components stop competing for the same slot. */
  --stm-alert-onContainerTip: #B5C6FA; /* Text of the Tip alert (#628) — on the primary-subtle tint (mirrors infoBanner.onContainerTip). */
  --stm-alert-borderTip: #7694F7; /* Leading bar of the Tip alert (#628) — the brand step, so the tip stays on the primary family its fill and icon use (mirrors infoBanner.borderTip). */
  --stm-alert-iconTip: #96ADF8; /* Lightbulb colour of the Tip alert (#628) — brand primary, matching the icon{Severity} = plain status-colour pattern (mirrors infoBanner.iconTip). */
  --stm-alert-borderWidth: 4px; /* Left border accent width — 4px for visible accent strip */
  --stm-alert-radius: 8px; /* Alert border radius — brand-configurable (default 8px) */
  --stm-alert-paddingX: 16px; /* Horizontal padding — 16px */
  --stm-alert-paddingY: 8px; /* Vertical padding — 8px for compact banners */
  --stm-alert-gap: 8px; /* Gap between icon and text content — 8px */
  --stm-alert-actionGap: 16px; /* Space before the trailing action (TOK-49). The action was positioned with `margin-left: auto` alone, so at full width it sat flush against the message with no minimum clearance. */
  --stm-alert-titleGap: 4px; /* Gap between title and body text — 4px */
  --stm-alert-iconSize: 20px; /* Alert icon size — 20px */
  --stm-alert-titleTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-alert-titleTypography-font-size: 14px;
  --stm-alert-titleTypography-font-weight: 700;
  --stm-alert-titleTypography-line-height: 1.5;
  --stm-alert-bodyTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-alert-bodyTypography-font-size: 14px;
  --stm-alert-bodyTypography-font-weight: 400;
  --stm-alert-bodyTypography-line-height: 1.5;

  /* appBar */
  --stm-appBar-container: #1E293B; /* App bar background */
  --stm-appBar-onContainer: #F8FAFC; /* App bar foreground text/icon color */
  --stm-appBar-border: #334155; /* Bottom border color separating the bar from page content */
  --stm-appBar-borderWidth: 1px; /* Bottom border width — 1px */
  --stm-appBar-height: 64px; /* App bar height (comfortable density, GAP-05 annotation 641:1835) — a page-chrome dimension, not the brand interactive minHeight (a 28/44px control height is wrong for a top bar). Literal because component tokens may not reference {primitives.*} and there is no core/brand 64px */
  --stm-appBar-heightCompact: 56px; /* App bar height for density="compact" (GAP-05) — 56px. TOK-37 DIALECT, decision pre-made: the core pair is density.rowHeightCompact (28px), which is a CONTROL height, not an app-bar height — an app bar is chrome, not a row. This dialect should stay its own slot; what TOK-37 actually wants here is the NAME already being right (`Compact`, not `Dense`), which it is. Recorded so the trigger does not migrate it onto a pair that means something else. */
  --stm-appBar-paddingX: 14px; /* Horizontal padding between edge and first/last slot */
  --stm-appBar-gap: 16px; /* Gap between logo / search / actions slots */
  --stm-appBar-elevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.05), 0px 1px 3px 0px rgba(255,255,255,0.10); /* Subtle elevation so the bar reads above scrolling content */
  --stm-appBar-insetBandContainer: #96ADF8; /* Fill of the full-bleed band around the inset bar (TOK-50) — the Retur backoffice chrome. The whole inset variant was built on raw core reach-ins, so the one piece of app chrome a brand most wants to own could not be re-skinned from the theme. */
  --stm-appBar-insetBandPadding: 16px; /* Inset of the bar inside its band — 16px, unchanged (TOK-50). */
  --stm-appBar-insetBarContainer: #1E293B; /* Fill of the inset bar itself (TOK-50) — a raw core reach-in until now. */
  --stm-appBar-insetBarRadius: 14px; /* Rounding of the inset bar, and of the context strip that fuses to its bottom edge (TOK-50). It was radius-2xl written out at three sites, which is exactly the kind of triplicate that drifts the first time one corner is retuned. TOK-40: promoted off core.radius.2xl onto the BRAND surface knob. The inset app bar is a floating SURFACE — the same family as cards and sheets — and was the only one reaching past the knob that shapes them. */
  --stm-appBar-insetBarPaddingX: 24px; /* Horizontal padding inside the inset bar — 24px, unchanged (TOK-50). */
  --stm-appBar-insetBarPaddingY: 8px; /* Vertical padding inside the inset bar — 8px, unchanged (TOK-50). */
  --stm-appBar-insetGap: 16px; /* Gap between the inset bar's slots — 16px, unchanged (TOK-50). */
  --stm-appBar-insetBorderWidth: 5px; /* Outline weight of the GDK-55 outlined band, which replaces the filled band with a frame — 5px, unchanged (TOK-50). */
  --stm-appBar-insetBorderColor: #96ADF8; /* Outline colour of the outlined band (TOK-50), previously a raw core reach-in at two sites. */
  --stm-appBar-logoColor: #96ADF8; /* Logo wordmark/brand text color */
  --stm-appBar-logoTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-appBar-logoTypography-font-size: 20px;
  --stm-appBar-logoTypography-font-weight: 700;
  --stm-appBar-logoTypography-line-height: 1.375;
  --stm-appBar-contextContainer: #FFD84A; /* Acting-context strip background — the second bar docked under the app bar when acting on behalf of a company (spec 2026-08-13). Warning-toned so the mode is unmistakable */
  --stm-appBar-onContextContainer: #0F172A; /* Text/icons/controls on the acting-context strip */
  --stm-appBar-contextPaddingX: 24px; /* Horizontal padding of the acting-context strip — 24px, unchanged (TOK-50). The strip had colour slots and raw core padding. */
  --stm-appBar-contextPaddingY: 8px; /* Vertical padding of the acting-context strip — 8px, unchanged (TOK-50). */

  /* autocomplete */
  --stm-autocomplete-sheetPaddingY: 8px; /* Vertical padding of the CMP-57 bottom sheet (TOK-45) — raw core. */
  --stm-autocomplete-sheetFilterPaddingX: 24px; /* Horizontal inset of the in-sheet filter field (TOK-45). Aligned with the options below it — the filter and the list it filters must share a left edge, which is a relationship the raw core value could not express. */
  --stm-autocomplete-sheetFilterGap: 8px; /* Gap between the filter row and the first option (TOK-45) — raw core. */
  --stm-autocomplete-sheetOptionPaddingX: 24px; /* Horizontal padding of a bottom-sheet option (TOK-45). Wider than the popover's for the same reason as select.sheetOptionPaddingX: a sheet is a thumb target. */
  --stm-autocomplete-sheetOptionPaddingY: 8px; /* Vertical padding of a bottom-sheet option (TOK-45) — raw core. */
  --stm-autocomplete-sheetOptionText: #F8FAFC; /* Bottom-sheet option ink (TOK-45) — raw core, while the popover option next to it read dropdown.optionText. */
  --stm-autocomplete-sheetEmptyText: #94A3B8; /* The 'no matches' line (TOK-45). It is the only text in the component a user sees when the component has failed them, and it was a raw core reach-in. */

  /* avatar */
  --stm-avatar-sizeSm: 32px; /* Small avatar — 28px, used in activity timelines */
  --stm-avatar-sizeMd: 44px; /* Medium avatar — 36px, used in top bar user menu */
  --stm-avatar-sizeLg: 52px; /* Large avatar — 44px, used on detail/profile screens */
  --stm-avatar-radius: 9999px; /* Avatar rounding — routed via brand.radius.avatar, which every brand currently pins to full circle (TOK-40 follow-up: its own knob precisely so squaring the pill family never forces square avatars; a brand CAN flip it, so this is a default, not an invariant). */
  --stm-avatar-container: #122666; /* Fallback background when no image — subtle brand tint */
  --stm-avatar-containerHover: #122666; /* Avatar fill under the pointer when the avatar is interactive — the resting fill today, so value-preserving (TOK-51). Avatars are the standard user-menu trigger and had no interactive states at all. */
  --stm-avatar-onContainer: #B5C6FA; /* Fallback initials text color — the on-subtle text grade (issue #68: raw primary was 3.19:1 on the primarySubtle fill in starter/systemate). Gated >=4.5:1 by the accessibility contrast test */
  --stm-avatar-border: #334155; /* Thin ring around avatar for contrast on colored surfaces */
  --stm-avatar-borderHover: #94A3B8; /* Avatar border under the pointer when interactive (TOK-51) — one outline step up, so a trigger avatar reads as pressable. */
  --stm-avatar-borderWidth: 1px; /* Ring width — 1px */
  --stm-avatar-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-avatar-typography-font-size: 12px;
  --stm-avatar-typography-font-weight: 700;
  --stm-avatar-typography-line-height: 1.5;
  --stm-avatar-typographySm-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-avatar-typographySm-font-size: 12px;
  --stm-avatar-typographySm-font-weight: 400;
  --stm-avatar-typographySm-line-height: 1.5;
  --stm-avatar-typographyLg-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-avatar-typographyLg-font-size: 16px;
  --stm-avatar-typographyLg-font-weight: 400;
  --stm-avatar-typographyLg-line-height: 1.5;
  --stm-avatar-groupOverlap: 8px; /* Overlap between stacked avatars in StmAvatarGroup (CMP-56) — 8px negative inline margin at the use site */
  --stm-avatar-groupRingWidth: 2px; /* Separating ring around each stacked avatar (CMP-56) — 2px so overlapped edges stay readable */
  --stm-avatar-groupRingColor: #1E293B; /* Stacked-avatar ring color — the surface behind the group, punching the overlap apart */
  --stm-avatar-groupSurplusContainer: #1E293B; /* Background of the +N surplus counter avatar (CMP-56) */
  --stm-avatar-groupSurplusText: #E2E8F0; /* Text color of the +N surplus counter (CMP-56) */
  --stm-avatar-groupSurplusSize: 44px; /* Diameter of the +N surplus chip — 40px (TOK-51). It was hard-pinned to the MEDIUM avatar size in CSS, so a group of small or large avatars ended in a chip of the wrong size. */
  --stm-avatar-groupSurplusSizeSm: 32px; /* Diameter of the +N chip in a small group (TOK-51). */
  --stm-avatar-groupSurplusSizeLg: 52px; /* Diameter of the +N chip in a large group (TOK-51). */
  --stm-avatar-groupSurplusRadius: 9999px; /* Rounding of the +N chip (TOK-51) — was borrowing avatar.radius directly, which is correct today but coupled the chip's shape to the portrait's for no stated reason. */

  /* backdrop */
  --stm-backdrop-scrim: rgba(0, 0, 0, 0.6); /* The dimming layer (TOK-45). */
  --stm-backdrop-onScrim: #1E293B; /* Ink for content sitting ON the dark scrim — a spinner, a message (TOK-45). It read core.color.surface, i.e. 'the light surface colour', used as a foreground: correct by value and misleading by name, which is exactly the kind of borrow a component slot makes visible. */
  --stm-backdrop-zIndex: 1200; /* Stacking (TOK-45) — the same rung as StmDrawer's internal backdrop, which was a comment rather than a shared value. */
  --stm-backdrop-motionDuration: 200ms; /* Fade-in length (TOK-45) — raw core. */
  --stm-backdrop-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Fade-in curve (TOK-45). */

  /* badge */
  --stm-badge-container: #96ADF8; /* Default filled badge background */
  --stm-badge-onContainer: #0F172A; /* Default filled badge text */
  --stm-badge-containerSecondary: #1E293B; /* Secondary filled badge background */
  --stm-badge-onContainerSecondary: #F8FAFC; /* Secondary filled badge text */
  --stm-badge-containerSuccess: #052e16; /* Filled badge background for color="success" (TOK-136/§PB-9). SUBTLE, not the solid success step, because that is what this family already does: `container` (primary) is the one loud badge and every semantic sibling is tinted — `containerSecondary` is secondarySubtle and `containerError` is errorSubtle. A badge is a small pill beside other content; only the primary one earns full saturation. */
  --stm-badge-onContainerSuccess: #BBF7D0; /* Text on the success badge (TOK-136/§PB-9). */
  --stm-badge-containerWarning: #4A3600; /* Filled badge background for color="warning" (TOK-136/§PB-9) — tinted, see containerSuccess for why. */
  --stm-badge-onContainerWarning: #FFEFAD; /* Text on the warning badge (TOK-136/§PB-9). */
  --stm-badge-containerInfo: #172554; /* Filled badge background for color="info" (TOK-136/§PB-9) — tinted, see containerSuccess for why. */
  --stm-badge-onContainerInfo: #BFDBFE; /* Text on the info badge (TOK-136/§PB-9). */
  --stm-badge-containerActive: #4A3600; /* In-progress / 'I gang' badge background — yellow tint */
  --stm-badge-onContainerActive: #FFEFAD; /* 'I gang' badge text — dark on yellow */
  --stm-badge-dotActive: #FFD84A; /* 'I gang' leading dot — yellow/amber */
  --stm-badge-borderActive: #FFE47A; /* 'I gang' badge border — yellow accent */
  --stm-badge-containerUndecided: #1E1B4B; /* 'Ikke besluttet' badge background — light warm violet */
  --stm-badge-onContainerUndecided: #C7D2FE; /* 'Ikke besluttet' badge text — dark warm violet */
  --stm-badge-dotUndecided: #818CF8; /* 'Ikke besluttet' leading dot — warm violet */
  --stm-badge-borderUndecided: #A5B4FC; /* 'Ikke besluttet' badge border — warm-violet accent (3.29:1 on the subtle fill, Q6) */
  --stm-badge-containerIrrelevant: #1E293B; /* 'Ikke relevant' badge background — neutral gray */
  --stm-badge-onContainerIrrelevant: #E2E8F0; /* 'Ikke relevant' badge text — muted */
  --stm-badge-dotIrrelevant: #94A3B8; /* 'Ikke relevant' leading dot — gray */
  --stm-badge-borderIrrelevant: #334155; /* 'Ikke relevant' badge border — subtle gray */
  --stm-badge-containerComplete: #052e16; /* 'Besluttet' / complete badge background — light green */
  --stm-badge-onContainerComplete: #BBF7D0; /* 'Besluttet' badge text — dark green */
  --stm-badge-dotComplete: #4ADE80; /* 'Besluttet' leading dot — green */
  --stm-badge-borderComplete: #86EFAC; /* 'Besluttet' badge border — green accent */
  --stm-badge-containerError: #4C0519; /* Error badge background — light red. Serves BOTH the error status pill and `StmBadge color="error"` (TOK-136/§PB-9). One pair rather than a second `containerError`-shaped name: the Blazor leg has read these slots for its badge error variant since the component shipped, and inventing a parallel filled-error slot would have made the two legs disagree to satisfy a naming symmetry nobody had asked for. */
  --stm-badge-onContainerError: #FECDD3; /* Error badge text — shared by the status pill and `color="error"` (TOK-136/§PB-9). */
  --stm-badge-dotError: #FB7185; /* Error leading dot — red */
  --stm-badge-borderError: #FDA4AF; /* Border of the error badge — TOK-52. Error was the ONLY family without a border slot: its rule reused containerError, so the pill had a border the same colour as its fill and read as borderless beside its four siblings, all of which carry an *Emphasis step. */
  --stm-badge-radius: 9999px; /* Pill rounding — brand-routed via brand.radius.pill (SETTL squares to 4px, TOK-40/118). */
  --stm-badge-borderWidth: 1px; /* Badge border width — 1px */
  --stm-badge-paddingX: 12px; /* Horizontal padding — 12px */
  --stm-badge-paddingY: 4px; /* Vertical padding — 4px */
  --stm-badge-minWidth: 20px; /* Minimum width for count badges — 20px */
  --stm-badge-minWidthSm: 32px; /* Floor width of the CIRCLE shape in the small size — TOK-52. The circle's minWidth was a single value across all three sizes, so a small count badge stayed as wide as a medium one and stopped being a circle. */
  --stm-badge-minWidthLg: 44px; /* Floor width of the CIRCLE shape in the large size — TOK-52. */
  --stm-badge-dotSize: 8px; /* Leading status dot diameter — 8px */
  --stm-badge-dotRadius: 9999px; /* Rounding of the leading status dot — TOK-52. Was a literal 50% at three sites; radius.full renders identically on the square dot box and, unlike brand.radius.pill, keeps the dot round under SETTL (TOK-118). */
  --stm-badge-gap: 6px; /* Gap between dot and label — 6px */
  --stm-badge-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-badge-typography-font-size: 12px;
  --stm-badge-typography-font-weight: 700;
  --stm-badge-typography-line-height: 1.5;
  --stm-badge-paddingXSm: 8px; /* Horizontal padding, size sm (ADR-021 size axis) */
  --stm-badge-paddingYSm: 1px; /* Vertical padding, size sm */
  --stm-badge-paddingXLg: 16px; /* Horizontal padding, size lg */
  --stm-badge-paddingYLg: 8px; /* Vertical padding, size lg */
  --stm-badge-dotSizeSm: 4px; /* Status dot diameter, size sm — 4px */
  --stm-badge-dotSizeLg: 16px; /* Status dot diameter, size lg */
  --stm-badge-typographySm-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-badge-typographySm-font-size: 12px;
  --stm-badge-typographySm-font-weight: 400;
  --stm-badge-typographySm-line-height: 1.5;
  --stm-badge-typographyLg-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-badge-typographyLg-font-size: 14px;
  --stm-badge-typographyLg-font-weight: 700;
  --stm-badge-typographyLg-line-height: 1.5;
  --stm-badge-outlineTextActive: #FFEFAD; /* Outline fill (ADR-023): active/warning text + dot + border on the page surface — gated >=4.5:1 by the accessibility contrast test. The family emphasis step failed the gate (border-grade ~3:1), so per ADR-023 this falls back to the family's darkest text-grade step (onWarningSubtle) */
  --stm-badge-outlineTextUndecided: #C7D2FE; /* Outline fill: undecided/info text + dot + border (ADR-023 text-grade fallback) */
  --stm-badge-outlineTextIrrelevant: #E2E8F0; /* Outline fill: irrelevant/neutral text + dot + border (no neutral emphasis step — ADR-023 fallback to the family's dark step) */
  --stm-badge-outlineTextComplete: #BBF7D0; /* Outline fill: complete/success text + dot + border (ADR-023 text-grade fallback) */
  --stm-badge-outlineTextError: #FECDD3; /* Outline fill: error text + dot + border (ADR-023 text-grade fallback) */
  --stm-badge-outlineTextPrimary: #B5C6FA; /* Outline fill: primary text + dot + border (ADR-023 text-grade fallback) */
  --stm-badge-dotPrimary: #0F172A; /* Solid primary badge leading dot (ADR-021 color axis: primary showcase) */
  --stm-badge-radiusRounded: 8px; /* shape="rounded" radius (TOK-118: was a raw core.radius.md reach-in — under SETTL the 8px "rounded" out-rounded the 4px "pill", inverting the axis; brand.radius.button keeps the ladder coherent per brand). */
  --stm-badge-radiusSquare: 0px; /* shape="square" radius (TOK-118: promoted from a raw core reach-in). */

  /* bottomNavigation */
  --stm-bottomNavigation-container: #1E293B; /* Bar fill. Surface, not surfaceContainer: the bar sits ON the page rather than in it, and its separation comes from the top hairline plus the elevation, not from a tonal step. */
  --stm-bottomNavigation-containerBorder: #334155; /* The bar's top hairline. Carried in ADDITION to the elevation because a bottom bar's shadow is cast UPWARD, where a shadow reads weakest — on a light page the hairline is what actually separates the bar from the content scrolling under it. */
  --stm-bottomNavigation-containerBorderWidth: 1px; /* Top hairline width. */
  --stm-bottomNavigation-elevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.07), 0px 4px 6px 0px rgba(255,255,255,0.08); /* Bar lift. Level 2 rather than the appBar's own: a bottom bar is the same class of surface as a top bar, and giving the two different depths would say they are different kinds of chrome when they are not. */
  --stm-bottomNavigation-zIndex: 1100; /* Stacking rung when the bar is pinned to the viewport (Fixed). Sticky, not overlay: a nav bar is persistent chrome that page content passes under, NOT something that covers the page — a dialog or drawer must still open over it. */
  --stm-bottomNavigation-paddingBlock: 4px; /* Bar padding, block axis. Deliberately small: the ITEM owns the touch target, so padding here only keeps the items off the hairline. Note the component adds env(safe-area-inset-bottom) to this when pinned, so the bar clears an iOS home indicator instead of hiding behind it. */
  --stm-bottomNavigation-paddingInline: 4px; /* Bar padding, inline axis. */
  --stm-bottomNavigation-minHeight: 52px; /* Minimum bar height. Rides the brand control ladder rather than a fixed 56px: on a dense brand (retur) the bar shrinks with every other control, and on a comfortable one (settl) it grows with them. A hardcoded Material height would be the one piece of chrome that ignores the brand. */
  --stm-bottomNavigation-itemGap: 1px; /* Gap between an item's icon and its label. Hairline: the two are one object and must read as a single stacked unit, not as an icon that happens to have text beneath it. */
  --stm-bottomNavigation-itemPaddingBlock: 4px; /* Item padding, block axis — part of the touch target. */
  --stm-bottomNavigation-itemPaddingInline: 4px; /* Item padding, inline axis. Items also flex to share the bar equally, so this is the MINIMUM inline breathing room, not the item's width. */
  --stm-bottomNavigation-itemRadius: 2px; /* Item hit-area radius — visible on hover and focus. */
  --stm-bottomNavigation-itemColor: #E2E8F0; /* Ink of an inactive item, icon and label alike. */
  --stm-bottomNavigation-itemColorActive: #96ADF8; /* Ink of the current item. Paired with the indicator pill below — colour alone must not be the only signal (WCAG 1.4.1). */
  --stm-bottomNavigation-itemContainerHover: #334155; /* Item hover fill. */
  --stm-bottomNavigation-indicatorContainer: #122666; /* Fill of the pill behind the CURRENT item's icon. This is the non-colour signal for the active tab: a shape appears, so the current item is still identifiable to a user who cannot separate the two inks. M3's nav bar does the same; MUI's older BottomNavigation does not, and relies on colour. */
  --stm-bottomNavigation-indicatorRadius: 9999px; /* Indicator pill radius — fully round, so the shape reads as a deliberate marker rather than as a second button. */
  --stm-bottomNavigation-indicatorPaddingBlock: 4px; /* Indicator padding around the glyph, block axis. Started at hairline and was raised after rendering it: a 1px ring around a 20px glyph made a 22px pill that the icon visibly filled to its edges, which reads as a cropped icon rather than as a marker behind one. */
  --stm-bottomNavigation-indicatorPaddingInline: 16px; /* Indicator padding around the glyph, inline axis. Wider than the block padding on purpose — the pill is a horizontal lozenge, and equal padding would make it a circle that reads as a button. */
  --stm-bottomNavigation-iconSize: 20px; /* Item glyph size. Governs the indicator BOX (as font-size), not the glyph element — sizing the child directly collides at equal specificity with StmIcon's own size classes, so which rule won would depend on stylesheet order. A bare svg child sizes itself at 1em against this; an StmIcon child keeps its own sm/md/lg contract, whose md is this same value. Identical treatment to breadcrumb.iconSize. */
  --stm-bottomNavigation-labelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-bottomNavigation-labelTypography-font-size: 12px;
  --stm-bottomNavigation-labelTypography-font-weight: 700;
  --stm-bottomNavigation-labelTypography-line-height: 1.5;
  --stm-bottomNavigation-focusRing: #7694F7; /* Item focus ring. */
  --stm-bottomNavigation-focusRingWidth: 2px; /* Item focus ring width. */
  --stm-bottomNavigation-focusRingOffset: -2px; /* Item focus ring offset. The INSET variant: a bottom bar's items sit hard against the bar edge, and an outward ring on the last item would be clipped by the viewport. */
  --stm-bottomNavigation-motionDuration: 120ms; /* Hover/active transition. */
  --stm-bottomNavigation-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Hover/active easing. */

  /* breadcrumb */
  --stm-breadcrumb-linkColor: #96ADF8; /* Breadcrumb link text color (brand-overridable; RETUR stays #296142 per Q9) */
  --stm-breadcrumb-linkColorHover: #B5C6FA; /* Breadcrumb link hover color */
  --stm-breadcrumb-currentColor: #E2E8F0; /* Current page text - not a link */
  --stm-breadcrumb-currentWeight: 400; /* Font weight of the current (last, non-link) crumb — TOK-53. Declared at the same weight the typography composite already resolves, so it is value-preserving today; a brand that wants the current page emphasised turns THIS instead of forking the composite. */
  --stm-breadcrumb-separatorColor: #94A3B8; /* Separator character color */
  --stm-breadcrumb-gap: 8px; /* Gap between items and separators - 8px */
  --stm-breadcrumb-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-breadcrumb-typography-font-size: 14px;
  --stm-breadcrumb-typography-font-weight: 400;
  --stm-breadcrumb-typography-line-height: 1.5;
  --stm-breadcrumb-iconColor: #E2E8F0; /* Leading-icon color on a breadcrumb item — Figma 977:20852, 1626:34291 */
  --stm-breadcrumb-iconGap: 4px; /* Gap between the leading icon and its label — 4px (core.space.xs; the description said 6px until TOK-53, it never resolved to that) */
  --stm-breadcrumb-iconSize: 1em; /* Leading-icon glyph box — TOK-53. 1em, so the icon tracks the crumb text size; replaces the hardcoded width/height:1em both legs used. */
  --stm-breadcrumb-ellipsisContainer: #1E293B; /* Fill of the CMP-45 collapsed-trail ellipsis expander — TOK-53. Was a raw core reach-in in the Blazor rule. */
  --stm-breadcrumb-ellipsisContainerHover: #334155; /* Ellipsis expander fill on hover — TOK-53. */
  --stm-breadcrumb-ellipsisText: #96ADF8; /* Ellipsis expander glyph color — TOK-53. Resolves with linkColor (the expander reads as a quiet crumb) but is its own knob so a brand can mute it without muting real links. */
  --stm-breadcrumb-ellipsisTextHover: #B5C6FA; /* Ellipsis expander glyph color on hover — TOK-53. */
  --stm-breadcrumb-ellipsisRadius: 2px; /* Ellipsis expander corner rounding — TOK-53. */
  --stm-breadcrumb-ellipsisPaddingX: 4px; /* Ellipsis expander horizontal padding — TOK-53. The button has no vertical padding by design: it sits on the crumb line-height so the trail height never changes when a trail collapses. */

  /* bulkBar */
  --stm-bulkBar-dividerOpacity: 0.3; /* Vertical divider transparency — 30% */
  --stm-bulkBar-dismissOpacity: 0.85; /* Dismiss control resting transparency — 85% */
  --stm-bulkBar-controlContainer: #B5C6FA; /* Field surface of DS inputs nested in the bar — one step darker than the bar so controls read as inset, not pasted-on light chrome (GDK-08). GDK-42 (2026-07-09): softened from primaryActive (a near-black hole on the dark pill) to the intermediate hover step */
  --stm-bulkBar-controlText: #0F172A; /* Text + label of nested inputs — the input-token default is dark-on-light and was UNREADABLE on the primary bar (owner 2026-07-07, GDK-08) */
  --stm-bulkBar-controlBorder: #7694F7; /* Border of nested inputs — a genuinely LIGHTER primary step (prototype border-blue-500; GDK-42 fixed the old primaryHover binding, which was darker than the bar and contradicted this description) */
  --stm-bulkBar-container: #96ADF8; /* Bar surface — TOK-54. The manifest held 5 slots and nothing for the bar itself; both legs painted this from raw core.color.primary. Value-preserving. */
  --stm-bulkBar-onContainer: #0F172A; /* Text/icon colour on the bar — TOK-54. Raw core.color.onPrimary in both legs. Value-preserving. */
  --stm-bulkBar-radius: 14px; /* Bar corner rounding — TOK-54. Both legs read `--stm-card-radius`, i.e. ANOTHER COMPONENT’s token: retuning the card silently moved the bulk bar. Points at the same brand.radius.card the card itself uses, so it is value-preserving (14px) while ending the borrow. */
  --stm-bulkBar-elevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.07), 0px 4px 6px 0px rgba(255,255,255,0.08); /* Bar shadow — TOK-54. (The scan’s “undefined --stm-elevation-md, no shadow ships” is STALE: TOK-34 made sm/md/lg real, and its own $description records this component as the reason. The shadow ships; what was missing was a slot to tune it.) Value-preserving. */
  --stm-bulkBar-paddingX: 24px; /* Bar horizontal padding — TOK-54. Raw core.space.lg (24px) in both legs. */
  --stm-bulkBar-paddingY: 8px; /* Bar vertical padding — TOK-54. Raw core.space.sm (8px) in both legs. */
  --stm-bulkBar-gap: 16px; /* Gap between the bar’s top-level regions (count, divider, actions, dismiss) — TOK-54. Raw core.space.md (16px). */
  --stm-bulkBar-actionsGap: 8px; /* Gap between the action controls — TOK-54. Raw core.space.sm (8px); distinct from `gap` so a dense action row does not force the whole bar tighter. */
  --stm-bulkBar-dividerWidth: 1px; /* Divider thickness — TOK-54. Was `var(--stm-border-width-thin, 1px)`: raw core PLUS a literal fallback, while the divider’s OPACITY was already a slot. */
  --stm-bulkBar-dividerColor: #0F172A; /* Divider colour — TOK-54. Both legs used `currentColor`, which on this bar resolves to onPrimary; naming it makes the divider tunable independently of the bar’s text colour. Value-preserving. */
  --stm-bulkBar-countWeight: 700; /* Weight of the selected-count text — TOK-54. Was a raw --stm-typography-weight-bold reach-in. Declared as a WEIGHT rather than the scan’s proposed countTypography composite: the count differs from the bar’s inherited text in weight alone, and a composite would duplicate family/size/line-height and let them drift. */
  --stm-bulkBar-dismissOpacityHover: 1; /* Dismiss opacity on hover — TOK-54. The resting opacity was a slot but the hover target was a literal 1, so raising the resting value could not narrow the hover delta. */
  --stm-bulkBar-focusRing: #0F172A; /* Focus-ring colour for controls on the bar — TOK-54. The shared core ring colour is tuned for neutral surfaces; on a filled primary bar it disappears, so both legs already hardcoded onPrimary here. Width and offset still come from the core ring quartet — only the colour is component-specific. */

  /* button */
  --stm-button-container: #96ADF8; /* Button background in default state */
  --stm-button-containerHover: #B5C6FA; /* Button background on hover */
  --stm-button-containerActive: #D1DBFC; /* Button background when pressed */
  --stm-button-containerDisabled: #1E293B; /* Button background when disabled — deliberately one step darker than core.color.disabledBackground so a dead button still reads as a control, not a field (TOK-13 divergence). On core.color.neutralSubtle — renamed from secondarySubtle, same value; this slot was always reaching for the grey tier, which is the evidence the old name was wrong */
  --stm-button-onContainer: #0F172A; /* Button text and icon color */
  --stm-button-onContainerUnavailable: #CBD5E1; /* Ink for the UNAVAILABLE state (#642/#646), one step darker than onContainerDisabled and deliberately not the same token. WCAG 1.4.3 exempts text in an INACTIVE component from any contrast requirement, and onContainerDisabled (#64748B on containerDisabled) measures 4.34:1 — fine under that exemption. An aria-disabled control is NOT inactive: it takes focus, fires events and is read out, so the exemption does not apply to it and 4.34:1 is a real shortfall. core.color.neutral lands at ~6.9:1 on the same fill. Measured in the browser on both sets rather than assumed, and kept SEPARATE so the natively disabled state is unchanged for every existing consumer. */
  --stm-button-onContainerDisabled: #94A3B8; /* Disabled button text color (core TOK-13 slot) */
  --stm-button-paddingX: 16px; /* Horizontal padding — 16px */
  --stm-button-paddingY: 10px; /* Vertical padding — 10px */
  --stm-button-gap: 8px; /* Gap between icon and text — 8px */
  --stm-button-radius: 8px; /* Button border radius — brand configurable */
  --stm-button-minHeight: 44px; /* Minimum button height — brand configurable */
  --stm-button-elevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.05), 0px 1px 3px 0px rgba(255,255,255,0.10); /* Button shadow for subtle depth */
  --stm-button-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-button-typography-font-size: 16px;
  --stm-button-typography-font-weight: 700;
  --stm-button-typography-line-height: 1.5;
  --stm-button-typography-letter-spacing: 0em;
  --stm-button-motionDuration: 120ms; /* Colour/elevation/press transition length (TOK-55) — raw core on both legs and in stmTheme's MuiButton root, i.e. three copies of one number. */
  --stm-button-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Transition curve (TOK-55) — the literal fallback `var(--stm-motion-easing-standard, ease)` was fixed earlier; the slot itself was still missing. */
  --stm-button-pressTranslate: 0.5px; /* Downward nudge on :active (TOK-55; Figma annotation 533:1820). Off the spacing ladder on purpose — it is a sub-pixel optical cue, not a layout step — but it was a bare literal in two stylesheets, so a brand that wanted a firmer or flatter press had nowhere to say so. */

  /* buttonBrand */
  --stm-buttonBrand-solid: #96ADF8; /* Rung HIGH fill — the loudest treatment of this intent. */
  --stm-buttonBrand-solidHover: #B5C6FA; /* Rung HIGH fill on hover. */
  --stm-buttonBrand-solidActive: #D1DBFC; /* Rung HIGH fill while pressed. */
  --stm-buttonBrand-onSolid: #0F172A; /* Ink on the rung-HIGH fill. */
  --stm-buttonBrand-tonal: #122666; /* Rung MEDIUM fill — the tonal treatment. NOTE the fill alone is NOT relied on to identify the control: the *Subtle roles are page tints and can land within 1.1:1 of the surface, so rung MEDIUM always draws outlineColor as well. */
  --stm-buttonBrand-tonalHover: #1A3791; /* Rung MEDIUM fill on hover. */
  --stm-buttonBrand-tonalActive: #2145B8; /* Rung MEDIUM fill while pressed. */
  --stm-buttonBrand-onTonal: #B5C6FA; /* Ink on the rung-MEDIUM fill. */
  --stm-buttonBrand-outlineColor: #7694F7; /* The BOUNDARY drawn by rungs MEDIUM and LOW. This is what makes the control identifiable (WCAG 1.4.11, 3:1) regardless of how close its fill lands to the surface. */
  --stm-buttonBrand-ink: #96ADF8; /* Text and icon colour for rungs LOW and NONE, which paint no fill. */
  --stm-buttonBrand-ghostHover: #122666; /* Hover wash for rungs LOW and NONE. */

  /* buttonDanger */
  --stm-buttonDanger-container: #FB7185; /* Solid destructive fill — the semantic error family, so it reads the same in every brand (right for a destructive action) */
  --stm-buttonDanger-containerHover: #FDA4AF; /* Fill on hover */
  --stm-buttonDanger-containerActive: #FECDD3; /* Fill while pressed */
  --stm-buttonDanger-containerDisabled: #1E293B; /* Disabled fill. Aliases the PRIMARY variant's disabled fill deliberately — a disabled destructive button should not still read as dangerous — but it is now an alias a brand can break, rather than a reach-in nobody could see. */
  --stm-buttonDanger-onContainer: #0F172A; /* Label on the destructive fill */
  --stm-buttonDanger-onContainerDisabled: #94A3B8; /* Disabled label — same aliasing reasoning as containerDisabled */
  --stm-buttonDanger-border: transparent; /* TOK-55: the variant declared `border: none` outright, which is not the same as a transparent border — it changes the box. Declared explicitly so a brand that wants an outlined destructive has somewhere to put it; `transparent` preserves today's rendering with `borderWidth` at 0. */
  --stm-buttonDanger-borderWidth: 0; /* TOK-55: zero, which is what `border: none` resolved to. The slot makes the difference between 'no border' and 'a border you cannot see' expressible. */
  --stm-buttonDanger-elevation: none; /* TOK-55: both legs hardcoded `box-shadow: none` with no explanation. It is deliberate — a solid destructive action sits flat, unlike the primary button, which carries button.elevation — and now it says so. */
  --stm-buttonDanger-paddingX: 16px; /* Horizontal padding — aliases the primary variant, which is what the CSS already reached for */
  --stm-buttonDanger-paddingY: 10px; /* Vertical padding — aliases the primary variant */
  --stm-buttonDanger-radius: 8px; /* Corner radius — aliases the primary variant */
  --stm-buttonDanger-minHeight: 44px; /* Minimum height — aliases the primary variant */
  --stm-buttonDanger-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-buttonDanger-typography-font-size: 16px;
  --stm-buttonDanger-typography-font-weight: 700;
  --stm-buttonDanger-typography-line-height: 1.5;
  --stm-buttonDanger-typography-letter-spacing: 0em;
  --stm-buttonDanger-solid: #FB7185; /* Rung HIGH fill — the loudest treatment of this intent. */
  --stm-buttonDanger-solidHover: #FDA4AF; /* Rung HIGH fill on hover. */
  --stm-buttonDanger-solidActive: #FECDD3; /* Rung HIGH fill while pressed. */
  --stm-buttonDanger-onSolid: #0F172A; /* Ink on the rung-HIGH fill. */
  --stm-buttonDanger-tonal: #4C0519; /* Rung MEDIUM fill — the tonal treatment. NOTE the fill alone is NOT relied on to identify the control: the *Subtle roles are page tints and can land within 1.1:1 of the surface, so rung MEDIUM always draws outlineColor as well. */
  --stm-buttonDanger-tonalHover: #881337; /* Rung MEDIUM fill on hover. */
  --stm-buttonDanger-tonalActive: #9F1239; /* Rung MEDIUM fill while pressed. */
  --stm-buttonDanger-onTonal: #FECDD3; /* Ink on the rung-MEDIUM fill. */
  --stm-buttonDanger-outlineColor: #FDA4AF; /* The BOUNDARY drawn by rungs MEDIUM and LOW. This is what makes the control identifiable (WCAG 1.4.11, 3:1) regardless of how close its fill lands to the surface. */
  --stm-buttonDanger-ink: #FB7185; /* Text and icon colour for rungs LOW and NONE, which paint no fill. */
  --stm-buttonDanger-ghostHover: #4C0519; /* Hover wash for rungs LOW and NONE. */

  /* buttonGroup */
  --stm-buttonGroup-container: #1E293B; /* Group background */
  --stm-buttonGroup-paddingX: 0px; /* Horizontal padding of the group shell — 0 by default, so this is value-preserving (TOK-56). The shell is a real bordered surface and had no inset of its own, so a group used as a toolbar segment sat its buttons flush against its own frame. */
  --stm-buttonGroup-paddingY: 0px; /* Vertical padding of the group shell — 0 by default (TOK-56); pairs with paddingX. */
  --stm-buttonGroup-border: #94A3B8; /* Outer and divider border */
  --stm-buttonGroup-borderWidth: 1px; /* Border thickness — 1px */
  --stm-buttonGroup-radius: 8px; /* Outer corner radius */
  --stm-buttonGroup-gapAttached: 0px; /* Gap between buttons in the ATTACHED (default) grouping — 0, unchanged (TOK-56). The single `gap: 0` slot could only describe joined buttons, so the detached grouping — the same set of actions read as separate buttons — was inexpressible. Replaces the single `gap` slot, retired in the same change rather than left behind as a second name for the attached value. */
  --stm-buttonGroup-gapSpaced: 8px; /* Gap between buttons in the SPACED grouping — 8px (TOK-56). In this mode the shared frame and the dividers are dropped: the buttons keep their own edges and simply sit together. */
  --stm-buttonGroup-disabledOpacity: 0.38; /* Opacity of a disabled group — 0.38, unchanged (TOK-56). */
  --stm-buttonGroup-onContainer: #F8FAFC; /* Ink for content a CONSUMER slots into this surface (CMP-65 / #621). The component's own parts each set their own colour and look perfect, so nothing here was missing until a HOST re-inked: an AuthLayout setting `color: onPrimary` for a green canvas left everything slotted in inheriting white, on white — 1.00:1. Defaults to core.color.onSurface, which is EXACTLY what slotted content already resolved to on an ordinary page, so this is value-preserving by construction and nothing moves except where it was already broken. */

  /* buttonInverse */
  --stm-buttonInverse-border: #94A3B8; /* Button border on dark surfaces — bright enough for AA 3:1 UI contrast on neutral.900 pill */
  --stm-buttonInverse-container: transparent; /* Resting fill (TOK-55). Hardcoded `transparent` in both legs, while buttonSecondary — the variant beside it whose fill is also nearly invisible — tokenizes exactly this. 'No fill' and 'a fill you cannot see' are different things to a brand that wants a resting tint here. */
  --stm-buttonInverse-borderHover: #E2E8F0; /* Brighter border on hover */
  --stm-buttonInverse-onContainer: #F1F5F9; /* Button text on dark surfaces — near-white for AAA 7:1 body text contrast on neutral.900 pill */
  --stm-buttonInverse-borderWidth: 1px; /* Outline width (TOK-55) — same raw core reach-in as buttonSecondary. */
  --stm-buttonInverse-minHeight: 44px; /* Minimum height (TOK-55) — the variant read the PRIMARY variant's slot, so retuning the inverse button's height silently moved the primary one too. Same resolved value; an alias a brand can break. */
  --stm-buttonInverse-elevation: none; /* Explicit 'no shadow' (TOK-55). The variant hardcoded `box-shadow: none` in BOTH legs while buttonDanger next to it read a token for exactly the same thing — so flatness was a decision in one variant and a literal in three. level.000 IS `none`, so nothing renders differently; what changes is that a brand giving its buttons a lift no longer has to skip these three. */
  --stm-buttonInverse-onContainerHover: #FFFFFF; /* Hover text — pure white for maximum contrast */
  --stm-buttonInverse-onContainerDisabled: #64748B; /* Disabled text — mid-gray, intentionally dim but not invisible */
  --stm-buttonInverse-paddingX: 16px; /* Horizontal padding — shared with primary */
  --stm-buttonInverse-paddingY: 10px; /* Vertical padding — shared with primary */
  --stm-buttonInverse-gap: 8px; /* Gap between icon and text — 8px (GDK-38: was missing; inverse toolbar buttons rendered icon crammed against label) */
  --stm-buttonInverse-radius: 8px; /* Button border radius — brand configurable */
  --stm-buttonInverse-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-buttonInverse-typography-font-size: 16px;
  --stm-buttonInverse-typography-font-weight: 700;
  --stm-buttonInverse-typography-line-height: 1.5;
  --stm-buttonInverse-typography-letter-spacing: 0em;

  /* buttonNeutral */
  --stm-buttonNeutral-solid: #CBD5E1; /* Rung HIGH fill — the loudest treatment of this intent. */
  --stm-buttonNeutral-solidHover: #E2E8F0; /* Rung HIGH fill on hover. */
  --stm-buttonNeutral-solidActive: #F1F5F9; /* Rung HIGH fill while pressed. */
  --stm-buttonNeutral-onSolid: #0F172A; /* Ink on the rung-HIGH fill. */
  --stm-buttonNeutral-tonal: #334155; /* Rung MEDIUM fill — the tonal treatment. NOTE the fill alone is NOT relied on to identify the control: the *Subtle roles are page tints and can land within 1.1:1 of the surface, so rung MEDIUM always draws outlineColor as well. */
  --stm-buttonNeutral-tonalHover: #334155; /* Rung MEDIUM fill on hover. */
  --stm-buttonNeutral-tonalActive: #475569; /* Rung MEDIUM fill while pressed. */
  --stm-buttonNeutral-onTonal: #F8FAFC; /* Ink on the rung-MEDIUM fill. */
  --stm-buttonNeutral-outlineColor: #94A3B8; /* The BOUNDARY drawn by rungs MEDIUM and LOW. This is what makes the control identifiable (WCAG 1.4.11, 3:1) regardless of how close its fill lands to the surface. */
  --stm-buttonNeutral-ink: #E2E8F0; /* Text and icon colour for rungs LOW and NONE, which paint no fill. */
  --stm-buttonNeutral-ghostHover: #334155; /* Hover wash for rungs LOW and NONE. */

  /* buttonSecondary */
  --stm-buttonSecondary-container: transparent; /* Transparent background in default state */
  --stm-buttonSecondary-containerHover: #122666; /* Subtle fill on hover — light enough to keep the primary-green label AA (BF-01: after the ramp re-base, primarySubtleHover/Active darken, so the secondary button steps one tint lighter) */
  --stm-buttonSecondary-containerActive: #1A3791; /* Stronger fill when pressed — still light enough for the primary-green label (BF-01) */
  --stm-buttonSecondary-containerDisabled: transparent; /* Stays transparent when disabled */
  --stm-buttonSecondary-onContainer: #96ADF8; /* Button text color matches primary */
  --stm-buttonSecondary-onContainerDisabled: #94A3B8; /* Disabled button text color (core TOK-13 slot) */
  --stm-buttonSecondary-border: #94A3B8; /* Border color in default state */
  --stm-buttonSecondary-borderHover: #96ADF8; /* Border becomes primary on hover */
  --stm-buttonSecondary-borderWidth: 1px; /* Outline width (TOK-55) — a raw core reach-in while the variant owned all three of its border COLOURS. A brand with a heavier outlined button had to fork the CSS. */
  --stm-buttonSecondary-borderDisabled: #334155; /* Faded border when disabled (core TOK-13 slot) */
  --stm-buttonSecondary-paddingX: 16px; /* Horizontal padding — 16px */
  --stm-buttonSecondary-paddingY: 10px; /* Vertical padding — 10px */
  --stm-buttonSecondary-gap: 8px; /* Gap between icon and text — 8px */
  --stm-buttonSecondary-radius: 8px; /* Button border radius — brand configurable */
  --stm-buttonSecondary-minHeight: 44px; /* Minimum button height — brand configurable */
  --stm-buttonSecondary-elevation: none; /* Explicit 'no shadow' (TOK-55). The variant hardcoded `box-shadow: none` in BOTH legs while buttonDanger next to it read a token for exactly the same thing — so flatness was a decision in one variant and a literal in three. level.000 IS `none`, so nothing renders differently; what changes is that a brand giving its buttons a lift no longer has to skip these three. */
  --stm-buttonSecondary-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-buttonSecondary-typography-font-size: 16px;
  --stm-buttonSecondary-typography-font-weight: 700;
  --stm-buttonSecondary-typography-line-height: 1.5;
  --stm-buttonSecondary-typography-letter-spacing: 0em;

  /* buttonText */
  --stm-buttonText-minHeight: 44px; /* Minimum height (TOK-55) — same aliasing as buttonInverse: it read the primary variant's slot. */
  --stm-buttonText-container: transparent; /* Resting fill (TOK-55). Hardcoded `transparent` in both legs, while buttonSecondary — the variant beside it whose fill is also nearly invisible — tokenizes exactly this. 'No fill' and 'a fill you cannot see' are different things to a brand that wants a resting tint here. */
  --stm-buttonText-elevation: none; /* Explicit 'no shadow' (TOK-55). The variant hardcoded `box-shadow: none` in BOTH legs while buttonDanger next to it read a token for exactly the same thing — so flatness was a decision in one variant and a literal in three. level.000 IS `none`, so nothing renders differently; what changes is that a brand giving its buttons a lift no longer has to skip these three. */
  --stm-buttonText-containerHover: rgba(255, 255, 255, 0.08); /* Subtle overlay on hover */
  --stm-buttonText-containerActive: rgba(255, 255, 255, 0.16); /* Stronger overlay when pressed */
  --stm-buttonText-onContainer: #96ADF8; /* Text color matches primary */
  --stm-buttonText-onContainerDisabled: #94A3B8; /* Disabled text button color (core TOK-13 slot) */
  --stm-buttonText-paddingX: 12px; /* Horizontal padding — 12px (tighter than filled buttons) */
  --stm-buttonText-paddingY: 8px; /* Vertical padding — 8px */
  --stm-buttonText-gap: 8px; /* Gap between icon and text — 8px */
  --stm-buttonText-radius: 8px; /* Button border radius — brand configurable */
  --stm-buttonText-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-buttonText-typography-font-size: 16px;
  --stm-buttonText-typography-font-weight: 700;
  --stm-buttonText-typography-line-height: 1.5;
  --stm-buttonText-typography-letter-spacing: 0em;

  /* card */
  --stm-card-container: #1E293B; /* Card background color — white */
  --stm-card-onContainer: #F8FAFC; /* Ink for content the CONSUMER slots into this surface (2026-08-25 ink sweep). The component paints `container` and inks only the parts it renders ITSELF (title, subtitle, items) — so slotted content inherits from whatever ancestor last set `color`. That is invisible on an ordinary page, where the inherited ink is already onSurface, and it fails exactly where a host deliberately re-inked: an AuthLayout setting `color: onPrimary` for a green canvas puts WHITE text inside this white surface. Defaults to core.color.onSurface, which is what slotted content resolves to today on a normal page — so nothing moves except where it was already broken. */
  --stm-card-containerHover: #1E293B; /* Card fill under the pointer when the card is a target — the resting surface today, so value-preserving (TOK-57). Cards are clickable in hub views and had no interactive states at all. */
  --stm-card-border: #334155; /* Card border color */
  --stm-card-borderHover: #94A3B8; /* Card border under the pointer when the card is a target (TOK-57) — one outline step up, so a clickable card reads as pressable. */
  --stm-card-borderWidth: 1px; /* Card border width — 1px */
  --stm-card-radius: 14px; /* Card border radius — brand-configurable (default 14px) */
  --stm-card-padding: 24px; /* Card inner padding — 24px */
  --stm-card-paddingCompact: 16px; /* Card padding in the compact density — 16px (TOK-57). A card nested inside a panel or a modal loses most of its usable width to the 24px comfortable padding. */
  --stm-card-gap: 16px; /* Spacing between card content sections */
  --stm-card-bodyGap: 16px; /* Vertical rhythm inside the card body when the body lays itself out as a stack — 16px, matching the card's own gap, so value-preserving (TOK-57). It was a PRIVATE var (--_stm-card-body-gap) with a raw fallback, i.e. a real design decision that no theme could reach. */
  --stm-card-elevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.05), 0px 1px 3px 0px rgba(255,255,255,0.10); /* Card shadow — subtle depth */
  --stm-card-elevationHover: inset 0px 0px 0px 9999px rgba(255,255,255,0.07), 0px 4px 6px 0px rgba(255,255,255,0.08); /* Lift shadow under the pointer when the card is a target (TOK-57). StmStatCard and StmContactCard both own one; the plain card did not, so a clickable card could not be given the same affordance without a hand-rolled shadow. */
  --stm-card-headerGap: 8px; /* Gap between card header (icon+title+badge) and body — 8px */
  --stm-card-headerGapCompact: 4px; /* Header inner gap and divider clearance in the compact density — 4px (TOK-57), so a compact card's header does not keep the comfortable card's breathing room. */
  --stm-card-titleColor: #F8FAFC; /* Card title text color */
  --stm-card-titleTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-card-titleTypography-font-size: 20px;
  --stm-card-titleTypography-font-weight: 700;
  --stm-card-titleTypography-line-height: 1.375;
  --stm-card-headerBorder: #334155; /* Divider between card header and content — shown when title is present */
  --stm-card-headerBorderWidth: 1px; /* Header divider width — 1px */
  --stm-card-footerBorder: #334155; /* Divider between card body and footer slot */
  --stm-card-footerBorderWidth: 1px; /* Footer divider width — 1px. Brands/consumers can set 0 for a flush, borderless footer; the per-instance opt-out is StmCard FooterDivider=false. */
  --stm-card-footerGap: 8px; /* Gap between footer actions — 8px, unchanged (TOK-57). It was a raw core reach-in in a file where headerGap has always been a slot. */
  --stm-card-footerPaddingY: 8px; /* Clearance between the footer rule and its content — 8px, unchanged (TOK-57). */
  --stm-card-subtitleColor: #E2E8F0; /* Card subtitle text color — muted */
  --stm-card-subtitleTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-card-subtitleTypography-font-size: 14px;
  --stm-card-subtitleTypography-font-weight: 400;
  --stm-card-subtitleTypography-line-height: 1.5;
  --stm-card-titleGroupGap: 2px; /* Gap between title and subtitle in the header title group — 2px (below the xs=4px scale step, so a bespoke dimension) */

  /* carousel */
  --stm-carousel-slideGap: 16px; /* Gap between slides. */
  --stm-carousel-padding: 0px; /* Inset of the scroll viewport. Zero by default so a full-bleed slide can touch the edge; set it when the slides are cards that need to breathe. */
  --stm-carousel-radius: 14px; /* Corner rounding of the scroll viewport — the brand surface knob, because a carousel is a surface holding content and not a control. */
  --stm-carousel-arrowSize: 44px; /* Diameter of a previous/next arrow button. Rides the brand control ladder, so an arrow is the same target size as any other control in that brand. */
  --stm-carousel-arrowContainer: #1E293B; /* Arrow button fill. */
  --stm-carousel-arrowContainerHover: #334155; /* Arrow hover fill. */
  --stm-carousel-arrowColor: #F8FAFC; /* Arrow glyph ink. */
  --stm-carousel-arrowColorDisabled: #94A3B8; /* Arrow glyph when the strip is already at that end. Disabled rather than hidden: a control that disappears at the boundary moves the other one, and the user loses the target they were aiming at. */
  --stm-carousel-arrowBorder: #334155; /* Arrow button outline. */
  --stm-carousel-arrowBorderWidth: 1px; /* Arrow outline width. */
  --stm-carousel-arrowElevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.07), 0px 4px 6px 0px rgba(255,255,255,0.08); /* Arrow lift — the arrows sit ON the strip, so they need to separate from whatever slide is behind them. */
  --stm-carousel-arrowOffset: 8px; /* Inset of an arrow from the viewport edge. */
  --stm-carousel-dotSize: 0.625rem; /* Pagination dot diameter. */
  --stm-carousel-dotGap: 4px; /* Gap between pagination dots. */
  --stm-carousel-dotColor: #94A3B8; /* Inactive pagination dot. */
  --stm-carousel-dotColorActive: #96ADF8; /* Active pagination dot. */
  --stm-carousel-dotsGap: 16px; /* Gap between the strip and the dot row. */
  --stm-carousel-focusRing: #7694F7; /* Focus ring on the arrows, the dots, and the scroll viewport itself. */
  --stm-carousel-focusRingWidth: 2px; /* Focus ring width. */
  --stm-carousel-focusRingOffset: 2px; /* Focus ring offset. */
  --stm-carousel-motionDuration: 150ms; /* Arrow/dot state transition. The SCROLL itself is native smooth-scroll, which the reduced-motion rule turns off — a token cannot describe that, and should not pretend to. */
  --stm-carousel-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Easing for the arrow/dot transitions. */

  /* checkbox */
  --stm-checkbox-containerChecked: #96ADF8; /* Checked background */
  --stm-checkbox-containerUnchecked: #1E293B; /* Fill of the RESTING (unchecked) box — the surface today, so value-preserving (TOK-58). The manifest described only the checked box, which left the box a consumer actually looks at most of the time un-themable. BLAZOR-ONLY BY CONSTRUCTION (TOK-46c): React's checkbox is MUI's, and MUI paints the entire box — outline, fill, corner and tick — as a single SVG glyph tinted by one `color`. There is no bordered, filled CSS box on that leg for this slot to describe. The Blazor twin draws a real box and binds it. Not a forgotten binding; closing it means replacing MUI's icon, which is a component decision, not a CSS line. */
  --stm-checkbox-containerCheckedHover: #B5C6FA; /* Hover state when checked */
  --stm-checkbox-border: #94A3B8; /* Border of the resting box (TOK-58) — the other half of the un-themable resting state. Value-preserving against the vendor default both legs inherited. */
  --stm-checkbox-borderHover: #CBD5E1; /* Border of the resting box under the pointer (TOK-58) — hover previously changed nothing at all until the box was checked. */
  --stm-checkbox-borderDisabled: #334155; /* Border of a disabled box (TOK-58). Disabled was an opacity over the live colours in both legs. */
  --stm-checkbox-containerDisabled: #1E293B; /* Fill of a disabled box (TOK-58) — pairs with borderDisabled. BLAZOR-ONLY BY CONSTRUCTION (TOK-46c): React's checkbox is MUI's, and MUI paints the entire box — outline, fill, corner and tick — as a single SVG glyph tinted by one `color`. There is no bordered, filled CSS box on that leg for this slot to describe. The Blazor twin draws a real box and binds it. Not a forgotten binding; closing it means replacing MUI's icon, which is a component decision, not a CSS line. */
  --stm-checkbox-disabledOpacity: 0.38; /* Opacity of a disabled checkbox — 0.38, unchanged (TOK-58). */
  --stm-checkbox-checkmark: #0F172A; /* Checkmark color. BLAZOR-ONLY BY CONSTRUCTION (TOK-46c): React's checkbox is MUI's, and MUI paints the entire box — outline, fill, corner and tick — as a single SVG glyph tinted by one `color`. There is no bordered, filled CSS box on that leg for this slot to describe. The Blazor twin draws a real box and binds it. Not a forgotten binding; closing it means replacing MUI's icon, which is a component decision, not a CSS line. */
  --stm-checkbox-checkmarkDisabled: #94A3B8; /* The tick/dash on a DISABLED checked box (TOK-132/§PB-5). TOK-58 decided that disabled should override the live colours rather than dim them — so a disabled checked box draws containerDisabled, a pale fill — and nobody noticed that `checkmark` is white, so the mark vanished into it. Both legs rendered a disabled CHECKED box as an empty pale square: not a dimmed state, a WRONG one. This keeps TOK-58's decision and makes the state readable. */
  --stm-checkbox-indeterminateMark: #0F172A; /* Colour of the tri-state dash (TOK-58). It shared `checkmark`, so a design that wants the indeterminate dash to read as weaker than a definite tick — which is the whole point of the state — could not say so. */
  --stm-checkbox-borderWidth: 2px; /* Border thickness - 2px. BLAZOR-ONLY BY CONSTRUCTION (TOK-46c): React's checkbox is MUI's, and MUI paints the entire box — outline, fill, corner and tick — as a single SVG glyph tinted by one `color`. There is no bordered, filled CSS box on that leg for this slot to describe. The Blazor twin draws a real box and binds it. Not a forgotten binding; closing it means replacing MUI's icon, which is a component decision, not a CSS line. */
  --stm-checkbox-size: 20px; /* Checkbox size - 20px */
  --stm-checkbox-radius: 2px; /* Corner rounding of the box — 2px, unchanged (TOK-58). The Blazor rule reached core.radius.sm with an inline apology explaining that the kendo-bridge default (8px) reads as a RADIO at checkbox size; that reasoning belongs in the token, not in a comment. NOTE: React's MUI leg draws the box as an SVG glyph, so it cannot consume a border-radius — a documented single-leg slot, like `border` and `borderWidth`. BLAZOR-ONLY BY CONSTRUCTION (TOK-46c): React's checkbox is MUI's, and MUI paints the entire box — outline, fill, corner and tick — as a single SVG glyph tinted by one `color`. There is no bordered, filled CSS box on that leg for this slot to describe. The Blazor twin draws a real box and binds it. Not a forgotten binding; closing it means replacing MUI's icon, which is a component decision, not a CSS line. */
  --stm-checkbox-sizeSm: 16px; /* Box size in the small variant — 16px (TOK-58), for checkboxes in dense table rows. */
  --stm-checkbox-sizeLg: 24px; /* Box size in the large variant — 24px (TOK-58), the A11Y-11 target floor for a standalone consent checkbox. */
  --stm-checkbox-labelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-checkbox-labelTypography-font-size: 16px;
  --stm-checkbox-labelTypography-font-weight: 400;
  --stm-checkbox-labelTypography-line-height: 1.5;
  --stm-checkbox-labelGap: 8px; /* Gap between box and label — 8px (TOK-47: Blazor consumed var(--stm-checkbox-labelGap, 8px) before this slot existed; the 8px fallback was the only real value). BLAZOR-ONLY (TOK-46c): MUI's FormControlLabel owns the control-to-label distance through the checkbox's own ripple padding, so setting a gap on that leg ADDS to spacing the vendor already supplies rather than replacing it. The Blazor row is a plain flex and binds this directly. */
  --stm-checkbox-motionDuration: 120ms; /* Fill/border transition length — 150ms (TOK-58), previously raw core in both legs. */
  --stm-checkbox-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Fill/border transition curve (TOK-58). */

  /* checklist */
  --stm-checklist-gap: 6px; /* Gap between checklist rows — 6px */
  --stm-checklist-gapSm: 4px; /* Gap between rows in the compact density — 4px (TOK-59). Was a raw core reach-in with a literal fallback, so the sm density lived outside the manifest entirely. */
  --stm-checklist-itemGap: 8px; /* Gap between icon and text within a row — 8px */
  --stm-checklist-iconSize: 16px; /* Check/circle icon size — 16px */
  --stm-checklist-iconSizeSm: 12px; /* Check/circle icon size in the compact density — 12px (TOK-59). The sm variant previously shrank only the row gap, so a compact checklist kept full-size 16px glyphs against smaller text. */
  --stm-checklist-iconComplete: #4ADE80; /* Completed item icon — green checkmark */
  --stm-checklist-iconPending: #94A3B8; /* Pending item icon — gray open circle */
  --stm-checklist-iconActive: #96ADF8; /* Active/current item icon — blue, in-progress */
  --stm-checklist-textComplete: #F8FAFC; /* Completed item text — normal weight */
  --stm-checklist-textPending: #E2E8F0; /* Pending item text — muted */
  --stm-checklist-textActive: #96ADF8; /* Active item text — blue to highlight current step */
  --stm-checklist-rowHover: #1E293B; /* Fill behind a clickable row on hover (TOK-59) — was a raw core reach-in. Same value today. */
  --stm-checklist-rowRadius: 2px; /* Corner rounding of the clickable row's hover/focus fill — 2px, unchanged (TOK-59). */
  --stm-checklist-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-checklist-typography-font-size: 14px;
  --stm-checklist-typography-font-weight: 400;
  --stm-checklist-typography-line-height: 1.5;
  --stm-checklist-typographySm-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-checklist-typographySm-font-size: 12px;
  --stm-checklist-typographySm-font-weight: 400;
  --stm-checklist-typographySm-line-height: 1.5;

  /* chip */
  --stm-chip-container: #1E293B; /* Default unselected chip background */
  --stm-chip-containerHover: #334155; /* Chip background on hover */
  --stm-chip-containerSelected: #F8FAFC; /* Selected chip fill — dark NEUTRAL capsule (prototype slate-800 analog). Deliberately NOT brand primary: selection state must not read as brand accent next to tinted status pills (owner 2026-07-07, GDK-20) */
  --stm-chip-containerSelectedNeutral: #1E293B; /* Fill of a PRESSED colour-less status pill (GDK-24) — a light neutral tint rather than the dark slab, which is reserved for the dot-less clear-pill. Was a raw core reach-in (TOK-60). */
  --stm-chip-onContainerSelectedNeutral: #F8FAFC; /* Label colour of a pressed colour-less status pill (GDK-24), previously raw core (TOK-60). */
  --stm-chip-borderSelectedNeutral: #CBD5E1; /* Border of a pressed colour-less status pill (GDK-24), previously raw core (TOK-60). */
  --stm-chip-border: #94A3B8; /* Default chip border — visible when unselected */
  --stm-chip-borderHover: #CBD5E1; /* Chip border on hover — stronger emphasis */
  --stm-chip-borderSelected: transparent; /* Selected chip border — transparent since fill provides contrast */
  --stm-chip-borderWidth: 1px; /* Chip outline weight — 1px, unchanged (TOK-60). Three border COLOURS were slots while the width was a raw core reach-in. */
  --stm-chip-onContainer: #E2E8F0; /* Unselected chip text color */
  --stm-chip-onContainerSelected: #1E293B; /* Selected chip text — surface-white on the dark neutral fill (GDK-20) */
  --stm-chip-radius: 9999px; /* Chip corner radius — follows each brand's chip radius (CMP-04/Q15: pill on retur/starter/systemate, 4px on square Settl; was hardcoded core.radius.full pill for all) */
  --stm-chip-paddingX: 14px; /* Horizontal padding inside chip — 14px */
  --stm-chip-paddingXSm: 4px; /* Horizontal padding of the small chip — 4px (TOK-60). The chip had no size axis while every sibling pill (badge, tag) has one. */
  --stm-chip-paddingY: 8px; /* Vertical padding inside chip — 8px */
  --stm-chip-paddingYSm: 0px; /* Vertical padding of the small chip — 0 (TOK-60): the small chip is height-driven by minHeightSm, not by padding. */
  --stm-chip-minHeight: 32px; /* Chip floor height — 28px (TOK-60). Chips are the densest interactive target in a filter row, and without a floor the hit area shrank with the label's line-height. Value-preserving at the default type ramp; it binds only when a brand shrinks the label. */
  --stm-chip-minHeightSm: 24px; /* Floor height of the small chip - 24px (TOK-60), the A11Y-11 target floor. Points at the icon ladder's lg step because it is the only 24px value in core; component tokens may not reference {primitives.*}. */
  --stm-chip-gap: 6px; /* Gap between chips in a group — 6px */
  --stm-chip-iconSize: 1em; /* Leading-icon glyph box — 1em (TOK-60). The Blazor leg sized it with a literal 1em and the React leg did not constrain it at all, so the same icon could render at two sizes. */
  --stm-chip-dotSize: 0.5em; /* Leading status dot diameter — 0.5em, unchanged (TOK-60). A literal in both legs; em rather than px so the dot tracks the label, which is why it is not on the space scale. */
  --stm-chip-dotRadius: 9999px; /* Status dot rounding (TOK-60) — was a literal 50%. radius.full renders identically on the square dot box and keeps the dot round under SETTL (TOK-118). */
  --stm-chip-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-chip-typography-font-size: 14px;
  --stm-chip-typography-font-weight: 400;
  --stm-chip-typography-line-height: 1.5;
  --stm-chip-typographySm-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-chip-typographySm-font-size: 12px;
  --stm-chip-typographySm-font-weight: 400;
  --stm-chip-typographySm-line-height: 1.5;
  --stm-chip-disabledOpacity: 0.38; /* Opacity of a disabled chip — 0.38, unchanged (TOK-60). */
  --stm-chip-transitionDuration: 120ms; /* Hover/press transition length — 150ms (TOK-60). Chips swapped fill and border instantly in both legs, which reads as a flicker in a filter row where several chips change at once. */
  --stm-chip-transitionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Hover/press transition curve (TOK-60). */
  --stm-chip-removeIconColor: #E2E8F0; /* Dismiss ✕ color at rest (CMP-51) — quieter than the label so the affordance doesn't compete */
  --stm-chip-removeIconColorHover: #F8FAFC; /* Dismiss ✕ color on hover/focus (CMP-51) */
  --stm-chip-removeIconBgHover: rgba(255, 255, 255, 0.08); /* Dismiss ✕ hover halo (CMP-51) — the standard hover overlay in a small circle */
  --stm-chip-statusInfo-container: #172554; /* Info status chip background — light tint */
  --stm-chip-statusInfo-onContainer: #BFDBFE; /* Info status chip text — Strong step (8.0:1 on the tint), tag-family tone; replaces the saturated-fill selected treatment that hit 5.17:1 white-on-info (a11y 2026-07-07) */
  --stm-chip-statusInfo-border: #172554; /* Info status chip border — decorative 200-weight step (prototype badge treatment); state is carried by tint + Strong text + dot + aria-pressed */
  --stm-chip-statusInfo-dot: #60A5FA; /* Leading status dot for the info chip (TOK-60). The dot bypassed its own status group entirely and reached core.color.info directly, so the one element that carries the hue when the pill is UNPRESSED was the one element the group could not restyle. */
  --stm-chip-statusInfo-borderSelected: #60A5FA; /* Border of the PRESSED info chip (GDK-25) — the saturated status colour rather than the Subtle step, because on cream/white host surfaces the pale border was too vague to anchor the pressed state. Was a raw core reach-in (TOK-60). */
  --stm-chip-statusSuccess-container: #052e16; /* Success status chip background */
  --stm-chip-statusSuccess-onContainer: #BBF7D0; /* Success status chip text — Strong step (6.8:1 on the tint); replaces saturated-fill selected white-on-success 3.30:1 AA FAIL (a11y 2026-07-07) */
  --stm-chip-statusSuccess-border: #052e16; /* Success status chip border — decorative 200-weight step */
  --stm-chip-statusSuccess-dot: #4ADE80; /* Leading status dot for the success chip (TOK-60). The dot bypassed its own status group entirely and reached core.color.success directly, so the one element that carries the hue when the pill is UNPRESSED was the one element the group could not restyle. */
  --stm-chip-statusSuccess-borderSelected: #4ADE80; /* Border of the PRESSED success chip (GDK-25) — the saturated status colour rather than the Subtle step, because on cream/white host surfaces the pale border was too vague to anchor the pressed state. Was a raw core reach-in (TOK-60). */
  --stm-chip-statusWarning-container: #4A3600; /* Warning status chip background */
  --stm-chip-statusWarning-onContainer: #FFEFAD; /* Warning status chip text — Strong step (6.0:1 on the tint); replaces saturated-fill selected white-on-warning 1.64:1 AA FAIL, the worst pairing in the family (a11y 2026-07-07) */
  --stm-chip-statusWarning-border: #4A3600; /* Warning status chip border — decorative 200-weight step */
  --stm-chip-statusWarning-dot: #FFD84A; /* Leading status dot for the warning chip (TOK-60). The dot bypassed its own status group entirely and reached core.color.warning directly, so the one element that carries the hue when the pill is UNPRESSED was the one element the group could not restyle. */
  --stm-chip-statusWarning-borderSelected: #FFD84A; /* Border of the PRESSED warning chip (GDK-25) — the saturated status colour rather than the Subtle step, because on cream/white host surfaces the pale border was too vague to anchor the pressed state. Was a raw core reach-in (TOK-60). */
  --stm-chip-statusError-container: #4C0519; /* Error status chip background */
  --stm-chip-statusError-onContainer: #FECDD3; /* Error status chip text — Strong step (7.3:1 on the tint); replaces saturated-fill selected white-on-error 4.70:1 (a11y 2026-07-07) */
  --stm-chip-statusError-border: #4C0519; /* Error status chip border — decorative 200-weight step */
  --stm-chip-statusError-dot: #FB7185; /* Leading status dot for the error chip (TOK-60). The dot bypassed its own status group entirely and reached core.color.error directly, so the one element that carries the hue when the pill is UNPRESSED was the one element the group could not restyle. */
  --stm-chip-statusError-borderSelected: #FB7185; /* Border of the PRESSED error chip (GDK-25) — the saturated status colour rather than the Subtle step, because on cream/white host surfaces the pale border was too vague to anchor the pressed state. Was a raw core reach-in (TOK-60). */
  --stm-chip-removeIconRadius: 9999px; /* Dismiss-✕ halo radius (TOK-118: was raw core.radius.full — a fully-round ✕ inside SETTL’s 4px chip; brand-routed so the halo follows the chip shape). */
  --stm-chip-removeIconPadding: 4px; /* Padding that grows the dismiss-x's hit area (and the negative margin that pulls the enlarged box back to the chip edge) — 4px, unchanged (TOK-60). */

  /* chunkProgressBar */
  --stm-chunkProgressBar-track: #334155; /* Empty chunk color */
  --stm-chunkProgressBar-fill: #96ADF8; /* Filled chunk color */
  --stm-chunkProgressBar-fillComplete: #4ADE80; /* All chunks filled — success */
  --stm-chunkProgressBar-label: #E2E8F0; /* Value label (e.g. 3/5) */
  --stm-chunkProgressBar-countColor: #E2E8F0; /* Colour of the "3 / 5" count (TOK-61). The count is a different reading from the label beside it and had no colour of its own — the Blazor leg painted it with the label token behind a raw-core fallback chain, and the React leg had no count element at all. */
  --stm-chunkProgressBar-chunkHeight: 8px; /* Height of a single chunk - 8px, unchanged (TOK-61); was a bare literal. */
  --stm-chunkProgressBar-chunkMinWidth: 4px; /* Floor width of a single chunk — 4px (TOK-61). A literal on the Blazor leg and absent from React, so a 20-step bar in a narrow column collapsed its chunks to hairlines on one leg and held them at 4px on the other. */
  --stm-chunkProgressBar-transitionDuration: 200ms; /* How long a chunk takes to change colour as progress advances — 300ms (TOK-61), previously raw core in both legs. */
  --stm-chunkProgressBar-transitionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Curve of the chunk colour change (TOK-61). */
  --stm-chunkProgressBar-chunkRadius: 2px; /* Chunk corner radius PROMOTED to {brand.radius.track} 2026-08-20 (TOK-40, owner: promote all four). Value-preserving in every brand — 2px on a thin filled bar is a PROPORTION, not a shape identity, so the promotion buys the SEAM and not a change. A brand that wants square bar ends now sets one knob instead of editing six components. */
  --stm-chunkProgressBar-chunkGap: 4px; /* Gap between chunks */
  --stm-chunkProgressBar-labelGap: 8px; /* Gap between the label, the track and the count — 8px (TOK-61). The legs disagreed: React reached raw core while Blazor reused chunkGap, which measures the seam BETWEEN chunks, so retuning that seam also moved the label away from the bar. */
  --stm-chunkProgressBar-labelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-chunkProgressBar-labelTypography-font-size: 12px;
  --stm-chunkProgressBar-labelTypography-font-weight: 400;
  --stm-chunkProgressBar-labelTypography-line-height: 1.5;
  --stm-chunkProgressBar-countTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-chunkProgressBar-countTypography-font-size: 12px;
  --stm-chunkProgressBar-countTypography-font-weight: 400;
  --stm-chunkProgressBar-countTypography-line-height: 1.5;

  /* circularProgress */
  --stm-circularProgress-track: #334155; /* Background circle track */
  --stm-circularProgress-fill: #96ADF8; /* Active arc color */
  --stm-circularProgress-fillComplete: #4ADE80; /* Arc color at 100% */
  --stm-circularProgress-label: #F8FAFC; /* Center percentage label */
  --stm-circularProgress-sizeSm: 24px; /* Small spinner diameter - 24px, unchanged (TOK-62); now on the icon ladder instead of a literal, because a 24px spinner is exactly an inline glyph swapped for a busy state. */
  --stm-circularProgress-sizeMd: 44px; /* Default spinner diameter - 40px, unchanged (TOK-62). */
  --stm-circularProgress-sizeLg: 64px; /* Large spinner diameter - 64px, unchanged (TOK-62). Literal: component tokens may not reference {primitives.*} and there is no core/brand 64px step (same reasoning as appBar.height). */
  --stm-circularProgress-strokeWidth: 4px; /* Arc thickness — 4px, unchanged (TOK-62/TOK-46). Declared since the manifest was written and consumed by NEITHER leg: both hardcoded a 4 in component code and derived the circle radius from it, so turning this token did nothing at all. The arc now sits on a fixed radius and the stroke grows symmetrically about that path from CSS, which has the side benefit that a thicker stroke no longer shrinks the circle. It points at the space primitive rather than core.border.width.* because this is spinner geometry, not a border (core.border.width.heavy is 5px and would silently change the value). */
  --stm-circularProgress-transitionDuration: 300ms; /* How long the determinate arc takes to travel to a new value — 500ms (TOK-62), previously raw core in both legs. */
  --stm-circularProgress-transitionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Curve of the arc travel (TOK-62) — a literal `ease` fallback beside a tokenized duration. */
  --stm-circularProgress-indeterminateDuration: 1400ms; /* Period of the indeterminate spin and dash — 1.4s (TOK-62). Both legs reached the core loop duration through a literal 1.4s fallback, so the spinner's speed was quietly duplicated in four places. */
  --stm-circularProgress-labelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-circularProgress-labelTypography-font-size: 12px;
  --stm-circularProgress-labelTypography-font-weight: 400;
  --stm-circularProgress-labelTypography-line-height: 1.5;
  --stm-circularProgress-labelFontSizeSm: 10px; /* Centre-label size in the small spinner (TOK-62) — the caption role does not fit inside the 24px circle. Deliberately a SIZE and not a second typography role: the only smaller role in the ramp is `overline`, which carries uppercase and wide tracking that would spread a two-character percentage across the circle it has to sit inside. Family, weight and line-height stay on labelTypography. */

  /* colorPicker */
  --stm-colorPicker-swatchBorder: #94A3B8; /* Swatch preview border */
  --stm-colorPicker-popupContainer: #1E293B; /* Popup background */
  --stm-colorPicker-popupBorder: #334155; /* Popup border */
  --stm-colorPicker-popupShadow: inset 0px 0px 0px 9999px rgba(255,255,255,0.08), 0px 8px 16px 0px rgba(255,255,255,0.08); /* Popup elevation */
  --stm-colorPicker-inputBg: #1E293B; /* Hex input background. REACT-ONLY, and deliberately recorded as such (TOK-46c): the Blazor StmColorPicker renders no hex field at all — no text input, no popup input row — so this slot has nothing to bind to on that leg. It is a missing TWIN, not a forgotten binding; the fix is a hex input on the Blazor picker, after which this becomes bindable. Until then check:tokenusage carries it as a known parity gap. */
  --stm-colorPicker-inputText: #F8FAFC; /* Hex input text. REACT-ONLY, and deliberately recorded as such (TOK-46c): the Blazor StmColorPicker renders no hex field at all — no text input, no popup input row — so this slot has nothing to bind to on that leg. It is a missing TWIN, not a forgotten binding; the fix is a hex input on the Blazor picker, after which this becomes bindable. Until then check:tokenusage carries it as a known parity gap. */
  --stm-colorPicker-label: #E2E8F0; /* Label text */
  --stm-colorPicker-swatchSize: 36px; /* Swatch preview size */
  --stm-colorPicker-swatchRadius: 8px; /* Swatch corner radius */
  --stm-colorPicker-popupRadius: 14px; /* Popup corner radius */
  --stm-colorPicker-popupPadding: 12px; /* Popup padding */
  --stm-colorPicker-paletteGap: 4px; /* Gap between palette swatches */
  --stm-colorPicker-paletteSwatchSize: 28px; /* Palette swatch size */
  --stm-colorPicker-hexInputWidth: 80px; /* Inline hex input width. REACT-ONLY, and deliberately recorded as such (TOK-46c): the Blazor StmColorPicker renders no hex field at all — no text input, no popup input row — so this slot has nothing to bind to on that leg. It is a missing TWIN, not a forgotten binding; the fix is a hex input on the Blazor picker, after which this becomes bindable. Until then check:tokenusage carries it as a known parity gap. */
  --stm-colorPicker-hexInputPaddingY: 4px; /* Hex input vertical padding. REACT-ONLY, and deliberately recorded as such (TOK-46c): the Blazor StmColorPicker renders no hex field at all — no text input, no popup input row — so this slot has nothing to bind to on that leg. It is a missing TWIN, not a forgotten binding; the fix is a hex input on the Blazor picker, after which this becomes bindable. Until then check:tokenusage carries it as a known parity gap. */
  --stm-colorPicker-hexInputPaddingX: 8px; /* Hex input horizontal padding. REACT-ONLY, and deliberately recorded as such (TOK-46c): the Blazor StmColorPicker renders no hex field at all — no text input, no popup input row — so this slot has nothing to bind to on that leg. It is a missing TWIN, not a forgotten binding; the fix is a hex input on the Blazor picker, after which this becomes bindable. Until then check:tokenusage carries it as a known parity gap. */
  --stm-colorPicker-checkColor: #FFFFFF; /* The tick drawn inside the selected swatch (TOK-63). A LITERAL on purpose, and the one place in this file that is: the tick sits on an arbitrary user-chosen colour, so it cannot ride any brand slot — which is exactly what went wrong. React marked it `stm-color-exempt: intrinsic white-on-any` and hardcoded #fff; Blazor bound core.color.onPrimary, a brand colour chosen for contrast against PRIMARY, not against whatever the user picked. Same component, two answers, and the Blazor one is wrong under any brand whose onPrimary is dark. */
  --stm-colorPicker-checkShadow: 0 1px 1px rgba(0, 0, 0, 0.3); /* Micro drop-shadow behind the tick (TOK-63) — the only thing keeping it legible on a pale swatch. Hardcoded identically in both legs; a literal here for the same reason as checkColor. */
  --stm-colorPicker-swatchBorderWidth: 2px; /* Trigger/palette swatch border width (TOK-63) — raw core on both legs while the swatch owned its colour and radius. */
  --stm-colorPicker-swatchBorderHover: #96ADF8; /* Swatch border on hover (TOK-63) — a raw core reach-in, so the picker could not be de-emphasised independently of the brand primary. TOK-63 close-out: React bound this to the HEX INPUT's focus border while drawing the trigger swatch's hover as a raw `box-shadow: 0 0 0 2px var(--stm-color-primary)` — a raw 2px, a raw core reach-in, and a different interaction shape from the Blazor twin, which recolours the border. Converged on the border recipe; the slot now colours the thing it is named for on both legs. */
  --stm-colorPicker-checkerSize: 8px; /* Square size of the checkerboard drawn behind a transparent or alpha colour (TOK-63). It was a literal `background-size: 8px 8px` plus four half-step `background-position` offsets on the Blazor leg, and REACT DREW NO CHECKERBOARD AT ALL — so an alpha swatch read as a solid tint on one leg and as a checkerboard on the other. The four offsets are derived from this value with calc(/2) rather than restated, which is what stops the pattern going out of phase when the size changes. */
  --stm-colorPicker-swatchBorderSelected: #F8FAFC; /* Border of the CURRENTLY SELECTED palette swatch (TOK-63) — deliberately not the hover colour: hover says "you could pick this", selected says "this is what you picked", and on a palette of colours those two must not look alike. */
  --stm-colorPicker-labelGap: 4px; /* Gap between the label and the trigger (TOK-63) — raw core. */
  --stm-colorPicker-triggerGap: 8px; /* Gap inside the trigger row (TOK-63) — raw core. REACT-ONLY (TOK-46c) for the same reason as popupGap: React's trigger is a ROW of swatch + hex input, while the Blazor root is a COLUMN of label + swatch spaced by labelGap. Two different layouts because one leg is missing a control. */
  --stm-colorPicker-popupGap: 16px; /* Gap between the popup's sections (TOK-63) — raw core. REACT-ONLY (TOK-46c): the Blazor popup holds a single child, the palette grid, because that leg has no hex input row to separate it from. A gap between one thing and nothing is not a forgotten binding. */
  --stm-colorPicker-popupOffset: 8px; /* Distance from the trigger to the popup (TOK-63). Owned by the picker for the same reason popover.offset and tooltip.offset are owned by theirs — it is a positioning constant of this surface. */
  --stm-colorPicker-hoverScale: 1.1; /* Palette-swatch hover magnification (TOK-63). The legs disagreed — React 1.1, Blazor 1.15 — which is a difference nobody chose; both are literals in CSS. 1.1 is the value the React leg has shipped and the quieter of the two. */
  --stm-colorPicker-motionDuration: 120ms; /* Swatch border/scale transition length (TOK-63) — raw core in four rules across the two legs. */
  --stm-colorPicker-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Swatch transition curve (TOK-63). */
  --stm-colorPicker-labelDisabled: #94A3B8; /* Label colour while the picker is disabled (TOK-63). Blazor reached raw core for it; React had NO disabled label treatment at all, so a disabled picker still announced itself in full-strength text on that leg. */
  --stm-colorPicker-focusRing: #7694F7; /* Focus ring colour on the trigger and palette swatches (TOK-63) — completes the trio; both legs reached raw core. TOK-63 also asked for a dedicated `swatchBorderFocus`; deliberately NOT minted — both legs already draw the swatch's focus with this trio, and a second focus vocabulary for one element inside one component is how the TOK-119 selected-vs-focus confusion happened in the first place. */
  --stm-colorPicker-focusRingWidth: 2px; /* Focus ring width (TOK-63). NOTE the legs disagreed here too — React used border.focusRingWidth, Blazor borrowed input.borderWidthFocus — so the same swatch had a different ring per framework. */
  --stm-colorPicker-focusRingOffset: 2px; /* Focus ring offset (TOK-63). */
  --stm-colorPicker-checkSize: 16px; /* Checkmark icon size inside selected swatch */
  --stm-colorPicker-popupMinWidth: 200px; /* Minimum popup width */

  /* contactCard */
  --stm-contactCard-container: #1E293B; /* Card background */
  --stm-contactCard-containerHover: #1E293B; /* Card fill under the pointer on an interactive card — the resting surface today, so this is value-preserving (TOK-64). The interactive card already declared a hover TRANSITION but set no hover value, so the animation had nothing to animate. */
  --stm-contactCard-border: #334155; /* Card border color */
  --stm-contactCard-borderHover: #334155; /* Card border under the pointer on an interactive card — the resting border today, so value-preserving (TOK-64). Pairs with containerHover to make the declared border-color transition expressible instead of half-dead. */
  --stm-contactCard-borderWidth: 1px; /* Card border width — 1px */
  --stm-contactCard-radius: 14px; /* Card corner radius — brand-configurable */
  --stm-contactCard-padding: 24px; /* Card inner padding */
  --stm-contactCard-gap: 16px; /* Vertical rhythm between card sections (header, identity, fields, footer) */
  --stm-contactCard-elevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.05), 0px 1px 3px 0px rgba(255,255,255,0.10); /* Subtle elevation */
  --stm-contactCard-transitionDuration: 150ms; /* Interactive-card hover transition length — 200ms (TOK-64), previously a raw core reach-in at both hover properties. */
  --stm-contactCard-transitionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Interactive-card hover transition curve (TOK-64) — the literal `ease` fallback beside a tokenized duration. */
  --stm-contactCard-headerGap: 8px; /* Gap between title and action, and header bottom padding — matches card.headerGap */
  --stm-contactCard-headerBorder: #334155; /* Divider under the title+action header */
  --stm-contactCard-headerBorderWidth: 1px; /* Header divider width — 1px */
  --stm-contactCard-titleColor: #F8FAFC; /* Card title color */
  --stm-contactCard-titleTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-contactCard-titleTypography-font-size: 20px;
  --stm-contactCard-titleTypography-font-weight: 700;
  --stm-contactCard-titleTypography-line-height: 1.375;
  --stm-contactCard-nameColor: #F8FAFC; /* Contact name color — the bold identity line */
  --stm-contactCard-nameTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-contactCard-nameTypography-font-size: 14px;
  --stm-contactCard-nameTypography-font-weight: 700;
  --stm-contactCard-nameTypography-line-height: 1.5;
  --stm-contactCard-metaColor: #E2E8F0; /* Role/relation meta line color — muted */
  --stm-contactCard-metaTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-contactCard-metaTypography-font-size: 14px;
  --stm-contactCard-metaTypography-font-weight: 400;
  --stm-contactCard-metaTypography-line-height: 1.5;
  --stm-contactCard-identityGap: 8px; /* Gap between the avatar slot and the name/meta text block */
  --stm-contactCard-labelColor: #E2E8F0; /* Field label (caption above value) color — matches StmTypography caption/secondary */
  --stm-contactCard-labelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-contactCard-labelTypography-font-size: 12px;
  --stm-contactCard-labelTypography-font-weight: 400;
  --stm-contactCard-labelTypography-line-height: 1.5;
  --stm-contactCard-valueColor: #F8FAFC; /* Field value color (plain, non-link values) */
  --stm-contactCard-linkColor: #96ADF8; /* Colour of a linked field value (tel:/mailto:) — the same link colour StmLink already paints, so value-preserving (TOK-64). The manifest's own $description promises that phone and email values render as links; until now the card could restyle every OTHER value colour but not that one. */
  --stm-contactCard-linkColorHover: #B5C6FA; /* Hover colour of a linked field value (TOK-64), value-preserving against StmLink's own hover step. */
  --stm-contactCard-valueTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-contactCard-valueTypography-font-size: 16px;
  --stm-contactCard-valueTypography-font-weight: 400;
  --stm-contactCard-valueTypography-line-height: 1.5;
  --stm-contactCard-fieldRowGap: 16px; /* Vertical gap between contact-field rows */
  --stm-contactCard-fieldColumnGap: 24px; /* Horizontal gutter between paired fields in the two-column layout */
  --stm-contactCard-fieldIconGap: 8px; /* Gap between a field's leading icon and its label/value block */
  --stm-contactCard-footerBorder: #334155; /* Divider above the footer slot (TOK-64). The header has a divider trio and the footer had none, so a card with both a header rule and a footer action looked lopsided. */
  --stm-contactCard-footerBorderWidth: 0px; /* Footer divider width — 0 by default, so this is value-preserving (TOK-64): the footer stays rule-less unless a consumer asks for the divider, which is the current rendering. */
  --stm-contactCard-footerPaddingTop: 0px; /* Space above the footer content, on top of the card's own section gap — 0 by default (TOK-64). Turn it up together with footerBorderWidth so the rule does not sit tight against the footer text. */
  --stm-contactCard-iconColor: #E2E8F0; /* Leading field icon color (mail/phone glyphs) — muted */
  --stm-contactCard-iconSize: 16px; /* Leading field-icon glyph box — 16px (TOK-64). The icon had a colour slot and no size, so the mail/phone glyphs sized themselves from whatever the caller passed. */
  --stm-contactCard-elevationHover: inset 0px 0px 0px 9999px rgba(255,255,255,0.07), 0px 4px 6px 0px rgba(255,255,255,0.08); /* Hover lift shadow (TOK-119: the old var(--stm-elevation-md, <component>) chain inverted when TOK-34 defined the alias — core permanently won and the component slot went dead; a real slot restores brandability at the same rendered value). */
  --stm-contactCard-onContainer: #F8FAFC; /* Ink for content a CONSUMER slots into this surface (CMP-65 / #621). The component's own parts each set their own colour and look perfect, so nothing here was missing until a HOST re-inked: an AuthLayout setting `color: onPrimary` for a green canvas left everything slotted in inheriting white, on white — 1.00:1. Defaults to core.color.onSurface, which is EXACTLY what slotted content already resolved to on an ordinary page, so this is value-preserving by construction and nothing moves except where it was already broken. */

  /* dataGrid */
  --stm-dataGrid-container: #1E293B; /* Grid background */
  --stm-dataGrid-border: #334155; /* Grid outer border color */
  --stm-dataGrid-borderWidth: 1px; /* Grid outer border width — 1px */
  --stm-dataGrid-radius: 10px; /* Grid container corner radius — brand-configurable */
  --stm-dataGrid-headerContainer: #1E293B; /* Column header row background */
  --stm-dataGrid-headerText: #F8FAFC; /* Column header text color */
  --stm-dataGrid-headerBorder: #334155; /* Header bottom border color */
  --stm-dataGrid-headerTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-dataGrid-headerTypography-font-size: 14px;
  --stm-dataGrid-headerTypography-font-weight: 700;
  --stm-dataGrid-headerTypography-line-height: 1.5;
  --stm-dataGrid-cellText: #F8FAFC; /* Row cell text color */
  --stm-dataGrid-cellBorder: #334155; /* Row separator color — the `gridLines=on` (Excel-dense) variant */
  --stm-dataGrid-cellBorderSubtle: #1E293B; /* Row separator color for the `gridLines=subtle` variant (DS default) — one neutral step lighter than cellBorder so the grid reads as a modern app table, not a spreadsheet. TOK-33: routed through core.color.outlineSubtle (same light value) so it flips in dark mode instead of pinning brand.neutral.100. */
  --stm-dataGrid-cellPaddingX: 12px; /* Cell horizontal padding — compact */
  --stm-dataGrid-cellPaddingYCompact: 6px; /* Cell vertical padding — compact density. TOK-37: repointed onto the core density pair. Not an arbitrary promotion — core.density.controlPaddingYCompact's own description says it is "anchored on dataGrid's compact cell padding", so the grid was the ORIGIN of the value and the two were running in parallel with nothing saying so. Same 6px; the derivation is now explicit and the core rung has its first consumer. */
  --stm-dataGrid-cellPaddingYStandard: 10px; /* Cell vertical padding — standard density */
  --stm-dataGrid-cellPaddingYComfortable: 12px; /* Cell vertical padding — comfortable density */
  --stm-dataGrid-cellTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-dataGrid-cellTypography-font-size: 14px;
  --stm-dataGrid-cellTypography-font-weight: 400;
  --stm-dataGrid-cellTypography-line-height: 1.5;
  --stm-dataGrid-rowHover: #334155; /* Row hover background */
  --stm-dataGrid-rowSelected: #122666; /* Selected row background */
  --stm-dataGrid-rowSelectedHover: #1A3791; /* Selected + hover row background */
  --stm-dataGrid-focusRingWidth: 2px; /* Cell/header focus ring width (TOK-65). focusRing had a colour slot and neither of its companions, so the trio was one third owned — stmTheme reached core for the other two. */
  --stm-dataGrid-focusRingOffset: -2px; /* Cell/header focus ring offset (TOK-65) — the INSET recipe, because a grid cell is packed against its neighbours and an outset ring would overlap them. */
  --stm-dataGrid-rowHeightCompact: 32px; /* Row height at compact density (TOK-65). The grid owned three cellPaddingY densities and no matching HEIGHT ladder, so density was expressed only as padding — which a cell with a taller control in it silently ignores. TOK-37: repointed onto core.density.rowHeightCompact — same 28px, same core.size.controlSm underneath, but reached through the density axis rather than beside it. */
  --stm-dataGrid-rowHeightStandard: 44px; /* Row height at standard density (TOK-65) — see rowHeightCompact. */
  --stm-dataGrid-rowHeightComfortable: 52px; /* Row height at comfortable density (TOK-65) — see rowHeightCompact. */
  --stm-dataGrid-headerMinHeight: 44px; /* Header row floor (TOK-65). The header inherited the row height, so a grid whose rows went compact took its column headers with them — and headers carry sort affordances that do not shrink. */
  --stm-dataGrid-columnSeparator: #1E293B; /* The draggable column-resize separator (TOK-65). It borrowed the subtle CELL border, so a grid that wanted quieter cell rules also lost the handle a user grabs to resize a column. */
  --stm-dataGrid-rowAlt: transparent; /* Zebra-stripe fill for alternate rows (TOK-65). Declared transparent, which is what ships: the grid does not stripe by default and the slot is the seam a brand needs to turn it on — StmTable's own zebra was likewise reachable only through a Blazor utility class until 2026-08-19. */
  --stm-dataGrid-overlayBackground: rgba(0, 0, 0, 0.6); /* Scrim behind the grid's own loading/no-rows overlay (TOK-65) — the vendor's, in both frameworks, so a busy grid dimmed differently per leg. */
  --stm-dataGrid-checkboxColor: #96ADF8; /* Row-selection checkbox (TOK-65). Selection is the grid's most-used interaction and its control was entirely the vendor's — the one place a Telerik grid and a MUI grid visibly disagreed about what 'selected' looks like. */
  --stm-dataGrid-rowTransitionDuration: 120ms; /* Row hover/selection tint transition (TOK-65) — the vendor's timing on both legs. */
  --stm-dataGrid-focusRing: #96ADF8; /* Keyboard focus ring on cells */
  --stm-dataGrid-sortIconColor: #E2E8F0; /* Column sort arrow icon color */
  --stm-dataGrid-sortIconActive: #96ADF8; /* Sort arrow color when column is sorted */
  --stm-dataGrid-toolbarContainer: #1E293B; /* Optional top toolbar background (density toggle, export, filter buttons) */
  --stm-dataGrid-toolbarBorder: #334155; /* Toolbar bottom divider */
  --stm-dataGrid-toolbarPaddingX: 12px; /* Toolbar horizontal padding */
  --stm-dataGrid-toolbarPaddingY: 10px; /* Toolbar vertical padding */
  --stm-dataGrid-emptyStateColor: #94A3B8; /* Empty-state placeholder text color */
  --stm-dataGrid-emptyStateTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-dataGrid-emptyStateTypography-font-size: 14px;
  --stm-dataGrid-emptyStateTypography-font-weight: 400;
  --stm-dataGrid-emptyStateTypography-line-height: 1.5;

  /* datePicker */
  --stm-datePicker-calendarContainer: #1E293B; /* Calendar popover surface background */
  --stm-datePicker-calendarElevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.08), 0px 8px 16px 0px rgba(255,255,255,0.08); /* Calendar popover elevation */
  --stm-datePicker-calendarRadius: 14px; /* Calendar popover corner radius */
  --stm-datePicker-calendarPadding: 16px; /* Inner padding of the calendar popover */
  --stm-datePicker-headerContainer: #1E293B; /* Calendar header bar background */
  --stm-datePicker-headerText: #F8FAFC; /* Month / year header text */
  --stm-datePicker-headerNavIcon: #E2E8F0; /* Previous / next month chevron color */
  --stm-datePicker-weekdayText: #94A3B8; /* Weekday abbreviation (Mo Tu We …) label color */
  --stm-datePicker-dayCellSize: 36px; /* Width and height of a single day cell */
  --stm-datePicker-dayCellRadius: 9999px; /* Day cell corner radius — full-circle to match MUI X default */
  --stm-datePicker-dayCellText: #F8FAFC; /* Day number text color */
  --stm-datePicker-dayCellTextOutsideMonth: #94A3B8; /* Day number text for dates outside the current month */
  --stm-datePicker-dayCellHoverBg: #334155; /* Hovered day cell background */
  --stm-datePicker-dayCellSelectedBg: #96ADF8; /* Selected day cell background */
  --stm-datePicker-dayCellSelectedText: #0F172A; /* Selected day cell text color */
  --stm-datePicker-headerTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-datePicker-headerTypography-font-size: 14px;
  --stm-datePicker-headerTypography-font-weight: 700;
  --stm-datePicker-headerTypography-line-height: 1.5;
  --stm-datePicker-weekdayTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-datePicker-weekdayTypography-font-size: 12px;
  --stm-datePicker-weekdayTypography-font-weight: 700;
  --stm-datePicker-weekdayTypography-line-height: 1.5;
  --stm-datePicker-dayCellTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-datePicker-dayCellTypography-font-size: 14px;
  --stm-datePicker-dayCellTypography-font-weight: 400;
  --stm-datePicker-dayCellTypography-line-height: 1.5;
  --stm-datePicker-dayCellSelectedHoverBg: #96ADF8; /* Fill of the SELECTED day while hovered (TOK-66). Both legs pinned it back to the selected fill so hover would not repaint the selection — correct, but expressed as a selector trick rather than a value, so a brand wanting a hover cue on the selected day had nowhere to put it. */
  --stm-datePicker-dayCellTodayText: #F8FAFC; /* Today's day number (TOK-66). Today was a BORDER and nothing else; the text colour was never separable, so a brand could not mark today by weight of ink instead of by outline. */
  --stm-datePicker-dayCellTodayBorderWidth: 1px; /* Width of today's outline (TOK-66). The two Blazor copies of the calendar CSS disagreed — the scoped inline sheet used core.border.width.thin, the popup sheet used input.borderWidth — and React hardcoded a bare `1px` in stmTheme. Three sources, one measure. */
  --stm-datePicker-dayCellDisabledText: #94A3B8; /* A day outside min/max (TOK-66) — left entirely to the vendor on both legs, so an out-of-range date looked different in React and Blazor. */
  --stm-datePicker-dayCellDisabledBg: transparent; /* Fill of a disabled day (TOK-66) — declared transparent so the hover fill cannot land on a day that cannot be chosen. */
  --stm-datePicker-dayCellFocusRing: #7694F7; /* Keyboard focus ring on a day cell (TOK-66). NEITHER leg styled day-cell focus, so the calendar's keyboard affordance was whatever the vendor drew — different per framework, and not the brand's. */
  --stm-datePicker-dayCellFocusRingWidth: 2px; /* Day-cell focus ring width (TOK-66). */
  --stm-datePicker-dayCellFocusRingOffset: -2px; /* Day-cell focus ring offset (TOK-66) — the INSET recipe, because a day cell is a small radius-clipped box packed against its neighbours; an outset ring would overlap the adjacent day. */
  --stm-datePicker-motionDuration: 120ms; /* Day-cell hover/selection transition (TOK-66) — neither leg transitioned at all, so selection snapped. */
  --stm-datePicker-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Day-cell transition curve (TOK-66). */
  --stm-datePicker-dayCellTodayBorder: #96ADF8; /* Border color marking today when not selected */
  --stm-datePicker-rangeFill: #122666; /* Fill between range-start and range-end day cells (date-range picker) */
  --stm-datePicker-rangeEndpointFill: #96ADF8; /* Fill on the range endpoints (start / end day cells) */

  /* definitionList */
  --stm-definitionList-rowPaddingY: 8px; /* Vertical padding per row */
  --stm-definitionList-rowPaddingX: 0px; /* Horizontal padding per row — 0 by default, so this is value-preserving (TOK-67). Rows were literally `padding: Y 0`, which made a boxed/inset variant — the list sitting inside a tinted panel that needs its rows inset from the panel edge — impossible to express without overriding the shorthand. */
  --stm-definitionList-rowGap: 16px; /* Horizontal gap between label (dt) and value (dd) */
  --stm-definitionList-labelColor: #E2E8F0; /* Label (dt) text color — muted */
  --stm-definitionList-labelWidth: 0px; /* Minimum width of the term (dt) column — 0 by default, so labels still size to content exactly as before (TOK-67). Set it to give a stack of definition lists one shared label column instead of a ragged one. */
  --stm-definitionList-labelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-definitionList-labelTypography-font-size: 12px;
  --stm-definitionList-labelTypography-font-weight: 700;
  --stm-definitionList-labelTypography-line-height: 1.5;
  --stm-definitionList-valueColor: #F8FAFC; /* Value (dd) text color */
  --stm-definitionList-valueTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-definitionList-valueTypography-font-size: 14px;
  --stm-definitionList-valueTypography-font-weight: 400;
  --stm-definitionList-valueTypography-line-height: 1.5;
  --stm-definitionList-actionGap: 0px; /* Extra space before the trailing action (APP-12 edit button), ON TOP of the row's own rowGap — 0 by default, so this is value-preserving (TOK-67). Set it when the action needs visibly more separation from the value than the label↔value gutter. */
  --stm-definitionList-actionColor: #96ADF8; /* Colour of a bare trailing action (TOK-67). It only reaches action content that inherits its colour — a nested StmButton/StmLink still paints itself — so it exists for the plain icon/glyph case, which previously inherited whatever the host set. */
  --stm-definitionList-dividerColor: #334155; /* Optional row divider color */
  --stm-definitionList-dividerWidth: 1px; /* Optional row divider width — 1px */

  /* divider */
  --stm-divider-color: #334155; /* Divider line color */
  --stm-divider-colorStrong: #94A3B8; /* Emphasis divider colour (TOK-68) — the heavier rule that closes a section rather than separating two rows inside one. outlineVariant is deliberately faint; a section break drawn in it disappears. */
  --stm-divider-thickness: 1px; /* Divider line thickness - 1px */
  --stm-divider-spacing: 24px; /* Vertical margin above and below divider - 24px */
  --stm-divider-spacingSm: 8px; /* Tight divider margin — 8px (TOK-68). The single 24px `spacing` is a section rule; a divider inside a menu or a dense card needs a fraction of it, and consumers were overriding the margin by hand to get there. */
  --stm-divider-spacingNone: 0px; /* Flush divider margin — 0 (TOK-68), for a rule that must sit exactly on a container edge or between two rows that already carry their own padding. */
  --stm-divider-insetStart: 0px; /* Leading inset of a horizontal rule — 0 by default, so this is value-preserving (TOK-68). List and menu dividers conventionally start at the text column rather than the container edge; that inset had to be hand-rolled. */
  --stm-divider-insetEnd: 0px; /* Trailing inset of a horizontal rule — 0 by default (TOK-68); pairs with insetStart. */
  --stm-divider-verticalMinLength: 1em; /* Floor length of a vertical rule — 1em (TOK-68). The twins disagreed here: Blazor set min-height:1em while React set height:100%, so in an auto-height flex row the same divider rendered a different length on each leg. Both legs now stretch (align-self) and share this floor. */

  /* drawer */
  --stm-drawer-container: #1E293B; /* Drawer surface background */
  --stm-drawer-onContainer: #F8FAFC; /* Ink for content the CONSUMER slots into this surface (2026-08-25 ink sweep). The component paints `container` and inks only the parts it renders ITSELF (title, subtitle, items) — so slotted content inherits from whatever ancestor last set `color`. That is invisible on an ordinary page, where the inherited ink is already onSurface, and it fails exactly where a host deliberately re-inked: an AuthLayout setting `color: onPrimary` for a green canvas puts WHITE text inside this white surface. Defaults to core.color.onSurface, which is what slotted content resolves to today on a normal page — so nothing moves except where it was already broken. */
  --stm-drawer-scrim: rgba(0, 0, 0, 0.6); /* Backdrop behind a modal drawer (TOK-69) — a raw core reach-in on the Blazor leg and MUI's own default on the React one, so the two legs dimmed the page by different amounts. */
  --stm-drawer-elevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.08), 0px 8px 16px 0px rgba(255,255,255,0.08); /* Drawer elevation */
  --stm-drawer-zIndex: 1200; /* Stacking band of the drawer panel (TOK-69). The Blazor leg computed it as calc(overlay + 1) inline, which is a stacking decision expressed as arithmetic in a stylesheet. */
  --stm-drawer-widthLeft: 280px; /* Default width for left-anchored drawer (navigation rail default) */
  --stm-drawer-widthRight: 400px; /* Default width for right-anchored drawer (detail panel) */
  --stm-drawer-widthSm: 320px; /* Size preset Sm — the PRM-01 narrow slide-in width (APP-21) */
  --stm-drawer-widthMd: 480px; /* Size preset Md — APP-04 slide-in steps 07/08 (manage-list panels) */
  --stm-drawer-widthLg: 600px; /* Size preset Lg — APP-04 slide-in steps 06/09/10/11 (quick-create/edit forms) */
  --stm-drawer-heightTop: 240px; /* Default height for top-anchored drawer */
  --stm-drawer-heightBottom: 320px; /* Default height for bottom-anchored drawer */
  --stm-drawer-transitionDurationEnter: 225ms; /* Open transition duration — the core enter step (225ms; was a smuggled $type:"other" literal until TOK-15 gave the core set enter/exit) */
  --stm-drawer-transitionDurationExit: 195ms; /* Close transition duration — the core exit step (195ms, faster than enter) */
  --stm-drawer-transitionTimingFunction: cubic-bezier(0, 0, 0.2, 1); /* Easing curve for slide-in motion — the core decelerate curve (elements entering) */
  --stm-drawer-headerPaddingX: 24px; /* Drawer header horizontal padding — 24px */
  --stm-drawer-headerPaddingY: 16px; /* Vertical padding of the header row - 16px (TOK-69/TOK-46). Declared since the manifest was written and consumed by NEITHER leg: both wrote `padding: 0 headerPaddingX`, so the row's height came from headerHeight alone and this slot moved nothing. The row is box-sizing: border-box and floors at headerHeight, so binding it does not change the rendered height. */
  --stm-drawer-headerGap: 8px; /* Gap between the header's title/slot and its close button — 8px, unchanged (TOK-69); raw core in both legs. */
  --stm-drawer-headerBorder: #334155; /* Header bottom border separator */
  --stm-drawer-headerBorderWidth: 1px; /* Weight of the header's bottom rule — 1px, unchanged (TOK-69). The colour was a slot and the width was not. */
  --stm-drawer-headerHeight: 72px; /* Fixed header-row height for the Header/Title slot (APP-04: every signup slide-in has a 72px title + ✕ header; Figma 775:1060) */
  --stm-drawer-footerHeight: 68px; /* Fixed footer-row height for the Footer slot (APP-04: 68px Annullér · primary-action footer; Figma 775:1100) */
  --stm-drawer-footerBorder: #334155; /* Footer top border separator (mirror of headerBorder) */
  --stm-drawer-footerBorderWidth: 1px; /* Weight of the footer's top rule — 1px, unchanged (TOK-69). */
  --stm-drawer-footerPaddingX: 24px; /* Horizontal padding of the footer row — 24px, unchanged (TOK-69). It reused headerPaddingX, so the two rows could never be inset differently even though only one of them holds buttons. */
  --stm-drawer-footerActionGap: 8px; /* Gap between footer action buttons — 8px, unchanged (TOK-69); raw core in both legs. */
  --stm-drawer-titleTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-drawer-titleTypography-font-size: 20px;
  --stm-drawer-titleTypography-font-weight: 700;
  --stm-drawer-titleTypography-line-height: 1.375;
  --stm-drawer-titleColor: #F8FAFC; /* Drawer title colour (TOK-69) — a raw core reach-in in both legs, sitting beside a fully tokenized titleTypography. */
  --stm-drawer-closeButtonSize: 34px; /* Header close-button hit-area box — 34×34, matches modal.closeButtonSize (off the 4px grid; see the BF-17 note there). Passes the 24px AA target floor (A11Y-11). */
  --stm-drawer-closeButtonRadius: 8px; /* Rounding of the close button's hit area — 8px, unchanged (TOK-69). The button had a SIZE slot and nothing else: its shape, colour and both state fills all reached core. TOK-40: promoted off core.radius.md onto the BRAND button knob. It is a button; under RETUR, where every button is a pill, this ✕ was the one that stayed a rounded square. */
  --stm-drawer-closeButtonColor: #E2E8F0; /* Close-button glyph colour — unchanged (TOK-69). */
  --stm-drawer-closeButtonContainerHover: rgba(255, 255, 255, 0.08); /* Close-button fill under the pointer — unchanged (TOK-69). */
  --stm-drawer-closeButtonContainerActive: rgba(255, 255, 255, 0.16); /* Close-button fill while pressed — unchanged (TOK-69). */
  --stm-drawer-contentPaddingX: 24px; /* Drawer content horizontal padding */
  --stm-drawer-contentPaddingY: 24px; /* Drawer content vertical padding */

  /* dropDownButton */
  --stm-dropDownButton-container: #1E293B; /* Button fill — secondary style by default */
  --stm-dropDownButton-containerHover: #334155; /* Hover fill */
  --stm-dropDownButton-containerActive: #334155; /* Active/pressed fill */
  --stm-dropDownButton-containerDisabled: #1E293B; /* Trigger fill when the button is disabled (TOK-71). Disabled was a blanket opacity over the live fill; button.json documents why a disabled control needs its own FILL — it must still read as a control, not as faded text. */
  --stm-dropDownButton-onContainerDisabled: #94A3B8; /* Trigger label colour when disabled (TOK-71) — the second half of the fill recipe StmButton uses. */
  --stm-dropDownButton-disabledOpacity: 0.38; /* Opacity of a disabled dropdown button — 0.38, unchanged (TOK-71). */
  --stm-dropDownButton-onContainer: #F8FAFC; /* Text and icon color */
  --stm-dropDownButton-border: #94A3B8; /* Button border */
  --stm-dropDownButton-primaryContainer: #96ADF8; /* Primary variant fill — bold CTA dropdown (GDK-01: the Godkendelsesportal 'Afslut sag ▾') */
  --stm-dropDownButton-primaryContainerHover: #B5C6FA; /* Primary variant hover fill */
  --stm-dropDownButton-primaryContainerActive: #D1DBFC; /* Primary variant active/pressed fill */
  --stm-dropDownButton-onPrimaryContainer: #0F172A; /* Primary variant text and icon color */
  --stm-dropDownButton-menuContainer: #1E293B; /* Dropdown menu background */
  --stm-dropDownButton-menuBorder: #334155; /* Dropdown menu border */
  --stm-dropDownButton-menuBorderWidth: 1px; /* Menu popup outline weight — 1px, unchanged (TOK-71). The TRIGGER had a borderWidth slot and the menu did not, so the two edges of one component were themable to different degrees. */
  --stm-dropDownButton-menuItemHover: #334155; /* Menu item hover */
  --stm-dropDownButton-menuText: #F8FAFC; /* Menu item text */
  --stm-dropDownButton-radius: 8px; /* Button corner radius */
  --stm-dropDownButton-menuRadius: 14px; /* Menu corner radius */
  --stm-dropDownButton-menuOffset: 4px; /* Distance between the trigger and its menu — 4px (TOK-71). A literal at four rules per leg, three of them carrying a lint suppression that described it as deferred. */
  --stm-dropDownButton-menuMaxHeight: 320px; /* Scroll ceiling of the menu — 320px (TOK-71). The menu was unbounded, so a long action list ran off the viewport with no way to reach the end; dropdown.popupMaxHeight is the precedent. */
  --stm-dropDownButton-paddingX: 14px; /* Horizontal padding */
  --stm-dropDownButton-paddingY: 8px; /* Vertical padding */
  --stm-dropDownButton-minHeight: 52px; /* Floor height of the trigger — 44px, the same large-control step StmButton uses (TOK-71). Without it the trigger sized purely to its padding and did not line up with a plain StmButton beside it in a toolbar. */
  --stm-dropDownButton-gap: 4px; /* Gap between label and chevron */
  --stm-dropDownButton-chevronSize: 16px; /* Chevron glyph box — 16px (TOK-71). The trigger's only affordance-marking glyph had no size of its own. */
  --stm-dropDownButton-menuItemGap: 8px; /* Gap between menu item icon and label */
  --stm-dropDownButton-menuPaddingY: 4px; /* Menu vertical padding */
  --stm-dropDownButton-menuContentPaddingBlock: 8px; /* Block inset of a TEMPLATED menu surface (MenuContent), issue #589. The flat Items path gets its inset from the DS's own item rendering; the templated path got a surface and no inset, so every consumer re-derived the same two paddings by hand — 9 times per leg in the story catalog alone, and a scoped sheet in returportal's ProfileMenu whose only remaining job was to restate them. spec/layout-primitives.md Rule 2.2 says padding is the CONTAINED component's responsibility, and here the containing component is this menu surface. StmPopover is the precedent: it pads its own content wrapper and its consumers do not have to. */
  --stm-dropDownButton-menuContentPaddingInline: 16px; /* Inline inset of a TEMPLATED menu surface (MenuContent), issue #589. 16px is the value all nine hand-rolls agreed on — identity blocks and item groups differed on the BLOCK axis (sm vs xs) and never on this one, which is what made it the surface's to own. */
  --stm-dropDownButton-menuItemPaddingX: 14px; /* Menu item horizontal padding */
  --stm-dropDownButton-menuItemPaddingY: 8px; /* Menu item vertical padding */
  --stm-dropDownButton-menuShadow: inset 0px 0px 0px 9999px rgba(255,255,255,0.07), 0px 4px 6px 0px rgba(255,255,255,0.08); /* Menu elevation shadow */
  --stm-dropDownButton-borderWidth: 1px; /* Button border width — 1px */
  --stm-dropDownButton-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-dropDownButton-typography-font-size: 16px;
  --stm-dropDownButton-typography-font-weight: 700;
  --stm-dropDownButton-typography-line-height: 1.5;
  --stm-dropDownButton-typography-letter-spacing: 0em;
  --stm-dropDownButton-motionDuration: 120ms; /* Trigger hover/press and chevron rotation length — 150ms (TOK-71). This is also a parity fix: only the Blazor leg animated. */
  --stm-dropDownButton-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Trigger/chevron transition curve (TOK-71). */
  --stm-dropDownButton-menuTextDisabled: #94A3B8; /* Text colour of a DISABLED menu item (issue #85) — the item stays visible and carries its reason in `title` rather than being dropped from the menu, so it needs a muted-but-legible grade. Also closes half of the TOK-71 "disabled pair" gap. */

  /* dropdown */
  --stm-dropdown-triggerIcon: #E2E8F0; /* Chevron icon color in trigger */
  --stm-dropdown-triggerIconSize: 16px; /* Caret/chevron box on the trigger (TOK-70). The legs disagreed and it showed in the layout: React reserved icon-size-LG of trailing clearance while Blazor drew its caret from icon-size-SM, so the same select left different amounts of room between its text and its arrow. Both legs now clear the same token. */
  --stm-dropdown-popupContainer: #1E293B; /* Dropdown popup background */
  --stm-dropdown-popupRadius: 8px; /* Popup border radius — brand-configurable */
  --stm-dropdown-popupBorder: #334155; /* Popup border color */
  --stm-dropdown-popupBorderWidth: 1px; /* Popup outline weight — 1px, unchanged (TOK-70). It was a bare literal at three sites including two in the React theme, while the colour beside it was a slot. */
  --stm-dropdown-popupElevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.08), 0px 8px 16px 0px rgba(255,255,255,0.08); /* Popup shadow for floating above content */
  --stm-dropdown-popupMaxHeight: 320px; /* Maximum dropdown popup height before scrolling */
  --stm-dropdown-popupPaddingY: 4px; /* Padding at the top and bottom of the option list — 4px (TOK-70). The mobile bottom-sheet variant invented its own value in CSS because the popup had none. */
  --stm-dropdown-optionPaddingX: 12px; /* Horizontal padding per option - 12px */
  --stm-dropdown-optionPaddingY: 8px; /* Vertical padding per option - 8px */
  --stm-dropdown-optionHover: #334155; /* Option background on hover */
  --stm-dropdown-optionSelected: #122666; /* Selected option background */
  --stm-dropdown-onOptionSelected: #96ADF8; /* Selected option text color */
  --stm-dropdown-optionDisabledText: #94A3B8; /* Colour of a disabled option (TOK-70) — a raw core reach-in here while listBox.json, the same widget shape, has owned the slot from the start. TOK-141 (§PB-13a) bound it on both legs and corrected the finding on the way: "the Blazor dropdown has no disabled option" is true of the TELERIK StmDropDown, whose TelerikDropDownList exposes no per-item disabled concept — a `.k-disabled` rule there would be one nothing could ever trigger, which is why none was written. It was NOT true of StmSelect, which is a native <select> on that leg: SelectOption.Disabled has emitted <option disabled> since issue #51 and the state simply had no colour. Safari does not honour `color` on an <option>; that is a UA limit, not a missing binding. On the React leg the actual greying was MUI's own untokenised `opacity: 0.38` sitting on top of this slot — now declared on core.opacity.disabled in stmTheme, same value, brand/mode-reachable. */
  --stm-dropdown-groupLabelColor: #94A3B8; /* Colour of an option-group heading (issue #196). Quieter than an option, because the heading is a boundary marker rather than a choice — a group label that reads as loud as its members re-creates the very ambiguity grouping exists to remove. */
  --stm-dropdown-groupLabelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-dropdown-groupLabelTypography-font-size: 10px;
  --stm-dropdown-groupLabelTypography-font-weight: 700;
  --stm-dropdown-groupLabelTypography-line-height: 1.5;
  --stm-dropdown-groupLabelTypography-letter-spacing: 0.08em;
  --stm-dropdown-groupLabelTypography-text-transform: uppercase;
  --stm-dropdown-optionTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-dropdown-optionTypography-font-size: 16px;
  --stm-dropdown-optionTypography-font-weight: 400;
  --stm-dropdown-optionTypography-line-height: 1.5;
  --stm-dropdown-optionMotionDuration: 120ms; /* Option hover/selection transition length — 150ms (TOK-70). Options swapped fill instantly, which reads as a flicker while arrowing through a long list. */
  --stm-dropdown-optionMotionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Option transition curve (TOK-70). */

  /* fab */
  --stm-fab-container: #96ADF8; /* FAB background */
  --stm-fab-containerHover: #B5C6FA; /* Hover background */
  --stm-fab-containerActive: #D1DBFC; /* Active/pressed background */
  --stm-fab-containerDisabled: #1E293B; /* Fill of a disabled FAB (TOK-72) — the same step StmButton uses, deliberately one shade darker than core.color.disabledBackground so a disabled FAB still reads as a control rather than a field (see the note on core.color.disabledBackground). Value-preserving against the Blazor leg, which already borrowed button.containerDisabled. The two legs disagreed on the whole recipe: Blazor borrowed button.containerDisabled while React only dimmed the live primary fill with an opacity, so a disabled FAB rendered different pixels per framework. Both legs now use this fill recipe, which is the one StmButton uses. */
  --stm-fab-onContainerDisabled: #94A3B8; /* Label/glyph colour of a disabled FAB (TOK-72) — the second half of the shared disabled recipe. */
  --stm-fab-onContainer: #0F172A; /* Icon/text color */
  --stm-fab-shadow: inset 0px 0px 0px 9999px rgba(255,255,255,0.08), 0px 8px 16px 0px rgba(255,255,255,0.08); /* Resting elevation */
  --stm-fab-shadowHover: inset 0px 0px 0px 9999px rgba(255,255,255,0.09), 0px 16px 32px 0px rgba(255,255,255,0.12); /* Hover elevation */
  --stm-fab-sizeSm: 44px; /* Small FAB diameter — 40px, unchanged (TOK-72). Was a bare literal, so the FAB axis was the one control ladder a brand could not rescale. */
  --stm-fab-sizeMd: 56px; /* Default FAB diameter - 56px, unchanged (TOK-72). Literal: component tokens may not reference {primitives.*} and there is no core/brand 56px step (same reasoning as appBar.height). */
  --stm-fab-sizeLg: 96px; /* Large FAB diameter - 96px, unchanged (TOK-72). Literal for the same reason as sizeMd. */
  --stm-fab-iconSize: 24px; /* FAB glyph box at the default size — 24px, unchanged (TOK-72); now on the icon ladder rather than a literal. */
  --stm-fab-iconSizeSm: 20px; /* FAB glyph box in the small size — 20px (TOK-72). One 24px glyph used to serve every size from the 40px FAB to the 96px one, so the small FAB was glyph-heavy and the large one nearly empty. */
  --stm-fab-iconSizeLg: 32px; /* FAB glyph box in the large size — 32px (TOK-72), so the 96px FAB reads as a large action rather than a small icon on a big disc. */
  --stm-fab-radius: 9999px; /* Corner radius — circle */
  --stm-fab-gap: 8px; /* Gap between icon and label — 8px */
  --stm-fab-extendedPaddingX: 16px; /* Extended (md) horizontal padding — 16px */
  --stm-fab-extendedPaddingXSm: 12px; /* Extended small horizontal padding — 12px */
  --stm-fab-extendedPaddingXLg: 24px; /* Extended large horizontal padding — 24px */
  --stm-fab-extendedRadius: 16px; /* Corner rounding of the extended (labelled) FAB — 16px, unchanged (TOK-72). */
  --stm-fab-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-fab-typography-font-size: 16px;
  --stm-fab-typography-font-weight: 700;
  --stm-fab-typography-line-height: 1.5;
  --stm-fab-typography-letter-spacing: 0em;
  --stm-fab-motionDuration: 150ms; /* Hover/press transition length — 200ms (TOK-72). The signature FAB hover lift ran on a raw core duration with a literal easing beside it. */
  --stm-fab-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Hover/press transition curve (TOK-72). */

  /* fixedCardGrid */
  --stm-fixedCardGrid-paddingX: 32px; /* Horizontal padding of the content wrapper — provides breathing room on viewport edges */
  --stm-fixedCardGrid-paddingXCompact: 16px; /* Horizontal wrapper padding in the compact density — 16px (TOK-73/TOK-37). The 32px/48px comfortable pair is sized for a full page; a grid embedded in a panel or a modal loses most of its usable width to it. */
  --stm-fixedCardGrid-paddingY: 48px; /* Vertical padding of the content wrapper — larger than horizontal for visual weight */
  --stm-fixedCardGrid-paddingYCompact: 24px; /* Vertical wrapper padding in the compact density — 24px (TOK-73/TOK-37). */
  --stm-fixedCardGrid-gap: 24px; /* Gap between cards in the grid — enough separation for distinct card identity */
  --stm-fixedCardGrid-rowGap: 24px; /* Vertical gap between card rows — 24px, the same value `gap` already applied to both axes, so this is value-preserving (TOK-73). Card grids routinely want more air between rows than between columns (the column gutter reads as a seam, the row gutter as a shelf); one shorthand could not say that. */
  --stm-fixedCardGrid-maxWidth: 1024px; /* Maximum width of the grid content area — keeps cards from stretching on wide viewports */
  --stm-fixedCardGrid-cardMinWidth: 280px; /* Minimum card width before the auto-fill grid wraps to fewer columns (TOK-47: both legs consumed var(--stm-fixedCardGrid-cardMinWidth, 280px) before this slot existed). The cardMinWidth PROP still overrides per-instance via the inline custom property; this is the themable default. NOTE: --stm-fixedCardGrid-cardHeight is deliberately NOT a token — it is a pure prop channel (default 1fr is structural, not themable). */

  /* floatingToolbar */
  --stm-floatingToolbar-container: #96ADF8; /* Toolbar background — the BRAND primary surface (owner 2026-07-08, GDK-21: the pill must carry the brand like StmBulkBar/ReturHeader; supersedes the GAP-02 644:1943 neutral-dark annotation, which rendered a black pill under retur). Dark under retur (deep green), brand-colored under every brand. */
  --stm-floatingToolbar-borderWidth: 1px; /* Container border weight (#618). The pill had fill + radius + elevation and NO border, which was invisible for as long as it was always the dark brand primary — the fill alone separated it. #601 inverts it on a canvas modal, and a surface-coloured pill floating over that modal's light content panel has nothing but a 12%-alpha shadow to say where it ends; the consumer reported it as "the toolbar has no edge". Defaults to THIN because a hairline is what every other pill-shaped surface here carries (card, tag, statusBadge) and it reads on both grounds. A consumer wanting a statement edge sets this to core.border.width.medium (3px, the rung #618 restored) or heavy — that is the point of it being a token rather than a hardcoded rule. */
  --stm-floatingToolbar-border: #5279F4; /* Container border colour (#618). Bound to the overlayBorder ROLE rather than enumerated per context, so it follows the #601/#602 canvas re-point with no second remap: off a canvas it is brand.400 on a brand.500 pill (a quiet inner-tone edge the dark pill does not need but is not harmed by), and inside one the role re-points to core.color.outline. It deliberately does NOT share controlBorder’s role: rendering both showed why. A control border sits on the pill’s own fill and should be a same-tone step; the pill’s outer edge sits against the panel behind it and must SEPARATE. Sharing one value gave 1.23:1 under starter — an edge you cannot see is the bug this issue reported, re-introduced one layer down. */
  --stm-floatingToolbar-radius: 14px; /* Toolbar border radius — brand-configurable (default 14px rounded pill) */
  --stm-floatingToolbar-padding: 16px; /* Container padding — 16px all sides */
  --stm-floatingToolbar-gap: 16px; /* Gap between groups and dividers — 16px */
  --stm-floatingToolbar-elevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.08), 0px 8px 16px 0px rgba(255,255,255,0.08); /* High elevation — floats above content */
  --stm-floatingToolbar-minHeight: 64px; /* Minimum toolbar height — 64px */
  --stm-floatingToolbar-offset: 32px; /* Distance from viewport edge (bottom/left/right) for the anchored pill — 32px */
  --stm-floatingToolbar-offsetCompact: 16px; /* Side inset of the docked bar below the collapse breakpoint (TOK-74, A11Y-12/WCAG 1.4.10). Raw core in both legs. The BREAKPOINT itself is deliberately NOT a token: a @media query cannot read a custom property, so the slot would be declared-and-dead — exactly what the check:tokenusage ratchet forbids. It belongs to TOK-38's SCSS channel, and three rows asked for it before that was written down. */
  --stm-floatingToolbar-enterOffset: 12px; /* Distance the pill travels on entry (TOK-74). The manifest's own $description has named it as spec — "slide up 12px with fade, 400ms ease" — since the component was written, while the number itself was a literal in SIX rules across the two legs. A spec nobody can read from the token set is not a spec. */
  --stm-floatingToolbar-motionDuration: 300ms; /* Entrance/exit length (TOK-74). CORRECTION to the $description above: it says 400ms, and both legs have always run core.motion.duration.slower = 300ms. The token records what SHIPS; 400ms was never true, and the prose is fixed rather than the behaviour, because changing the timing is a design decision and this row is not one. (Δ17's 300-vs-400 finding is this correction; nothing further is owed.) */
  --stm-floatingToolbar-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Entrance/exit curve (TOK-74) — raw core in four rules across the two legs. */
  --stm-floatingToolbar-zIndex: 9999; /* Stacking order (TOK-74). The pill sits above ALL content including modals — a deliberate, unusual choice that was a raw core reach-in, so nothing in the component's own manifest said it was deliberate. TST-06 locks the layering; this makes the claim addressable. */
  --stm-floatingToolbar-secondaryBorderWidth: 2px; /* Border weight that distinguishes the middle tier from the ghost hairline. The MIDDLE tier of the toolbar's three ranks (FLOATINGTOOLBAR-EMPHASIS, #626). Opt in with Class="stm-c-floating-toolbar__secondary", the same way the CTA is opted into, so ghost stays the default and no required parameter is added. IT IS A WEIGHT RATHER THAN A FILL BECAUSE A FILL WAS MEASURED AND DOES NOT WORK: a lighter step of the pill's hue reads 1.39:1 against the pill (imperceptible) with a 3.68:1 label (under AA), and a wash of the pill's own ink trades separation against legibility at every opacity from 14% to 30% — failing AA at all of them, because systemate's pill starts at 5.17:1 ink-on-surface and has no margin to spend. Weight costs no contrast at all: the label never leaves the pill surface. */
  --stm-floatingToolbar-secondaryBorder: #0F172A; /* Border colour for the middle tier — the same ink as the ghost, only heavier. The MIDDLE tier of the toolbar's three ranks (FLOATINGTOOLBAR-EMPHASIS, #626). Opt in with Class="stm-c-floating-toolbar__secondary", the same way the CTA is opted into, so ghost stays the default and no required parameter is added. Aliases ghostBorder so a context re-pointing the overlay roles reaches it too (the #602 mechanism); the two must never drift, or one tier inks differently from the other on a canvas. */
  --stm-floatingToolbar-secondaryColor: #0F172A; /* Label colour for the middle tier — identical to the ghost by design. The MIDDLE tier of the toolbar's three ranks (FLOATINGTOOLBAR-EMPHASIS, #626). Opt in with Class="stm-c-floating-toolbar__secondary", the same way the CTA is opted into, so ghost stays the default and no required parameter is added. The RANK is carried entirely by the border weight; changing the ink as well would spend contrast the measurement says is not available. */
  --stm-floatingToolbar-secondaryContainer: transparent; /* Resting fill for the middle tier — transparent, as the ghost is. The MIDDLE tier of the toolbar's three ranks (FLOATINGTOOLBAR-EMPHASIS, #626). Opt in with Class="stm-c-floating-toolbar__secondary", the same way the CTA is opted into, so ghost stays the default and no required parameter is added. Declared as its own slot rather than inherited so a brand CAN give the tier a fill where its palette has the headroom, without the DS assuming every brand does. */
  --stm-floatingToolbar-secondaryContainerHover: #B5C6FA; /* Hover fill for the middle tier. The MIDDLE tier of the toolbar's three ranks (FLOATINGTOOLBAR-EMPHASIS, #626). Opt in with Class="stm-c-floating-toolbar__secondary", the same way the CTA is opted into, so ghost stays the default and no required parameter is added. */
  --stm-floatingToolbar-statusColor: #0F172A; /* Save/sync status text (issue #38, TOK-74) — it borrowed the GHOST BUTTON's colour, so restyling the ghost buttons silently restyled a text region that is not a button. */
  --stm-floatingToolbar-statusMinWidth: 0; /* Stable footprint for the save/sync status slot (FLOATINGTOOLBAR-WIDTH-JITTER, #625). The pill is centre-anchored with content-driven width, so a width change inside it moves BOTH edges and the whole bar reads as jumping — and the status slot is the one region designed to change on its own, cycling Kladde/Gemmer/Gemt on every debounced autosave. DEFAULTS TO 0 ON PURPOSE: only the consumer knows their longest status string, and a min-width guessed here would be wrong on a narrow viewport and would balloon the bar in the states that render a single button. Set it once per consumer; the slot is private, which is why this has to be a token rather than a consumer CSS rule. */
  --stm-floatingToolbar-statusGap: 4px; /* Gap between the status glyph and its text (TOK-74) — raw core. */
  --stm-floatingToolbar-statusTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-floatingToolbar-statusTypography-font-size: 12px;
  --stm-floatingToolbar-statusTypography-font-weight: 700;
  --stm-floatingToolbar-statusTypography-line-height: 1.5;
  --stm-floatingToolbar-ctaContainerActive: #1A3791; /* CTA fill while pressed (TOK-74). Neither leg had one, so the primary action of the toolbar gave no press feedback at all — it aliases hover today, which is what it effectively rendered. */
  --stm-floatingToolbar-ghostContainerActive: #B5C6FA; /* Ghost-button fill while pressed (TOK-74) — same missing state as the CTA, same value-preserving alias. */
  --stm-floatingToolbar-clearance: 96px; /* Bottom clearance pages keep free for the docked pill — ALIASES core.layout.toolbarClearance (TOK-121: two independent 96px tokens existed; the layout token is the single owner, this slot is the component-scoped alias). */
  --stm-floatingToolbar-dividerColor: #0F172A; /* Vertical divider between button groups — hairline in the on-primary ink so it reads on the branded pill (GDK-21; was neutral.400 on the neutral-dark pill) */
  --stm-floatingToolbar-dividerWidth: 1px; /* Divider thickness — 1px */
  --stm-floatingToolbar-dividerHeight: 32px; /* Divider height — 28px, vertically centered */
  --stm-floatingToolbar-groupGap: 16px; /* Gap between buttons within a group — 16px */
  --stm-floatingToolbar-ghostContainer: transparent; /* Ghost button bg — transparent, the pill shows through. Visual anchor is the border, not a fill. Hover state gets a subtle fill for feedback */
  --stm-floatingToolbar-ghostContainerHover: #B5C6FA; /* Ghost button hover bg — the primary hover step on the branded pill (GDK-21; was neutral.800 on the neutral-dark pill) */
  --stm-floatingToolbar-ghostBorder: #0F172A; /* Ghost button border — on-primary ink on the branded pill (GDK-21; the buttonInverse neutrals were tuned for a neutral surface and read as smudges here). TOK-46: the token was never bound. StmButton draws the inverse variant's 1px border, and this toolbar overrode it to a literal `border-color: transparent` in all three states, in both legs — so the recolour GDK-21 asked for was implemented as a removal. Four signals said the border was meant to survive (this value, its hover twin, the $description, and an accessibility test asserting 3:1 against floatingToolbar.container in all eight brand×mode combinations — nobody contrast-tests a border they intend to hide) against one unexplained literal, so it is bound now. This CHANGES THE RENDER: the toolbar's ghost buttons gain the outline they were specified with. */
  --stm-floatingToolbar-ghostBorderHover: #0F172A; /* Ghost button hover border — on-primary ink (GDK-21). Bound in TOK-46 alongside ghostBorder, and reused for the pressed state for the same reason ghostColorHover is (TOK-74). */
  --stm-floatingToolbar-ghostColor: #0F172A; /* Ghost button text — on-primary ink on the branded pill (GDK-21) */
  --stm-floatingToolbar-ghostColorHover: #0F172A; /* Ghost button hover text — on-primary ink (GDK-21) */
  --stm-floatingToolbar-ghostColorDisabled: #64748B; /* Ghost button disabled text — from buttonInverse (neutral-600) */
  --stm-floatingToolbar-ghostRadius: 8px; /* Ghost button radius — from buttonInverse (brand-aware) */
  --stm-floatingToolbar-ghostPaddingX: 16px; /* Ghost button horizontal padding — from buttonInverse */
  --stm-floatingToolbar-ghostPaddingY: 10px; /* Ghost button vertical padding — from buttonInverse */
  --stm-floatingToolbar-ctaContainer: #0F172A; /* CTA button background — INVERTED on the branded pill (GDK-21): a primary CTA on the now-primary pill was invisible, so the accent flips to on-primary ink with primary text */
  --stm-floatingToolbar-ctaContainerHover: #1A3791; /* CTA hover background — the light primary tint (GDK-21) */
  --stm-floatingToolbar-ctaColor: #96ADF8; /* CTA button text — primary ink on the inverted CTA (GDK-21) */
  --stm-floatingToolbar-ctaRadius: 8px; /* CTA border radius — from button primary (brand-aware) */
  --stm-floatingToolbar-ctaPaddingX: 24px; /* CTA horizontal padding — 24px, wider than ghost for emphasis */
  --stm-floatingToolbar-ctaMinHeight: 44px; /* CTA min height — from button primary (brand-aware) */
  --stm-floatingToolbar-ctaElevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.05), 0px 1px 3px 0px rgba(255,255,255,0.10); /* CTA button glow — subtle elevation shadow on primary bg */
  --stm-floatingToolbar-countTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-floatingToolbar-countTypography-font-size: 14px;
  --stm-floatingToolbar-countTypography-font-weight: 700;
  --stm-floatingToolbar-countTypography-line-height: 1.5;
  --stm-floatingToolbar-dismissOpacity: 0.85; /* Resting opacity of the clear-selection control (issue #80) — de-emphasised against the actions, full opacity on hover. Same value as bulkBar.dismissOpacity, the sibling this promotes */
  --stm-floatingToolbar-controlContainer: #B5C6FA; /* Fill for DS controls (selects, inputs) nested in the pill (issue #80) — the input tokens default to dark-on-light and were unreadable on the primary surface. Re-pointed via the toolbar's own CSS so consumers stop copy-pasting the remap (it existed in 5 places). Mirrors bulkBar.controlContainer */
  --stm-floatingToolbar-controlText: #0F172A; /* Text colour for DS controls nested in the pill (issue #80). Mirrors bulkBar.controlText */
  --stm-floatingToolbar-controlBorder: #7694F7; /* Border colour for DS controls nested in the pill (issue #80). Mirrors bulkBar.controlBorder */

  /* formField */
  --stm-formField-labelColor: #F8FAFC; /* Form label text color */
  --stm-formField-labelColorDisabled: #94A3B8; /* Label colour when the field is disabled (TOK-75). The Blazor leg borrowed input.textDisabled — a token describing the INPUT's text, not its label — and the React leg had no rule at all, so a disabled field kept a live-looking label. */
  --stm-formField-labelGap: 6px; /* Gap between label and input - 6px */
  --stm-formField-requiredColor: #FB7185; /* Required asterisk color */
  --stm-formField-requiredGap: 4px; /* Space between the label text and its required asterisk — 4px (TOK-75). The asterisk had a colour and no spacing, so it sat flush against the last letter of the label. */
  --stm-formField-helpColor: #E2E8F0; /* Help text color below input */
  --stm-formField-helpGap: 4px; /* Gap between input and help/error text - 4px */
  --stm-formField-warningGap: 4px; /* Space above the warning line — 4px, matching helpGap (TOK-75). `warningColor` has been declared since the manifest was written but the composite had no warning ELEMENT, so a soft warning ("this looks unusual, but you may proceed") was unbuildable through StmFormField: only the input scaffolds consumed the colour. */
  --stm-formField-errorColor: #FB7185; /* Error message text color */
  --stm-formField-errorGap: 4px; /* Gap above the inline error line (TOK-46). It read formField.helpGap: the error and the help line sit in the same slot under the control, so the value is right and the NAME was not — a brand tightening help text silently tightened errors too. */
  --stm-formField-errorTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-formField-errorTypography-font-size: 12px;
  --stm-formField-errorTypography-font-weight: 400;
  --stm-formField-errorTypography-line-height: 1.5;
  --stm-formField-warningColor: #FFEFAD; /* Warning message text color (issue #62) - the text-grade warning step; readable on the page surface AND on the warningSubtle input wash (raw warning yellow fails contrast on both) */
  --stm-formField-labelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-formField-labelTypography-font-size: 14px;
  --stm-formField-labelTypography-font-weight: 700;
  --stm-formField-labelTypography-line-height: 1.5;
  --stm-formField-helpTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-formField-helpTypography-font-size: 12px;
  --stm-formField-helpTypography-font-weight: 400;
  --stm-formField-helpTypography-line-height: 1.5;

  /* formSectionStack */
  --stm-formSectionStack-gap: 16px; /* Vertical gap between form sections — matches the section-level rhythm */
  --stm-formSectionStack-sectionPadding: 24px; /* Internal padding of each form section card */
  --stm-formSectionStack-sectionRadius: 14px; /* Corner radius of each form section card — brand-configurable, matches card radius */
  --stm-formSectionStack-sectionGap: 16px; /* Gap between elements inside a form section (label, fields, help text) */
  --stm-formSectionStack-sectionHeaderGap: 16px; /* Space under a section's heading — 16px, matching sectionGap so this is value-preserving (TOK-76). One `sectionGap` slot did FOUR jobs: the heading's inner gap, the heading's margin, the content stack rhythm, and the error line's margin — so tuning the rhythm inside a section also moved its title. */
  --stm-formSectionStack-sectionTitleTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-formSectionStack-sectionTitleTypography-font-size: 16px;
  --stm-formSectionStack-sectionTitleTypography-font-weight: 700;
  --stm-formSectionStack-sectionTitleTypography-line-height: 1.5;
  --stm-formSectionStack-sectionTitleColor: #F8FAFC; /* Section heading colour (TOK-76) — pairs with sectionTitleTypography. */
  --stm-formSectionStack-sectionBackground: #1E293B; /* Background of each form section card */
  --stm-formSectionStack-sectionElevation: none; /* Section shadow — none by default, so this is value-preserving (TOK-76). Sections are card-shaped (surface + brand card radius + outline) and card.json owns an elevation slot for the same anatomy; without one a section could not be lifted off a tinted page. */
  --stm-formSectionStack-sectionBorder: #94A3B8; /* Border color of each form section card */
  --stm-formSectionStack-sectionBorderWidth: 1px; /* Section outline weight — 1px, unchanged (TOK-76). The colour was a slot while the width was a raw core reach-in, at four sites across the two legs; TOK-121 flagged this family for exactly that mix, which diverges the day a brand turns its border knob. */
  --stm-formSectionStack-sectionBorderInvalid: #FB7185; /* Section outline when the section holds a validation failure (LAY-04) — a raw core reach-in until now (TOK-76). */
  --stm-formSectionStack-sectionErrorText: #FB7185; /* Section-level error message colour (TOK-76). The invalid section could draw a red edge but had no token for the sentence explaining why. */
  --stm-formSectionStack-requiredColor: #FB7185; /* Required marker inside a section heading (TOK-76) — the formField precedent, so a whole required SECTION can be marked the same way one required field is. */
  --stm-formSectionStack-descriptionColor: #E2E8F0; /* Section description line under the heading (TOK-76) — the formField helpColor precedent. */
  --stm-formSectionStack-sectionOnBackground: #F8FAFC; /* Ink for content a CONSUMER slots into this surface (CMP-65 / #621). The component's own parts each set their own colour and look perfect, so nothing here was missing until a HOST re-inked: an AuthLayout setting `color: onPrimary` for a green canvas left everything slotted in inheriting white, on white — 1.00:1. Defaults to core.color.onSurface, which is EXACTLY what slotted content already resolved to on an ordinary page, so this is value-preserving by construction and nothing moves except where it was already broken. */

  /* fullScreenModal */
  --stm-fullScreenModal-contentMaxWidthDefault: 860px; /* DESIGN RECORD (TOK-46b ②) — the default content-column cap, mirrored by the React `DEFAULT_CONTENT_MAX_WIDTH` and the Blazor `DefaultContentMaxWidth` constants. It cannot be BOUND, and that is the finding rather than an oversight: `contentMaxWidth` is a NUMBER prop the consumer supplies (720|860|1024|1440|1600|1920|2200, validated against the same allowlist on both legs), and a prop channel cannot read a custom property — the limit already recorded for splitter.minPaneSize, snackbar.autoHideDuration and tooltip.showDelay in the parity spec's §PB-0. TOK-46b filed this as the same shape as TOK-83's widthSm/Md/Lg; it is not. Those back a NAMED preset (sm/md/lg), which a token can resolve in CSS. This backs an arbitrary number, so the only thing a token can honestly hold is the DEFAULT — which is a real design decision (860 is the single-column form/wizard measure) and had no address anywhere outside two source constants. The 720/1024 rungs are the consumer's to pass; 1440 and above are the wide-workspace rungs the Godkendelsesportal case modal needed. */
  --stm-fullScreenModal-zIndex: 1250; /* Stacking of the panel (TOK-45) — raw core, and read again as `calc(… - 1)` for the scrim, which is the pair below. */
  --stm-fullScreenModal-stickyZIndex: 1100; /* Stacking of the sticky header WITHIN the panel (TOK-45) — raw core. A separate rung on purpose: it stacks against the panel's own content, not against the page. */
  --stm-fullScreenModal-panelElevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.08), 0px 8px 16px 0px rgba(255,255,255,0.08); /* Panel shadow (TOK-45) — raw core, and paired with a hairline outline in the same declaration. */
  --stm-fullScreenModal-panelBorder: #334155; /* The hairline outline drawn as a second shadow ring (TOK-45) — raw core, and carrying a LITERAL `1px` fallback beside its width token. */
  --stm-fullScreenModal-panelBorderWidth: 1px; /* Width of that outline ring (TOK-45) — the literal fallback retires with it. */
  --stm-fullScreenModal-headerGapTight: 4px; /* Gap inside a header cluster — icon to text (TOK-45) — raw core. */
  --stm-fullScreenModal-headerGap: 8px; /* Gap between header clusters (TOK-45) — raw core. */
  --stm-fullScreenModal-titleColor: #F8FAFC; /* Header title ink (TOK-45) — raw core, while the popup modal beside it now has modal.titleColor (TOK-83). */
  --stm-fullScreenModal-closeContainerActive: rgba(255, 255, 255, 0.16); /* Close-button fill while pressed (TOK-45) — raw core. */

  /* infoBanner */
  --stm-infoBanner-container: #172554; /* Banner background — light blue tint */
  --stm-infoBanner-onContainer: #BFDBFE; /* Banner text color — dark blue for contrast */
  --stm-infoBanner-iconColor: #60A5FA; /* Leading icon color — blue */
  --stm-infoBanner-border: #93C5FD; /* Banner outline colour (TOK-77). Paints nothing until borderWidth is turned up — the default banner is fill-only, and that is unchanged. */
  --stm-infoBanner-borderWidth: 0px; /* Banner outline weight — 0 by default, so this is value-preserving (TOK-77). The pair exists so a banner shown on a tinted panel, where the subtle fill stops separating it from its background, can be given an edge without a hand-rolled override. */
  --stm-infoBanner-containerTip: #122666; /* Tip appearance background — light brand tint. Deliberately the primary-subtle family, NOT warningSubtle: alert.containerWarning already resolves to {core.color.warningSubtle}, so a warning-tinted tip would render identically to a warning alert. The brand tint reads as branded guidance and stays distinct from all four alert severities and from the default info-blue banner in every brand */
  --stm-infoBanner-onContainerTip: #B5C6FA; /* Tip appearance text color — on the primary-subtle tint */
  --stm-infoBanner-iconTip: #96ADF8; /* Tip lightbulb icon color — brand primary (mirrors alert's icon{Severity} = plain status-color pattern) */
  --stm-infoBanner-borderTip: #7694F7; /* Outline colour for the tip appearance (TOK-77) — the brand step, so the outlined tip stays on the primary family its fill and icon already use. */
  --stm-infoBanner-iconSize: 16px; /* Icon size — 16px, compact */
  --stm-infoBanner-radius: 8px; /* Banner border radius — brand-configurable (default 8px) */
  --stm-infoBanner-paddingX: 16px; /* Horizontal padding — 16px */
  --stm-infoBanner-paddingY: 8px; /* Vertical padding — 8px, compact single-line */
  --stm-infoBanner-gap: 8px; /* Gap between icon and text — 8px */
  --stm-infoBanner-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-infoBanner-typography-font-size: 14px;
  --stm-infoBanner-typography-font-weight: 400;
  --stm-infoBanner-typography-line-height: 1.5;

  /* input */
  --stm-input-label: #F8FAFC; /* Inline field-label text color (date/time/masked pickers). Medium-weight companion to formField.labelColor (TOK-08 — promoted from a fallback-only ref). */
  --stm-input-labelGap: 6px; /* Gap between an inline field label and its control — 6px. Unifies the former 4px/6px fallbacks (TOK-08). */
  --stm-input-labelFontSize: 14px; /* Inline field-label size — 14px, matching formField.labelTypography size (TOK-08 — was a 0.875rem fallback). */
  --stm-input-labelFontWeight: 500; /* Inline field-label weight — medium (500); intentionally lighter than formField's bold label (TOK-08 — was a 500 fallback). */
  --stm-input-container: #1E293B; /* Input background color */
  --stm-input-containerDisabled: #1E293B; /* Input background when disabled — the core TOK-13 slot (resolves to surfaceVariant) */
  --stm-input-border: #94A3B8; /* Default border color */
  --stm-input-borderHover: #96ADF8; /* Border on hover — brand primary (Q14/CMP-02: the sticker sheet showed no hover, so RETUR signed off border→primary, previewing the focus color) */
  --stm-input-borderFocus: #96ADF8; /* Border when focused - brand primary */
  --stm-input-borderError: #FB7185; /* Border in error state */
  --stm-input-borderWarning: #FFD84A; /* Border in warning state (issue #62) - advisory attention, not invalid */
  --stm-input-containerWarning: #4A3600; /* Input background in warning state (issue #62) - the subtle warning wash */
  --stm-input-borderDisabled: #334155; /* Border when disabled - faded (core TOK-13 slot) */
  --stm-input-borderWidth: 1px; /* Default border width - 1px */
  --stm-input-borderWidthFocus: 2px; /* Border thickness when focused - 2px */
  --stm-input-text: #F8FAFC; /* Input text color */
  --stm-input-textDisabled: #94A3B8; /* Input text when disabled (core TOK-13 slot) */
  --stm-input-placeholder: #94A3B8; /* Placeholder text color */
  --stm-input-paddingX: 12px; /* Horizontal padding inside input - 12px */
  --stm-input-paddingY: 8px; /* Vertical padding inside input - 8px */
  --stm-input-radius: 8px; /* Input border radius — brand-configurable */
  --stm-input-labelInsetX: 12px; /* Horizontal inset of the resting (un-floated) label on the React/MUI leg (TOK-78). StmTextField hardcoded MUI's untokenized 14px in four rules while StmSelect used input.paddingX (12px) in four more, so the two labels sat 2px apart ON THE SAME FORM — a misalignment nobody chose and nobody could see in either file alone. Aligns on the select's value, which is also what the Blazor native field pads on. */
  --stm-input-labelLineBox: 1.4375em; /* MUI's own label line-height, used to centre the resting label height-relatively: translateY = (fieldHeight - labelLineBox)/2 (TOK-78). A VENDOR constant rather than a design decision, which is exactly why it needs a name — it appeared as a bare `1.4375em` in eight rules across two components, and nothing said where the number came from. Em-relative on purpose: it scales if a brand changes the input font. */
  --stm-input-motionDuration: 120ms; /* Border/shadow transition on focus and hover (TOK-78) — raw core in every field component on both legs, i.e. the same number written out roughly a dozen times. */
  --stm-input-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Field transition curve (TOK-78). */
  --stm-input-containerHover: #1E293B; /* Field fill on hover (TOK-78). Only the BORDER changed on hover; the fill had no slot at all, so a brand whose fields lift on hover could not express it. Aliases the resting fill, which is what shipped. */
  --stm-input-textareaMinHeight: 52px; /* Floor for the multiline variant (TOK-78). The textarea explicitly zeroed its min-block-size to escape the single-line control height, which left it with NO floor: a `rows`-less textarea collapsed to one line and looked exactly like the single-line field it is not. */
  --stm-input-affixContainer: #1E293B; /* Fill of the non-editable affix segment (GAP-07 / TOK-78) — a raw core reach-in, so the affix could not be tinted independently of every other surfaceVariant on the page. */
  --stm-input-affixText: #E2E8F0; /* Ink of the affix segment (GAP-07 / TOK-78) — the muted colour that signals "not editable"; raw core. */
  --stm-input-affixPaddingX: 8px; /* Horizontal padding of the affix segment (GAP-07 / TOK-78) — raw core, and written with a LITERAL fallback (`var(--stm-space-sm, 8px)`), which is the pattern check:tokenlint's --strict ratchet exists to retire. */
  --stm-input-clearColor: #E2E8F0; /* Clear-✕ (and expand) icon at rest (CMP-49 / TOK-78) — raw core. */
  --stm-input-clearColorHover: #F8FAFC; /* Clear-✕ icon on hover/focus (CMP-49 / TOK-78) — raw core. */
  --stm-input-clearContainerHover: rgba(255, 255, 255, 0.08); /* Clear-✕ hover halo (CMP-49 / TOK-78) — raw core. */
  --stm-input-clearRadius: 9999px; /* Clear-✕ corner radius (CMP-49 / TOK-78) — raw core. */
  --stm-input-clearPaddingX: 4px; /* Clear-✕ horizontal padding (CMP-49 / TOK-78) — raw core. */
  --stm-input-focusRing: #7694F7; /* Focus ring on the field's inner controls — the clear ✕ and the expand button (TOK-78). Completes the trio; raw core on both legs. */
  --stm-input-focusRingWidth: 2px; /* Inner-control focus ring width (TOK-78). */
  --stm-input-focusRingOffset: -2px; /* Inner-control focus ring offset (TOK-78) — the INSET recipe. It was written as `calc(-1 * var(--stm-border-focusRingWidth))`, which hard-couples the offset to the WIDTH: change the width and the inset silently follows, which is not what an offset means. */
  --stm-input-minHeight: 44px; /* Minimum height for touch target - brand configurable */
  --stm-input-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-input-typography-font-size: 16px;
  --stm-input-typography-font-weight: 400;
  --stm-input-typography-line-height: 1.5;

  /* link */
  --stm-link-color: #96ADF8; /* Default link color (brand-overridable; RETUR stays #296142 per Q9) */
  --stm-link-colorHover: #B5C6FA; /* Link hover color */
  --stm-link-colorActive: #D1DBFC; /* Link active/pressed color */
  --stm-link-colorVisited: #D1DBFC; /* Visited link color - slightly darker primary */
  --stm-link-colorDisabled: #94A3B8; /* Colour of a disabled link (TOK-79). Neither leg had a disabled treatment at all, so a link rendered inert by its host still read as an available action. */
  --stm-link-iconGap: 4px; /* Gap between a link's leading/trailing icon and its text — 4px, unchanged (TOK-79). Was a raw core reach-in on the Blazor leg and had no React equivalent at all. */
  --stm-link-iconSize: 1em; /* Link icon glyph box — 1em, so the icon tracks the link text (TOK-79). Was a literal width/height:1em on the Blazor leg only. */
  --stm-link-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-link-typography-font-size: 16px;
  --stm-link-typography-font-weight: 400;
  --stm-link-typography-line-height: 1.5;
  --stm-link-underlineOffset: 2px; /* Distance between the text baseline and its underline (TOK-79). StmLink is the only component in the system that renders an underline, so these two metrics have always been core tokens with exactly one consumer; promoting them lets the link's underline be retuned without moving every future underline in the system. */
  --stm-link-underlineThickness: 1px; /* Weight of the link underline (TOK-79) — see underlineOffset. */

  /* listBox */
  --stm-listBox-paneContainer: #1E293B; /* Pane background */
  --stm-listBox-paneBorder: #334155; /* Pane border color */
  --stm-listBox-paneBorderWidth: 1px; /* Pane outline weight — 1px, unchanged (TOK-80). The pane's colour and radius were slots while the width was a bare literal, which is the one of the three that a brand's border knob would otherwise silently move. */
  --stm-listBox-paneRadius: 14px; /* Pane border radius — brand-configurable */
  --stm-listBox-paneMaxHeight: 320px; /* Pane height before its option list scrolls */
  --stm-listBox-headerText: #E2E8F0; /* Pane title text color */
  --stm-listBox-headerTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-listBox-headerTypography-font-size: 14px;
  --stm-listBox-headerTypography-font-weight: 700;
  --stm-listBox-headerTypography-line-height: 1.5;
  --stm-listBox-headerGap: 8px; /* Space under a pane's title — 8px, unchanged (TOK-80). It borrowed optionPaddingY, which measures the inside of a ROW: two unrelated measurements locked together. */
  --stm-listBox-listPaddingY: 8px; /* Padding at the top and bottom of the scrolling list — 8px, unchanged (TOK-80). Also borrowed optionPaddingY. */
  --stm-listBox-optionPaddingX: 12px; /* Horizontal padding per option */
  --stm-listBox-optionPaddingY: 8px; /* Vertical padding per option */
  --stm-listBox-optionText: #F8FAFC; /* Colour of an option at rest (TOK-80). Selected and disabled options had colour slots and the RESTING option — the state most rows are in — reached raw core. */
  --stm-listBox-optionHover: #334155; /* Option background on hover */
  --stm-listBox-optionSelected: #122666; /* Highlighted (marked-for-transfer) option background */
  --stm-listBox-optionSelectedHover: #1A3791; /* Fill of a SELECTED option under the pointer (TOK-80). Hover and selection shared one rule, so pointing at a selected row replaced its tint with the neutral hover fill — the row appeared to deselect itself under the cursor. */
  --stm-listBox-onOptionSelected: #96ADF8; /* Highlighted option text color */
  --stm-listBox-optionDisabledText: #94A3B8; /* Disabled option text color */
  --stm-listBox-optionTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-listBox-optionTypography-font-size: 16px;
  --stm-listBox-optionTypography-font-weight: 400;
  --stm-listBox-optionTypography-line-height: 1.5;
  --stm-listBox-gap: 16px; /* Gap between panes and the transfer-button column */
  --stm-listBox-controlsGap: 8px; /* Gap between the transfer buttons in the middle column — 8px, unchanged (TOK-80); a raw core reach-in in a file where every other gap is a slot. */
  --stm-listBox-optionMotionDuration: 120ms; /* Option hover/selection transition length — 150ms (TOK-80). Rows swapped fill instantly, which reads as a flicker when the transfer buttons move several at once. */
  --stm-listBox-optionMotionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Option transition curve (TOK-80). */

  /* loadingOverlay */
  --stm-loadingOverlay-scrim: rgba(15, 23, 42, 0.90); /* The veil over the busy region (TOK-45). Renamed at the core in the same change (TOK-44): it was called 'glass' and never blurred anything. */
  --stm-loadingOverlay-zIndex: 1; /* Stacking of the scrim over its region (TOK-45) — raw core. It is deliberately the RAISED rung, not overlay: this covers a region, not the page. */
  --stm-loadingOverlay-gap: 8px; /* Gap between the indicator and its label (TOK-45) — raw core. */
  --stm-loadingOverlay-skeletonPadding: 16px; /* Inset of the skeleton variant, which stretches top-aligned instead of centring (TOK-45) — raw core. */
  --stm-loadingOverlay-skeletonGap: 8px; /* Gap between skeleton rows (TOK-45) — raw core. */
  --stm-loadingOverlay-skeletonRowHeight: 16px; /* Height of a skeleton row (TOK-45). It read a SPACING step for a height — which happens to be the right number and is the wrong axis, so a brand retuning its spacing scale would have silently resized the placeholder text. */
  --stm-loadingOverlay-skeletonRowRadius: 2px; /* Rounding of a skeleton row (TOK-45) — raw core. PROMOTED to {brand.radius.track} 2026-08-20 (TOK-40, owner: promote all four). Value-preserving in every brand — 2px on a thin filled bar is a PROPORTION, not a shape identity, so the promotion buys the SEAM and not a change. A brand that wants square bar ends now sets one knob instead of editing six components. */
  --stm-loadingOverlay-skeletonRowContainer: #1E293B; /* Fill of a skeleton row (TOK-45) — raw core. */
  --stm-loadingOverlay-labelColor: #E2E8F0; /* Busy-label ink (TOK-45) — raw core. */
  --stm-loadingOverlay-labelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-loadingOverlay-labelTypography-font-size: 14px;
  --stm-loadingOverlay-labelTypography-font-weight: 400;
  --stm-loadingOverlay-labelTypography-line-height: 1.5;
  --stm-loadingOverlay-pulseDuration: 1400ms; /* Skeleton pulse cadence (TOK-45) — raw core. The loop rung is right (calm, ongoing) and was unstated. */
  --stm-loadingOverlay-pulseEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Skeleton pulse curve (TOK-45). */

  /* logo */
  --stm-logo-color: #96ADF8; /* Logo fill color — brand primary. Drives the monochrome (pos/neg) lockup via currentColor: resolves to the brand primary on light surfaces, flips to onPrimary on dark. */
  --stm-logo-colorOriginal: #C1D6B0; /* RETUR 'original' two-tone lockup — light-green container fill (glyphs stay white). Literal brand-asset color from Figma Main Logo 838:95642 / area logos 838:95427; not a scale step, so it lives here rather than in the closed brand palette contract. Re-seat into a brand primitive if RETUR formalizes a light-green slot. */
  --stm-logo-colorOriginalGlyph: #FFFFFF; /* Knockout wordmark inside the two-tone "original" area lockup — white, unchanged (TOK-81). stm-color-exempt: like colorOriginal beside it this is artwork, not theme, but it was a bare #fff literal in both legs, so the one half of the lockup that a brand might legitimately re-tint (a lighter container needs a darker wordmark) was the half with no token. */
  --stm-logo-height: 44px; /* Logo height constraint — 36px default */
  --stm-logo-areaHeight: 32px; /* Area-logo height — 28px (TOK-47: StmAreaLogo consumed var(--stm-arealogo-height, 28px), a namespace that existed in no manifest; folded into logo.* and the CSS repointed to --stm-logo-areaHeight) */
  --stm-logo-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-logo-typography-font-size: 20px;
  --stm-logo-typography-font-weight: 700;
  --stm-logo-typography-line-height: 1.375;

  /* menu */
  --stm-menu-container: #1E293B; /* Menu surface background */
  --stm-menu-border: #334155; /* Menu popup outline (TOK-82). The surface had a fill, a radius and a shadow and no edge, so on a light-on-light page the popup's boundary was carried entirely by the shadow; dropdown.popupBorder is the precedent. */
  --stm-menu-borderWidth: 1px; /* Menu popup outline weight — 1px (TOK-82); pairs with `border`. */
  --stm-menu-containerRadius: 14px; /* Menu corner radius */
  --stm-menu-elevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.08), 0px 8px 16px 0px rgba(255,255,255,0.08); /* Menu elevation — above popovers, below modals */
  --stm-menu-minWidth: 160px; /* Minimum menu width so single-word items don't crowd */
  --stm-menu-maxHeight: 320px; /* Maximum menu height before scrolling kicks in */
  --stm-menu-itemContainer: transparent; /* Default menu-item background */
  --stm-menu-itemContainerHover: #334155; /* Hovered menu-item background */
  --stm-menu-itemContainerSelected: #122666; /* Selected menu-item background — subtle brand tint */
  --stm-menu-itemText: #F8FAFC; /* Menu-item text color */
  --stm-menu-itemTextSelected: #96ADF8; /* Selected menu-item text color */
  --stm-menu-itemPaddingX: 16px; /* Menu-item horizontal padding — 16px */
  --stm-menu-itemPaddingY: 8px; /* Menu-item vertical padding — 8px; tighter than Button */
  --stm-menu-itemMinHeight: 24px; /* Floor height of a menu item — 24px, the A11Y-11 target floor (TOK-82). Item height came from padding plus whatever line-height the host set, so a menu of one-line items in a compact host could fall under the target minimum. */
  --stm-menu-itemGap: 8px; /* Gap between icon and label inside a menu item */
  --stm-menu-itemTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-menu-itemTypography-font-size: 16px;
  --stm-menu-itemTypography-font-weight: 400;
  --stm-menu-itemTypography-line-height: 1.5;
  --stm-menu-itemIconSize: 16px; /* Menu item leading-icon box — 16px (TOK-82). The item had a GAP slot for an icon it could not size. */
  --stm-menu-itemIconColor: #E2E8F0; /* Menu item leading-icon colour (TOK-82) — muted below the label, the same relationship StmModuleSidebar uses. */
  --stm-menu-itemTextDisabled: #94A3B8; /* Disabled menu-item text (TOK-47: Blazor consumed var(--stm-menu-itemTextDisabled, var(--stm-color-disabledText)) before this slot existed; the value matches the old fallback, so rendering is unchanged) */
  --stm-menu-dividerColor: #334155; /* Divider between item groups */
  --stm-menu-dividerWidth: 1px; /* Weight of a menu section divider — 1px (TOK-82/TOK-46). `dividerColor` was declared from the start and consumed by neither leg: the menu had a dividerSPACING but no rule to draw. */
  --stm-menu-dividerSpacing: 4px; /* Vertical spacing around a divider */
  --stm-menu-motionDuration: 120ms; /* Item hover/selection transition length — 150ms (TOK-82), previously raw core. */
  --stm-menu-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Item transition curve (TOK-82). */

  /* modal */
  --stm-modal-overlay: rgba(0, 0, 0, 0.6); /* Modal backdrop using scrim overlay */
  --stm-modal-container: #1E293B; /* Modal container background — white */
  --stm-modal-onContainer: #F8FAFC; /* Ink for content the CONSUMER slots into this surface (2026-08-25 ink sweep). The component paints `container` and inks only the parts it renders ITSELF, so slotted content inherits from whatever ancestor last set `color`. Invisible on an ordinary page and wrong exactly where a host deliberately re-inked — an AuthLayout setting `color: onPrimary` for a green canvas puts WHITE text inside this white surface. Defaults to core.color.onSurface, what slotted content already resolves to on a normal page, so nothing moves except where it was already broken. */
  --stm-modal-containerRadius: 14px; /* Modal container border radius — brand-configurable (default 14px for popup modals, 0 for full-screen) */
  --stm-modal-minWidth: 192px; /* Minimum modal width - 192px */
  --stm-modal-maxWidth: 640px; /* Maximum modal width for readability */
  --stm-modal-elevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.09), 0px 16px 32px 0px rgba(255,255,255,0.12); /* Highest elevation for modals */
  --stm-modal-headerContainer: #1E293B; /* Modal header bar background */
  --stm-modal-headerBorder: #334155; /* Header bottom border separator */
  --stm-modal-headerPaddingX: 24px; /* Header horizontal padding — 24px */
  --stm-modal-headerPaddingY: 16px; /* Header vertical padding — 16px */
  --stm-modal-headerGap: 8px; /* Gap between header items — 8px */
  --stm-modal-closeColor: #E2E8F0; /* Close button icon color */
  --stm-modal-closeSize: 20px; /* Close button icon size — 20px */
  --stm-modal-insetMargin: 16px; /* Viewport margin of the inset/native modal shell (TOK-83). Raw core in a `calc(100vw - 2 * …)` on the native shell and in the inset variant's `inset:` — the same measure, written twice as arithmetic nobody could retune. */
  --stm-modal-insetElevation: 0 0 0 100vmax; /* The inset workspace's scrim, painted as a 100vmax box-shadow SPREAD rather than a real backdrop (large-dialog-over-scrim). TOK-83: the geometry was a literal in both legs. The colour stays modal.overlay — this slot is the spread, which is why it carries no colour of its own. */
  --stm-modal-widthSm: 400px; /* Small preset width (TOK-83). The size axis was untokenized AND the two legs disagreed about what the axis MEANT: Blazor mapped sm/md/lg to 400/600/900px in a C# switch, while React passed the same prop straight to MUI's `maxWidth` breakpoint keys, which are 600/900/1200px. One prop name, two ladders — a divergence a shared API actively hid. */
  --stm-modal-widthMd: 600px; /* Medium preset width (TOK-83) — see widthSm. */
  --stm-modal-widthLg: 900px; /* Large preset width (TOK-83) — see widthSm. */
  --stm-modal-titleColor: #F8FAFC; /* Modal title ink (TOK-83). The title had a typography composite and no colour of its own, so it inherited whatever the surface set — the one piece of text in the component a brand would most want to address. */
  --stm-modal-closeRadius: 12px; /* Close-button corner radius (TOK-83). `--stm-radius-xl` was hardcoded across THREE shells (StmModal, StmFullScreenModal x2), and modal.containerRadius sat right beside them being bypassed — the component owned a radius slot and used a raw core one anyway. */
  --stm-modal-closeBorder: #334155; /* Close-button outline (TOK-83) — raw core across the same three shells. */
  --stm-modal-closeBorderWidth: 1px; /* Close-button outline width (TOK-83) — raw core. */
  --stm-modal-closeContainerHover: rgba(255, 255, 255, 0.08); /* Close-button hover halo (TOK-83) — raw core. */
  --stm-modal-focusRing: #7694F7; /* Focus ring on the close button and other modal chrome (TOK-83) — completes the trio; raw core on both legs. */
  --stm-modal-focusRingWidth: 2px; /* Modal chrome focus ring width (TOK-83). */
  --stm-modal-focusRingOffset: 2px; /* Modal chrome focus ring offset (TOK-83). */
  --stm-modal-actionsGap: 4px; /* Gap between header actions (TOK-83) — raw core. TOK-142 (§PB-13b) gave it a second leg: React's StmDialog rendered DialogTitle/DialogContent/DialogActions and nothing else — no header slot, no close button — so a Blazor modal showed a ✕ and its React twin did not, and the guard could only ever see it as a missing gap. React now has `headerActions` (Blazor's WindowActions) and a `closeButton` defaulting to the same rule Blazor applies: the slot wins, otherwise a ✕ IF there is an onClose for it to call. The 34×34 anatomy is not new to that leg — StmFullScreenModal's close button is what closeButtonSize was drawn against. */
  --stm-modal-footerGap: 8px; /* Gap between footer buttons (TOK-83) — raw core, and DELIBERATELY wider than actionsGap: header actions are icon-sized, footer buttons carry labels. */
  --stm-modal-footerPaddingX: 24px; /* Footer horizontal padding (TOK-83). The footer read the HEADER's padding slots, so retuning the header silently retuned the footer — an alias now, which is the same value and a seam a brand can break. */
  --stm-modal-footerPaddingY: 16px; /* Footer vertical padding (TOK-83) — see footerPaddingX. */
  --stm-modal-footerBorder: #334155; /* Footer top rule (TOK-83) — read the header's border slot for the same reason, with the same consequence. */
  --stm-modal-footerBorderWidth: 1px; /* Footer top-rule width (TOK-83) — raw core, and the third copy of one measure: stmTheme hardcoded the header border's width too (Delta-17). */
  --stm-modal-headerBorderWidth: 1px; /* Header bottom-rule width (TOK-83). Raw core in the component CSS and a bare literal in stmTheme's MuiDialogTitle — the second and third sources for the same hairline. */
  --stm-modal-closeButtonSize: 34px; /* Close button hit-area box — 34×34 (Figma 265:1093). BF-17 AUDIT: 34px is OFF the 4px spacing grid (no primitive — the scale jumps space.8=32 → space.9=36), so it stays a literal rather than a {primitives.space.*} ref. Flagged for design to confirm 34 or snap to 32/36; passes the 24px AA target floor (A11Y-11) regardless. */
  --stm-modal-chromeBarHeight: 48px; /* ModalChromeShell bar height — exactly 48px (Figma 645:1160; vs AppBar 64px). BF-17: equals primitives.space.12, but kept a literal because component tokens must not reference {primitives.*} directly and there is no core/brand 48px size. Now tokenized (was a hardcoded 48px with a 'do not tokenize until BF-17 lands' note). */
  --stm-modal-chromeStepCircleSize: 20px; /* ModalChromeShell compact-stepper step-circle diameter — 20px (Figma Stepper/Compact 644:1876) */
  --stm-modal-chromeStepConnectorHeight: 2px; /* ModalChromeShell compact-stepper connector line thickness — 2px */
  --stm-modal-chromeStepDigitSize: 11px; /* ModalChromeShell compact-stepper step-number font size — 11px, fits a digit in the 20px circle */
  --stm-modal-contentBackground: #1E293B; /* Scrollable content area background — subtle gray behind form cards */
  --stm-modal-containerCanvas: #96ADF8; /* Panel fill for Surface=Canvas (issue #600). A modal opened WITHOUT a scrim over a dark page is part of that page's composition — that is the whole point of Scrim=false — so a light paper panel floating on a dark canvas reads as two unrelated surfaces. Bound to core.color.primary, which is the same token appBar.insetBandContainer uses for its band: the DS already decided that primary IS the canvas surface, and a modal that fused with the page using a DIFFERENT colour would be a second answer to a settled question. */
  --stm-modal-contentBackgroundCanvas: #96ADF8; /* Body fill for Surface=Canvas (issue #600). Equal to containerCanvas on purpose: the point of the canvas surface is that the body FUSES with the panel and the page, so a tonal step between them would reintroduce the seam the variant exists to remove. The header is deliberately NOT canvas — it keeps headerContainer, so the sticky chrome still separates from the body. */
  --stm-modal-onCanvas: #0F172A; /* Ink on the canvas surface (issue #600). This slot is the reason the variant belongs in the DS rather than in a consumer's override: bound to the ON-PAIR of the canvas fill, contrast holds in every brand and both modes BY CONSTRUCTION and is covered by the palette contract the DS already enforces. A consumer re-pointing --stm-modal-container from outside gets the fill and not this, which is exactly how a dark panel ends up with dark text. */
  --stm-modal-contentPaddingX: 32px; /* Content area horizontal padding — 32px */
  --stm-modal-contentPaddingY: 24px; /* Content area vertical padding — 24px */
  --stm-modal-contentGap: 16px; /* Gap between content cards — 16px */
  --stm-modal-titleTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-modal-titleTypography-font-size: 20px;
  --stm-modal-titleTypography-font-weight: 700;
  --stm-modal-titleTypography-line-height: 1.375;
  --stm-modal-bodyTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-modal-bodyTypography-font-size: 16px;
  --stm-modal-bodyTypography-font-weight: 400;
  --stm-modal-bodyTypography-line-height: 1.5;

  /* modalChrome */
  --stm-modalChrome-stepContainer: transparent; /* Fill of a PENDING step circle (TOK-45). Declared rather than omitted: transparent is a choice here, and the two filled states below only read as progress against it. */
  --stm-modalChrome-stepBorder: #334155; /* Outline of a pending step circle (TOK-45) — raw core. */
  --stm-modalChrome-onStep: #E2E8F0; /* Digit inside a pending step (TOK-45) — raw core. */
  --stm-modalChrome-stepContainerCurrent: #96ADF8; /* Fill of the CURRENT step (TOK-45) — raw core. */
  --stm-modalChrome-onStepCurrent: #0F172A; /* Digit on the current step (TOK-45) — raw core. */
  --stm-modalChrome-stepContainerComplete: #4ADE80; /* Fill of a COMPLETED step (TOK-45) — raw core. Success rather than primary is a deliberate distinction (done vs here) that lived only in the CSS. */
  --stm-modalChrome-onStepComplete: #0F172A; /* Digit on a completed step (TOK-45) — raw core. */
  --stm-modalChrome-stepRadius: 9999px; /* Step-circle rounding (TOK-45) — raw core. Deliberately NOT brand-routed: a numbered step is a circle for the same ergonomic reason an avatar is (TOK-118), not as brand chrome. */
  --stm-modalChrome-stepBorderWidth: 1px; /* Step-circle outline width (TOK-45) — raw core. */
  --stm-modalChrome-connectorContainer: #334155; /* The line between steps, unreached (TOK-45) — raw core. */
  --stm-modalChrome-connectorContainerComplete: #4ADE80; /* The line between steps, already travelled (TOK-45) — raw core, and the other half of the done-vs-here distinction. */
  --stm-modalChrome-connectorWidth: 24px; /* Length of the connector between steps (TOK-45). It read a SPACING step for a length — the right number on the wrong axis, so a brand retuning its spacing scale would have silently restretched the rail. */
  --stm-modalChrome-paddingX: 24px; /* Horizontal inset of the chrome bar (TOK-45) — raw core. */

  /* moduleCard */
  --stm-moduleCard-container: #1E293B; /* Card background — white */
  --stm-moduleCard-containerHover: #334155; /* Card fill under the pointer when the card is clickable (TOK-84). The clickable card had a cursor and a focus ring but no pointer feedback at all, so a mouse user got no confirmation the card was a target. */
  --stm-moduleCard-containerActive: #475569; /* Card fill while pressed (TOK-84) — the second half of the clickable card's feedback. */
  --stm-moduleCard-containerDisabled: #1E293B; /* Card fill when disabled — the resting surface today, so value-preserving (TOK-84). Disabled was expressed purely as an opacity on the Blazor leg and not at all on the React one. */
  --stm-moduleCard-disabledOpacity: 0.38; /* Opacity of a disabled card — 0.38, unchanged (TOK-84). It was a raw core reach-in on the Blazor leg and had no React counterpart, so a disabled module card looked live in React. */
  --stm-moduleCard-dimmedOpacity: 0.55; /* Opacity of the dimmed (not-relevant) card — 0.55, unchanged (TOK-84). It was a literal in both legs and in the prop documentation, so three places had to agree. */
  --stm-moduleCard-border: #334155; /* Card border */
  --stm-moduleCard-borderWidth: 1px; /* Card border — 1px */
  --stm-moduleCard-radius: 14px; /* Card corner radius — brand-configurable (default 14px) */
  --stm-moduleCard-padding: 24px; /* Card inner padding — 24px */
  --stm-moduleCard-gap: 16px; /* Gap between major sections — 16px */
  --stm-moduleCard-elevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.05), 0px 1px 3px 0px rgba(255,255,255,0.10); /* Subtle card shadow */
  --stm-moduleCard-headerIconSize: 24px; /* Module icon size in the card header — 24px. TOK-46: the slot was declared and never bound, so the header icon took whatever size StmIcon defaulted to. Re-routed off {core.space.lg} onto the icon ramp at the same 24px: the right number was on the wrong axis, so a brand retuning its SPACING scale would have resized this glyph. */
  --stm-moduleCard-headerGap: 8px; /* Gap between icon and title — 8px */
  --stm-moduleCard-titleColor: #F8FAFC; /* Module name text — bold black */
  --stm-moduleCard-titleTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-moduleCard-titleTypography-font-size: 20px;
  --stm-moduleCard-titleTypography-font-weight: 700;
  --stm-moduleCard-titleTypography-line-height: 1.375;
  --stm-moduleCard-descriptionColor: #F8FAFC; /* Module description text */
  --stm-moduleCard-descriptionTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-moduleCard-descriptionTypography-font-size: 14px;
  --stm-moduleCard-descriptionTypography-font-weight: 400;
  --stm-moduleCard-descriptionTypography-line-height: 1.5;
  --stm-moduleCard-exampleColor: #94A3B8; /* Example text — muted gray ('EKSEMPLER: ...') */
  --stm-moduleCard-exampleTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-moduleCard-exampleTypography-font-size: 12px;
  --stm-moduleCard-exampleTypography-font-weight: 400;
  --stm-moduleCard-exampleTypography-line-height: 1.5;
  --stm-moduleCard-exampleLetterSpacing: 0.5px; /* Uppercase example-label letter-spacing — 0.5px, no scale match. The single source for this element's tracking (#312): the caption role it aliases declares none, so there is no second value to disagree with */
  --stm-moduleCard-exampleGap: 2px; /* Gap between example label and text — 2px (below the xs=4px scale step, so a bespoke dimension) */
  --stm-moduleCard-checklistGap: 6px; /* Gap between checklist items — 6px */
  --stm-moduleCard-checkIconComplete: #4ADE80; /* Completed checklist item icon — green checkmark */
  --stm-moduleCard-checkIconPending: #94A3B8; /* Pending checklist item icon — gray circle */
  --stm-moduleCard-checkTextComplete: #F8FAFC; /* Completed checklist item text — regular weight */
  --stm-moduleCard-checkTextPending: #E2E8F0; /* Pending checklist item text — muted */
  --stm-moduleCard-checkIconSize: 16px; /* Checklist icon size — 16px */
  --stm-moduleCard-checkIconWeight: 700; /* Weight of the checklist tick/circle glyph — bold, unchanged (TOK-84); was a raw core reach-in. */
  --stm-moduleCard-checkGap: 8px; /* Gap between check icon and text — 8px */
  --stm-moduleCard-checkTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-moduleCard-checkTypography-font-size: 14px;
  --stm-moduleCard-checkTypography-font-weight: 400;
  --stm-moduleCard-checkTypography-line-height: 1.5;
  --stm-moduleCard-warningColor: #FB7185; /* Warning text at bottom — red ('Mangler påkrævede felter') */
  --stm-moduleCard-warningIconColor: #FB7185; /* Warning icon — red triangle */
  --stm-moduleCard-warningTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-moduleCard-warningTypography-font-size: 12px;
  --stm-moduleCard-warningTypography-font-weight: 400;
  --stm-moduleCard-warningTypography-line-height: 1.5;
  --stm-moduleCard-progressTrack: #334155; /* Progress strip background — light gray track */
  --stm-moduleCard-progressFill: #4ADE80; /* Progress strip fill — green to show completion */
  --stm-moduleCard-progressHeight: 4px; /* Height of the progress strip along the card's bottom edge - 4px, unchanged (TOK-84); was a bare literal that the CSS then repeated as a fallback. */
  --stm-moduleCard-onContainer: #F8FAFC; /* Ink for content a CONSUMER slots into this surface (CMP-65 / #621). The component's own parts each set their own colour and look perfect, so nothing here was missing until a HOST re-inked: an AuthLayout setting `color: onPrimary` for a green canvas left everything slotted in inheriting white, on white — 1.00:1. Defaults to core.color.onSurface, which is EXACTLY what slotted content already resolved to on an ordinary page, so this is value-preserving by construction and nothing moves except where it was already broken. */

  /* moduleSidebar */
  --stm-moduleSidebar-container: #1E293B; /* Rail background — variant surface, one tint off the page */
  --stm-moduleSidebar-border: #334155; /* Rail edge border and the header/footer section dividers */
  --stm-moduleSidebar-borderWidth: 1px; /* Border/divider width — 1px */
  --stm-moduleSidebar-width: 240px; /* Rail width. Live SettlCare app measures ~220–250px; 240px is the confirmed shell value. Bespoke — no core.size step fits a nav rail */
  --stm-moduleSidebar-widthCollapsed: 72px; /* Width of the icon-only rail (TOK-85). Bespoke like `width`: 72px is the 240px rail's item padding (2x16) plus a 20px glyph plus enough slack that the hover fill still reads as a rounded tile rather than a hairline square. No core.size step fits a nav rail. */
  --stm-moduleSidebar-paddingY: 16px; /* Rail top/bottom padding — 16px */
  --stm-moduleSidebar-sectionPaddingX: 24px; /* Horizontal padding of the header (brand) and footer (user) sections — 24px */
  --stm-moduleSidebar-sectionGap: 16px; /* Vertical rhythm around the section dividers — header padding-bottom + gap to nav, footer padding-top — 16px */
  --stm-moduleSidebar-navPaddingX: 8px; /* Horizontal inset of the nav list — 8px, so item hover/active fills stay inside the rail */
  --stm-moduleSidebar-itemGap: 2px; /* Vertical gap between nav items. Bespoke — sits between core.space.hairline (1px) and xs (4px); no scale step fits */
  --stm-moduleSidebar-itemPaddingX: 16px; /* Nav item horizontal padding — 16px */
  --stm-moduleSidebar-itemPaddingY: 10px; /* Nav item vertical padding — 10px (compact control padding) */
  --stm-moduleSidebar-itemRadius: 8px; /* Nav item corner radius */
  --stm-moduleSidebar-itemIconGap: 8px; /* Gap between a nav item's icon and its label — 8px */
  --stm-moduleSidebar-itemIconSize: 20px; /* Nav item glyph box — 20px (TOK-85). Was a literal size=md passed to StmIcon in the component body of both legs, so the rail's icon scale lived in TSX/Razor rather than in the theme. */
  --stm-moduleSidebar-itemColor: #F8FAFC; /* Nav item label color — default state */
  --stm-moduleSidebar-itemWeight: 500; /* Nav item label weight, resting — 500 (TOK-85). The itemTypography composite's own $description already promised that 'weight is layered per state', but both states reached the core weight ramp directly, so the promise was unkeepable from the manifest. */
  --stm-moduleSidebar-itemWeightActive: 700; /* Nav item label weight on the current page — 700 (TOK-85), the second half of the layered-weight contract. */
  --stm-moduleSidebar-itemIconColor: #94A3B8; /* Nav item icon color — default state, muted below the label */
  --stm-moduleSidebar-itemHover: #1E293B; /* Nav item hover fill — one neutral step above the surfaceVariant rail (the rail already sits on the variant tint, so containerHover would be two steps) */
  --stm-moduleSidebar-itemHoverText: #F8FAFC; /* Nav item label colour on hover — unchanged from the resting itemColor today, so this is value-preserving (TOK-85). Hover moved the fill only; a brand whose hover fill is dark enough to swallow the resting label had no way to lift the text with it. */
  --stm-moduleSidebar-itemActive: #1A3791; /* Active (current-page) nav item fill — light primary tint */
  --stm-moduleSidebar-onItemActive: #B5C6FA; /* Active nav item label + icon color — the on-container text grade (issue #68: raw primary was 3.19:1 on primaryContainer in starter/systemate; retargeted the same way components.navigation was, per this token's own NOTE). Gated >=4.5:1 by the accessibility contrast test */
  --stm-moduleSidebar-itemTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-moduleSidebar-itemTypography-font-size: 14px;
  --stm-moduleSidebar-itemTypography-font-weight: 400;
  --stm-moduleSidebar-itemTypography-line-height: 1.5;
  --stm-moduleSidebar-motionDuration: 120ms; /* Nav item hover/active transition length — 150ms (TOK-85). Was a raw core reach-in, so the rail could not be slowed independently of every other fast transition. */
  --stm-moduleSidebar-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Nav item transition curve (TOK-85). This is the one pair where the repo-wide easing sweep dropped the literal `ease` fallback rather than tokenizing it, which hid it from every later grep. */

  /* navLink */
  --stm-navLink-container: transparent; /* Resting pill fill (TOK-45). Declared explicitly rather than omitted: 'no fill' and 'a fill you cannot see' are different things to a brand that wants a resting chip. */
  --stm-navLink-containerHover: #122666; /* Fill on hover for a NON-active link (GDK-49, TOK-45) — the tint rung of the ladder. */
  --stm-navLink-containerActive: #96ADF8; /* Fill of the ACTIVE link (GDK-49, TOK-45) — the filled rung. */
  --stm-navLink-containerActiveHover: #B5C6FA; /* Fill when hovering the ACTIVE link (GDK-49, TOK-45). Deepens rather than erasing — the rung that makes the ladder a ladder, and the one a brand is most likely to get wrong if it has to re-derive it. */
  --stm-navLink-colorUnavailable: #94A3B8; /* Ink for a nav entry the user may not reach (#644). NOT an opacity step on onContainer: the entry stays FOCUSABLE so it can explain itself, and dimming a focusable label drops it below contrast — which is what the consumer who filed #646 refused. core.color.disabledText is a real ink chosen for exactly this, and it is the same role StmButton's own unavailable state uses, so the two read as one state across the library. */
  --stm-navLink-onContainer: #F8FAFC; /* Resting label ink (TOK-45) — raw core. */
  --stm-navLink-onContainerActive: #0F172A; /* Label ink on the filled active pill (TOK-45) — raw core. */
  --stm-navLink-paddingX: 8px; /* Horizontal padding (TOK-45) — raw core. */
  --stm-navLink-paddingY: 4px; /* Vertical padding (TOK-45) — raw core. */
  --stm-navLink-radius: 9999px; /* Pill rounding (TOK-45). Routed through the BRAND pill knob rather than core.radius.full, which is what the CSS reached for: SETTL squares its pill family (TOK-40/118) and this link was silently exempt from that identity decision. */
  --stm-navLink-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-navLink-typography-font-size: 16px;
  --stm-navLink-typography-font-weight: 700;
  --stm-navLink-typography-line-height: 1.5;
  --stm-navLink-typography-letter-spacing: 0em;
  --stm-navLink-focusRing: #7694F7; /* Focus ring colour (TOK-45) — completes the trio; raw core on both legs. */
  --stm-navLink-focusRingWidth: 2px; /* Focus ring width (TOK-45). */
  --stm-navLink-focusRingOffset: 2px; /* Focus ring offset (TOK-45). */
  --stm-navLink-motionDuration: 120ms; /* Fill/ink transition length (TOK-45) — raw core, four times across the two legs. */
  --stm-navLink-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Transition curve (TOK-45). */

  /* navigation */
  --stm-navigation-container: #1E293B; /* Navigation bar background */
  --stm-navigation-border: #334155; /* Bottom border separating nav from content */
  --stm-navigation-borderWidth: 1px; /* Weight of the bar's bottom rule — 1px, unchanged (TOK-86). The colour was a slot while the width was a raw core reach-in; TOK-121 also flagged this family for mixing brand-routed core widths with component slots, which diverge the day a brand turns its border knob. */
  --stm-navigation-elevation: none; /* Shadow under the bar — none by default, so this is value-preserving (TOK-86). StmAppBar owns one for the same reason: app chrome that scrolls over content needs to be able to lift off it, and the bottom hairline is not always enough. */
  --stm-navigation-brandColor: #BBF7D0; /* Brand mark / logo text color — dark green, WCAG AA on white (7:1). Uses core.color.successStrong instead of core.color.success which fails text-on-surface contrast. */
  --stm-navigation-brandTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-navigation-brandTypography-font-size: 20px;
  --stm-navigation-brandTypography-font-weight: 700;
  --stm-navigation-brandTypography-line-height: 1.375;
  --stm-navigation-height: 64px; /* Navigation bar height - 64px */
  --stm-navigation-paddingX: 24px; /* Horizontal padding inside nav bar */
  --stm-navigation-itemColor: #E2E8F0; /* Default nav item text color */
  --stm-navigation-itemColorHover: #F8FAFC; /* Nav item text on hover */
  --stm-navigation-itemHover: #334155; /* Nav item background on hover — subtle fill */
  --stm-navigation-itemActive: #122666; /* Active nav item background */
  --stm-navigation-onItemActive: #B5C6FA; /* Active nav item text — dark primary, WCAG AA on primarySubtle bg. Was core.color.primary (3.19:1 fail); onPrimaryContainer hits ~8:1. */
  --stm-navigation-itemWeightActive: 700; /* Weight of the current-page item — bold, unchanged (TOK-86); was a raw core reach-in beside a fully tokenized itemTypography. */
  --stm-navigation-itemPaddingX: 12px; /* Horizontal padding per nav item - 12px */
  --stm-navigation-itemPaddingY: 8px; /* Vertical padding per nav item - 8px */
  --stm-navigation-itemRadius: 8px; /* Nav item border radius — brand-configurable */
  --stm-navigation-itemGap: 4px; /* Gap between nav items - 4px */
  --stm-navigation-itemTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-navigation-itemTypography-font-size: 16px;
  --stm-navigation-itemTypography-font-weight: 700;
  --stm-navigation-itemTypography-line-height: 1.5;
  --stm-navigation-itemTypography-letter-spacing: 0em;
  --stm-navigation-motionDuration: 120ms; /* Item hover transition length — 150ms (TOK-86). This is also a parity fix: only the Blazor leg animated at all. */
  --stm-navigation-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Item hover transition curve (TOK-86). */
  --stm-navigation-groupGap: 24px; /* Gap between labelled link groups (GAP-08) — clearly wider than itemGap so groups read as clusters */
  --stm-navigation-groupLabelColor: #94A3B8; /* Group heading color (GAP-08) — tertiary, matches the SettlCare eyebrow precedent; WCAG AA on surface */
  --stm-navigation-groupLabelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-navigation-groupLabelTypography-font-size: 10px;
  --stm-navigation-groupLabelTypography-font-weight: 700;
  --stm-navigation-groupLabelTypography-line-height: 1.5;
  --stm-navigation-groupLabelTypography-letter-spacing: 0.08em;
  --stm-navigation-groupLabelTypography-text-transform: uppercase;
  --stm-navigation-onContainer: #F8FAFC; /* Ink for content a CONSUMER slots into this surface (CMP-65 / #621). The component's own parts each set their own colour and look perfect, so nothing here was missing until a HOST re-inked: an AuthLayout setting `color: onPrimary` for a green canvas left everything slotted in inheriting white, on white — 1.00:1. Defaults to core.color.onSurface, which is EXACTLY what slotted content already resolved to on an ordinary page, so this is value-preserving by construction and nothing moves except where it was already broken. */

  /* pageHeader */
  --stm-pageHeader-container: #1E293B; /* Page header background */
  --stm-pageHeader-borderBottom: #334155; /* Bottom border separating header from content */
  --stm-pageHeader-containerCanvas: #96ADF8; /* Surface=Canvas header band (#632) — the primary-filled page canvas. A header on a canvas page had only two options before this: keep its white band (two unrelated surfaces) or go Bare, which strips the surface and leaves the INK, i.e. dark title on a dark canvas. Same named-surface shape as StmFullScreenModal's containerCanvas (#600). */
  --stm-pageHeader-onCanvas: #0F172A; /* Ink for every text part of a Surface=Canvas header (#632) — title, entity id / subtitle, title-row meta, slim meta. Bound to the on-pair of containerCanvas, so contrast holds in every brand and both modes by construction; this pairing is exactly what a consumer re-pointing the container token from outside cannot get, and is why the fill and the ink ship together. There is no muted-on-primary step in the core roles, so the canvas header inks the muted parts at full strength rather than inventing one here (same call as StmFullScreenModal's formField label/help re-point). */
  --stm-pageHeader-borderBottomWidth: 1px; /* Bottom border width — 1px */
  --stm-pageHeader-paddingX: 14px; /* Horizontal padding */
  --stm-pageHeader-paddingY: 12px; /* Vertical padding */
  --stm-pageHeader-minHeight: 0px; /* Floor height of the header band — 0 by default, so this is value-preserving (TOK-87). It exists so a set of pages whose headers carry different content (one with an entity id and status, one with a bare title) still band to the same height instead of stepping as the user navigates. */
  --stm-pageHeader-elevation: none; /* Shadow under the header — none by default, so value-preserving (TOK-87). Needed the moment the header sticks: a hairline alone does not separate a fixed band from content scrolling under it. */
  --stm-pageHeader-stickyZIndex: 1100; /* Stacking band of the sticky header (TOK-87) — the same sticky layer StmModuleSidebar's fixed rail uses, so the two pieces of page chrome cannot fight. */
  --stm-pageHeader-stackGap: 16px; /* Vertical gap between breadcrumb, title row, and tab row */
  --stm-pageHeader-titleColor: #F8FAFC; /* Title text color */
  --stm-pageHeader-titleTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-pageHeader-titleTypography-font-size: 36px;
  --stm-pageHeader-titleTypography-font-weight: 700;
  --stm-pageHeader-titleTypography-line-height: 1.1;
  --stm-pageHeader-titleRowGap: 16px; /* Horizontal gap between title, entityId, meta chips, actions within the title row */
  --stm-pageHeader-entityIdColor: #E2E8F0; /* Entity id color — muted */
  --stm-pageHeader-titleMetaColor: #E2E8F0; /* Colour of the right-hand title-row meta (TOK-87) — was borrowing entityIdColor, which describes the id printed beside the TITLE, a different reading on the other side of the row. Same muted value today. */
  --stm-pageHeader-entityIdTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-pageHeader-entityIdTypography-font-size: 16px;
  --stm-pageHeader-entityIdTypography-font-weight: 400;
  --stm-pageHeader-entityIdTypography-line-height: 1.5;
  --stm-pageHeader-titleToMetaGap: 8px; /* Gap between title+id and the meta chip strip */
  --stm-pageHeader-metaGap: 8px; /* Gap BETWEEN meta items (the CVR line, a status chip) — 8px, unchanged (TOK-87). titleToMetaGap used to do two unrelated jobs: the distance from the title to the meta cluster, and the distance between the items inside it, so tightening one tightened the other. */
  --stm-pageHeader-metaSlimGap: 4px; /* Slim meta variant (PLAN-19) — tighter title→meta / meta-item gap used on non-overview tabs and subtitle-style headers */
  --stm-pageHeader-metaSlimColor: #E2E8F0; /* Slim meta variant text color — muted, matches entityId weighting (PLAN-19) */
  --stm-pageHeader-metaSlimTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-pageHeader-metaSlimTypography-font-size: 14px;
  --stm-pageHeader-metaSlimTypography-font-weight: 400;
  --stm-pageHeader-metaSlimTypography-line-height: 1.5;

  /* pagination */
  --stm-pagination-barContainer: #1E293B; /* Background of the BAR that WRAPS a paginator, not the paginator (TOK-134/§PB-7). Renamed from `container` because the footer-shaped name was hiding what these five actually describe: React bound them only inside the DataGrid footer, so a paginator mounted anywhere else — above a grid, under a list — drew no chrome at all. `bar*` = the wrapper; `navButton*`/`gap`/`labelTypography` = the control. */
  --stm-pagination-barOnContainer: #F8FAFC; /* Ink of the wrapping BAR — the range label and the page-size text (TOK-143, renamed from `onContainer`). It sat next to `barContainer` under the un-prefixed name, breaking the X / onX pairing the DS uses everywhere else and reading as a mistake to anyone who did not watch TOK-134 stop at five slots. */
  --stm-pagination-barBorder: #334155; /* Colour of the top border separating the bar from the content above it (TOK-143, renamed from `border`). §PB-7 predicted this rename would need no new bindings; reading the CSS first showed why that was wrong — see barBorderWidth. */
  --stm-pagination-barBorderWidth: 1px; /* Width of the bar's top separator — 1px (TOK-143, renamed from `borderWidth`). TOK-143 proposed renaming this and `border` to `bar*` on the grounds that all four bar surfaces already bound them. They did — and so did the NAV BUTTON, on both legs: `.stm-c-pagination__btn` reserved `border: <borderWidth> solid transparent` and painted `border-color: <border>` on hover. One slot was drawing two different edges on two different elements, which is the same overload TOK-102 found in `splitter.border`. So the rename came with a SPLIT rather than a relabel: the separator keeps these two, the item got navButtonBorderWidth/navButtonBorderHover. Value-preserving in every theme. */
  --stm-pagination-barMinHeight: 44px; /* Minimum height of the wrapping bar — `brand.size.controlMd`, so a paginator bar is as tall as any other control row (TOK-134/§PB-7, renamed from `minHeight`). */
  --stm-pagination-barPaddingX: 12px; /* Horizontal padding inside the wrapping bar (TOK-134/§PB-7, renamed from `paddingX`). */
  --stm-pagination-barPaddingY: 10px; /* Vertical padding inside the wrapping bar — `paddingCompact`, deliberately tighter than paddingX: a bar is wide and short (TOK-134/§PB-7, renamed from `paddingY`). */
  --stm-pagination-gap: 16px; /* Gap between the rows-per-page group, the range label, and the nav buttons */
  --stm-pagination-navButtonBorderWidth: 1px; /* Border width reserved on a nav button (TOK-143). The rule is `border: <this> solid transparent` at rest so the hover border does not shift the row — one width serving base and hover, which is why there is no separate hover-width slot. Split out of `borderWidth`, which also drew the BAR's top separator; same resolved 1px. */
  --stm-pagination-navButtonBorderHover: #334155; /* Border colour a nav button reveals on hover (TOK-143) — the only state in which its border is visible. Split out of `border`, which also coloured the BAR's top separator; same resolved value, and now the two edges can be tuned apart. */
  --stm-pagination-navButtonColor: #E2E8F0; /* Prev/next icon button color */
  --stm-pagination-navButtonHover: #96ADF8; /* Prev/next icon button hover color */
  --stm-pagination-navButtonDisabled: #94A3B8; /* Prev/next disabled color */
  --stm-pagination-navButtonSize: 32px; /* Prev/next hit target size */
  --stm-pagination-navButtonSizeSmall: 32px; /* Nav/page button hit target at size=small (TOK-88) — was raw core controlSm in the Blazor size modifier */
  --stm-pagination-navButtonSizeLarge: 52px; /* Nav/page button hit target at size=large (TOK-88) — was raw core controlLg in the Blazor size modifier */
  --stm-pagination-navButtonRadius: 8px; /* Nav/page button corner radius (TOK-88) — was a raw core radius-md, so a brand could not shape the pager independently. TOK-40: promoted off core.radius.md onto the BRAND button knob — the prev/next controls are buttons and were shaped by core while every other button in the row followed the brand. */
  --stm-pagination-navButtonPaddingX: 10px; /* Nav/page button HORIZONTAL padding (TOK-88). Fixes a real mis-wire: the Blazor rule fed horizontal padding from paddingY (a vertical token), so the two axes could never be tuned apart. Declared at paddingCompact — the value paddingY already resolved to — so the fix is semantic, not visual */
  --stm-pagination-navButtonActiveContainer: #96ADF8; /* Fill of the CURRENT page button (TOK-88) — the most brand-visible pager state, previously raw core primary in both legs */
  --stm-pagination-onNavButtonActive: #0F172A; /* Label colour on the current page button (TOK-88) — pairs with navButtonActiveContainer */
  --stm-pagination-navButtonActiveBorder: #96ADF8; /* Border of the current page button (TOK-88) — declared separately so an outlined-active brand treatment is expressible */
  --stm-pagination-ellipsisColor: #E2E8F0; /* The truncation ellipsis between page ranges (TOK-88) — borrowed labelColor before, so it could not be de-emphasised on its own. Same resolved value */
  --stm-pagination-focusRing: #7694F7; /* Keyboard focus ring colour on nav/page buttons (TOK-88) — completes the trio with focusRingWidth + focusRingOffset */
  --stm-pagination-focusRingWidth: 2px; /* Focus ring stroke width on nav/page buttons (TOK-88) */
  --stm-pagination-focusRingOffset: -2px; /* Focus ring offset on nav/page buttons (TOK-88) — the INSET recipe, since the buttons are radius-clipped (TOK-35) */
  --stm-pagination-motionDuration: 120ms; /* Hover/active colour transition on nav/page buttons (TOK-88) — was raw core duration-fast in the Blazor transition */
  --stm-pagination-barLabelColor: #E2E8F0; /* Colour of the range and 'Rows per page' labels inside the bar (TOK-134/§PB-7, renamed from `labelColor`). Wrapper-scoped like its four siblings; the label's TYPE is `labelTypography`, which belongs to the control and keeps its name. */
  --stm-pagination-labelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-pagination-labelTypography-font-size: 12px;
  --stm-pagination-labelTypography-font-weight: 700;
  --stm-pagination-labelTypography-line-height: 1.5;

  /* panelBar */
  --stm-panelBar-container: #1E293B; /* Panel background */
  --stm-panelBar-headerBg: #1E293B; /* Top-level header background */
  --stm-panelBar-headerBgHover: #334155; /* Header hover */
  --stm-panelBar-headerBgExpanded: #334155; /* Fill of an EXPANDED top-level header (TOK-89). Until now an open panel's header looked exactly like a closed one, so the only cue that a section was open was the chevron. Matches accordion.headerBgActive, which solves the same problem the same way. */
  --stm-panelBar-headerText: #F8FAFC; /* Header text */
  --stm-panelBar-childHeaderBg: #1E293B; /* Nested child header background */
  --stm-panelBar-childHeaderBgHover: #334155; /* Fill of a nested child header under the pointer (TOK-89). It reused the TOP-LEVEL headerBgHover — same value today, so this is value-preserving, but the child header rests on a different surface than its parent, so the two hovers have no reason to be locked together. */
  --stm-panelBar-childHeaderText: #E2E8F0; /* Nested child header text */
  --stm-panelBar-contentText: #E2E8F0; /* Leaf content text */
  --stm-panelBar-contentBg: #1E293B; /* Fill of the content region — the panel surface showing through today, so value-preserving (TOK-89). The region had a text colour and no fill of its own, so content could not be tinted away from its header. */
  --stm-panelBar-border: #334155; /* Panel border */
  --stm-panelBar-icon: #E2E8F0; /* Expand/collapse chevron */
  --stm-panelBar-iconSize: 1em; /* Chevron glyph box — 1em (TOK-89). The chevron had a colour and no size and simply inherited the header's font-size; 1em reproduces that exactly while making it a knob. */
  --stm-panelBar-disabledOpacity: 0.38; /* Opacity of a disabled panel item — 0.38, unchanged (TOK-89). */
  --stm-panelBar-borderWidth: 1px; /* Border width — 1px */
  --stm-panelBar-radius: 14px; /* Outer corner radius */
  --stm-panelBar-paddingX: 14px; /* Horizontal padding */
  --stm-panelBar-paddingY: 16px; /* Vertical padding */
  --stm-panelBar-indentStep: 24px; /* Left indent per nesting level */
  --stm-panelBar-headerTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-panelBar-headerTypography-font-size: 16px;
  --stm-panelBar-headerTypography-font-weight: 700;
  --stm-panelBar-headerTypography-line-height: 1.5;
  --stm-panelBar-childTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-panelBar-childTypography-font-size: 14px;
  --stm-panelBar-childTypography-font-weight: 700;
  --stm-panelBar-childTypography-line-height: 1.5;
  --stm-panelBar-motionDuration: 150ms; /* Header hover transition length — 200ms (TOK-89). */
  --stm-panelBar-iconMotionDuration: 200ms; /* Chevron rotation length — 300ms (TOK-89). Deliberately a SECOND duration: the two animations already ran at different core steps, and collapsing them would be a silent visual change rather than a tokenization. */
  --stm-panelBar-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Curve shared by the header hover and the chevron rotation (TOK-89) — both carried a literal `ease` beside a tokenized duration. */
  --stm-panelBar-onContainer: #F8FAFC; /* Ink for content a CONSUMER slots into this surface (CMP-65 / #621). The component's own parts each set their own colour and look perfect, so nothing here was missing until a HOST re-inked: an AuthLayout setting `color: onPrimary` for a green canvas left everything slotted in inheriting white, on white — 1.00:1. Defaults to core.color.onSurface, which is EXACTLY what slotted content already resolved to on an ordinary page, so this is value-preserving by construction and nothing moves except where it was already broken. */

  /* popover */
  --stm-popover-container: #1E293B; /* Popover background */
  --stm-popover-border: #334155; /* Popover border */
  --stm-popover-dividerColor: #334155; /* Rule above the actions footer (TOK-90) — it reused `border`, the popover's OUTER edge, which is a different edge on a different surface. Same value today. */
  --stm-popover-dividerWidth: 1px; /* Weight of the actions-footer rule (TOK-90). This is also a parity fix: React drew it with a raw core width while Blazor drew it with the popover's own borderWidth, so the two legs' footers were one brand-knob turn away from differing. */
  --stm-popover-headerText: #F8FAFC; /* Header text color */
  --stm-popover-bodyText: #E2E8F0; /* Body text color */
  --stm-popover-shadow: inset 0px 0px 0px 9999px rgba(255,255,255,0.08), 0px 8px 16px 0px rgba(255,255,255,0.08); /* Popover elevation shadow */
  --stm-popover-radius: 14px; /* Corner radius */
  --stm-popover-borderWidth: 1px; /* Border thickness — 1px */
  --stm-popover-paddingX: 12px; /* Horizontal popover padding — same value the single `padding` slot applied to both axes, so value-preserving (TOK-90). Menus and modals both split their padding; a popover whose body is a list wants tighter sides than top and bottom. Replaces the single `padding` slot, which was retired in the same change rather than left behind as a second source of truth for the same measurement. */
  --stm-popover-paddingY: 12px; /* Vertical popover padding — value-preserving (TOK-90). */
  --stm-popover-maxWidth: 320px; /* Maximum width */
  --stm-popover-offset: 8px; /* Distance between the anchor and the popover — 8px (TOK-90). It was a JS literal in BOTH legs (a `const gap = 8` in the React positioner and a `gap ?? 8` in stm-interop), so the one measurement that decides how the popover sits against its trigger lived outside the theme. Both legs now read this token at position time and keep their explicit prop as an override. */
  --stm-popover-gap: 8px; /* Gap between header/body/actions */
  --stm-popover-headerGap: 0px; /* Extra space between the header and the body, on top of the column `gap` — 0 by default, so this is value-preserving (TOK-90). One `gap` slot used to do three unrelated jobs (the column stack, the action button row, the footer's top padding); the row and the footer now have their own slot, and this one lets a titled popover breathe under its heading without widening every gap in the surface. */
  --stm-popover-actionsGap: 8px; /* Gap between action buttons in the footer, and the footer's top padding — 8px, unchanged (TOK-90). */
  --stm-popover-headerTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-popover-headerTypography-font-size: 16px;
  --stm-popover-headerTypography-font-weight: 700;
  --stm-popover-headerTypography-line-height: 1.5;
  --stm-popover-bodyTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-popover-bodyTypography-font-size: 14px;
  --stm-popover-bodyTypography-font-weight: 400;
  --stm-popover-bodyTypography-line-height: 1.5;
  --stm-popover-motionDuration: 225ms; /* Popover enter animation length (TOK-90) — the documented enter duration, which core/motion.json pairs with easing.decelerate for exactly this case ("surfaces arriving on screen: drawers, dialogs, popovers"). All four placement modifiers set a transform-origin 'for animation direction' and no animation existed to use it; the origins were dead weight in both legs. */
  --stm-popover-motionEasing: cubic-bezier(0, 0, 0.2, 1); /* Popover enter animation curve (TOK-90) — a decelerating curve, because the popover is entering rather than leaving. */

  /* progressBar */
  --stm-progressBar-track: #1E293B; /* Background track color — unfilled portion */
  --stm-progressBar-fill: #96ADF8; /* Default fill color — can be overridden contextually with module/area color */
  --stm-progressBar-fillComplete: #4ADE80; /* Fill color when progress = 100% — switches to success green */
  --stm-progressBar-labelColor: #F8FAFC; /* Colour of the in-bar dashboard label (TOK-91) — a raw core reach-in until now, which meant the label could not be flipped to an on-fill contrast colour once the fill sweeps under it. Same onSurface today. */
  --stm-progressBar-height: 6px; /* Default track height 6px — the context variants have their own slots: heightCard 4px / heightModal 3px / heightDashboard 28px (TOK-121: the old note predated the slots and contradicted heightDashboard) */
  --stm-progressBar-radius: 2px; /* Track corner radius — subtle rounding PROMOTED to {brand.radius.track} 2026-08-20 (TOK-40, owner: promote all four). Value-preserving in every brand — 2px on a thin filled bar is a PROPORTION, not a shape identity, so the promotion buys the SEAM and not a change. A brand that wants square bar ends now sets one knob instead of editing six components. */
  --stm-progressBar-radiusModal: 0px; /* Corner rounding of the full-bleed modal hairline — 0, unchanged (TOK-91). It was a literal `border-radius: 0` with a comment explaining it, in both legs. */
  --stm-progressBar-labelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-progressBar-labelTypography-font-size: 12px;
  --stm-progressBar-labelTypography-font-weight: 700;
  --stm-progressBar-labelTypography-line-height: 1.5;
  --stm-progressBar-heightCard: 4px; /* Card-context track height (TOK-91/TOK-47: both legs consumed this var with a 4px fallback before the slot existed — the fallback was the only real value) */
  --stm-progressBar-heightModal: 3px; /* Modal full-bleed hairline track height (TOK-91) */
  --stm-progressBar-heightDashboard: 28px; /* Dashboard filled-bar track height — tall enough for the in-bar label (TOK-91) */
  --stm-progressBar-fillTransitionDuration: 300ms; /* How long the fill takes to travel to a new value — 500ms (TOK-91). Raw core in both legs, and the one motion a consumer actually notices on a progress bar. */
  --stm-progressBar-fillTransitionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Curve of the fill travel (TOK-91) — a literal `ease` fallback beside a tokenized duration. */
  --stm-progressBar-indeterminateDuration: 1400ms; /* Period of the indeterminate sweep — 1.4s (TOK-91). Raw core; the busy sweep and the determinate travel are different motions and had no way to be tuned apart. */

  /* progressIndicator */
  --stm-progressIndicator-container: #1E293B; /* Summary card background — white card on page */
  --stm-progressIndicator-containerBorder: #334155; /* Summary card border */
  --stm-progressIndicator-containerRadius: 14px; /* Summary card border radius — brand-configurable, matches card radius */
  --stm-progressIndicator-containerPadding: 24px; /* Summary card padding — 24px */
  --stm-progressIndicator-containerGap: 8px; /* Gap between summary rows — 8px */
  --stm-progressIndicator-titleColor: #F8FAFC; /* Summary title text — 'Samlet fremskridt — 35%' */
  --stm-progressIndicator-titleTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-progressIndicator-titleTypography-font-size: 14px;
  --stm-progressIndicator-titleTypography-font-weight: 700;
  --stm-progressIndicator-titleTypography-line-height: 1.5;
  --stm-progressIndicator-statsColor: #E2E8F0; /* Stats text — '2/5 besluttet · 0/1 konfigureret' */
  --stm-progressIndicator-statsTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-progressIndicator-statsTypography-font-size: 14px;
  --stm-progressIndicator-statsTypography-font-weight: 400;
  --stm-progressIndicator-statsTypography-line-height: 1.5;
  --stm-progressIndicator-dotSize: 8px; /* Status dot diameter — 8px */
  --stm-progressIndicator-barTrack: #1E293B; /* Progress bar track (empty portion) — muted surface */
  --stm-progressIndicator-barFill: #96ADF8; /* Progress bar fill (completed portion) — primary */
  --stm-progressIndicator-barHeight: 6px; /* Progress track height — 6px (no matching sizing scale step) */
  --stm-progressIndicator-barRadius: 2px; /* Progress track corner radius — sm (~4px; was 3px literal, small upward shift) PROMOTED to {brand.radius.track} 2026-08-20 (TOK-40, owner: promote all four). Value-preserving in every brand — 2px on a thin filled bar is a PROPORTION, not a shape identity, so the promotion buys the SEAM and not a change. A brand that wants square bar ends now sets one knob instead of editing six components. */
  --stm-progressIndicator-connectorThickness: 2px; /* StmProgressOverview dots-variant connector thickness — 2px hairline */
  --stm-progressIndicator-connectorMinLength: 16px; /* StmProgressOverview dots-variant connector minimum length — 16px */
  --stm-progressIndicator-segmentThickness: 6px; /* StmProgressOverview bar-variant segment thickness — 6px */
  --stm-progressIndicator-segmentMinLength: 32px; /* StmProgressOverview bar-variant vertical segment minimum length — 32px */
  --stm-progressIndicator-dotActive: #FFD84A; /* In-progress status dot — orange/yellow ('I gang') */
  --stm-progressIndicator-dotUndecided: #96ADF8; /* Undecided status dot — blue ('Ikke besluttet') */
  --stm-progressIndicator-dotComplete: #4ADE80; /* Completed status dot — green */
  --stm-progressIndicator-labelActive: #FFD84A; /* In-progress legend text */
  --stm-progressIndicator-labelUndecided: #96ADF8; /* Undecided legend text — blue */
  --stm-progressIndicator-labelComplete: #4ADE80; /* Completed legend text */
  --stm-progressIndicator-legendGap: 16px; /* Gap between legend items — 16px */
  --stm-progressIndicator-legendDotGap: 4px; /* Gap between a step indicator and its label — TOK-92. The scan asked for a NEW itemLabelGap and left this dead, but the two name the same measure, and shipping both would be the second-source-of-truth problem (§TOK-47). This slot is wired instead. Its value moved from brand.space.gapTight (6px) to core.space.xs (4px) to match what both legs actually draw — unobservable, since nothing consumed it. NOTE the live sibling progressSummary.legendDotGap is a DIFFERENT slot and stays at 6px. */
  --stm-progressIndicator-labelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-progressIndicator-labelTypography-font-size: 12px;
  --stm-progressIndicator-labelTypography-font-weight: 400;
  --stm-progressIndicator-labelTypography-line-height: 1.5;
  --stm-progressIndicator-containerBorderWidth: 1px; /* Card border width — TOK-92. Both legs drew this from raw core.border.width.thin while every other card property was tokenised; the sibling progressSummary already owns a borderWidth slot. Value-preserving. */
  --stm-progressIndicator-elevation: none; /* Card elevation — TOK-92. Declared at level 000 (no shadow) so this is VALUE-PRESERVING: the card is flat today and stays flat. The sibling progressSummary sits at level 001, so a brand that wants the two cards to match now has the knob; raising it here is a design decision, not a token fix. */
  --stm-progressIndicator-segmentRadius: 9999px; /* Bar-variant segment corner rounding — TOK-92. The segment reached into core.radius.full directly while the sibling bar already had barRadius, so the two shapes could not be tuned apart. Value-preserving. */
  --stm-progressIndicator-labelWeightActive: 700; /* Weight of the ACTIVE step label — TOK-92. Was a raw --stm-typography-weight-bold reach-in (BF-21 brand bold: 600 under retur, 700 elsewhere). Declared as a weight rather than the spec’s proposed labelTypographyActive composite: the active label differs from labelTypography in weight ALONE, and a second composite would duplicate family/size/lineHeight and let them drift apart. */

  /* progressSummary */
  --stm-progressSummary-container: #1E293B; /* Card background */
  --stm-progressSummary-border: #334155; /* Card border */
  --stm-progressSummary-borderWidth: 1px; /* Card border width — 1px */
  --stm-progressSummary-radius: 14px; /* Card corner radius */
  --stm-progressSummary-padding: 24px; /* Inner padding — 24px */
  --stm-progressSummary-gap: 8px; /* Gap between rows — 8px */
  --stm-progressSummary-elevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.05), 0px 1px 3px 0px rgba(255,255,255,0.10); /* Subtle card shadow */
  --stm-progressSummary-titleColor: #F8FAFC; /* Title text color */
  --stm-progressSummary-titleTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-progressSummary-titleTypography-font-size: 16px;
  --stm-progressSummary-titleTypography-font-weight: 700;
  --stm-progressSummary-titleTypography-line-height: 1.5;
  --stm-progressSummary-fractionColor: #94A3B8; /* Fraction text (2/5 · 0/1) — muted */
  --stm-progressSummary-fractionTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-progressSummary-fractionTypography-font-size: 12px;
  --stm-progressSummary-fractionTypography-font-weight: 400;
  --stm-progressSummary-fractionTypography-line-height: 1.5;
  --stm-progressSummary-legendTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-progressSummary-legendTypography-font-size: 12px;
  --stm-progressSummary-legendTypography-font-weight: 400;
  --stm-progressSummary-legendTypography-line-height: 1.5;
  --stm-progressSummary-headerGap: 16px; /* Gap between title and fraction in header row — 16px */
  --stm-progressSummary-legendDotGap: 6px; /* Gap between legend dot and label — 6px (no matching space scale step) */
  --stm-progressSummary-trackHeight: 8px; /* Progress bar track height */
  --stm-progressSummary-trackRadius: 9999px; /* Progress track end rounding — brand-routed via brand.radius.pill (SETTL squares to 4px, TOK-40/118). Consumers must NOT carry a 999px fallback (contradicts every brand). */
  --stm-progressSummary-trackColor: #334155; /* Progress bar background track */
  --stm-progressSummary-fillColor: #96ADF8; /* Progress bar fill — brand primary */
  --stm-progressSummary-fillColorComplete: #4ADE80; /* Bar fill once the summary reaches 100% (TOK-93). Its sibling StmProgressBar has had fillComplete from the start; this card could reach full and still read as in-progress blue. */
  --stm-progressSummary-legendGap: 24px; /* Gap between legend items — 24px */
  --stm-progressSummary-legendDotSize: 8px; /* Legend status dot diameter */
  --stm-progressSummary-legendDotRadius: 9999px; /* Legend dot rounding (TOK-93) — was a literal 50% in both legs. radius.full renders identically on the square dot box and keeps the dot round under SETTL, whose brand.radius.pill squares to 4px (TOK-118). */
  --stm-progressSummary-legendTextColor: #94A3B8; /* Legend label text */
  --stm-progressSummary-dotActive: #FFD84A; /* Active/in-progress legend dot — orange */
  --stm-progressSummary-dotComplete: #4ADE80; /* Legend dot for a completed bucket (TOK-93). The legend could say active, undecided and irrelevant but not done — the one status a progress summary most needs to show. */
  --stm-progressSummary-dotUndecided: #96ADF8; /* Undecided legend dot — blue */
  --stm-progressSummary-dotIrrelevant: #94A3B8; /* Irrelevant legend dot — gray */
  --stm-progressSummary-fillTransitionDuration: 300ms; /* How long the fill takes to travel to a new value — 500ms (TOK-93), previously raw core. */
  --stm-progressSummary-fillTransitionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Curve of the fill travel (TOK-93) — a literal `ease` fallback beside a tokenized duration. */
  --stm-progressSummary-onContainer: #F8FAFC; /* Ink for content a CONSUMER slots into this surface (CMP-65 / #621). The component's own parts each set their own colour and look perfect, so nothing here was missing until a HOST re-inked: an AuthLayout setting `color: onPrimary` for a green canvas left everything slotted in inheriting white, on white — 1.00:1. Defaults to core.color.onSurface, which is EXACTLY what slotted content already resolved to on an ordinary page, so this is value-preserving by construction and nothing moves except where it was already broken. */

  /* proseList */
  --stm-proseList-markerColor: #E2E8F0; /* Bullet/number ink. Quieter than the item text on purpose — the marker is structure, not content, and a full-strength bullet reads as a second sentence-initial glyph. */
  --stm-proseList-itemGap: 4px; /* Vertical gap between items. 4px, not a paragraph gap: items in a prose list are one thought each, and spacing them like paragraphs breaks the list into unrelated lines. */
  --stm-proseList-indent: 24px; /* Marker column — the inline start padding that holds the bullet/number. 24px replaces the UA's 40px, which was tuned for a document, not for a DS surface already inset by its container. */

  /* radio */
  --stm-radio-circleSize: 20px; /* Radio circle outer diameter - 20px */
  --stm-radio-dotSize: 10px; /* Inner dot diameter when selected - 10px */
  --stm-radio-border: #94A3B8; /* Unselected radio border */
  --stm-radio-borderHover: #96ADF8; /* Radio border on hover */
  --stm-radio-borderSelected: #96ADF8; /* Selected radio border */
  --stm-radio-fill: #96ADF8; /* Selected radio inner dot color */
  --stm-radio-borderWidth: 2px; /* Radio circle border width - 2px */
  --stm-radio-groupGap: 12px; /* Vertical gap between radio options - 12px */
  --stm-radio-labelGap: 8px; /* Gap between radio circle and label text - 8px */
  --stm-radio-optionDescriptionGap: 4px; /* Gap between a radio option's label and its helper/description line (2-line option, issue #24) - 4px */
  --stm-radio-cardContainer: #1E293B; /* Radio card variant background - for billing method cards */
  --stm-radio-cardContainerSelected: #122666; /* Selected radio card background */
  --stm-radio-cardBorder: #94A3B8; /* Radio card border */
  --stm-radio-cardBorderSelected: #96ADF8; /* Selected radio card border - brand primary */
  --stm-radio-cardPadding: 16px; /* Radio card internal padding - 16px */
  --stm-radio-cardRadius: 14px; /* Radio card border radius — 10px. TOK-40 STRAGGLER, with the decision pre-made so the trigger does not have to re-derive it: this should become {brand.radius.card}, because it IS a card and the knob exists. It is not repointed here because that is a VALUE change per brand (starter/systemate 10px→14px, retur 10px→12px, settl 10px→base), and under SETTL that change is the POINT — TOK-118 squared the whole family and this slot silently stayed round. Do it the next time this component is touched, with eyes on. PROMOTED 2026-08-20 (owner: promote all four), per the pre-made decision above. Rendered per brand before merging — starter/systemate 10->14px, retur 10->12px, settl -> base (4px), and under SETTL that change IS the point. */
  --stm-radio-labelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-radio-labelTypography-font-size: 16px;
  --stm-radio-labelTypography-font-weight: 400;
  --stm-radio-labelTypography-line-height: 1.5;
  --stm-radio-label: #F8FAFC; /* Radio option label color (TOK-47: Blazor consumed var(--stm-radio-label, inherit) before this slot existed; onSurface is the ambient body color the old `inherit` resolved to in practice) */
  --stm-radio-descriptionColor: #E2E8F0; /* Helper/description line under a 2-line option (issue #24) — TOK-94. Both legs painted this from raw core; value-preserving. */
  --stm-radio-descriptionTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-radio-descriptionTypography-font-size: 14px;
  --stm-radio-descriptionTypography-font-weight: 400;
  --stm-radio-descriptionTypography-line-height: 1.5;
  --stm-radio-cardBorderWidth: 1px; /* Card-variant border width — TOK-94. The card family tokenised every colour but left the width on raw core, so a brand could not thicken the card outline. */
  --stm-radio-transitionDuration: 120ms; /* Border-colour transition on the circle — TOK-94. Blazor reached into core.motion directly and React had no transition at all; this is the single knob both legs now read. */
  --stm-radio-transitionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Easing for the circle transition — TOK-94. Retires the `, ease` literal fallback the Blazor rule carried. */

  /* rating */
  --stm-rating-filled: #FFD84A; /* Filled star color — amber/gold */
  --stm-rating-empty: #334155; /* Empty star outline */
  --stm-rating-hovered: #FFE47A; /* Star color on hover preview */
  --stm-rating-starSize: 24px; /* Individual star size */
  --stm-rating-starSizeSm: 20px; /* Star box in the small variant — 20px (TOK-95). The variant used to be `calc(starSize * 0.8)`, a magic multiplier that resolved to 19.2px: a sub-pixel star on a system where every other glyph sits on the icon scale. Snapping it to icon.size.md moves the small star by 0.8px and puts it back on the ladder. */
  --stm-rating-starSizeLg: 32px; /* Star box in the large variant — 32px (TOK-95). Was `calc(starSize * 1.4)` = 33.6px, the same sub-pixel problem as starSizeSm; 32px is the nearest space step and moves the large star by 1.6px. */
  --stm-rating-hoverScale: 1.1; /* How much a star grows under the pointer (TOK-95) — a literal scale(1.1) in both legs. It is the component's only pointer feedback, so a brand that wants a calmer rating had to override the rule. */
  --stm-rating-gap: 4px; /* Gap between stars */
  --stm-rating-transitionDuration: 150ms; /* Length of the colour and scale transition on a star — 200ms (TOK-95), previously raw core in both legs. */
  --stm-rating-transitionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Curve of the star transition (TOK-95) — a literal `ease` fallback beside a tokenized duration. */
  --stm-rating-disabledOpacity: 0.38; /* Opacity of a disabled rating — 0.38, unchanged (TOK-95). */

  /* richTextEditor */
  --stm-richTextEditor-container: #1E293B; /* Editing surface — the sheet the copy is written on. Matches the input surface rather than the page, so the editor reads as a field. */
  --stm-richTextEditor-onContainer: #F8FAFC; /* Ink for the edited content. Declared rather than inherited (CMP-65): the editable region holds CONSUMER content, so a host that re-inked its canvas would otherwise leave the copy on an ink chosen for a different surface. */
  --stm-richTextEditor-border: #94A3B8; /* Resting border. Aliases the input border so an editor and a text field in the same form read as the same class of control. */
  --stm-richTextEditor-borderHover: #E2E8F0; /* Border on hover. */
  --stm-richTextEditor-borderFocus: #96ADF8; /* Border while the editing surface has focus. */
  --stm-richTextEditor-borderError: #FB7185; /* Border in the invalid state — the same trigger border StmDropDown and StmTextField use, so validation looks identical across the form. */
  --stm-richTextEditor-borderWidth: 1px; /* Border weight. */
  --stm-richTextEditor-radius: 8px; /* Corner radius. On the shared input radius, not the button one. */
  --stm-richTextEditor-toolbarContainer: #1E293B; /* Toolbar ground — one step off the editing surface so the chrome is visibly not the content. */
  --stm-richTextEditor-toolbarBorder: #334155; /* Rule between the toolbar and the editing surface. */
  --stm-richTextEditor-toolbarPaddingX: 4px; /* Toolbar inline padding. */
  --stm-richTextEditor-toolbarPaddingY: 4px; /* Toolbar block padding. */
  --stm-richTextEditor-toolbarGap: 4px; /* Gap between toolbar tools. */
  --stm-richTextEditor-toolColor: #E2E8F0; /* Resting ink for a toolbar tool. */
  --stm-richTextEditor-toolColorActive: #96ADF8; /* Ink for a tool whose formatting is active at the cursor. */
  --stm-richTextEditor-toolContainerHover: #334155; /* Hover fill for a toolbar tool. */
  --stm-richTextEditor-toolContainerActive: #122666; /* Fill for a tool whose formatting is active at the cursor. */
  --stm-richTextEditor-toolRadius: 2px; /* Corner radius of a toolbar tool. */
  --stm-richTextEditor-toolSize: 32px; /* Hit target for a toolbar tool. On the shared control axis so it matches every other small control. */
  --stm-richTextEditor-contentPaddingX: 16px; /* Inline padding of the editing surface. */
  --stm-richTextEditor-contentPaddingY: 8px; /* Block padding of the editing surface. */
  --stm-richTextEditor-minHeight: 12rem; /* Default height of the editing surface. In rem so it follows the text it holds — an e-mail body wants roughly a dozen lines before it scrolls, and a fixed px height would not track a brand's type scale. */
  --stm-richTextEditor-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-richTextEditor-typography-font-size: 16px;
  --stm-richTextEditor-typography-font-weight: 400;
  --stm-richTextEditor-typography-line-height: 1.5;
  --stm-richTextEditor-disabledContainer: #1E293B; /* Editing surface when the editor is not interactive. */
  --stm-richTextEditor-disabledText: #94A3B8; /* Ink when the editor is not interactive. */
  --stm-richTextEditor-disabledBorder: #334155; /* Border when the editor is not interactive. */
  --stm-richTextEditor-content-heading1Typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-richTextEditor-content-heading1Typography-font-size: 56px;
  --stm-richTextEditor-content-heading1Typography-font-weight: 700;
  --stm-richTextEditor-content-heading1Typography-line-height: 1.1;
  --stm-richTextEditor-content-heading2Typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-richTextEditor-content-heading2Typography-font-size: 44px;
  --stm-richTextEditor-content-heading2Typography-font-weight: 700;
  --stm-richTextEditor-content-heading2Typography-line-height: 1.1;
  --stm-richTextEditor-content-heading3Typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-richTextEditor-content-heading3Typography-font-size: 36px;
  --stm-richTextEditor-content-heading3Typography-font-weight: 700;
  --stm-richTextEditor-content-heading3Typography-line-height: 1.1;
  --stm-richTextEditor-content-heading4Typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-richTextEditor-content-heading4Typography-font-size: 28px;
  --stm-richTextEditor-content-heading4Typography-font-weight: 700;
  --stm-richTextEditor-content-heading4Typography-line-height: 1.1;
  --stm-richTextEditor-content-headingColor: #F8FAFC; /* Ink for every content heading. Uses the heading role rather than onSurface, so headings keep their emphasis when a brand darkens body ink. */
  --stm-richTextEditor-content-headingSpaceBefore: 24px; /* Space above a content heading. Larger than the space below, so a heading groups with the text it introduces rather than floating between blocks. */
  --stm-richTextEditor-content-headingSpaceAfter: 4px; /* Space below a content heading, before its first paragraph. */
  --stm-richTextEditor-content-paragraphSpace: 8px; /* Space between consecutive paragraphs in the content. */
  --stm-richTextEditor-content-linkColor: #96ADF8; /* Link ink inside the content. The link role, not primary - a link in prose is not a button. */
  --stm-richTextEditor-content-linkColorHover: #B5C6FA; /* Link ink on hover in the editor and preview. Inert in e-mail, which has no hover. */
  --stm-richTextEditor-content-listPaddingInline: 24px; /* Indent of a <ul>/<ol> from the content edge. */
  --stm-richTextEditor-content-listItemSpace: 1px; /* Space between list items. Deliberately tight - a reminder-mail list is a list, not a set of paragraphs. */
  --stm-richTextEditor-content-listMarkerColor: #E2E8F0; /* Bullet/number ink, one step back from the item text so the marker does not compete with it. */
  --stm-richTextEditor-content-blockquoteBorder: #334155; /* The rule down the inline-start edge of a blockquote. */
  --stm-richTextEditor-content-blockquoteBorderWidth: 5px; /* Thickness of the blockquote rule. Heavy rather than thin: at thin it reads as a rendering artefact in a mail client that drops the padding. */
  --stm-richTextEditor-content-blockquotePaddingInline: 16px; /* Gap between the blockquote rule and its text. */
  --stm-richTextEditor-content-blockquoteColor: #E2E8F0; /* Blockquote ink - quieter than body, since a quote is secondary to the copy around it. */
  --stm-richTextEditor-content-codeContainer: #1E293B; /* Fill behind inline <code> and <pre>. */
  --stm-richTextEditor-content-codeOnContainer: #F8FAFC; /* Ink for code. Full-strength: code in this content is usually an identifier or a reference number in a contract, which has to be legible. */
  --stm-richTextEditor-content-codeRadius: 1px; /* Corner radius of a code fill. */
  --stm-richTextEditor-content-codePaddingX: 1px; /* Horizontal padding inside a code fill. */
  --stm-richTextEditor-content-codePaddingY: 0px; /* Vertical padding inside inline code - none, so an inline span does not disturb the line box around it. */
  --stm-richTextEditor-content-codeTypography-font-family: Roboto Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
  --stm-richTextEditor-content-codeTypography-font-size: 14px;
  --stm-richTextEditor-content-codeTypography-font-weight: 400;
  --stm-richTextEditor-content-codeTypography-line-height: 1.5;
  --stm-richTextEditor-content-tableBorder: #334155; /* Cell borders. The four production member-contract documents ARE tables, which is why the sanitiser keeps the table element set at all. */
  --stm-richTextEditor-content-tableBorderWidth: 1px; /* Cell border thickness. */
  --stm-richTextEditor-content-tableCellPaddingX: 8px; /* Horizontal padding inside a table cell. */
  --stm-richTextEditor-content-tableCellPaddingY: 4px; /* Vertical padding inside a table cell. */
  --stm-richTextEditor-content-tableHeaderContainer: #1E293B; /* Fill behind a <th> row. */
  --stm-richTextEditor-content-tableHeaderOnContainer: #F8FAFC; /* Ink in a <th> row. */
  --stm-richTextEditor-content-ruleColor: #334155; /* An <hr> in the content. */
  --stm-richTextEditor-content-ruleThickness: 1px; /* Thickness of a content <hr>. */
  --stm-richTextEditor-content-ruleSpace: 16px; /* Space above and below a content <hr>. */
  --stm-richTextEditor-content-imageRadius: 2px; /* Corner radius on a content <img>. The reminder mails embed their logo as a cid: reference, so this is a real rendering path rather than a hypothetical one. */
  --stm-richTextEditor-block-space: 16px; /* Space above and below an inserted block, separating it from the prose. */
  --stm-richTextEditor-block-calloutRadius: 8px; /* Callout corner radius. */
  --stm-richTextEditor-block-calloutPaddingX: 16px; /* Callout horizontal padding. */
  --stm-richTextEditor-block-calloutPaddingY: 8px; /* Callout vertical padding. */
  --stm-richTextEditor-block-calloutGap: 8px; /* Gap between a callout accent edge and its text. */
  --stm-richTextEditor-block-calloutAccentWidth: 5px; /* Width of the callout inline-start accent. Heavy because the accent IS the severity signal once a mail client drops the background fill - which many do. */
  --stm-richTextEditor-block-buttonContainer: #96ADF8; /* Fill of an inserted call-to-action link. A LINK styled as a button, never a <button>: the sanitiser drops <button> with its content, and a mail client cannot submit one. */
  --stm-richTextEditor-block-buttonOnContainer: #0F172A; /* Ink on the call-to-action link. */
  --stm-richTextEditor-block-buttonRadius: 8px; /* Call-to-action corner radius. */
  --stm-richTextEditor-block-buttonPaddingX: 16px; /* Call-to-action horizontal padding. */
  --stm-richTextEditor-block-buttonPaddingY: 8px; /* Call-to-action vertical padding. */
  --stm-richTextEditor-block-buttonTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-richTextEditor-block-buttonTypography-font-size: 16px;
  --stm-richTextEditor-block-buttonTypography-font-weight: 700;
  --stm-richTextEditor-block-buttonTypography-line-height: 1.5;
  --stm-richTextEditor-block-buttonTypography-letter-spacing: 0em;
  --stm-richTextEditor-blockCalloutInfo-container: #172554; /* Callout fill for info. The light surface tint, not the saturated container - a callout is a passage of text, and saturated fill behind a paragraph fails contrast at body size. */
  --stm-richTextEditor-blockCalloutInfo-border: #BFDBFE; /* Callout accent edge for info - the part that survives a mail client stripping backgrounds. */
  --stm-richTextEditor-blockCalloutInfo-onContainer: #BFDBFE; /* Callout ink for info. */
  --stm-richTextEditor-blockCalloutSuccess-container: #052e16; /* Callout fill for success. The light surface tint, not the saturated container - a callout is a passage of text, and saturated fill behind a paragraph fails contrast at body size. */
  --stm-richTextEditor-blockCalloutSuccess-border: #BBF7D0; /* Callout accent edge for success - the part that survives a mail client stripping backgrounds. */
  --stm-richTextEditor-blockCalloutSuccess-onContainer: #BBF7D0; /* Callout ink for success. */
  --stm-richTextEditor-blockCalloutWarning-container: #4A3600; /* Callout fill for warning. The light surface tint, not the saturated container - a callout is a passage of text, and saturated fill behind a paragraph fails contrast at body size. */
  --stm-richTextEditor-blockCalloutWarning-border: #FFEFAD; /* Callout accent edge for warning - the part that survives a mail client stripping backgrounds. */
  --stm-richTextEditor-blockCalloutWarning-onContainer: #FFEFAD; /* Callout ink for warning. */
  --stm-richTextEditor-blockCalloutError-container: #4C0519; /* Callout fill for error. The light surface tint, not the saturated container - a callout is a passage of text, and saturated fill behind a paragraph fails contrast at body size. */
  --stm-richTextEditor-blockCalloutError-border: #FECDD3; /* Callout accent edge for error - the part that survives a mail client stripping backgrounds. */
  --stm-richTextEditor-blockCalloutError-onContainer: #FECDD3; /* Callout ink for error. */

  /* segmentedControl */
  --stm-segmentedControl-container: #1E293B; /* Outer track/container background — light gray */
  --stm-segmentedControl-containerBorder: #334155; /* Container border color */
  --stm-segmentedControl-containerBorderWidth: 1px; /* Container border width — 1px */
  --stm-segmentedControl-containerRadius: 9999px; /* Container outer radius — brand-routed pill (SETTL squares to 4px, TOK-40/118) */
  --stm-segmentedControl-containerPadding: 2px; /* Inner padding of track — 2px to give selected pill inset */
  --stm-segmentedControl-itemContainer: transparent; /* Unselected item background — transparent */
  --stm-segmentedControl-itemContainerSelected: #1E293B; /* Selected item background — white pill stands out from track */
  --stm-segmentedControl-itemContainerSelectedPositive: #4ADE80; /* Selected 'Ja' item — green filled pill */
  --stm-segmentedControl-itemContainerSelectedNegative: #FB7185; /* Selected 'Nej' item — red filled pill */
  --stm-segmentedControl-itemText: #E2E8F0; /* Unselected item text color */
  --stm-segmentedControl-itemTextSelected: #F8FAFC; /* Selected item text — neutral selected */
  --stm-segmentedControl-itemTextSelectedPositive: #0F172A; /* Selected 'Ja' text — white on green */
  --stm-segmentedControl-itemTextSelectedNegative: #0F172A; /* Selected 'Nej' text — white on red */
  --stm-segmentedControl-itemRadius: 9999px; /* Individual item radius — brand-routed pill (TOK-40/118) */
  --stm-segmentedControl-itemPaddingX: 12px; /* Horizontal padding per pill — 12px */
  --stm-segmentedControl-compactPaddingX: 8px; /* Horizontal segment padding in the compact density — 8px, unchanged (TOK-96). The compact variant was built entirely from raw core reach-ins, and its target height existed only as a code comment ("~24px"). */
  --stm-segmentedControl-itemPaddingY: 6px; /* Vertical padding per pill — 6px */
  --stm-segmentedControl-compactPaddingY: 1px; /* Vertical segment padding in the compact density — 1px, unchanged (TOK-96). */
  --stm-segmentedControl-itemGap: 2px; /* Gap between pill items — 2px */
  --stm-segmentedControl-compactGap: 1px; /* Shell padding and segment gap in the compact density — 1px, unchanged (TOK-96). */
  --stm-segmentedControl-compactMinHeight: 24px; /* Floor height of the compact control — 24px (TOK-96). It was the variant's stated design target and lived only in a CSS comment, so nothing enforced it. */
  --stm-segmentedControl-caretSize: 8px; /* Box of the overflow caret drawn on the wrapper — 8px, unchanged (TOK-96). It was derived as calc(--stm-icon-size-sm / 2), which is a value hidden inside arithmetic on a foreign token. */
  --stm-segmentedControl-caretThickness: 2px; /* Stroke weight of the overflow caret — 2px, unchanged (TOK-96). */
  --stm-segmentedControl-labelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-segmentedControl-labelTypography-font-size: 14px;
  --stm-segmentedControl-labelTypography-font-weight: 400;
  --stm-segmentedControl-labelTypography-line-height: 1.5;
  --stm-segmentedControl-itemTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-segmentedControl-itemTypography-font-size: 14px;
  --stm-segmentedControl-itemTypography-font-weight: 700;
  --stm-segmentedControl-itemTypography-line-height: 1.5;
  --stm-segmentedControl-itemContainerHover: #334155; /* Unselected item background on hover (TOK-96/TOK-47: the control had no hover state; also the real slot behind React's ToggleButton theme section, which requested phantom buttonGroup-* names) */
  --stm-segmentedControl-itemContainerDisabled: #1E293B; /* Fill of a disabled segment (TOK-96). The control had NO disabled treatment at all in either leg, so a segment the user cannot pick looked identical to one they can. */
  --stm-segmentedControl-itemTextDisabled: #94A3B8; /* Label colour of a disabled segment (TOK-96). */
  --stm-segmentedControl-disabledOpacity: 0.38; /* Opacity of a disabled control — 0.38 (TOK-96). */
  --stm-segmentedControl-itemTextHover: #F8FAFC; /* Unselected item text on hover — steps up from onSurfaceVariant for feedback (TOK-96) */
  --stm-segmentedControl-elevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.05), 0px 1px 3px 0px rgba(255,255,255,0.10); /* Subtle shadow on selected pill for depth */
  --stm-segmentedControl-overflowBackdropZIndex: 1; /* Stacking of the overflow menu's click-outside backdrop — 1, unchanged (TOK-96). It was a bare literal in a component that otherwise tokenizes everything. BLAZOR-ONLY BY CONSTRUCTION (TOK-46c): past `maxVisible` the React leg's overflow tail is a NATIVE `<select>` (CMP-42 — no appearance reset, platform arrow and AT for free), which the browser renders in the top layer with no stacking context of ours to place. The Blazor twin draws its own menu and backdrop, which is why only that leg needs a z-index for them. */
  --stm-segmentedControl-overflowMenuZIndex: 2; /* Stacking of the overflow menu — 2, unchanged (TOK-96). Deliberately a literal rather than a core.zIndex step: it must sit exactly ONE above overflowBackdropZIndex inside the control's own local stacking context, and every core step is a page-level band that would lift the menu out of it. BLAZOR-ONLY BY CONSTRUCTION (TOK-46c): past `maxVisible` the React leg's overflow tail is a NATIVE `<select>` (CMP-42 — no appearance reset, platform arrow and AT for free), which the browser renders in the top layer with no stacking context of ours to place. The Blazor twin draws its own menu and backdrop, which is why only that leg needs a z-index for them. */
  --stm-segmentedControl-squareContainerRadius: 8px; /* shape="square" container radius (TOK-118: raw core.radius.md made the SQUARE variant rounder than SETTL’s 4px "pill" — axis inversion; brand.radius.button keeps square ≤ pill per brand). */
  --stm-segmentedControl-squareItemRadius: 2px; /* shape="square" item radius — 2px (TOK-118: promoted from a raw core reach-in; stays below every brand’s pill). */
  --stm-segmentedControl-squareItemBorderWidth: 1px; /* Outline weight of a square-variant segment — 1px, unchanged (TOK-96). The square variant's radius pair became tokens in TOK-118 and its border width stayed a raw core reach-in. */
  --stm-segmentedControl-onContainer: #F8FAFC; /* Ink for content a CONSUMER slots into this surface (CMP-65 / #621). The component's own parts each set their own colour and look perfect, so nothing here was missing until a HOST re-inked: an AuthLayout setting `color: onPrimary` for a green canvas left everything slotted in inheriting white, on white — 1.00:1. Defaults to core.color.onSurface, which is EXACTLY what slotted content already resolved to on an ordinary page, so this is value-preserving by construction and nothing moves except where it was already broken. */

  /* select */
  --stm-select-sheetPaddingY: 8px; /* Vertical padding of the CMP-57 bottom-sheet option list (TOK-45) — raw core. */
  --stm-select-sheetOptionPaddingX: 24px; /* Horizontal padding of a bottom-sheet option (TOK-45). DELIBERATELY wider than the popover's dropdown.optionPaddingX: a sheet is a thumb target at the bottom of a phone, not a pointer target in a popover. */
  --stm-select-sheetOptionPaddingY: 8px; /* Vertical padding of a bottom-sheet option (TOK-45) — raw core. */
  --stm-select-sheetOptionText: #F8FAFC; /* Bottom-sheet option ink (TOK-45) — raw core, while the popover option next to it read dropdown.optionText. Same control, two families. */
  --stm-select-sheetOptionTextDisabled: #94A3B8; /* A disabled ('coming soon') option in the sheet (issue #51, TOK-45) — raw core. */
  --stm-select-caretClearance: 12px; /* Trailing room reserved for the caret, doubled and added to the icon size (TOK-45/TOK-70). It is input.paddingX by intent — the gap before the arrow should match the gap after the text — and that intent was expressed as arithmetic in the CSS rather than as a name. */
  --stm-select-sizerTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-select-sizerTypography-font-size: 14px;
  --stm-select-sizerTypography-font-weight: 400;
  --stm-select-sizerTypography-line-height: 1.5;

  /* skeleton */
  --stm-skeleton-container: #334155; /* Skeleton fill — the neutral step with visible contrast on surface in both modes (light neutral.200 / dark neutral.700). surfaceVariant was indistinguishable from white surface cards (issue #69 SKELETON-SURFACE-CONTRAST) */
  --stm-skeleton-radius: 2px; /* Corner rounding of the text/rectangular placeholder blocks and the statCard lines — 2px, unchanged (TOK-97). Every shape reached --stm-radius-sm directly, so a placeholder could not be rounded to match the real content it stands in for. PROMOTED to {brand.radius.track} 2026-08-20 (TOK-40, owner: promote all four). Value-preserving in every brand — 2px on a thin filled bar is a PROPORTION, not a shape identity, so the promotion buys the SEAM and not a change. A brand that wants square bar ends now sets one knob instead of editing six components. */
  --stm-skeleton-radiusCircular: 9999px; /* Corner rounding of the circular variant (TOK-97) — was a literal 50%. radius.full is a large pixel radius, which renders identically on the square box the circular variant always is. */
  --stm-skeleton-waveHighlight: rgba(255, 255, 255, 0.08); /* Colour of the travelling wave highlight (TOK-97). Was a literal rgba(255,255,255,.3) baked into the gradient in both legs, which is a sheen in light mode and a blown-out white bar over the dark neutral.700 fill; core.overlay.shimmer flips to 8% in dark. */
  --stm-skeleton-rectangularHeight: 100px; /* Default height of the rectangular variant — 100px, no scale match */
  --stm-skeleton-textHeight: 1em; /* Height of the text variant — 1em, so a placeholder line tracks the font size of the text it replaces (TOK-97). Was a literal. */
  --stm-skeleton-circularSize: 40px; /* Default width and height of the circular variant — 40px, no scale match */
  --stm-skeleton-pulseDuration: 1500ms; /* Pulse animation period — 1.5s (TOK-97). The component reached the core loop duration through a literal fallback; the slot lets a brand slow the breathing without touching every looping animation in the system. */
  --stm-skeleton-pulseEasing: cubic-bezier(0.42, 0, 0.58, 1); /* Pulse easing — symmetric ease-in-out, so the fade reads as breathing rather than a flick (TOK-97). */
  --stm-skeleton-pulseOpacityMin: 0.4; /* Dimmest point of the pulse — 0.4 (TOK-97). Was a keyframe literal, so the floor could not be raised for a brand whose placeholder fill is already faint. */
  --stm-skeleton-waveDuration: 1600ms; /* Wave sweep period — 1.6s (TOK-97). */
  --stm-skeleton-waveEasing: cubic-bezier(0, 0, 1, 1); /* Wave easing — linear, because any curve makes a continuous sweep visibly pulse at the loop seam (TOK-97). */

  /* slider */
  --stm-slider-track: #334155; /* Unfilled track background */
  --stm-slider-trackDisabled: #1E293B; /* Track fill when the slider is disabled (TOK-98). Disabled was an opacity over the LIVE colours, so a disabled slider still showed brand-primary fill at 38%. */
  --stm-slider-trackFill: #96ADF8; /* Filled portion of track */
  --stm-slider-thumb: #96ADF8; /* Thumb fill color */
  --stm-slider-thumbBorder: #0F172A; /* Thumb border/ring */
  --stm-slider-thumbBorderWidth: 2px; /* Weight of the thumb's contrasting ring - 2px (TOK-98). thumbBorder named the COLOUR of a ring whose width was a literal, so the knob's legibility against a filled track could not be tuned. */
  --stm-slider-thumbHover: #B5C6FA; /* Thumb on hover */
  --stm-slider-thumbActive: #D1DBFC; /* Thumb while dragging */
  --stm-slider-thumbDisabled: #334155; /* Thumb fill when the slider is disabled (TOK-98) - pairs with trackDisabled. */
  --stm-slider-thumbFocusRing: #122666; /* Focus ring around thumb */
  --stm-slider-tickMark: #94A3B8; /* Discrete step tick marks */
  --stm-slider-tickSize: 4px; /* Tick-mark diameter - 4px (TOK-98). Ticks reused trackHeight, which coupled a mark's size to the thickness of the line it sits on: thicken the track and every tick grew with it. */
  --stm-slider-label: #E2E8F0; /* Min/max/current value label */
  --stm-slider-trackHeight: 4px; /* Track thickness */
  --stm-slider-trackMarginY: 8px; /* Vertical room reserved around the track so the thumb's halo is not clipped - 8px, unchanged (TOK-98). A literal that the file itself flagged as deferred (TOK-03). */
  --stm-slider-rangeTrackMarginY: 14px; /* Vertical room around the RANGE slider's track - 14px, unchanged (TOK-98). Bespoke and deliberately not trackMarginY: the range variant stacks a value bubble above its thumbs and needs the extra clearance. */
  --stm-slider-thumbSize: 20px; /* Thumb diameter */
  --stm-slider-thumbRadius: 9999px; /* Thumb corner radius — ALWAYS a circle (TOK-118: the TOK-40 pill repoint squared SETTL thumbs to 4px; thumbs are an ergonomic constant like avatar/fab, not brand chrome). trackRadius stays brand-routed. */
  --stm-slider-trackRadius: 9999px; /* Track corner radius — brand-routed via brand.radius.pill (SETTL squares to 4px by identity decision 2026-08-14) */
  --stm-slider-focusRingWidth: 4px; /* Width of the halo drawn around a focused/active thumb - 4px, unchanged (TOK-98). It was a literal inside a `0 0 0 4px` box-shadow at four sites while the DECLARED focusRingSize (40px, a hit-area measurement, not a ring width) sat dead beside it - two names for two different things, only one of them real. */
  --stm-slider-labelGap: 4px; /* Gap between slider and labels */
  --stm-slider-transitionDuration: 120ms; /* Thumb hover/press transition length - 150ms (TOK-98). The thumb swapped fill and halo instantly. */
  --stm-slider-transitionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Thumb transition curve (TOK-98). */
  --stm-slider-bubblePaddingY: 2px; /* Value bubble vertical padding */
  --stm-slider-bubblePaddingX: 6px; /* Value bubble horizontal padding */
  --stm-slider-bubbleContainer: #96ADF8; /* Fill of the value bubble above the thumb (TOK-98). The bubble borrowed trackFill, thumbBorder and trackRadius - three tokens describing OTHER parts - so it could not be restyled without moving the track with it. */
  --stm-slider-bubbleOnContainer: #0F172A; /* Text colour of the value bubble (TOK-98). */
  --stm-slider-bubbleRadius: 2px; /* Rounding of the value bubble (TOK-98) - it borrowed trackRadius, a pill, which is the wrong shape for a tooltip-like box. */
  --stm-slider-bubbleOffset: 4px; /* Distance between the value bubble and the thumb - 4px, unchanged (TOK-98); a literal inside two calc() expressions. */
  --stm-slider-labelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-slider-labelTypography-font-size: 12px;
  --stm-slider-labelTypography-font-weight: 400;
  --stm-slider-labelTypography-line-height: 1.5;

  /* snackbar */
  --stm-snackbar-minWidth: 288px; /* Minimum snackbar width — 288px, no scale match */
  --stm-snackbar-maxWidth: 560px; /* Maximum snackbar width — 560px, no scale match */
  --stm-snackbar-enterDuration: 200ms; /* Entrance animation duration — 200ms (nearest scale step to the previous hardcoded 225ms). BLAZOR-ONLY IN CSS (TOK-46c), and the same shape as this file's autoHideDuration/stackMax design records: the Blazor leg runs the enter/exit as a CSS animation and binds the duration; React hands the number to MUI's Snackbar transition as a prop, and a framework prop cannot read a custom property. Change the token and the React default together. */
  --stm-snackbar-exitDuration: 195ms; /* How long the toast takes to leave - 195ms, the documented exit step (deliberately quicker than enter, so dismissal feels immediate). TOK-99 left this undeclared on purpose: the Blazor leg unmounted the element the instant Open went false, so there was no exit to time and the slot would have been dead on arrival. That is fixed here - the component now holds the element for one exit animation before unmounting - so the slot has a real consumer on both legs. BLAZOR-ONLY IN CSS (TOK-46c), and the same shape as this file's autoHideDuration/stackMax design records: the Blazor leg runs the enter/exit as a CSS animation and binds the duration; React hands the number to MUI's Snackbar transition as a prop, and a framework prop cannot read a custom property. Change the token and the React default together. */
  --stm-snackbar-autoHideDuration: 6000; /* Milliseconds before a toast self-dismisses (TOK-99) — was a literal 6000 in BOTH legs. Mirrored as the component default in each leg (a number token can't reach a JS/C# default at runtime; change together), same contract as stackMax */
  --stm-snackbar-container: #F8FAFC; /* Default toast fill (TOK-99) — the INVERSE surface, i.e. the on-surface ink used as a background. Was raw core in Blazor and unreachable in React */
  --stm-snackbar-onContainer: #1E293B; /* Default toast text colour (TOK-99) — the inverse of the fill */
  --stm-snackbar-actionColor: #1E293B; /* Colour of the action slot (TOK-99) — inherited onContainer before, so an emphasised action link on the inverse surface was not expressible. Declared at the same value; brands can differentiate it */
  --stm-snackbar-paddingX: 24px; /* Toast horizontal padding (TOK-99) — was raw core lg */
  --stm-snackbar-paddingY: 8px; /* Toast vertical padding (TOK-99) — was raw core sm */
  --stm-snackbar-gap: 16px; /* Gap between the message and the action slot (TOK-99) — was raw core md */
  --stm-snackbar-radius: 8px; /* Toast corner radius (TOK-99) — was raw core radius-md, so a squared brand could not shape the toast. TOK-40: promoted off core.radius.md onto the BRAND alert knob. A snackbar is a transient alert — same routing validationSummary took in TOK-45, and for the same reason: a brand that reshapes how warnings look should reshape all of them, not the ones that happen to sit in a panel. */
  --stm-snackbar-elevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.08), 0px 8px 16px 0px rgba(255,255,255,0.08); /* Toast shadow (TOK-99) — was raw core level-003 plus a hardcoded rgba fallback that shadowed the token */
  --stm-snackbar-offset: 24px; /* Distance from the viewport edge for a SINGLE toast, all three anchors (TOK-99) — was raw core lg at each of bottom/left/right. The sibling stackOffset already existed for the toast stack; this is its single-toast counterpart */
  --stm-snackbar-containerInfo: #60A5FA; /* Info severity fill (TOK-99) — was raw core status colour */
  --stm-snackbar-onContainerInfo: #0F172A; /* Info severity text (TOK-99) — replaces a raw token plus a hardcoded #fff fallback */
  --stm-snackbar-containerSuccess: #4ADE80; /* Success severity fill (TOK-99) */
  --stm-snackbar-onContainerSuccess: #0F172A; /* Success severity text (TOK-99) — the old #fff fallback was outright WRONG here (onSuccess is near-black #052e16); it never fired only because the token exists */
  --stm-snackbar-containerWarning: #FFD84A; /* Warning severity fill (TOK-99) */
  --stm-snackbar-onContainerWarning: #0F172A; /* Warning severity text (TOK-99) — same wrong-#fff-fallback story as success (onWarning is #4A3600) */
  --stm-snackbar-containerError: #FB7185; /* Error severity fill (TOK-99) */
  --stm-snackbar-onContainerError: #0F172A; /* Error severity text (TOK-99) */
  --stm-snackbar-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-snackbar-typography-font-size: 14px;
  --stm-snackbar-typography-font-weight: 400;
  --stm-snackbar-typography-line-height: 1.5;
  --stm-snackbar-stackGap: 8px; /* Vertical gap between stacked toasts (CMP-32 toast service) — 8px */
  --stm-snackbar-stackOffset: 24px; /* Distance from the viewport corner to the toast stack (CMP-32) — 24px */
  --stm-snackbar-stackMax: 5; /* Max toasts visible at once (CMP-32) — older ones drop first. Mirrored as the service default in BOTH legs (a number token can't reach a JS/C# default at runtime; change together) */

  /* speedDial */
  --stm-speedDial-gap: 8px; /* Gap between the trigger and the first action, and between actions. */
  --stm-speedDial-actionSize: 44px; /* Diameter of one action button. Rides the brand control ladder rather than fab.sizeSm: an action is a control the user aims at, and it should be the same target size as any other control in that brand. */
  --stm-speedDial-actionContainer: #1E293B; /* Action button fill. Surface rather than the FAB's primary — the trigger is the primary action and the actions are secondary to it, so making them all primary would flatten the hierarchy the component exists to express. */
  --stm-speedDial-actionContainerHover: #334155; /* Action hover fill. */
  --stm-speedDial-actionColor: #F8FAFC; /* Action glyph ink. */
  --stm-speedDial-actionBorder: #334155; /* Action outline. */
  --stm-speedDial-actionBorderWidth: 1px; /* Action outline width. */
  --stm-speedDial-actionElevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.07), 0px 4px 6px 0px rgba(255,255,255,0.08); /* Action lift — one step below the FAB's own, so the trigger still reads as the anchor of the group. */
  --stm-speedDial-labelContainer: #F8FAFC; /* Fill of the tooltip-style label beside an action. A DARK chip on a light page: the labels float over arbitrary content, and an inverted pill is the one treatment that stays legible over anything. Same inversion recipe tooltip.container uses (onSurface as a FILL) — the DS has no inverseSurface family, and inventing one for two slots would be a second vocabulary for the same idea. */
  --stm-speedDial-labelColor: #1E293B; /* Label ink on the inverted chip — the other half of tooltip.onContainer's recipe. */
  --stm-speedDial-labelPaddingX: 8px; /* Label chip inline padding. */
  --stm-speedDial-labelPaddingY: 4px; /* Label chip block padding. */
  --stm-speedDial-labelRadius: 8px; /* Label chip rounding — the alert knob, which is the DS's small-informational-surface shape. */
  --stm-speedDial-labelGap: 8px; /* Gap between an action button and its label chip. */
  --stm-speedDial-labelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-speedDial-labelTypography-font-size: 12px;
  --stm-speedDial-labelTypography-font-weight: 700;
  --stm-speedDial-labelTypography-line-height: 1.5;
  --stm-speedDial-focusRing: #7694F7; /* Focus ring on the trigger and the actions. */
  --stm-speedDial-focusRingWidth: 2px; /* Focus ring width. */
  --stm-speedDial-focusRingOffset: 2px; /* Focus ring offset. */
  --stm-speedDial-motionDuration: 120ms; /* Open/close transition for the action stack. */
  --stm-speedDial-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Easing for the open/close transition. */
  --stm-speedDial-zIndex: 1100; /* Stacking level of the dial. The zIndex ladder has no `fab` rung and StmFab declares no z-index at all — it relies on its consumer's stacking context. A dial DOES need one, because its expanded stack must sit above the content it overlaps; `sticky` is the rung below popover/dropdown, which is right: the dial is page furniture, not a popup. */

  /* split */
  --stm-split-gap: 24px; /* Gap between main and rail columns — 24px default */
  --stm-split-railWidthSm: 280px; /* Narrow rail — compact sidebars, mobile-first layouts */
  --stm-split-railWidthMd: 360px; /* Standard rail — Personal Details + Contact Info cards */
  --stm-split-railWidthLg: 440px; /* Wide rail — rich sidebars with activity feeds */
  --stm-split-railWidthXl: 560px; /* Extra-large rail width — 560px (TOK-100). The ladder stopped at 440px, which is narrow for a rail that carries a form or a preview pane rather than a summary; consumers were overriding the grid template by hand to get past it. Literal like its three siblings: a rail width is a bespoke layout dimension with no core step. */
  --stm-split-railMinWidth: 0px; /* Floor width of the rail cell — 0 by default, so this is value-preserving (TOK-100). The rail track is a fixed width, so the MAIN column is what gets squeezed as the container narrows; a consumer who overrides the track to a fraction had no way to stop the rail collapsing instead. */

  /* splitButton */
  --stm-splitButton-container: #96ADF8; /* Button fill */
  --stm-splitButton-containerHover: #B5C6FA; /* Hover fill */
  --stm-splitButton-containerActive: #D1DBFC; /* Active/pressed fill */
  --stm-splitButton-containerSecondary: #1E293B; /* Fill of the secondary (outlined) variant (TOK-101). The whole secondary block bypassed this namespace and reached core directly, while button.json co-locates its variants — so a brand could restyle the filled split button and not the outlined one. */
  --stm-splitButton-containerSecondaryHover: #334155; /* Secondary-variant fill under the pointer (TOK-101), unchanged. */
  --stm-splitButton-containerSecondaryActive: #475569; /* Secondary-variant fill while pressed (TOK-101), unchanged. */
  --stm-splitButton-onContainerSecondary: #F8FAFC; /* Secondary-variant label colour (TOK-101), unchanged. */
  --stm-splitButton-borderSecondary: #94A3B8; /* Secondary-variant outline, which also paints the divider in that variant (TOK-101), unchanged. */
  --stm-splitButton-onContainer: #0F172A; /* Text and icon color */
  --stm-splitButton-divider: #0F172A; /* Vertical divider between action and dropdown */
  --stm-splitButton-dividerWidth: 1px; /* Width of the rule between the action and the trigger — 1px (TOK-101). Its colour and opacity were tokens while its width was a literal, so the one property that decides whether the seam reads at all was untunable. */
  --stm-splitButton-dividerOpacity: 0.25; /* Divider transparency — 25% */
  --stm-splitButton-borderWidth: 1px; /* Outline weight of the secondary variant and of the menu popup — 1px, unchanged (TOK-101). Both were raw core reach-ins. */
  --stm-splitButton-menuContainer: #1E293B; /* Dropdown menu background */
  --stm-splitButton-menuBorder: #334155; /* Dropdown menu border */
  --stm-splitButton-menuItemHover: #334155; /* Menu item hover */
  --stm-splitButton-menuText: #F8FAFC; /* Menu item text */
  --stm-splitButton-radius: 8px; /* Button corner radius */
  --stm-splitButton-menuRadius: 14px; /* Menu corner radius */
  --stm-splitButton-menuOffset: 4px; /* Gap between the button and its dropdown — 4px (TOK-101). A literal at four rules per leg, two of which carried a lint suppression describing it as deferred. */
  --stm-splitButton-paddingX: 14px; /* Horizontal padding */
  --stm-splitButton-paddingY: 8px; /* Vertical padding */
  --stm-splitButton-gap: 8px; /* Gap between an icon and the label inside the action button — 8px (TOK-101). The action button is a flex row that never set one, so an icon sat flush against its text. */
  --stm-splitButton-minHeight: 52px; /* Floor height of the button pair — 44px, the same large-control step StmButton uses (TOK-101). Without it a split button sized purely to its padding and did not line up with a plain StmButton beside it in a toolbar. */
  --stm-splitButton-triggerPaddingX: 8px; /* Dropdown trigger horizontal padding */
  --stm-splitButton-menuItemGap: 8px; /* Gap between menu item icon and label */
  --stm-splitButton-menuPaddingY: 4px; /* Menu vertical padding */
  --stm-splitButton-menuItemPaddingX: 14px; /* Menu item horizontal padding */
  --stm-splitButton-menuItemPaddingY: 8px; /* Menu item vertical padding */
  --stm-splitButton-menuShadow: inset 0px 0px 0px 9999px rgba(255,255,255,0.07), 0px 4px 6px 0px rgba(255,255,255,0.08); /* Menu elevation shadow */
  --stm-splitButton-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-splitButton-typography-font-size: 16px;
  --stm-splitButton-typography-font-weight: 700;
  --stm-splitButton-typography-line-height: 1.5;
  --stm-splitButton-typography-letter-spacing: 0em;
  --stm-splitButton-motionDuration: 120ms; /* Hover/press transition length — 150ms (TOK-101). This is also a parity fix: only the Blazor leg animated the fill at all, so the same button faded on one leg and snapped on the other. */
  --stm-splitButton-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Hover/press transition curve (TOK-101). */
  --stm-splitButton-disabledOpacity: 0.38; /* Opacity of a disabled split button — 0.38, unchanged (TOK-101). */

  /* splitter */
  --stm-splitter-container: #1E293B; /* Pane background */
  --stm-splitter-paneContainer: transparent; /* Fill of ONE pane, issue #597. Distinct from `container`, which despite its $description is the SPLITTER's background, not a pane's — the splitter had no pane fill at all, which is why StmSplitter.stories.razor invented two raw hex greys (#f5f5f5 / #fafafa) to tell the two panes apart. Defaults to transparent so today's render is unchanged for anyone not asking for a fill; a consumer wanting a list-beside-detail contrast re-points this one token instead of writing a wrapper div. */
  --stm-splitter-panePaddingBlock: 16px; /* Block inset of a pane, issue #597. The splitter shipped the drag handle, the grip dots, the min-size clamp and the keyboard resize, and nothing about what goes INSIDE a pane — `.stm-c-splitter__pane` was three lines and none of them was an inset. Six call sites wrote the same `padding: var(--stm-space-md)` wrapper. A splitter exists to divide a working area, and every one of those wants its content inset from the divider. */
  --stm-splitter-panePaddingInline: 16px; /* Inline inset of a pane, issue #597. Same value as the block axis in the six hand-rolls; kept as two slots because a pane against a divider is not symmetric in principle, and one shared slot would make that undiscussable. */
  --stm-splitter-handleBg: #1E293B; /* Drag handle bar background */
  --stm-splitter-handleBgHover: #334155; /* Handle hover */
  --stm-splitter-handleIcon: #E2E8F0; /* Handle grip dots */
  --stm-splitter-border: #334155; /* Handle border */
  --stm-splitter-handleWidth: 8px; /* Handle bar thickness */
  --stm-splitter-handleRadius: 2px; /* Handle corner radius */
  --stm-splitter-minPaneSize: 100px; /* Minimum pane size */
  --stm-splitter-radius: 8px; /* Splitter container corner rounding — TOK-102. Blazor reached into core.radius.md directly and React had no container radius at all; value-preserving against the Blazor leg, which is the design the two converged on. */
  --stm-splitter-borderWidth: 1px; /* Splitter border width — TOK-102. Both legs drew this from raw core.border.width.thin while the border COLOUR was already a slot. */
  --stm-splitter-containerMinHeight: 200px; /* Minimum height of the split container — TOK-102. Was a literal 200px in the Blazor rule and absent from React, so the two collapsed differently in a short parent. Literal px matches handleWidth’s precedent in this manifest: it is a component geometry, not a step on a shared scale. */
  --stm-splitter-gripDotSize: 3px; /* Diameter of one grip dot on the drag handle — TOK-102. Was hardcoded AND divergent: React drew 4px dots, Blazor 3px, a visible parity break. Standardised on 3px: the handle is 8px wide, so a 4px dot left barely 2px of total side margin. */
  --stm-splitter-gripDotGap: 2px; /* Gap between grip dots — TOK-102. Hardcoded and divergent with gripDotSize (React 3px, Blazor 2px). Standardised on 2px, matching the 3px dot. */
  --stm-splitter-motionDuration: 120ms; /* Handle background transition — TOK-102. Both legs reached into core.motion directly. (The scan’s “base vs fast per leg” is stale: both already read fast.) */
  --stm-splitter-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Easing for the handle transition — TOK-102. Retires the `, ease` literal fallback both legs carried. */
  --stm-splitter-onContainer: #F8FAFC; /* Ink for content a CONSUMER slots into this surface (CMP-65 / #621). The component's own parts each set their own colour and look perfect, so nothing here was missing until a HOST re-inked: an AuthLayout setting `color: onPrimary` for a green canvas left everything slotted in inheriting white, on white — 1.00:1. Defaults to core.color.onSurface, which is EXACTLY what slotted content already resolved to on an ordinary page, so this is value-preserving by construction and nothing moves except where it was already broken. */

  /* statCard */
  --stm-statCard-container: #1E293B; /* Card background */
  --stm-statCard-border: #334155; /* Card border color */
  --stm-statCard-borderActive: #96ADF8; /* Border of the selected (filter-toggle) card — was a raw core reach-in (TOK-103). Same primary today. */
  --stm-statCard-borderWidth: 1px; /* Card border width — 1px */
  --stm-statCard-ringWidthActive: 2px; /* Width of the ring that marks the selected card — 2px, unchanged (TOK-103). Was a `0 0 0 2px` box-shadow literal with a raw-core width and a duplicated 2px fallback. */
  --stm-statCard-radius: 14px; /* Card corner radius — brand-configurable */
  --stm-statCard-padding: 24px; /* Card inner padding */
  --stm-statCard-paddingCompact: 16px; /* Card padding in the compact density — 16px (TOK-103/GDK-02). The compact variant re-bound `padding` to a raw core step inline, which meant the density lived in CSS rather than in the manifest and could not be retuned per brand. */
  --stm-statCard-gap: 4px; /* Vertical gap between label, value, sublabel */
  --stm-statCard-rowGap: 4px; /* Gap between the text block and the icon badge in the card's top row — 4px, unchanged (TOK-103). It was the one raw core reach-in in a file where every other gap is a slot. */
  --stm-statCard-elevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.05), 0px 1px 3px 0px rgba(255,255,255,0.10); /* Subtle elevation */
  --stm-statCard-labelColor: #E2E8F0; /* Top label color — muted */
  --stm-statCard-labelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-statCard-labelTypography-font-size: 12px;
  --stm-statCard-labelTypography-font-weight: 700;
  --stm-statCard-labelTypography-line-height: 1.5;
  --stm-statCard-valueColor: #F8FAFC; /* Main metric value color */
  --stm-statCard-valueInfo: #60A5FA; /* Value colour for tone=info (TOK-103). The five KPI tones were ten raw core declarations in CSS; tag.json and alert.json enumerate their tones as slots and this now matches. */
  --stm-statCard-valueSuccess: #4ADE80; /* Value colour for tone=success (TOK-103). */
  --stm-statCard-valueWarning: #FFD84A; /* Value colour for tone=warning (TOK-103). */
  --stm-statCard-valueError: #FB7185; /* Value colour for tone=error (TOK-103). */
  --stm-statCard-valuePrimary: #96ADF8; /* Value colour for tone=primary (TOK-103). */
  --stm-statCard-valueTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-statCard-valueTypography-font-size: 36px;
  --stm-statCard-valueTypography-font-weight: 700;
  --stm-statCard-valueTypography-line-height: 1.1;
  --stm-statCard-valueTypographyCompact-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-statCard-valueTypographyCompact-font-size: 28px;
  --stm-statCard-valueTypographyCompact-font-weight: 700;
  --stm-statCard-valueTypographyCompact-line-height: 1.1;
  --stm-statCard-sublabelColor: #94A3B8; /* Sublabel/footnote color */
  --stm-statCard-sublabelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-statCard-sublabelTypography-font-size: 12px;
  --stm-statCard-sublabelTypography-font-weight: 400;
  --stm-statCard-sublabelTypography-line-height: 1.5;
  --stm-statCard-iconBadgeContainer: #1A3791; /* Top-right icon badge background — Figma 1495:31545. Tinted primary square that contains the badge icon */
  --stm-statCard-iconBadgeContainerInfo: #172554; /* Icon-badge fill for tone=info (TOK-103). The badge glyph itself reuses the matching value colour, which is what the CSS already did. */
  --stm-statCard-iconBadgeContainerSuccess: #052e16; /* Icon-badge fill for tone=success (TOK-103). */
  --stm-statCard-iconBadgeContainerWarning: #4A3600; /* Icon-badge fill for tone=warning (TOK-103). */
  --stm-statCard-iconBadgeContainerError: #4C0519; /* Icon-badge fill for tone=error (TOK-103). */
  --stm-statCard-iconBadgeContainerPrimary: #122666; /* Icon-badge fill for tone=primary (TOK-103). */
  --stm-statCard-iconBadgeOnContainer: #96ADF8; /* Icon color inside the badge */
  --stm-statCard-iconBadgeRadius: 8px; /* Icon badge corner radius — rounded square, not pill */
  --stm-statCard-iconBadgePadding: 8px; /* Padding around the badge icon */
  --stm-statCard-iconBadgeSize: 24px; /* Icon size inside the badge — 24px */
  --stm-statCard-labelLetterSpacing: 0.05em; /* Uppercase top-label letter-spacing — the TOK-10 wide step (0.05em; this token was the scale's precedent) */
  --stm-statCard-elevationHover: inset 0px 0px 0px 9999px rgba(255,255,255,0.07), 0px 4px 6px 0px rgba(255,255,255,0.08); /* Hover lift shadow (TOK-119: the old var(--stm-elevation-md, <component>) chain inverted when TOK-34 defined the alias — core permanently won and the component slot went dead; a real slot restores brandability at the same rendered value). */
  --stm-statCard-motionDuration: 150ms; /* Interactive-card hover transition length — 200ms (TOK-103), previously raw core at both transitioned properties. */
  --stm-statCard-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Interactive-card hover transition curve (TOK-103) — the literal `ease` fallback beside a tokenized duration. */
  --stm-statCard-onContainer: #F8FAFC; /* Ink for content a CONSUMER slots into this surface (CMP-65 / #621). The component's own parts each set their own colour and look perfect, so nothing here was missing until a HOST re-inked: an AuthLayout setting `color: onPrimary` for a green canvas left everything slotted in inheriting white, on white — 1.00:1. Defaults to core.color.onSurface, which is EXACTLY what slotted content already resolved to on an ordinary page, so this is value-preserving by construction and nothing moves except where it was already broken. */

  /* stepper */
  --stm-stepper-circleSize: 44px; /* Step circle diameter - 40px */
  --stm-stepper-circleSizeActive: 44px; /* Active step circle diameter — same as circleSize; the Figma master (758:1985) keeps all steps equal-sized (was controlLg 44px) */
  --stm-stepper-circleRadius: 14px; /* Step indicator corner radius — squircle per the Figma master (758:1985), not a full circle; follows the brand shape language (retur: 12px) */
  --stm-stepper-circleBorderWidth: 1px; /* Border on the pending step indicator (Figma master: grey ring); active/complete render borderless */
  --stm-stepper-circleBorderColor: #334155; /* Pending step indicator border color */
  --stm-stepper-circleContainer: #1E293B; /* Pending step circle background */
  --stm-stepper-circleContainerActive: #96ADF8; /* Active step circle background */
  --stm-stepper-circleContainerComplete: #4ADE80; /* Completed step circle background — green to distinguish from active (blue) */
  --stm-stepper-onCircle: #E2E8F0; /* Pending step number text color */
  --stm-stepper-onCircleActive: #0F172A; /* Active step number text color */
  --stm-stepper-onCircleComplete: #0F172A; /* Completed step checkmark color — white on green */
  --stm-stepper-circleFontWeight: 700; /* Weight of the digit inside a step circle — bold, unchanged (TOK-104); a raw core reach-in. */
  --stm-stepper-checkSize: 50%; /* Size of the completed-step checkmark as a fraction of its circle — 50%, unchanged (TOK-104). A percentage rather than a length on purpose: the mark scales with whatever circleSize a brand sets, which is why it is a literal and not a space step. */
  --stm-stepper-connectorColor: #334155; /* Connecting line between pending steps */
  --stm-stepper-connectorColorComplete: #4ADE80; /* Connecting line between completed steps — green to match completed circles */
  --stm-stepper-connectorHeight: 2px; /* Connector line thickness - 2px */
  --stm-stepper-connectorMinLength: 10px; /* Floor length of the connector between two steps — 10px, unchanged (TOK-104). Bespoke: it is the point below which the line stops reading as a connection, not a step on any scale. */
  --stm-stepper-sideConnectorLength: 24px; /* Connector length in the side-label layout — 24px, unchanged (TOK-104); a raw core reach-in. */
  --stm-stepper-labelColor: #E2E8F0; /* Step label text for pending steps */
  --stm-stepper-labelColorActive: #F8FAFC; /* Step label text for active step */
  --stm-stepper-labelFontWeightActive: 700; /* Weight of the CURRENT step's label — bold, unchanged (TOK-104); a raw core reach-in beside a fully tokenized labelTypography. */
  --stm-stepper-labelGap: 8px; /* Gap between circle and label - 8px */
  --stm-stepper-labelOffset: 10px; /* Top padding separating the step label from the circle above it - 10px */
  --stm-stepper-dotSize: 8px; /* Dot diameter in the dots variant — 8px, unchanged (TOK-104). It borrowed --stm-progressIndicator-dotSize at FIVE sites: a foreign component's namespace, with a literal fallback beside it, deciding this component's geometry. */
  --stm-stepper-dotRadius: 9999px; /* Dot rounding in the dots variant (TOK-104) — was a literal 50%. */
  --stm-stepper-gap: 16px; /* Air between a connector segment and its neighboring indicators — 16px per side (758:1985 draws short centered segments with ~30px air in fixed 100px cells; our cells flex, so the token carries the rhythm) */
  --stm-stepper-compactCircleSize: 20px; /* Compact variant (GAP-04, Figma 644:1876) — step circle diameter; extracted from the modal-chrome embedded stepper */
  --stm-stepper-compactCircleRadius: 9999px; /* Rounding of the compact step circle — fully round, unchanged (TOK-104). This is the ONE compact property that genuinely differs from the base circle (whose circleRadius is the brand card radius); every other compact colour now points at the base slots instead of reaching core past them. */
  --stm-stepper-compactDigitSize: 11px; /* Compact variant — digit size fitting the 20px circle */
  --stm-stepper-compactConnectorWidth: 24px; /* Compact variant — connector segment length */
  --stm-stepper-compactConnectorHeight: 2px; /* Compact variant — connector thickness */
  --stm-stepper-labelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-stepper-labelTypography-font-size: 12px;
  --stm-stepper-labelTypography-font-weight: 400;
  --stm-stepper-labelTypography-line-height: 1.5;

  /* switch */
  --stm-switch-track: #1E293B; /* Track background when off */
  --stm-switch-trackOn: #96ADF8; /* Track background when on */
  --stm-switch-trackHover: #334155; /* Track hover when off */
  --stm-switch-trackOnHover: #B5C6FA; /* Track hover when on */
  --stm-switch-trackDisabled: #1E293B; /* Track fill when the switch is disabled (TOK-105). Disabled was expressed purely as an opacity over the LIVE colours, so a disabled ON switch still read as brand-primary at 38%. */
  --stm-switch-thumb: #1E293B; /* Thumb color */
  --stm-switch-thumbDisabled: #1E293B; /* Thumb fill when the switch is disabled — the resting surface today, so value-preserving (TOK-105); it pairs with trackDisabled so the knob does not dissolve into a dimmed track. */
  --stm-switch-disabledOpacity: 0.38; /* Opacity of a disabled switch — 0.38, unchanged (TOK-105). */
  --stm-switch-trackWidth: 34px; /* Track width — 34px (TOK-139/§PB-12). Re-pointed from {brand.size.controlLg} (44px), which was never a relationship, only a coincidence: a switch track is not 'as wide as a large button is tall'. It is thumbSize + trackHeight — 20 + 14 — which is exactly MUI's geometry and exactly one thumb-width of travel. A literal because 34 is off the spacing scale (32 and 36 are the neighbours) and importing a rung for one vendor's number is worse than owning the number here, the way appBar.height and colorPicker.swatchSize already do. NOTE this drops brand reach on switch SIZE; brand still reshapes the track through switch.radius (SETTL squares it, TOK-118). */
  --stm-switch-trackHeight: 14px; /* Track height — 14px (TOK-139/§PB-12). Re-pointed from {core.space.lg} (24px). With thumbSize 20 the thumb OVERHANGS the track by 3px on every side, which is the switch's anatomy and not an accident: the knob reads as a raised object above a recessed slot. The old 24px track swallowed the thumb, and the two legs drew two different components — see the render table in §PB-12. */
  --stm-switch-thumbSize: 20px; /* Thumb diameter - 20px */
  --stm-switch-radius: 9999px; /* Track rounding — brand-routed via brand.radius.pill (SETTL squares to 4px). The THUMB has its own always-circular thumbRadius (TOK-118: the single slot squared Blazor thumbs while React/MUI thumbs stayed native-round — leg divergence). */
  --stm-switch-borderWidth: 1px; /* Track outline weight under forced colors, where the fill is stripped and only a border keeps the track visible — 1px (TOK-105). React had a literal here and Blazor a token, so the same accessibility rescue was written two ways. */
  --stm-switch-labelGap: 8px; /* Gap between track and label — 8px (TOK-47: Blazor consumed var(--stm-switch-labelGap, 8px) before this slot existed) */
  --stm-switch-labelColor: #F8FAFC; /* Switch label colour (TOK-105). The Blazor leg rendered its label with NO styling at all and the React leg borrowed checkbox.labelTypography through MuiFormControlLabel, so the switch's own label had no colour anywhere. */
  --stm-switch-labelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-switch-labelTypography-font-size: 16px;
  --stm-switch-labelTypography-font-weight: 400;
  --stm-switch-labelTypography-line-height: 1.5;
  --stm-switch-motionDuration: 120ms; /* Track/thumb transition length — 150ms (TOK-105). Blazor animated both on a raw core duration; React bound no transition at all, so the thumb teleported on one leg and slid on the other. */
  --stm-switch-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Track/thumb transition curve (TOK-105). */
  --stm-switch-thumbRadius: 9999px; /* Thumb corner radius — ALWAYS a circle (TOK-118); bound explicitly in both legs so the twins agree under square brands. */

  /* tab */
  --stm-tab-container: #1E293B; /* Tab bar background color */
  --stm-tab-barBorder: #334155; /* Baseline rule under the tab bar (TOK-106) - a raw core reach-in across three files and both legs, so the edge that separates the tab strip from its panel could not be retuned from the theme. */
  --stm-tab-barBorderWidth: 1px; /* Weight of the tab bar's baseline rule - 1px, unchanged (TOK-106). TOK-121 flagged this family specifically: the rule was drawn with --stm-space-hairline, a SPACING token used as a border width. */
  --stm-tab-itemColor: #E2E8F0; /* Inactive tab text color */
  --stm-tab-itemColorHover: #F8FAFC; /* Tab text on hover — stronger emphasis */
  --stm-tab-itemColorActive: #96ADF8; /* Active tab text — brand primary color */
  --stm-tab-itemColorDisabled: #94A3B8; /* Label colour of a disabled tab (TOK-106). A tab that cannot be opened looked exactly like one that can. Both legs since TOK-131 (§PB-4): the Blazor tabs had no disabled concept at all until then — StmTabItem carries `Disabled`, the tab gets `aria-disabled` (not the `disabled` attribute, so it stays in the tablist for a screen reader), and roving focus SKIPS it, which matters here because this strip activates on arrow. */
  --stm-tab-itemBgActive: #1A3791; /* Active tab background tint — subtle brand highlight */
  --stm-tab-indicatorColor: #96ADF8; /* Active tab underline indicator color */
  --stm-tab-indicatorHeight: 2px; /* Active indicator line thickness — 2px */
  --stm-tab-itemPaddingX: 18px; /* Horizontal padding per tab — 18px */
  --stm-tab-itemPaddingY: 10px; /* Vertical padding per tab — 10px */
  --stm-tab-panelPaddingBlock: 24px; /* Block inset of a tab PANEL (StmTabPanel, issue #598). tab.json had itemPadding{X,Y} for the strip and nothing for the panel, because the DS shipped the tablist and not the panel — it emitted aria-controls and handed the other end over as a string. Two panels in one ConfigModal story then invented the same off-scale 60px inset, which is what happens when nothing names it. */
  --stm-tab-panelPaddingInline: 0px; /* Inline inset of a tab PANEL (StmTabPanel, issue #598). Zero by default: a panel's content aligns with the tab strip above it, and any other value is a decision the surface makes, not the tab component. Named rather than omitted so a brand or a consumer has somewhere to put that decision. */
  --stm-tab-minHeight: 52px; /* Floor height of a tab item - 44px (TOK-106). The legs disagreed: MUI floors its tab at a magic 48px and the Blazor twin hugged its content (~41px), so the same tab strip was a different height per framework. 44px is the large-control step, which is what the strip sits beside. */
  --stm-tab-itemGap: 8px; /* Gap between tab items — 8px */
  --stm-tab-labelGap: 6px; /* Gap between icon and text inside a tab label — 6px */
  --stm-tab-dotSize: 8px; /* Leading status dot diameter in a Tab/Rich item — 8px */
  --stm-tab-dotColor: #94A3B8; /* Status dot on a tab (TOK-106) - dotSize existed and the dot had no colour of its own. */
  --stm-tab-dotColorActive: #96ADF8; /* Status dot on the SELECTED tab (TOK-106). */
  --stm-tab-dotRadius: 9999px; /* Status dot rounding (TOK-106) - was a literal 50%. */
  --stm-tab-progressHeight: 3px; /* Optional progress stripe height below the tab bar — 3px */
  --stm-tab-progressTrackColor: #334155; /* Track behind a tab's inline progress bar (TOK-106). The FILL and the HEIGHT were tokens and the track was raw core - the one part of the bar that is visible when progress is zero. */
  --stm-tab-itemRadius: 14px; /* Tab item corner radius — 12px, "rounded for pill-like shape". TOK-40 STRAGGLER, decision pre-made: {brand.radius.surface}, NOT {brand.radius.pill} despite the prose — a pill knob resolves to 9999px in three of four brands, which would turn a tab into a lozenge. Deferred because it is a value change (starter/systemate 12→14px, retur unchanged at 12, settl → base, which is again the SETTL squaring TOK-118 wanted). PROMOTED 2026-08-20 (owner: promote all four), exactly as the pre-made decision above says: surface, not pill. Rendered per brand before merging — starter/systemate 12->14px, retur unchanged at 12, settl -> base (4px), which is the SETTL squaring TOK-118 wanted and this slot had silently opted out of. */
  --stm-tab-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-tab-typography-font-size: 14px;
  --stm-tab-typography-font-weight: 700;
  --stm-tab-typography-line-height: 1.5;
  --stm-tab-countColor: #94A3B8; /* Numeric count beside inactive tab label (e.g. 'Invoices 10') */
  --stm-tab-countColorActive: #96ADF8; /* Numeric count beside active tab label */
  --stm-tab-countTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-tab-countTypography-font-size: 12px;
  --stm-tab-countTypography-font-weight: 700;
  --stm-tab-countTypography-line-height: 1.5;
  --stm-tab-motionDuration: 120ms; /* Tab hover/selection transition length - 150ms (TOK-106). It was a literal 120ms in the React theme before the easing sweep and a raw core reach-in after it; either way the tab strip could not be tuned on its own. */
  --stm-tab-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Tab transition curve (TOK-106). */

  /* table */
  --stm-table-headerContainer: #1E293B; /* Table header row background */
  --stm-table-headerText: #F8FAFC; /* Table header text color */
  --stm-table-cellContainer: #1E293B; /* Default cell background — white */
  --stm-table-cellText: #F8FAFC; /* Cell text color */
  --stm-table-cellPaddingX: 12px; /* Horizontal cell padding — 12px */
  --stm-table-cellPaddingXCompact: 8px; /* Horizontal cell padding in the dense density - 8px (TOK-107). Every sibling data surface has a density axis and the table did not, so a table in a panel was re-padded by hand. TOK-37: renamed from cellPadding{X,Y}Dense. The density modifier standardized on `compact` and `dense` was retired; the toolbar's own migration note claimed it was "the last component still speaking the retired dialect" — StmTable still was, in its tokens, its class AND its public prop. Value unchanged; this is the naming half. The VALUE half stays deferred: 4px here against core.density.controlPaddingYCompact's 6px is a per-component visual decision, not a rename. */
  --stm-table-cellPaddingY: 10px; /* Vertical cell padding — 10px */
  --stm-table-cellPaddingYCompact: 4px; /* Vertical cell padding in the dense density - 4px (TOK-107). TOK-37: renamed from cellPadding{X,Y}Dense. The density modifier standardized on `compact` and `dense` was retired; the toolbar's own migration note claimed it was "the last component still speaking the retired dialect" — StmTable still was, in its tokens, its class AND its public prop. Value unchanged; this is the naming half. The VALUE half stays deferred: 4px here against core.density.controlPaddingYCompact's 6px is a per-component visual decision, not a rename. */
  --stm-table-border: #334155; /* Row/cell separator border color */
  --stm-table-borderWidth: 1px; /* Border width — 1px */
  --stm-table-headerBorderWidth: 1px; /* Weight of the rule under the header row - 1px, unchanged (TOK-107). The header underline and the rules between body rows shared one borderWidth, so the header could never be separated more heavily than two adjacent rows are. */
  --stm-table-stickyHeaderShadow: inset 0px 0px 0px 9999px rgba(255,255,255,0.05), 0px 1px 3px 0px rgba(255,255,255,0.10); /* Shadow under a pinned header row (CMP-54 / TOK-107). A sticky header separated from the rows scrolling under it by a hairline alone, which disappears the moment a row's own fill meets it. */
  --stm-table-radius: 10px; /* Table container border radius — brand-configurable (default 8px) */
  --stm-table-rowAlt: #1E293B; /* Alternating row background */
  --stm-table-rowHover: #334155; /* Row hover background */
  --stm-table-rowSelected: #122666; /* Selected row background */
  --stm-table-headerTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-table-headerTypography-font-size: 14px;
  --stm-table-headerTypography-font-weight: 700;
  --stm-table-headerTypography-line-height: 1.5;
  --stm-table-cellTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-table-cellTypography-font-size: 14px;
  --stm-table-cellTypography-font-weight: 400;
  --stm-table-cellTypography-line-height: 1.5;

  /* tag */
  --stm-tag-container: #1E293B; /* Neutral tag background — light container tint */
  --stm-tag-onContainer: #E2E8F0; /* Neutral tag text — muted on-surface */
  --stm-tag-border: #E2E8F0; /* Neutral tag boundary — the label's OWN ink step, not a tint of the surface (#631, WCAG 1.4.11). outlineVariant measured 2.41:1 on white and 1.25:1 on the dark surface, so the pill had no boundary and read as loose floating text. Same shape as ADR-023's outline badge, where one text-grade token carries text + dot + border: because the tag container sits within ~1.1:1 of the surface, the label gate (>=4.5:1 on container) already guarantees this clears 3:1 on the surface, in every brand and both modes. */
  --stm-tag-containerInfo: #172554; /* Info tag background */
  --stm-tag-onContainerInfo: #BFDBFE; /* Info tag text — Strong step: the 12px label needs 4.5:1 on the near-white tint (P2 a11y 2026-07-06; infoEmphasis is a border step and hit 3.37:1) */
  --stm-tag-borderInfo: #BFDBFE; /* Info tag boundary — the label's own Strong step (#631). infoSubtle measured 1.42:1 on white, and in dark mode it resolves to the SAME value as containerInfo, i.e. no border at all. */
  --stm-tag-containerSuccess: #052e16; /* Success tag background */
  --stm-tag-onContainerSuccess: #BBF7D0; /* Success tag text — Strong step: the 12px label needs 4.5:1 on the near-white tint (P2 a11y 2026-07-06; successEmphasis is a border step and hit 2.17:1) */
  --stm-tag-borderSuccess: #BBF7D0; /* Success tag boundary — the label's own Strong step (#631). successSubtle measured 1.21:1 on white and equalled containerSuccess in dark. */
  --stm-tag-containerWarning: #4A3600; /* Warning tag background */
  --stm-tag-onContainerWarning: #FFEFAD; /* Warning tag text — Strong step: the 12px label needs 4.5:1 on the near-white tint (P2 a11y 2026-07-06) */
  --stm-tag-borderWarning: #FFEFAD; /* Warning tag boundary — the label's own Strong step (#631). The worst of the five: warningSubtle measured 1.15:1 on white over a container that is itself 1.02:1, i.e. essentially the surface. warningEmphasis is NOT a candidate here — yellow tops out at 1.49:1 on white, which is why the Strong step is the only step that works for this family. */
  --stm-tag-containerDanger: #4C0519; /* Danger tag background */
  --stm-tag-onContainerDanger: #FECDD3; /* Danger tag text — Strong step: the 12px label needs 4.5:1 on the near-white tint (P2 a11y 2026-07-06; errorEmphasis is a border step) */
  --stm-tag-borderDanger: #FECDD3; /* Danger tag boundary — the label's own Strong step (#631). errorSubtle measured 1.41:1 on white and equalled containerDanger in dark. */
  --stm-tag-radius: 9999px; /* Tag rounding — brand-routed via brand.radius.pill (SETTL squares to 4px, TOK-40/118). */
  --stm-tag-borderWidth: 1px; /* Tag border width — 1px */
  --stm-tag-paddingX: 8px; /* Horizontal padding — 8px (compact, tighter than a badge) */
  --stm-tag-paddingY: 4px; /* Vertical padding — 4px */
  --stm-tag-minHeight: 32px; /* Tag floor height — TOK-108. 28px, which is exactly what the resting tag already measures (12px x 1.5 line-height + 2x4px padding + 2x1px border), so this is value-preserving today; it exists so a tag keeps the small-control baseline when it sits in a dense row next to a size='small' button or input, and so a brand that shrinks the label ramp does not shrink the pill below that floor. */
  --stm-tag-gap: 4px; /* Gap between an optional leading icon and the label — 4px */
  --stm-tag-iconSize: 1em; /* Leading-icon glyph box — TOK-108. 1em, so the icon tracks the tag label size; replaces the hardcoded width/height:1em both legs carried (breadcrumb.iconSize precedent). */
  --stm-tag-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-tag-typography-font-size: 12px;
  --stm-tag-typography-font-weight: 700;
  --stm-tag-typography-line-height: 1.5;

  /* tileLayout */
  --stm-tileLayout-container: #1E293B; /* Layout background */
  --stm-tileLayout-tileBg: #1E293B; /* Tile card background */
  --stm-tileLayout-tileBorder: #334155; /* Tile card border */
  --stm-tileLayout-tileShadow: inset 0px 0px 0px 9999px rgba(255,255,255,0.05), 0px 1px 3px 0px rgba(255,255,255,0.10); /* Tile resting shadow */
  --stm-tileLayout-tileShadowDrag: inset 0px 0px 0px 9999px rgba(255,255,255,0.08), 0px 8px 16px 0px rgba(255,255,255,0.08); /* Tile shadow while dragging */
  --stm-tileLayout-dragHandleBg: #1E293B; /* Drag handle area */
  --stm-tileLayout-dragHandleIcon: #E2E8F0; /* Drag handle grip icon */
  --stm-tileLayout-titleColor: #F8FAFC; /* Tile title colour (TOK-109) — was a raw core reach-in sitting right beside a fully tokenized titleTypography, so the tile could restyle its title's font but not its colour. */
  --stm-tileLayout-placeholderBg: #122666; /* Drop placeholder highlight */
  --stm-tileLayout-placeholderRadius: 14px; /* Corner rounding of the drop placeholder — same as tileRadius today, so value-preserving (TOK-109). It borrowed tileRadius without declaring it, which reads as intentional coupling but is not: the placeholder is a highlight behind the tile and can legitimately be squarer or rounder than the card it stands in for. */
  --stm-tileLayout-dragOpacity: 0.5; /* Opacity of the tile being dragged — 0.5 (TOK-109). Was a literal, then a raw core reach-in with a literal fallback; the ghost is the one place a brand may legitimately want a different reading (a very light brand surface disappears at 0.5). */
  --stm-tileLayout-gap: 16px; /* Gap between tiles */
  --stm-tileLayout-tileRadius: 14px; /* Tile corner radius */
  --stm-tileLayout-tilePadding: 12px; /* Tile content padding */
  --stm-tileLayout-tileBorderWidth: 1px; /* Tile border width */
  --stm-tileLayout-handleHeight: 32px; /* Drag handle height */
  --stm-tileLayout-headerGap: 8px; /* Gap between header children (drag handle, title) */
  --stm-tileLayout-headerPaddingY: 8px; /* Header vertical padding */
  --stm-tileLayout-dragHandleSize: 1rem; /* Drag handle grip icon size */
  --stm-tileLayout-titleTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-tileLayout-titleTypography-font-size: 16px;
  --stm-tileLayout-titleTypography-font-weight: 700;
  --stm-tileLayout-titleTypography-line-height: 1.5;
  --stm-tileLayout-focusRing: #7694F7; /* Keyboard focus ring colour on a tile slot (issue #565). TOK-109 asked for this trio and it was DELIBERATELY not added then: the slot had no tabindex and reordering was drag-and-drop only, so a :focus-visible rule would have been decoration on an element that could never take focus — and worse, it would have made the component look covered by a guard that never fires. The slots are focusable now, so the ring finally means something. */
  --stm-tileLayout-focusRingWidth: 2px; /* Focus ring stroke width on a tile slot (issue #565). */
  --stm-tileLayout-focusRingOffset: 2px; /* Focus ring offset on a tile slot (issue #565) — the OUTSET recipe, because the slot wraps a card that already carries tileRadius and a shadow; an inset ring would sit on top of the card's own edge. */
  --stm-tileLayout-grabbedOutline: #96ADF8; /* Outline of a tile that has been PICKED UP for a keyboard move (issue #565). Distinct from the focus ring on purpose: focus says "the arrows will move the cursor", grabbed says "the arrows will move THIS TILE", and a keyboard user who cannot tell the two apart cannot use the feature. */
  --stm-tileLayout-motionDuration: 200ms; /* Tile reorder/lift transition length — 300ms (TOK-109). Reached raw core at three sites across the two legs. */
  --stm-tileLayout-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Tile reorder/lift transition curve (TOK-109). */
  --stm-tileLayout-onTileBg: #F8FAFC; /* Ink for content a CONSUMER slots into a TILE (tileLayout.tileBg), which is a different surface from the layout's own container (CMP-65 / #621). The component's own parts each set their own colour and look perfect, so nothing here was missing until a HOST re-inked: an AuthLayout setting `color: onPrimary` for a green canvas left everything slotted in inheriting white, on white — 1.00:1. Defaults to core.color.onSurface, which is EXACTLY what slotted content already resolved to on an ordinary page, so this is value-preserving by construction and nothing moves except where it was already broken. */
  --stm-tileLayout-onContainer: #F8FAFC; /* Ink for content a CONSUMER slots into the tile LAYOUT's own container (CMP-65 / #621) — distinct from onTileBg, which inks a tile. Defaults to core.color.onSurface, exactly what slotted content already resolved to on an ordinary page, so nothing moves except where a host had re-inked and it was already broken. */

  /* timePicker */
  --stm-timePicker-maxWidth: 200px; /* Maximum width of the time input TOK-111: bound on both legs since 2026-08-20 — it was React-only before, which is why the whole timePicker namespace showed as paritySingleLeg. */

  /* timeline */
  --stm-timeline-itemGap: 16px; /* Vertical gap between activity items */
  --stm-timeline-avatarToContentGap: 8px; /* Horizontal gap between avatar and text block */
  --stm-timeline-avatarSize: 44px; /* Avatar gutter width — 40px, the same control-medium step StmAvatar size md resolves to (TOK-110). The avatar node is caller-supplied, so rows had no shared baseline and text blocks stepped left and right down the list. Applied as a min-width on the avatar cell, so a larger avatar still widens its own gutter but a smaller one (or a missing one) keeps the column aligned. */
  --stm-timeline-contentGap: 4px; /* Vertical gap between the (author+timestamp) header row and the body paragraph */
  --stm-timeline-headerGap: 8px; /* Horizontal gap between the author name and its timestamp — 8px (TOK-110). It used to borrow avatarToContentGap, which measures a DIFFERENT axis (the avatar gutter); the two were only accidentally equal, so retuning the gutter silently retuned the header. Same 8px today. */
  --stm-timeline-authorColor: #F8FAFC; /* Author name color */
  --stm-timeline-authorTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-timeline-authorTypography-font-size: 14px;
  --stm-timeline-authorTypography-font-weight: 700;
  --stm-timeline-authorTypography-line-height: 1.5;
  --stm-timeline-timestampColor: #94A3B8; /* Timestamp color — muted */
  --stm-timeline-timestampTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-timeline-timestampTypography-font-size: 12px;
  --stm-timeline-timestampTypography-font-weight: 400;
  --stm-timeline-timestampTypography-line-height: 1.5;
  --stm-timeline-bodyColor: #F8FAFC; /* Activity body text color */
  --stm-timeline-bodyTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-timeline-bodyTypography-font-size: 14px;
  --stm-timeline-bodyTypography-font-weight: 400;
  --stm-timeline-bodyTypography-line-height: 1.5;
  --stm-timeline-dividerColor: #334155; /* Optional row divider color */
  --stm-timeline-dividerWidth: 1px; /* Optional row divider width — 1px */
  --stm-timeline-dividerPaddingBottom: 16px; /* Space between an item's content and the rule below it in the `divided` variant — 16px, matching itemGap so a divided list keeps even rhythm above and below the rule (TOK-110/TOK-46: dividerColor and dividerWidth were declared for a variant that had never been built). */

  /* toggleButton */
  --stm-toggleButton-container: transparent; /* Resting fill (TOK-45) — aliases the segmented item it borrowed. */
  --stm-toggleButton-containerHover: #1E293B; /* Hover fill (TOK-45). Note what it borrowed: the segmented control's SHELL colour, used as a button's hover — a borrow that only makes sense while the two components are identical. */
  --stm-toggleButton-onContainer: #E2E8F0; /* Resting ink (TOK-45). */
  --stm-toggleButton-border: #334155; /* Outline colour (TOK-45). */
  --stm-toggleButton-borderWidth: 1px; /* Outline width (TOK-45). */
  --stm-toggleButton-paddingX: 12px; /* Horizontal padding (TOK-45). */
  --stm-toggleButton-paddingY: 6px; /* Vertical padding (TOK-45). */
  --stm-toggleButton-radius: 8px; /* Corner radius (TOK-45) — the one value it did NOT borrow, and a raw core reach-in instead. A standalone toggle is not a segment in a track, so it is right that this differs; it just had no name. PROMOTED 2026-08-20 (owner: promote all four). It IS a button and this is the button knob, so retur's standalone toggle becomes a PILL (2px -> 9999px) and starter/systemate go 2px -> 8px; settl -> base. That retur change is a real design statement rather than a rename, which is why the row held it for a decision — it was made, and rendered in all four brands before merging. The sentence above stays true: a standalone toggle is not a segment in a track, and it now says so by reading the BUTTON knob rather than the segmented control's. */
  --stm-toggleButton-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-toggleButton-typography-font-size: 16px;
  --stm-toggleButton-typography-font-weight: 700;
  --stm-toggleButton-typography-line-height: 1.5;
  --stm-toggleButton-typography-letter-spacing: 0em;
  --stm-toggleButton-focusRing: #7694F7; /* Focus ring colour (TOK-45) — raw core. */
  --stm-toggleButton-focusRingWidth: 2px; /* Focus ring width (TOK-45). */
  --stm-toggleButton-focusRingOffset: 2px; /* Focus ring offset (TOK-45). */
  --stm-toggleButton-motionDuration: 120ms; /* Fill/ink/border transition (TOK-45) — raw core, six times across the two legs. */
  --stm-toggleButton-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Transition curve (TOK-45). */
  --stm-toggleButton-opacityDisabled: 0.38; /* Disabled dimming (TOK-45) — raw core. */

  /* toolbar */
  --stm-toolbar-container: #1E293B; /* Toolbar background — plain surface; sits ON content chrome, not app chrome */
  --stm-toolbar-onContainer: #F8FAFC; /* Toolbar content colour — TOK-112. The bar coloured nothing and let its contents inherit whatever the host set; appBar owns its own onContainer for exactly this reason. Value-preserving: onSurface is what a toolbar on a plain surface already inherited. */
  --stm-toolbar-border: #334155; /* Bottom hairline separating the toolbar from its content region */
  --stm-toolbar-borderWidth: 1px; /* Bottom hairline weight — TOK-112. The colour was a slot while the width was a raw core reach-in (--stm-border-width-thin), so a brand turning its border knob moved this edge without the toolbar being able to opt out. Same 1px today. */
  --stm-toolbar-radius: 0px; /* Toolbar corner rounding — TOK-112. Square by default (unchanged); the slot exists so a toolbar docked at the top of a card can match brand.radius.card instead of cutting a square corner across a rounded surface. */
  --stm-toolbar-paddingX: 16px; /* Horizontal padding — 16px */
  --stm-toolbar-paddingXCompact: 8px; /* Dense horizontal padding — 8px (TOK-112). The dense variant used to change ONLY minHeight, so a dense toolbar kept comfortable 16px insets — half a density axis. */
  --stm-toolbar-paddingY: 8px; /* Vertical padding — 8px */
  --stm-toolbar-paddingYCompact: 4px; /* Dense vertical padding — 4px (TOK-112), completing the density axis with paddingXDense/gapDense. */
  --stm-toolbar-gap: 8px; /* Gap between toolbar items — 8px */
  --stm-toolbar-gapCompact: 4px; /* Dense item gap — 4px (TOK-112). */
  --stm-toolbar-minHeight: 52px; /* Comfortable toolbar height — 44px (brand control size) */
  --stm-toolbar-minHeightCompact: 44px; /* Compact toolbar height — 40px (MUI dense variant analog). TOK-37 standardized the density modifier on `--compact` and retired `--dense`; the toolbar was the last component still speaking the retired dialect, in its tokens, its class and its public prop. Value unchanged — this is the naming half of the migration, not the value half (which is a per-component visual decision the row defers to the next touch). */

  /* tooltip */
  --stm-tooltip-container: #F8FAFC; /* Dark background for contrast — inverts surface/text relationship */
  --stm-tooltip-onContainer: #1E293B; /* Light text on dark tooltip background */
  --stm-tooltip-maxWidth: 320px; /* Maximum tooltip width for readability */
  --stm-tooltip-elevation: inset 0px 0px 0px 9999px rgba(255,255,255,0.08), 0px 8px 16px 0px rgba(255,255,255,0.08); /* Shadow for floating above content */
  --stm-tooltip-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-tooltip-typography-font-size: 12px;
  --stm-tooltip-typography-font-weight: 400;
  --stm-tooltip-typography-line-height: 1.5;
  --stm-tooltip-showDelay: 600ms; /* Hover dwell before the tooltip appears (CMP-43) — long enough that incidental pointer travel doesn't flash tooltips. DESIGN RECORD for both legs: the React enterDelayMs and Blazor ShowDelay defaults mirror this value as numbers (framework props can't consume a CSS var) — change all three together. */
  --stm-tooltip-hideDelay: 100ms; /* Grace period before the tooltip hides (CMP-43) — survives small pointer slips. Mirrored by React leaveDelayMs / Blazor HideDelay defaults; change together. */
  --stm-tooltip-radius: 8px; /* Tooltip corner rounding — TOK-113. Was a missing slot: Blazor wrote `var(--stm-radius-sm, 4px)` in a rule that never matched (see below) and React bound nothing. Both legs measure 8px today, so core.radius.md is value-preserving; the scan’s “MUI 4px” was wrong. TOK-40: promoted off core.radius.md onto the BRAND popup knob. A tooltip is a popup by any reading, and it was the one floating surface exempt from the brand's popup shape. Unchanged under starter/retur/systemate (all md); SETTL squares its family (base), which is the identity decision this slot was silently opting out of — the same reason navLink.radius moved to brand.radius.pill in TOK-45. */
  --stm-tooltip-paddingX: 8px; /* Tooltip horizontal padding — TOK-113. Neither leg bound it; both happen to render 8px, so this is value-preserving and now tunable. */
  --stm-tooltip-paddingY: 4px; /* Tooltip vertical padding — TOK-113. Neither leg bound it; both render 4px. */
  --stm-tooltip-borderWidth: 0; /* Tooltip border width — TOK-113. Blazor killed the vendor border with `border: none`, React left it to MUI (which draws none). Declared at 0 so both legs state it explicitly and a brand can add an outline; value-preserving. */
  --stm-tooltip-offset: 4px; /* Gap between the target and the tooltip (issue #197). Owned by the tooltip because it is a positioning constant of THIS surface — the same reasoning that gives popover.offset and dropDownButton.menuOffset their own slots (TOK-114 retired the idea of a shared menu.offset for exactly this reason). Read by the JS positioner, so it must resolve to a length. BLAZOR-ONLY IN CSS (TOK-46c): React positions the tooltip with MUI's Popper, whose offset is a modifier taking a NUMBER — same prop-channel limit as this file's showDelay/hideDelay design records. The Blazor twin offsets in CSS and binds it. */
  --stm-tooltip-arrowSize: 12px; /* Tooltip arrow/callout size — TOK-113. Colour was bound in both legs, size was vendor default (MUI scales its triangle from font-size; Telerik’s callout is a fixed box). Both measure 12px wide today, so this is value-preserving on both. */
  --stm-tooltip-zIndex: 10000; /* Tooltip stacking level — TOK-113. Bound on the REACT leg only, deliberately. MUI already resolves to 1500, matching this token; Telerik stacks its popup layer at 10002 and forcing our scale onto it would drop the tooltip BELOW Telerik’s own dialogs and popups. Each leg is internally consistent with its own vendor’s layer system, which matters more than a shared integer. */

  /* treeView */
  --stm-treeView-indentation: 24px; /* Indent per depth level. On core.space rather than a vendor default, which is the specific thing the library scan asked for: MUI X exposes it as itemChildrenIndentation and Telerik bakes it into .k-treeview-group padding, so without one token the two legs indent by different amounts and a tree screenshots differently per framework. */
  --stm-treeView-itemHeight: 44px; /* Minimum height of one row. Rides the brand control ladder: a tree row is a control the user aims at, and on a dense brand it should tighten with every other control rather than stay at a vendor's fixed 32px. */
  --stm-treeView-itemPaddingBlock: 4px; /* Row padding, block axis. */
  --stm-treeView-itemPaddingInline: 8px; /* Row padding, inline axis. */
  --stm-treeView-itemGap: 8px; /* Gap between a row's expand chevron, its optional checkbox, and its label. */
  --stm-treeView-itemRadius: 2px; /* Row hit-area radius — visible on hover, selection and focus. */
  --stm-treeView-itemColor: #F8FAFC; /* Row label ink. */
  --stm-treeView-container: #1E293B; /* Fill of the TREE, not of a row. This slot started life as itemContainer — a resting fill on every row — and rendering killed that: a surface per row turns each label into a floating white chip on the page background, because a row is only as wide as its text until it is told otherwise. The surface belongs to the container; rows sit ON it and are transparent until hovered or selected. */
  --stm-treeView-itemContainerHover: #334155; /* Row hover fill. */
  --stm-treeView-itemContainerSelected: #122666; /* Selected row fill. Subtle rather than the saturated primary: a tree can have many rows selected at once, and a page of saturated bars is unreadable. */
  --stm-treeView-itemColorSelected: #B5C6FA; /* Selected row ink — the on-pair of the selected fill, so contrast holds in every brand and both modes. */
  --stm-treeView-itemContainerSelectedHover: #1A3791; /* Hover on an already-selected row. Deepens rather than reverting to the plain hover fill — the GDK-49 ladder StmNavLink uses: hovering a selected thing must not look like deselecting it. */
  --stm-treeView-itemColorDisabled: #94A3B8; /* Ink of a disabled row. */
  --stm-treeView-iconColor: #94A3B8; /* Expand/collapse chevron ink. Muted against the label on purpose — the chevron is a control, not content, and at every depth it repeats. */
  --stm-treeView-iconSize: 16px; /* Expand/collapse chevron size. */
  --stm-treeView-typography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-treeView-typography-font-size: 16px;
  --stm-treeView-typography-font-weight: 400;
  --stm-treeView-typography-line-height: 1.5;
  --stm-treeView-focusRing: #7694F7; /* Row focus ring. */
  --stm-treeView-focusRingWidth: 2px; /* Row focus ring width. */
  --stm-treeView-focusRingOffset: -2px; /* Row focus ring offset. The INSET variant, because a tree row spans the full width of its container and an outward ring on the first or last row would be clipped. */
  --stm-treeView-motionDuration: 120ms; /* Hover/selection transition. */
  --stm-treeView-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Hover/selection easing. */

  /* typography */
  --stm-typography-colorPrimary: #F8FAFC; /* The `primary` intent, and the resting ink of the component (TOK-45). NOT named `color`: that would emit --stm-typography-color, which IS the override channel — registering it would make every fallback unreachable and collapse all fourteen intents onto one. The first draft of this manifest did exactly that and produced `var(--stm-typography-color, var(--stm-typography-color))`, a self-referential fallback, which is what the collision looks like when it happens. */
  --stm-typography-colorSecondary: #E2E8F0; /* The `secondary` intent (TOK-45) — raw core. */
  --stm-typography-colorSubtle: #94A3B8; /* The `subtle` intent (TOK-45) — raw core. */
  --stm-typography-colorDisabled: #94A3B8; /* The `disabled` intent (TOK-45). It shared onSurfaceSubtle with `subtle`, so disabled text and merely-quiet text were indistinguishable AND inseparable — core.color.disabledText is the TOK-13 slot that exists for exactly this. */
  --stm-typography-colorError: #FB7185; /* The `error` intent (TOK-45) — raw core. */
  --stm-typography-colorHeading: #F8FAFC; /* Default ink for display/h1–h6 (GDK-43, TOK-45). Brands re-point it — retur to its primary green — and it was reached through a two-deep var() fallback chain rather than a slot. */

  /* upload */
  --stm-upload-dropzoneBg: #1E293B; /* Drop zone background */
  --stm-upload-dropzoneBgActive: #122666; /* Drop zone background when file dragged over. Both legs since TOK-130 (§PB-3): the Blazor dropzone had no drag-active state at all — resting and hover only — so this slot had nothing to paint on that leg. The state is a depth-counted dragenter/dragleave pair on both legs now, which does NOT flicker when the pointer crosses the icon or the label. */
  --stm-upload-dropzoneBorder: #334155; /* Drop zone dashed border */
  --stm-upload-dropzoneBorderActive: #96ADF8; /* Drop zone border when active. Both legs since TOK-130 (§PB-3): the Blazor dropzone had no drag-active state at all — resting and hover only — so this slot had nothing to paint on that leg. The state is a depth-counted dragenter/dragleave pair on both legs now, which does NOT flicker when the pointer crosses the icon or the label. */
  --stm-upload-dropzoneText: #E2E8F0; /* Drop zone instruction text */
  --stm-upload-dropzoneIcon: #96ADF8; /* Upload icon color */
  --stm-upload-fileItemBg: #1E293B; /* File list item background */
  --stm-upload-fileItemText: #F8FAFC; /* File name text */
  --stm-upload-fileItemMeta: #E2E8F0; /* File size/type text */
  --stm-upload-fileItemBorder: #334155; /* File item border */
  --stm-upload-removeIcon: #FB7185; /* Remove file icon */
  --stm-upload-successIcon: #4ADE80; /* Upload complete icon */
  --stm-upload-progressTrack: #334155; /* File upload progress track */
  --stm-upload-progressFill: #96ADF8; /* File upload progress fill */
  --stm-upload-radius: 14px; /* Drop zone corner radius */
  --stm-upload-fileItemRadius: 8px; /* File item corner radius */
  --stm-upload-borderWidth: 2px; /* Drop zone border thickness */
  --stm-upload-padding: 18px; /* Drop zone internal padding */
  --stm-upload-fileItemPadding: 8px; /* File item padding */
  --stm-upload-gap: 8px; /* Gap between file items */
  --stm-upload-dropzoneGap: 8px; /* Gap between dropzone icon and label */
  --stm-upload-fileItemGap: 8px; /* Gap between file item children (icon, name, meta) */
  --stm-upload-progressHeight: 4px; /* Progress bar height */
  --stm-upload-progressRadius: 2px; /* Progress bar corner radius PROMOTED to {brand.radius.track} 2026-08-20 (TOK-40, owner: promote all four). Value-preserving in every brand — 2px on a thin filled bar is a PROPORTION, not a shape identity, so the promotion buys the SEAM and not a change. A brand that wants square bar ends now sets one knob instead of editing six components. */
  --stm-upload-removeButtonSize: 24px; /* Remove button width/height */
  --stm-upload-removeButtonRadius: 2px; /* Remove button corner radius */
  --stm-upload-removeButtonIconSize: 18px; /* Remove button icon font size */
  --stm-upload-statusIconSize: 16px; /* File status icon width/height. TOK-114: React never bound it — the status glyph carried its own width/height=16 SVG attributes, so a brand could retune the slot and the React leg would not move. */
  --stm-upload-dropzoneIconSize: 32px; /* Drop-zone icon width/height (TOK-114). Both legs hardcoded 32 as SVG width/height ATTRIBUTES, which is a size no stylesheet can reach — the token could not have been honoured even if it had existed. */
  --stm-upload-removeButtonBgHover: #334155; /* Remove-button hover background (TOK-114). Both legs used upload.fileItemBorder for this — a BORDER colour standing in for a surface, so retuning the file-item border silently retinted a hover state. Same resolved value; the two measures are separable now. */
  --stm-upload-dropzoneBgHover: #1E293B; /* Drop-zone hover background (TOK-114). Hover reused dropzoneBgActive, conflating "the pointer is over the zone" with "a file is being dragged onto it" — two states a brand may well want to distinguish, and one of which the keyboard never reaches. Same resolved value as the active slot today. */
  --stm-upload-dropzoneBorderHover: #96ADF8; /* Drop-zone hover border (TOK-114) — the pair of dropzoneBgHover. Same resolved value as dropzoneBorderActive today. */
  --stm-upload-fileItemBorderWidth: 1px; /* File-item border width (TOK-114) — a raw core reach-in on both legs while the drop zone next to it had owned its borderWidth all along. */
  --stm-upload-errorColor: #FB7185; /* Error text, the error status glyph and an errored file's name (TOK-114). Split from removeIcon, which was doing three jobs: a REMOVE affordance and an ERROR signal are different roles that merely happen to be red, and a brand that wants a neutral remove ✕ could not have one. */
  --stm-upload-statusFontWeight: 700; /* Weight of the file-status glyph (TOK-114) — a raw core reach-in on the Blazor leg, and unset on React, so the same tick rendered at different weights. */
  --stm-upload-focusRing: #7694F7; /* Keyboard focus ring colour on the drop zone and the remove button (TOK-114) — completes the trio with focusRingWidth + focusRingOffset; both legs reached raw core. */
  --stm-upload-focusRingWidth: 2px; /* Focus ring stroke width on the drop zone and remove button (TOK-114). */
  --stm-upload-focusRingOffset: 2px; /* Focus ring offset on the drop zone and remove button (TOK-114) — the OUTSET recipe; the drop zone is a large dashed surface, so an inset ring would sit on its own border. */
  --stm-upload-motionDuration: 150ms; /* Drop-zone hover/drag colour transition (TOK-114) — raw core on both legs. */
  --stm-upload-motionEasing: cubic-bezier(0.4, 0, 0.2, 1); /* Drop-zone transition curve (TOK-114). */
  --stm-upload-progressMotionDuration: 200ms; /* Progress-bar fill transition (TOK-114) — deliberately its OWN slot rather than motionDuration: a bar tracking real upload progress wants a slower, smoother sweep than a hover tint, and they were already different durations in the CSS. */
  --stm-upload-labelTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-upload-labelTypography-font-size: 14px;
  --stm-upload-labelTypography-font-weight: 700;
  --stm-upload-labelTypography-line-height: 1.5;
  --stm-upload-fileNameTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-upload-fileNameTypography-font-size: 14px;
  --stm-upload-fileNameTypography-font-weight: 400;
  --stm-upload-fileNameTypography-line-height: 1.5;
  --stm-upload-metaTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-upload-metaTypography-font-size: 12px;
  --stm-upload-metaTypography-font-weight: 400;
  --stm-upload-metaTypography-line-height: 1.5;

  /* validationSummary */
  --stm-validationSummary-container: #4C0519; /* Panel fill (TOK-45) — raw core. */
  --stm-validationSummary-border: #FB7185; /* Panel outline (TOK-45) — raw core. Deliberately the full-strength error colour against the subtle surface: this is the one block on the page that should not be missed. */
  --stm-validationSummary-borderWidth: 1px; /* Outline width (TOK-45) — raw core. */
  --stm-validationSummary-onContainer: #FECDD3; /* Message ink (TOK-45) — raw core. errorStrong rather than error, because plain error-red on an errorSubtle wash fails contrast (issue #65 / RT-828); the pairing is the point and it was invisible. */
  --stm-validationSummary-radius: 8px; /* Corner radius (TOK-45). Routed through the BRAND alert knob rather than core.radius.md, which is what the CSS reached for: this IS an alert by any reading, and it was the only one exempt from the brand's alert shape. */
  --stm-validationSummary-padding: 16px; /* Panel padding (TOK-45) — raw core. */
  --stm-validationSummary-titleGap: 4px; /* Gap between the title and the message list (TOK-45) — raw core. */
  --stm-validationSummary-listInset: 24px; /* Bullet indent of the message list (TOK-45) — raw core. */
  --stm-validationSummary-titleTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-validationSummary-titleTypography-font-size: 14px;
  --stm-validationSummary-titleTypography-font-weight: 700;
  --stm-validationSummary-titleTypography-line-height: 1.5;
  --stm-validationSummary-messageTypography-font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif;
  --stm-validationSummary-messageTypography-font-size: 14px;
  --stm-validationSummary-messageTypography-font-weight: 400;
  --stm-validationSummary-messageTypography-line-height: 1.5;
}
