/* ============================================================================
   Cordis Suite — design tokens v2.0
   One source for CRM (React), MachineControlServer (Blazor), Studio/Modeler
   and every new application.

   Rules:
   1. Application code uses ONLY the semantic tokens (--cordis-*).
      The primitives (--cordis-blue-500 etc.) exist solely to feed them.
   2. Never a hex in application code.
   3. Dark mode = redefining the semantic layer only.
   4. Two axes and no more: profile (Shell / Studio) × theme (light / dark).
      The density axis was retired by decision D11.
   ========================================================================= */

:root {
  /* ---------- Primitives · blue (sampled from the Cordis logo) ---------- */
  --cordis-blue-50:  #EBF7FD;
  --cordis-blue-100: #D2ECFA;
  --cordis-blue-200: #A9DCF5;
  --cordis-blue-300: #8FD3F5;  /* logo highlight */
  --cordis-blue-400: #45B1E4;
  --cordis-blue-500: #1F9CD8;  /* logo base — the Cordis blue */
  --cordis-blue-600: #1487C8;  /* logo shadow */
  --cordis-blue-700: #0F6A97;
  --cordis-blue-800: #0B4F71;
  --cordis-blue-900: #08364E;
  --cordis-blue-950: #06222F;  /* navigation rail */

  /* ---------- Primitives · green (logo) ---------- */
  --cordis-green-50:  #EDF8EC;
  --cordis-green-100: #D7EFD5;
  --cordis-green-500: #3BAA35;  /* logo base */
  --cordis-green-600: #2E8A2A;
  --cordis-green-700: #1F6B20;  /* logo deep */

  /* ---------- Primitives · red (logo) ---------- */
  --cordis-red-50:  #FDECEE;
  --cordis-red-100: #FAD3D6;
  --cordis-red-500: #E20717;  /* logo base */
  --cordis-red-600: #C00612;
  --cordis-red-700: #8F0510;

  /* ---------- Primitives · amber (derived, not in the logo) ---------- */
  --cordis-amber-50:  #FEF6E7;
  --cordis-amber-100: #FBE7C2;
  --cordis-amber-500: #C97A0E;
  --cordis-amber-700: #8A5208;

  /* ---------- Primitives · neutral (cool, blue-tinted) ---------- */
  --cordis-n-0:   #FFFFFF;
  --cordis-n-25:  #F7F9FB;
  --cordis-n-50:  #F1F4F8;
  --cordis-n-100: #E5EAF0;
  --cordis-n-200: #D2DAE3;
  --cordis-n-300: #B2BECD;
  --cordis-n-400: #8494A8;
  --cordis-n-500: #5F7085;
  --cordis-n-600: #465667;
  --cordis-n-700: #33404E;
  --cordis-n-800: #212B36;
  --cordis-n-900: #131A21;

  /* ---------- Semantic · surface ---------- */
  --cordis-bg:             var(--cordis-n-50);
  --cordis-surface:        var(--cordis-n-0);
  --cordis-surface-sunken: var(--cordis-n-25);
  --cordis-surface-raised: var(--cordis-n-0);
  --cordis-border:         var(--cordis-n-100);
  --cordis-border-strong:  var(--cordis-n-200);
  --cordis-overlay:        rgba(19, 26, 33, 0.45);

  /* ---------- Semantic · text ---------- */
  /* THREE STEPS, AND ALL THREE ARE REAL. Until 2026-08 `muted` and `faint` were both n-500 --
     the same value -- so a caller reaching for the fainter of the two got no visual result and no
     warning, and a three-level hierarchy silently collapsed to two. The old comment here defended
     the duplication by rejecting the wrong alternative: n-400 (#8494A8) measures 3.10:1 on
     --cordis-surface and is indeed a border grey rather than a text colour, so lightening `faint`
     was never the answer.
     The answer was the rung that was missing: `muted` moves DOWN to n-600 and `faint` keeps the
     pixels it already had. Nothing that reads `faint` changes appearance; `muted` gets darker,
     which no contrast rule can object to. Measured on --cordis-surface (#FFFFFF):
         text 14.4:1   muted 7.5:1   faint 5.1:1
     which is also, and this is the argument for these particular values, the ladder dark has had
     all along on its own --cordis-surface (#242932): 14.6 / 7.2 / 5.7. The themes now agree on
     what three levels of text mean instead of only one of them having three.
     Reported by a consumer, 2026-08 (item 4). Pinned by TextLadderTests -- both themes, both
     directions: ordered, every step visible, every rung >= 4.5:1. */
  --cordis-text:         var(--cordis-n-800);
  --cordis-text-strong:  var(--cordis-n-900);
  --cordis-text-muted:   var(--cordis-n-600);
  --cordis-text-faint:   var(--cordis-n-500);
  --cordis-text-on-fill: #FFFFFF;

  /* ---------- Semantic · action ----------
     THE BRAND BLUE IS THE PRIMARY. Blue 500 #1F9CD8 is the colour sampled from
     the logo and the one the specification document (reference/index.html)
     shows, and a filled Cordis button is now that colour rather than a darker
     relative of it. Decided 2026-08.

     What this costs, stated plainly rather than left to be discovered: white on
     #1F9CD8 measures 3.1:1, so a filled primary button does not reach the 4.5:1
     the rest of this system holds itself to. That was weighed and the brand won,
     on the same grounds as the Studio status bar (D4) — a filled button is a
     large, high-salience target with a label in semibold, not body copy. The
     rule the suite still keeps is the one about TEXT: --cordis-primary-text stays
     blue-700 so blue-on-white prose passes, and the hover and active states step
     down the ramp, so the two states a user is actually reading during an
     interaction both pass.

     Hover/active step to 600/700 rather than 700/800: the resting state is no
     longer the darkest rung, so the whole interaction ladder shifts up one. */
  --cordis-primary:         var(--cordis-blue-500);
  --cordis-primary-hover:   var(--cordis-blue-600);
  --cordis-primary-active:  var(--cordis-blue-700);
  --cordis-accent:          var(--cordis-primary);
  --cordis-primary-soft:    var(--cordis-blue-50);
  --cordis-primary-soft-bd: var(--cordis-blue-200);
  --cordis-primary-text:    var(--cordis-blue-700); /* blue text on white — AA */
  /* ONE ACCENT SURFACE. The focus ring is the same blue as everything else
     accent-coloured, and it says `var(--cordis-primary)` rather than naming a
     rung so it cannot drift from one again.

     It used to be blue-700, and the reason it was is worth keeping because it
     stopped being true. The ring was once blue-500 at 30% ALPHA, which resolves
     to about #C8E7F6 on white and measures ~1.4:1 — a ring the system claimed
     was visible and was not. Stepping the colour down the ramp fixed the
     symptom. But the ring is now a full-opacity 2px box-shadow, and at full
     opacity blue-500 on white measures 3.1:1, which is what WCAG 1.4.11 asks of
     a non-text UI component (3:1) — the 4.5:1 bar the old comment cited is the
     TEXT rule, applied here to a border. Holding a border to the text threshold
     is what put a second blue in the system, and a second accent blue is a
     bigger defect than 3.1:1 on a 2px outline: Cordis Studio has exactly one
     accent (its app.css declares `--cordis-blue: #1f9cd8` and everything reads
     it), and a suite whose focus ring is a different blue from its status bar
     does not look like one product.

     A box-shadow ring already follows the focused element's OWN border-radius,
     which is why the universal :focus-visible rule in cordis.css does not force
     a radius onto it — see the comment there. */
  --cordis-focus-color:     var(--cordis-primary);
  --cordis-focus-ring:      0 0 0 2px var(--cordis-focus-color);

  /* ---------- Semantic · entry canvas ----------
     The brand gradient behind login, licence and activation screens.
     Taken from MachineControlServer, but rebuilt out of the ramp. */
  --cordis-canvas-brand: linear-gradient(215deg,
      var(--cordis-n-100) 0%,
      var(--cordis-blue-200) 42%,
      var(--cordis-blue-700) 100%);
  --cordis-canvas-brand-text: var(--cordis-blue-900);

  /* ---------- Semantic · navigation ---------- */
  --cordis-nav-bg:        var(--cordis-blue-950);
  --cordis-nav-text:      #B7D5E8;
  --cordis-nav-text-on:   #FFFFFF;
  --cordis-nav-active:    var(--cordis-blue-700);
  --cordis-nav-hover:     rgba(255, 255, 255, 0.07);
  --cordis-nav-divider:   rgba(255, 255, 255, 0.09);
  /* Text and status ON a navigation surface. The ordinary --cordis-text-* tokens
     are for light surfaces and become unreadable on nav-bg. */
  --cordis-nav-text-muted: #8FB3CC;
  --cordis-nav-accent:     #8FD3F5;
  --cordis-nav-warning:    #F0C983;
  --cordis-nav-success:    #9BE096;
  --cordis-nav-danger:     #FF9EA3;

  /* ---------- Semantic · status bar ----------
     Decision D4, decided 2026-08, and the answer is per PROFILE because the two
     bars are not the same object. In SHELL the value below applies: dark
     blue-950 on brand blue, 5.3:1, because a Shell status bar carries readable
     information a user is expected to parse — a record count, a save state, a
     validation summary. In STUDIO it is white (cordis-studio.css overrides it):
     Studio's bar is window chrome in the manner of an IDE, glanced at rather
     than read, and white on brand blue is what a Cordis workbench has always
     looked like. 3.1:1, accepted deliberately there and nowhere else.

     The bar is brand-coloured in both themes, so this pair is declared in the
     dark layer too. */
  --cordis-statusbar-bg:   var(--cordis-blue-500);
  --cordis-statusbar-text: var(--cordis-blue-950);

  /* ---------- Semantic · inverse surface ----------
     A surface that deliberately contrasts with the page instead of belonging to
     it: the Shell toast pill, and anything else that has to read as laid OVER
     the document. It needs its own pair because the obvious shortcut does not
     work — `background: var(--cordis-text-strong)` over
     `color: var(--cordis-n-0)` is an inversion that only holds while the text
     colour is dark, and under [data-theme="dark"] --cordis-text-strong IS
     #FFFFFF, so the surface measures 1:1 against its own text and disappears.
     That was a real, shipped defect in the retired `.cds-toast`, found only
     because the family merge put the two toasts side by side.

     Redeclared in the dark layer below, which is the whole point: an inverse
     surface is the one thing that must flip when the theme does. */
  --cordis-inverse-bg:   var(--cordis-n-900);
  --cordis-inverse-text: var(--cordis-n-25);

  /* ---------- Semantic · tooltip chip ----------
     Deliberately dark in BOTH themes, which is why the four values are literal
     and are not redeclared in the dark layer below. A tooltip is a transient
     chip laid over the page rather than a surface belonging to it, and every
     system the suite is measured against draws it dark on light and dark on
     dark. The alternative — a chip that inverts with the theme — makes the
     hint indistinguishable from the panel it covers in dark mode, which is the
     exact defect the retired `.cds-tooltip` had (it was built out of the TEXT
     ramp: --cordis-text-strong ground over --cordis-n-0 text, and in dark mode
     both resolve to #FFFFFF, so the chip measured 1:1 and was invisible).

     Promoted out of compat/cordis-legacy.css by D10 family merge #6. */
  --cordis-tooltip-bg:     #2A2E38;
  --cordis-tooltip-fg:     #EEF1F6;
  --cordis-tooltip-border: rgba(255, 255, 255, 0.10);
  --cordis-tooltip-kbd:    #9AA2AF;

  /* ---------- Semantic · status ---------- */
  --cordis-success:      var(--cordis-green-500);
  --cordis-success-soft: var(--cordis-green-50);
  --cordis-success-text: var(--cordis-green-700);
  --cordis-warning:      var(--cordis-amber-500);
  --cordis-warning-soft: var(--cordis-amber-50);
  --cordis-warning-text: var(--cordis-amber-700);
  --cordis-danger:       var(--cordis-red-500);
  --cordis-danger-soft:  var(--cordis-red-50);
  --cordis-danger-text:  var(--cordis-red-700);
  --cordis-info:         var(--cordis-blue-500);
  --cordis-info-soft:    var(--cordis-blue-50);
  --cordis-info-soft-bd: var(--cordis-blue-200);  /* .cordis-notice draws a real border; the severity
                                               variants deliberately blend theirs into the fill */
  --cordis-info-text:    var(--cordis-blue-700);
  --cordis-neutral-soft: var(--cordis-n-100);
  --cordis-neutral-text: var(--cordis-n-600);

  /* ---------- Category badges (kind, not state) ----------
     Taken from the CRM. A fixed assignment per value list; never for
     status — that has five meanings and a shape of its own (pill with a dot). */
  --cordis-cat-blue:   #0F6A97;  --cordis-cat-blue-soft:   rgba(31,156,216,0.13);
  --cordis-cat-violet: #4A2FB8;  --cordis-cat-violet-soft: rgba(124,92,255,0.12);
  --cordis-cat-amber:  #8A5208;  --cordis-cat-amber-soft:  rgba(245,165,36,0.13);
  --cordis-cat-orange: #A44205;  --cordis-cat-orange-soft: rgba(249,115,22,0.12);
  --cordis-cat-green:  #1F6B20;  --cordis-cat-green-soft:  rgba(18,161,80,0.12);
  --cordis-cat-grey:   #4A5560;  --cordis-cat-grey-soft:   rgba(105,121,131,0.13);
  --cordis-cat-red:    #A11220;  --cordis-cat-red-soft:    rgba(229,72,77,0.12);

  /* ---------- Studio · data types ----------
     The only hue outside the four brand colours. For user-defined data types
     (enumerations, complex types) in Studio only. */
  --cordis-type-violet: #8B5CF6;
  --cordis-type-violet-dark: #B79BFF;  /* on dark surfaces; 8B5CF6 does not reach 4.5:1 there */

  /* ---------- Data visualisation (one blue ramp; status = meaning) -------- */
  --cordis-chart-1: var(--cordis-blue-700);
  --cordis-chart-2: var(--cordis-blue-500);
  --cordis-chart-3: var(--cordis-blue-400);
  --cordis-chart-4: var(--cordis-blue-300);
  --cordis-chart-5: var(--cordis-n-300);
  /* The dashed gridline, aliased to --cordis-border rather than naming a grey of its
     own. The two answer the same question -- what is the quietest line still visible
     on a surface -- and the dark half of this token answered it wrongly for a year:
     it was #1B262E, which is --cordis-neutral-soft's value, a soft BADGE FILL picked
     against the dark bg (#1B1F26) and therefore DARKER than the #242932 card a chart
     is drawn on. 1.05:1. The gridlines were not there. That is precisely the defect
     the --cordis-border comment in the dark block below records, one token over, so
     it gets the same answer rather than a second measurement. */
  --cordis-chart-track: var(--cordis-border);

  /* ---------- Typography ----------
     Both families are SHIPPED as of decision D6 (2026-08): cordis.css declares
     the @font-face for each against fonts/IBMPlexSans-latin.woff2 and
     fonts/IBMPlexMono-latin.woff2. The system-ui tail is a genuine fallback for
     the frame before the face arrives, not the value every application quietly
     rendered in for the first year of this file's life. */
  --cordis-font:      'IBM Plex Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --cordis-font-mono: 'IBM Plex Mono', ui-monospace, 'Cascadia Mono', monospace;
  --cordis-fs-display: 30px; --cordis-lh-display: 38px;
  --cordis-fs-h1:  24px;     --cordis-lh-h1:  32px;
  --cordis-fs-h2:  18px;     --cordis-lh-h2:  26px;
  --cordis-fs-h3:  15px;     --cordis-lh-h3:  22px;
  --cordis-fs-body:14px;     --cordis-lh-body:21px;
  --cordis-fs-sm:  13px;     --cordis-lh-sm:  20px;
  --cordis-fs-xs:  12px;     --cordis-lh-xs:  18px;
  --cordis-fs-caption: 11px; --cordis-ls-caption: 0.08em;
  --cordis-fw-regular: 400; --cordis-fw-medium: 500; --cordis-fw-semibold: 600; --cordis-fw-bold: 700;

  /* ---------- Space (4px grid) ---------- */
  --cordis-space-1: 4px;  --cordis-space-2: 8px;  --cordis-space-3: 12px;
  --cordis-space-4: 16px; --cordis-space-5: 24px; --cordis-space-6: 32px;
  --cordis-space-7: 48px; --cordis-space-8: 64px;

  /* ---------- Radius ----------
     Decision D9 (2026-08): the profiles diverge in CHARACTER, not only in size,
     and radius is one of the three places that happens. This is the Shell scale
     and it takes the web end — controls 6, panels 10, modals 16, the CRM's own
     values.

     Studio keeps this 6px small step, unchanged, and takes 8px for both md and lg
     (cordis-studio.css): a workbench panel is a region of a window, and a 16px
     corner on a splitter pane looks like a card that has come loose. Those two
     numbers are MEASURED off Cordis Studio's own stylesheet rather than chosen,
     and the measurement is a mild embarrassment to D9 — the pre-D9 compromise
     scale was 6/8/12, so on radius specifically the compromise had Studio very
     nearly right and it is Shell that moved. Radius is the weakest of D9's three
     divergences; shadow and the spacing rhythm are where the profiles really do
     part company. */
  --cordis-radius-sm: 6px; --cordis-radius-md: 10px; --cordis-radius-lg: 16px;
  --cordis-radius-pill: 999px;

  /* ---------- Elevation ----------
     The second half of D9. A Shell page is cards and sheets on a background, so
     the ladder is soft and generous. Studio nulls --cordis-shadow-sm outright and
     tightens the other two (cordis-studio.css): a workbench separates surfaces
     with 1px borders, and a resting panel that floats is a panel that looks
     detachable when it is not. Overlays keep a shadow in both profiles, because
     an overlay has to read as being above the thing it covers. */
  --cordis-shadow-sm: 0 1px 2px rgba(19, 26, 33, 0.06);
  --cordis-shadow-md: 0 4px 12px rgba(19, 26, 33, 0.08);
  --cordis-shadow-lg: 0 12px 32px rgba(19, 26, 33, 0.14);

  /* ---------- Motion ----------
     Durations from this document, curves from the CRM. --cordis-ease for anything
     moving from one state to another, --cordis-ease-out for anything arriving. */
  --cordis-ease:     cubic-bezier(0.4, 0, 0.2, 1);
  --cordis-ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --cordis-dur-fast: 120ms; --cordis-dur: 180ms; --cordis-dur-slow: 260ms;

  /* ---------- Metrics · the Shell scale ----------
     These seven were "density · comfortable" until D11 (2026-08) retired the
     density axis. There is no [data-density="compact"] any more: it moved five of
     these to roughly Studio's values, which is precisely the coincidence that
     made Studio look like "compact Shell" and hid the fact that the profiles are
     two kinds of application. Profile is the axis; there is no third one.

     --cordis-fs-row is decision D7, and it is declared HERE, among the metrics,
     rather than up in the type scale — that placement is the answer. Row text
     scales with the row it sits in: 14px in a 44px Shell row, 12.5px in a 26px
     Studio row (cordis-studio.css). Before this, a tree row's text size was
     pinned to a global step while the row height came from the profile, so
     making Studio's rows taller left the text where it was. A 12.5px step is
     therefore NOT a new rung on the suite's type scale — it is a metric that
     belongs to a profile, which is why it never fitted between --cordis-fs-xs and
     --cordis-fs-sm. */
  --cordis-control-h:    36px;
  --cordis-control-px:   16px;
  --cordis-control-fs:   14px;
  --cordis-row-h:        44px;
  /* A tree row IS a list row until Studio tightens it, so the Shell value is the
     general row height rather than a number of its own. cordis-studio.css declares
     the 26px workbench value. */
  --cordis-tree-row-h:   var(--cordis-row-h);
  --cordis-fs-row:       var(--cordis-fs-body);
  /* The height of a status pill / category tag, and it is a ROW metric rather than a
     step on some tag scale — same reasoning as --cordis-fs-row above (decision D7).
     A tag's usual home is a table cell, and both shapes used to set a literal 4px of
     block padding, so their height was decided by the font and came out at ~23px in
     BOTH profiles: comfortable in a 44px Shell row and flush against the borders of a
     26px Studio one, which read as a broken cell rather than a tag. Deriving it from
     the row keeps the same air on either side in both profiles. */
  --cordis-tag-h:        24px;
  --cordis-card-p:       20px;
  --cordis-section-gap:  24px;
  --cordis-nav-item-h:   36px;
  /* D10 family merge #2. Both halves of the switch family were right about different things — the
     component's DOM (a <label> wrapping a hidden native input, so keyboard and screen-reader
     behaviour stay free) and the asset layer's SIZE — and the reason the component looked like an
     afterthought beside a 36px control is that it hardcoded 24x14, which is Studio's switch, and
     rendered it in Shell too. cordis-studio.css already overrode the plain half to exactly those
     numbers, so the profile scale existed and the component could not see it. Now both read these.

     Only three values are needed: the two halves compute their own thumb inset and travel from
     them, because one has a 1px border and the other does not. */
  --cordis-switch-w:     38px;
  --cordis-switch-h:     22px;
  --cordis-switch-thumb: 16px;
}

/* Dark — taken from Cordis Studio: warm-neutral greys, no blue tint.
   Only the semantic layer changes. The brand stays blue. */
[data-theme="dark"] {
  --cordis-bg:             #1B1F26;  /* editor / canvas */
  --cordis-surface:        #242932;  /* panels, cards */
  --cordis-surface-sunken: #16181D;  /* activity rail, deepest surface */
  --cordis-surface-raised: #30404F;  /* selected blocks, popovers */
  /* Lifted from #2B2F35 in 2026-08. At #2B2F35 a border measured 1.11:1 against the
     #242932 panel it divides, which is not a line anyone can see -- the same class of
     defect as the 1.4:1 focus ring fixed above, and found the same way: Cordis Studio
     holds five bordered panels at once and its row dividers, toolbar rules and splitter
     edges had disappeared on dark. Studio's own token block had raised this value on
     purpose before the extraction, with a comment saying so; that reasoning is not
     Studio-specific, so it belongs to the canonical palette rather than to a profile
     override. 1.36:1 -- still a hairline, deliberately, but a visible one.
     MIRRORED in css/compat/cordis-legacy.css; change both. */
  --cordis-border:         #363C47;
  --cordis-border-strong:  #3A4350;
  --cordis-overlay:        rgba(0, 0, 0, 0.60);

  --cordis-text:        #FFFFFF;
  --cordis-text-strong: #FFFFFF;
  --cordis-text-muted:  #AEB7C2;
  --cordis-text-faint:  #98A2B0;  /* stays readable on raised surfaces */

  /* THE ACCENT SURFACE DOES NOT CHANGE HUE ON DARK. Blue-500 here as in light, so
     the status bar, a filled button, a selected row, a checked switch, the
     segmented toggle and the focus ring are all one colour in all four
     profile x theme combinations. It said blue-400 for a while, on the reasoning
     that "on dark the accent has to be lighter to reach AA" — which is true of
     accent TEXT and false of an accent SURFACE, and conflating the two is what
     put two blues on the same screen. The tell was visible in the showcase:
     --cordis-statusbar-bg is brand blue in both themes (D4) while --cordis-primary
     stepped to blue-400, so in dark the status bar and every other accent
     disagreed by one rung.

     Accent TEXT still steps, and that is the whole distinction: --cordis-primary-text
     is blue-300 here and blue-700 in light, because blue-on-a-ground being READ
     has to pass 4.5:1 and a filled surface does not. This is exactly what Cordis
     Studio's own app.css does — one --cordis-blue for every accent surface in
     both themes, with --cordis-selected-fg stepping per theme for the text on it.

     Hover and active step LIGHTER from blue-500 rather than darker, because the
     ground is dark: 500 → 400 → 300. */
  --cordis-primary:         var(--cordis-blue-500);
  --cordis-primary-hover:   var(--cordis-blue-400);
  --cordis-primary-active:  var(--cordis-blue-300);
  --cordis-accent:          var(--cordis-primary);
  --cordis-primary-soft:    #0A2E42;
  --cordis-primary-soft-bd: #135372;
  --cordis-primary-text:    var(--cordis-blue-300);
  --cordis-focus-color:     var(--cordis-primary);
  --cordis-focus-ring:      0 0 0 2px var(--cordis-focus-color);
  /* Dark text on the blue fill, which is why blue-500 works here: #06222F on
     #1F9CD8 is a far wider margin than white on it. */
  --cordis-text-on-fill:    #06222F;

  --cordis-nav-bg:      #16181D;
  --cordis-nav-text:    #98A2B0;
  --cordis-nav-divider: rgba(255, 255, 255, 0.08);
  --cordis-nav-text-muted: #AEB7C2;
  /* Studio sets the status bar in the brand blue with dark text. */
  --cordis-statusbar-bg:   var(--cordis-blue-500);
  --cordis-statusbar-text: var(--cordis-blue-950);

  /* The inverse surface flips: a pale chip over a dark page. See the light layer
     for why this pair exists rather than being read off the text ramp. */
  --cordis-inverse-bg:   #E5EAF0;
  --cordis-inverse-text: #131A21;

  --cordis-canvas-brand: linear-gradient(215deg, #16232C 0%, #0B4F71 55%, #06222F 100%);
  --cordis-canvas-brand-text: var(--cordis-blue-300);

  --cordis-success:      #5BC454;
  --cordis-success-soft: #12290F;
  --cordis-success-text: #9BE096;
  --cordis-warning:      #D8941F;
  --cordis-warning-soft: #3A3630;
  --cordis-warning-text: #F0C983;
  --cordis-danger:       #F4565C;
  --cordis-danger-soft:  #2E1113;
  --cordis-danger-text:  #FF9EA3;
  --cordis-info:         var(--cordis-blue-400);
  --cordis-info-soft:    #0A2E42;
  --cordis-info-soft-bd: #135372;
  --cordis-info-text:    var(--cordis-blue-300);
  --cordis-neutral-soft: #1B262E;
  --cordis-neutral-text: #93A6B4;

  /* THE CATEGORY INK STEPS ONE LIGHTER ON DARK, like every other hue (rule 4).
     It did not until 2026-08: the seven --cordis-cat-* inks were declared once, on :root, and this
     block did not touch them — so a .cordis-cat chip on dark drew a light-theme ink chosen for a
     WHITE ground on a #242932 panel. Measured against --cordis-surface, the light values land at
     blue 2.45  violet 1.66  amber 2.29  orange 2.34  green 2.21  grey 1.92  red 1.82 — all of them
     under the 3:1 floor for any legible mark, and cat-violet at 1.66:1 is not low contrast, it is an
     invisible chip. Reported by a consumer measuring all seven (2026-08, item 2).
     Only the INK moves. The -soft companions are rgba() washes of the bright hue, so they composite
     against whatever surface they land on and already adapted — that is why the fills looked right
     and only the text did not, which is exactly what made this survive a dark-theme review.
     Each value keeps its hue and saturation and raises lightness to clear 4.5:1 on the LIGHTEST dark
     surface (--cordis-surface-raised #30404F, i.e. a chip inside a popover). On a panel that leaves
     6.2:1, and in the real chip case — ink on its own soft wash over a panel — 4.9 to 5.5:1.
     Not mirrored into css/compat/cordis-legacy.css's dark block, and deliberately: that mirror exists
     for the families Studio's class-based dark mechanism needs, and .cordis-cat--* is Shell-only
     furniture (css/cordis.css:474-480 are the only readers in the suite). Pinned by
     CategoryInkContrastTests. */
  --cordis-cat-blue:   #40B3EC;
  --cordis-cat-violet: #AD9FE6;
  --cordis-cat-amber:  #F19315;
  --cordis-cat-orange: #F98C49;
  --cordis-cat-green:  #38C23A;
  --cordis-cat-grey:   #9FAAB5;
  --cordis-cat-red:    #F28B95;

  --cordis-chart-1: var(--cordis-blue-300);
  --cordis-chart-2: var(--cordis-blue-400);
  --cordis-chart-3: var(--cordis-blue-500);
  --cordis-chart-4: var(--cordis-blue-700);
  --cordis-chart-5: #35444F;
  --cordis-chart-track: var(--cordis-border);   /* #363C47 -- see the :root block */

  --cordis-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.40);
  --cordis-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.45);
  --cordis-shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.55);
}

/* ============================================================================
   Bootstrap bridge — paste this AFTER bootstrap.min.css in MachineControlServer.
   Replaces the Bootswatch Sandstone tokens with the Cordis tokens.
   ========================================================================= */
/* HOW FAR A BRIDGE OF VARIABLES CAN GET YOU, because this was reported as a gap and it is half a gap.
   It carried 17 names; Bootstrap 5.3 reads roughly fifty at the DOCUMENT level, and a page migrated
   about 80% of the way before a .form-control, a striped row or an .alert-success came back in the
   consuming application's own Bootswatch oatmeal (2026-08, feedback item 1). The rest of that surface
   is now here.
   Two things it still cannot reach, and knowing which is which saves an afternoon:
     * COMPONENT-SCOPED variables. Most of Bootstrap 5.3's variables are declared INSIDE a component's
       own rule (`.btn { --bs-btn-bg: … }`, `.alert { --bs-alert-bg: … }`), so a :root declaration of
       one does nothing at all. Reaching those means a block per component, and the variant classes
       below are the reason it is rarely needed: 5.3's `.alert-info` resolves its own
       `--bs-alert-bg` from `--bs-info-bg-subtle`, which IS a root name, so mapping the subtle and
       emphasis families fixes every alert, badge and list-group variant in one go.
     * LITERALS IN A THEME'S OWN RULES. No variable value can beat `.alert-info { background-color:
       #29abe0 }` — a rule at a specificity a token does not compete with. That needs rules, and they
       live in css/compat/bootswatch-sandstone.css, which is opt-in beside this file.
   The `-rgb` triples stay hand-written because CSS cannot decompose a hex into channels; each one
   duplicates a token and has to be edited with it. Keep the set as small as the components force. */
:root {
  /* ---- theme colours ---- */
  --bs-primary:      var(--cordis-primary);
  --bs-primary-rgb:  31, 156, 216;
  --bs-secondary:    var(--cordis-n-500);
  --bs-secondary-rgb: 95, 112, 133;
  --bs-success:      var(--cordis-success);
  --bs-success-rgb:  46, 158, 58;
  --bs-danger:       var(--cordis-danger);
  --bs-danger-rgb:   205, 23, 32;
  --bs-warning:      var(--cordis-warning);
  --bs-warning-rgb:  201, 122, 14;
  --bs-info:         var(--cordis-info);
  --bs-info-rgb:     31, 156, 216;
  --bs-light:        var(--cordis-n-50);
  --bs-light-rgb:    241, 244, 248;
  --bs-dark:         var(--cordis-n-800);
  --bs-dark-rgb:     33, 43, 54;

  /* ---- the subtle / emphasis families (5.3) ----
     These are what every alert, badge, list-group and table variant resolves ITS own component
     variables from, so they are the highest-value half of this whole block. */
  --bs-primary-text-emphasis:   var(--cordis-primary-text);
  --bs-secondary-text-emphasis: var(--cordis-text-muted);
  --bs-success-text-emphasis:   var(--cordis-success-text);
  --bs-info-text-emphasis:      var(--cordis-info-text);
  --bs-warning-text-emphasis:   var(--cordis-warning-text);
  --bs-danger-text-emphasis:    var(--cordis-danger-text);
  --bs-light-text-emphasis:     var(--cordis-text);
  --bs-dark-text-emphasis:      var(--cordis-text-strong);

  --bs-primary-bg-subtle:   var(--cordis-primary-soft);
  --bs-secondary-bg-subtle: var(--cordis-neutral-soft);
  --bs-success-bg-subtle:   var(--cordis-success-soft);
  --bs-info-bg-subtle:      var(--cordis-info-soft);
  --bs-warning-bg-subtle:   var(--cordis-warning-soft);
  --bs-danger-bg-subtle:    var(--cordis-danger-soft);
  --bs-light-bg-subtle:     var(--cordis-surface-sunken);
  --bs-dark-bg-subtle:      var(--cordis-n-200);

  --bs-primary-border-subtle:   var(--cordis-primary-soft-bd);
  --bs-secondary-border-subtle: var(--cordis-border-strong);
  --bs-success-border-subtle:   var(--cordis-success-soft);
  --bs-info-border-subtle:      var(--cordis-info-soft-bd);
  --bs-warning-border-subtle:   var(--cordis-warning-soft);
  --bs-danger-border-subtle:    var(--cordis-danger-soft);
  --bs-light-border-subtle:     var(--cordis-border);
  --bs-dark-border-subtle:      var(--cordis-border-strong);

  /* ---- body, and the three text levels 5.3 exposes ---- */
  --bs-body-bg:          var(--cordis-bg);
  --bs-body-bg-rgb:      241, 244, 248;
  --bs-body-color:       var(--cordis-text);
  --bs-body-color-rgb:   33, 43, 54;
  --bs-emphasis-color:   var(--cordis-text-strong);
  --bs-secondary-color:  var(--cordis-text-muted);
  --bs-secondary-bg:     var(--cordis-surface-sunken);
  --bs-tertiary-color:   var(--cordis-text-faint);
  --bs-tertiary-bg:      var(--cordis-surface-sunken);
  --bs-heading-color:    var(--cordis-text-strong);

  /* ---- type ---- */
  --bs-body-font-family:  var(--cordis-font);
  --bs-body-font-size:    var(--cordis-fs-body);
  --bs-body-font-weight:  var(--cordis-fw-regular);
  --bs-body-line-height:  var(--cordis-lh-body);
  --bs-font-monospace:    var(--cordis-font-mono);
  --bs-code-color:        var(--cordis-primary-text);
  --bs-highlight-bg:      var(--cordis-warning-soft);

  /* ---- borders, radii, elevation ---- */
  --bs-border-color:             var(--cordis-border);
  --bs-border-color-translucent: var(--cordis-border);
  --bs-border-width:             1px;
  --bs-border-style:             solid;
  --bs-border-radius:      var(--cordis-radius-sm);
  --bs-border-radius-sm:   var(--cordis-radius-sm);
  --bs-border-radius-lg:   var(--cordis-radius-md);
  --bs-border-radius-xl:   var(--cordis-radius-lg);
  --bs-border-radius-xxl:  var(--cordis-radius-lg);
  --bs-border-radius-pill: var(--cordis-radius-pill);
  --bs-box-shadow-sm:      var(--cordis-shadow-sm);
  --bs-box-shadow:         var(--cordis-shadow-md);
  --bs-box-shadow-lg:      var(--cordis-shadow-lg);

  /* ---- links, focus, form validation ---- */
  --bs-link-color:           var(--cordis-primary-text);
  --bs-link-color-rgb:       15, 106, 151;
  --bs-link-hover-color:     var(--cordis-primary-active);
  --bs-focus-ring-color:     var(--cordis-focus-color);
  --bs-form-valid-color:            var(--cordis-success-text);
  --bs-form-valid-border-color:     var(--cordis-success);
  --bs-form-invalid-color:          var(--cordis-danger-text);
  --bs-form-invalid-border-color:   var(--cordis-danger);
}
/* Bootswatch sets buttons and tabs in capitals; Cordis does not. */
.btn, .nav-link, .dropdown-item, .page-link {
  text-transform: none;
  letter-spacing: 0;
  font-weight: var(--cordis-fw-semibold);
}
