/* ═════════════════════════════════════════════════════════════════════════════
   Solvix design tokens — ONE definition, four rails.

   Served by the shell at /_platform/tokens.css. Loaded by:

       the shell          platform/shell/html/index.html          <link>
       Delivery Solution  injected into <head> by P1/frontend-runtime/
                          inject-config.js, ahead of nav.css
       UAT Portal         P2/frontend/index.html                  <link>
       Governance         P3/frontend/index.html                  <link>
       /admin             platform/shell/html/admin/index.html    <link>
                          platform/shell/html/admin/analytics/index.html

   Four RAILS, five pages: /admin renders the vanilla rail from nav.js, so it
   is a fifth <link> but not a fifth implementation. That distinction used to
   be cosmetic and is not any more — .sx-icon (below) is a RULE served from
   here, so a surface that renders a rail without linking this file gets the
   fallback attributes on every glyph and nothing else.

   WHY THIS FILE EXISTS
   --------------------
   Before it, the rail spec existed in four places in four idioms: nav.css,
   the shell's own bespoke token block, P2's tailwind.config.js plus hard-coded
   hex in Layout.jsx/PlatformNav.jsx, and P3's three-layer stack (globals.css ->
   tailwind.config.ts -> hard-coded hex in Sidebar.tsx). A palette change cost
   four edits and the four had already drifted apart on border, hover, active
   and idle colours. The shell — which the handover said owned the spec —
   matched it on exactly one value: 232px.

   These are the numbers nav.css already documented, because nav.css was the
   only one of the four that implemented the spec on every axis. It is now the
   only one that *defines* it.

   HOW TO USE IT
   -------------
   Consume the variables; never restate the literals. Tailwind users can reach
   them with arbitrary values, e.g. bg-[var(--sx-item-active-bg)], which keeps
   one source of truth without a Tailwind config change in either SPA.

   THIS FILE ALSO CARRIES ONE SHARED RULE: .sx-icon, near the bottom.

   That is a deliberate widening of the framing above, and the old framing is
   what killed two tokens. --sx-icon-size and --sx-icon-stroke sat here from the
   day the file was written and had ZERO consumers: all four rails hard-coded
   width="16" height="16" stroke-width="1.6" on every glyph, because "define a
   number, never a rule" gives a declarative token nowhere to land -- an SVG's
   size lives on the element, and no variable can reach it without a selector.
   So "make the icons smaller" was a four-file change in four idioms, which is
   exactly the drift this file exists to stop. The rule makes the tokens live.
   Every icon in every rail must carry class="sx-icon"; see the rule for the
   exact contract for a vanilla <svg> and for a React icon component.

   The bar for adding another rule here is that bar: a token that cannot be
   consumed without one. Component styling still belongs in nav.css or the
   SPA's own stylesheet.

   WHAT DOES NOT BELONG HERE
   -------------------------
   Anything specific to one app. Whiz's --mint scale, Matrix's gray ramp and
   the portal's brand green stay where they are. This file is the surface the
   three apps have in common: the rail, the platform chrome, and the shared
   accent. Adding app-specific tokens here is how a shared file becomes a
   fourth place to look.

   Matrix's own palette is deliberately NOT normalised: recolouring it means
   ~40 hex swaps inside public/index.html, a 39,056-line file under a
   never-edit rule. Its rail follows this file; its pages do not.
   ═════════════════════════════════════════════════════════════════════════════ */

:root {
  /* ── The one thing this file says about theme ────────────────────────────
     All four surfaces are light-only: there is no prefers-color-scheme and no
     [data-theme] block anywhere in this file, nav.css or feedback.js. Declaring
     it makes that intent enforceable rather than merely true by omission.

     Without this line the OS still wins over everything the browser draws
     ITSELF rather than from our CSS — scrollbars, <select> menus, date pickers,
     the canvas behind a short page. On a dark-mode machine that produced light
     pages with dark scrollbars and dark dropdowns, on every app. Our own
     colours were never the whole surface.

     That dark palette now exists: see the :root[data-theme="dark"] block
     below. It went in as ONE block in this file rather than four, which is
     what "in all four renderers together" was asking for -- every consumer
     switches at once and none can be dark while another is light.

     This line stays `light` rather than becoming `light dark`, which is the
     one piece of the original instruction not followed. `light dark` hands the
     choice back to the OS whenever no explicit theme is set, and that is
     precisely the behaviour deleted from the shell and the login page: light
     pages with dark scrollbars and dark dropdowns on a dark-mode machine.
     Explicit `light` here and explicit `dark` in the block below keeps the
     decision in our CSS, where a toggle can actually reach it. */
  color-scheme: light;

  /* ── The rail ────────────────────────────────────────────────────────────
     Geometry first, because these are the numbers that make four separately
     rendered sidebars read as one component. Item pitch is height + gap:
     28 + 2 = 30px.

     THE RAIL MUST FIT A 1366x768 LAPTOP WITH EVERY GROUP EXPANDED, INCLUDING
     THE FOOTER. That is the constraint these numbers were solved for.

     THE BUDGET WAS WRONG, AND THIS IS THE CORRECTION. The arithmetic below used
     to be checked against "roughly 670-680px of page height on a 768px screen",
     which was an estimate and not a measurement. It has now been measured on the
     live origin, from the reporter's own screenshots: those are captured at 1.5x
     device pixel ratio, where the 232px rail is 348 image px wide. 348/232 = 1.5
     exactly, so the whole image divides by 1.5, and the usable page height that
     came back was ~633 CSS px -- roughly 40px less than the estimate. That is
     the entire reason the footer was cut in half on screen with the
     Administration row present: the old total of 649 did not fit in 633, and the
     16px it overflowed by came out of the pinned footer.

     SO THE TARGET IS 600px, NOT 670. Counted from nav.json (1 + 5 + 7 items)
     plus the Administration entry each renderer adds above the groups, which is
     the platform-administrator worst case -- one row more than anyone else sees:

       rail header      --sx-head-h 44 + 1px rule + --sx-head-mb 4         49
       Administration   --sx-admin-mt 2 + 28 row + --sx-admin-mb 6         36
       Delivery group   4 label-mt-first + 13 label + 3 mb + 1x30          50
       UAT group        10 label-mt + 13 label + 3 mb + 5x30              176
       Governance grp   10 label-mt + 13 label + 3 mb + 7x30              236
       scroll area's own --sx-scroll-pad-b                                  6
       footer           --sx-foot-h, 1px top rule included                 44
                                                                        ------
                                                                           597

     597 against 633 leaves 36px of headroom -- about one and a fifth rows. That
     is deliberately not generous: the next thing added to nav.json spends 30 of
     it. What protects the rail past that point is not headroom but the pinned
     footer: the item list scrolls under it, so the sign-out control cannot be
     pushed off-screen the way it would be by a footer inside the scroll flow.
     The 36px is there so nothing scrolls on a laptop TODAY, not so that rows are
     free.

     The Administration row is 28px TOTAL, not 28 plus its rule. It is a
     border-box .solvix-item, so --sx-admin-rule-gap and the 1px separator are
     inside the 28, which is why the table above adds only the two margins.

     Margins between groups are carried by --sx-group-mt on the LABEL alone.
     They used to be carried by the label's margin AND a margin-top on the group
     box, which is why the four rails did not agree: nav.css's panel is a block,
     so the two collapsed into one 14px gap, while P2's and P3's flex columns do
     not collapse margins and got 28px. One margin resolves identically in both.

     The 28px row is below the 44px pointer target Apple and Google recommend,
     and that is a deliberate, bounded call: the permanent rail only exists at
     >=900px (--sx-rail-breakpoint), where the input device is a mouse. The
     same rows do appear in the drawer below 900px, so the floor that actually
     binds is WCAG 2.5.8 AA target size at 24x24 CSS px, which a 28px row
     spanning the full 232px clears. Do not take this as licence to shrink
     further: 28px is 4px off that floor and it is the floor for this rail. If
     another row has to be found, take it out of a group margin or find a row to
     delete -- not out of --sx-item-h. */
  --sx-rail-w:            232px;
  --sx-rail-w-collapsed:   64px;   /* icon-only; see html.sx-rail-collapsed
                                      below. Same 64px the shell's landing page
                                      has always collapsed to. */
  --sx-rail-bg:           #FFFFFF;
  --sx-rail-border:       #E2E8F0;

  --sx-item-h:            28px;     /* 36 -> 30 -> 28; see the arithmetic above.
                                       The biggest lever there is this one: it
                                       is multiplied by 14 rows. */
  --sx-item-gap:          2px;      /* pitch = --sx-item-h + --sx-item-gap */
  --sx-item-radius:       8px;      /* NOT 12px. P3's rounded-lg resolves to
                                       --r-lg: 12px, so it needs the explicit
                                       rounded-[8px] or a token reference. */
  --sx-item-pad-x:        12px;
  --sx-item-icon-gap:     10px;
  --sx-item-font-size:    13.5px;   /* NOT reduced with the row. 13.5 on 28 is
                                       compact and still comfortably legible --
                                       VS Code's sidebar rows are ~22px. Text
                                       size is the last thing to give, because
                                       it is the only part of the rail anyone
                                       actually reads. */
  --sx-item-font-weight:  500;
  --sx-item-font-weight-active: 600;

  --sx-item-idle-fg:      #475569;
  --sx-item-hover-bg:     #F1F5F9;
  --sx-item-hover-fg:     #0F172A;
  --sx-item-active-bg:    #E6F4F2;
  --sx-item-active-fg:    #0F766E;
  --sx-item-active-hover-bg: #DBEDEA;

  --sx-group-font-size:   10px;
  --sx-group-tracking:    .14em;
  --sx-group-fg:          #64748B;
  --sx-group-font-weight: 600;

  /* The label's own box, stated rather than inherited. .solvix-panel sets a
     1.5 line-height, so a unitless value made the 10px label a 15px box on that
     surface and whatever the host page's line-height produced on the other
     three. An explicit px line-height is what makes the row budget above the
     same number on all four. 13px is the 10px glyph plus 3px of leading, which
     is as tight as an uppercase-only line can go without clipping descenders --
     and there are none, because the labels are uppercased. */
  --sx-group-line-h:      13px;
  /* The ONLY separation between groups. Not paired with a margin on the group
     box: see the note above about margin collapsing resolving differently in a
     block panel and a flex column. */
  --sx-group-mt:          10px;
  --sx-group-mt-first:     4px;     /* the first label sits under the header
                                       rule, which already supplies separation */
  --sx-group-mb:           3px;

  /* ── The Administration row, and the box it scrolls in ───────────────────
     NEW TOKENS, and they are new for the reason this whole file exists: these
     four numbers appear in the fit arithmetic above, so they are load-bearing,
     and until now every one of them was a hard literal in nav.css (`margin:2px
     0 8px; padding-bottom:7px`, `padding:0 10px 8px`) with hand-copied twins in
     the other three rails. A budget whose terms are literals is a budget that
     silently stops being true.

     --sx-admin-rule-gap is INSIDE the 28px border-box row -- it is the air
     between the row's text and the separator underneath it, not height added
     to the row. It costs the budget nothing; it is here so the four rails draw
     the separator in the same place. */
  --sx-admin-mt:           2px;
  --sx-admin-mb:           6px;
  --sx-admin-rule-gap:     6px;
  --sx-scroll-pad-x:      10px;
  --sx-scroll-pad-b:       6px;
  --sx-scroll-pad-x-collapsed: 8px;

  /* ── Icons ───────────────────────────────────────────────────────────────
     LIVE as of the rail-fit work; these were decorative before. Every glyph in
     every rail carries class="sx-icon", and the rule at the bottom of this file
     turns these three into the rendered size, stroke and opacity. Changing the
     icon size is now this one line and nothing else.

     16 -> 15 -> 14, tracking the row: the glyph holds a constant proportion of
     the row it sits in (half of it), so it never starts reading heavy next to
     13.5px text as the row tightens. At 14px on a 28px row there is 7px of air
     above and below.

     --sx-ctl-icon-size is DELIBERATELY NOT THIS. The rail's chrome controls
     (close, collapse) are not row glyphs; they are the only affordance in their
     corner of the header and shrinking them with the rows makes them hard to
     hit for no budget gain, since the header is one fixed-height row either
     way. --sx-chev-size is smaller still because the disclosure chevron sits
     against a 10px uppercase label, not against 13.5px text. */
  --sx-icon-size:         14px;
  --sx-icon-stroke:       1.6;
  --sx-icon-opacity:      .9;
  --sx-ctl-icon-size:     15px;
  --sx-chev-size:         13px;
  --sx-chev-opacity:      .75;

  /* The shared radius for every ICON BUTTON in the rail -- close, collapse,
     sign out, and any control a rail ever adds to the footer row beside them.
     (The shell's Theme toggle was the candidate when this token was written; it
     was deleted rather than folded in -- see the note at the top of
     shell/html/index.html. The token is still the right shape for the next one.)
     NOT --sx-item-radius (8px), which is the nav ROW's radius: a square-ish
     26px chip at 8px reads as a rounded rectangle where a nav row at 8px reads
     as a pill. P3 already documents this distinction on its own sign-out
     button; this is that constant, named. */
  --sx-ctl-radius:         6px;

  /* ── The rail header: TMC mark + wordmark ──────────────────────────────
     44px, down from 48 and originally 56. The 28px mark still clears it with
     8px of air top and bottom. nav.css used to restate 56px as a literal; it
     reads this token now, so the header is one number like everything else.

     --sx-head-mb is the gap between the header's bottom rule and the first
     thing in the scroller, and it is in the fit arithmetic, so it is a token
     rather than nav.css's old literal `margin:0 0 4px`. */
  --sx-head-h:            44px;
  --sx-head-mb:            4px;
  --sx-head-gap:          10px;
  --sx-mark-size:         28px;
  --sx-mark-radius:       7px;
  --sx-mark-bg:           #0F766E;
  --sx-mark-fg:           #FFFFFF;
  --sx-mark-font-size:    10.5px;
  --sx-wordmark-size:     13.5px;
  --sx-wordmark-fg:       #0F172A;
  --sx-subtitle-size:     9px;
  --sx-subtitle-tracking: .18em;
  --sx-subtitle-fg:       #64748B;

  /* ── The rail footer: who you are, and the way out ───────────────────────
     New. Before this the sign-out affordance existed on two surfaces out of
     four: the shell's .whoami and the UAT Portal's .sx-rail-foot. Delivery
     Solution's rail had no footer element at ALL, and Governance had a profile
     card that opened a modal, so on two of the three applications there was no
     way to sign out from the rail you were looking at.

     --sx-foot-h is the TOTAL height including the 1px top rule, under
     box-sizing:border-box, and it is a MIN-height: the collapsed 64px rail
     stacks the avatar above the button and is allowed to grow past it.

     44px, down from 48, AND THE PADDING CAME DOWN WITH IT -- that pairing is
     not tidiness, it is the difference between a footer and an overflowing
     footer. The content box is `--sx-foot-h - 1px rule - 2 x --sx-foot-pad-y`.
     At 44 with the old 8px padding that is 27px, and the two text rows need
     12 x 1.3 + 10.5 x 1.3 = 29.25px. The name and the email would have been
     clipped by the border rather than merely tight. At 6px padding the content
     box is 31px, which clears the text block by 1.75px, the 24px avatar by 7px
     and the 26px sign-out chip by 5px.

     --sx-avatar-size 26 -> 24 is proportion, not budget: it does not touch the
     total, and the footer's height is fixed either way. A 26px disc under a
     28px nav row looked like the heaviest thing in the rail.

     24px is also exactly the WCAG 2.5.8 AA floor, which matters here and not
     on the avatar alone: in P2 and P3 the identity row is CLICKABLE (a link to
     /profile, a button that opens the profile modal), so the target is the
     whole 31px-tall row, not the disc. --sx-foot-btn-size is 26px for the same
     reason in reverse -- the sign-out chip IS its own target, so it is kept
     2px clear of the floor rather than sitting on it.

     The footer is PINNED -- outside the scrolling item list, not the last
     thing in it. A footer inside the scroll flow is a footer that a
     platform administrator on a 768px screen has to scroll to reach, which is
     the same as not having one. Which is, almost exactly, what the reporter
     saw before this pass: the footer was pinned and correct, and the rail
     above it was 16px too tall for the viewport, so the pinned row was pushed
     half out the bottom of the window.

     --sx-who-gap and --sx-who-line-h were literals (9px, 1.3) hand-copied into
     nav.css, P2's styles.css and the shell. They are here now because the
     line-height is a term in the arithmetic above and the gap decides whether
     the name column has room to be read. */
  --sx-foot-h:            44px;
  --sx-foot-pad-x:        10px;
  --sx-foot-pad-y:         6px;
  --sx-foot-gap:           8px;
  --sx-foot-btn-size:     26px;
  --sx-avatar-size:       24px;
  --sx-avatar-font-size:  10px;
  --sx-who-gap:            9px;
  --sx-who-name-size:     12px;
  --sx-who-sub-size:      10.5px;
  --sx-who-line-h:        1.3;

  /* The collapsed 64px rail stacks the footer into a column; this is the gap
     between the avatar and the sign-out chip there, and the padding it uses
     instead of --sx-foot-pad-x (zero horizontal, so both children centre in
     the full 64px). Three rails restate `gap:6px; padding:8px 0` by hand. */
  --sx-collapsed-gap:      6px;
  --sx-collapsed-pad-y:    8px;

  /* ── Platform palette ────────────────────────────────────────────────────
     Matrix's palette is the platform palette; primary is #0F766E. The shell
     previously used its own greened neutrals (--rule:#DCE3E1,
     --muted:#6B7B79, --ink-2:#3E4C4A, --accent-soft:#E0EFED) which is why /
     read as a different product from the three apps it launches. */
  --sx-accent:            #0F766E;
  --sx-accent-hover:      #115E56;
  --sx-accent-soft:       #E6F4F2;
  --sx-accent-fg:         #FFFFFF;

  --sx-page-bg:           #F8FAFC;
  --sx-surface:           #FFFFFF;
  --sx-surface-2:         #F1F5F9;
  --sx-ink:               #0F172A;
  --sx-ink-2:             #1E293B;
  --sx-muted:             #64748B;
  --sx-faint:             #94A3B8;
  --sx-border:            #E2E8F0;
  --sx-border-soft:       #EDF1F5;

  /* Semantic. Separate from the accent on purpose: a teal "success" and a
     teal brand are indistinguishable at a glance. */
  --sx-ok:                #15803D;
  --sx-warn:              #A65B12;
  --sx-danger:            #B0271F;

  /* ── Shared chrome ───────────────────────────────────────────────────── */
  --sx-focus-ring:        #0F766E;
  --sx-focus-width:       2px;
  --sx-shadow-card:       0 1px 2px rgba(15, 23, 42, .05),
                          0 12px 32px -24px rgba(15, 23, 42, .28);
  --sx-shadow-pop:        0 1px 2px rgba(15, 23, 42, .18),
                          0 6px 18px -8px rgba(15, 23, 42, .4);
  --sx-radius-card:       10px;

  /* Below 900px the rail is a drawer over the page, so it needs a lift and a
     scrim. Both were literals in nav.css (`0 0 40px -12px rgba(15,23,42,.45)`,
     `rgba(15,23,42,.34)`) and both are COLOURS, which means they had no way to
     follow the dark block below -- a slate-tinted scrim over a #141414 page
     reads as a grey wash rather than a dimmer. */
  --sx-rail-shadow:       0 0 40px -12px rgba(15, 23, 42, .45);
  --sx-scrim-bg:          rgba(15, 23, 42, .34);

  /* The two durations the rail animates at. Hover and colour changes take the
     fast one, the drawer slide takes the slow one. They were .12s/.16s/.18s
     literals across nav.css and duration-150 in P3's Tailwind, so the same
     interaction already ran at two speeds depending on which rail you were
     looking at. .16s is folded into .18s: a chevron and a drawer visibly
     starting together is worth more than 20ms of difference nobody asked for. */
  --sx-t-fast:            .12s;
  --sx-t-slow:            .18s;

  --sx-font-sans:         -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
                          Helvetica, Arial, sans-serif;
  --sx-font-mono:         ui-monospace, 'SF Mono', SFMono-Regular, Menlo,
                          Consolas, 'Liberation Mono', monospace;

  /* ── Breakpoint ──────────────────────────────────────────────────────────
     Informational: CSS cannot use a custom property in a media query. It is
     recorded here because the four rails currently switch at THREE different
     widths — 720 (shell forces icon-only), 768 (P2 and P3, Tailwind `md`) and
     900 (Matrix, nav.css). Between 768 and 900 the two SPAs show a permanent
     rail and Matrix shows a floating launcher instead. 900px is the number to
     converge on; whoever changes a breakpoint must change all four. */
  --sx-rail-breakpoint:   900px;

  /* Layout offset for the platform's floating affordances (feedback button,
     Matrix's copilot launcher) so they clear the rail on wide screens.
     Consumers add their own inset to this. */
  --sx-rail-offset:       var(--sx-rail-w);

  /* ── The bottom-right corner is PLATFORM-OWNED ───────────────────────────
     The feedback button sits here, in the same place in all three apps and on
     the platform home — being in the same place everywhere is the entire point
     of having one button instead of three.

     So the corner needs an owner, and a rule for everyone else:

         app-specific floating elements in the bottom-right start at
         --sx-fab-stack, not at 0.

     Before this, three things claimed the same corner: Governance's
     AssistantChat launcher (a 53px circle at bottom:24 right:24, z-index
     2147483000), its Toaster (right:18 bottom:76), and Delivery Solution's own
     feedback FAB (bottom:24 right:24 — now retired, which is what frees the
     corner in Matrix). Matrix's #copilotLauncher is bottom-LEFT and unaffected.

     Governance's two are the only consumers today; both read --sx-fab-stack. */
  --sx-fab-inset:         24px;   /* the button's offset from the corner */
  --sx-fab-h:             36px;   /* its height */
  --sx-fab-gap:           12px;   /* breathing room above it */
  --sx-fab-stack:         calc(var(--sx-fab-inset) + var(--sx-fab-h) + var(--sx-fab-gap));
}

/* ── Dark ─────────────────────────────────────────────────────────────────────
   THE SEAM THIS CLOSES, measured on Governance rather than guessed at.

   Governance's own palette is dark BY DEFAULT -- its :root is the dark set
   (--bg-0: #191919) and [data-theme="light"] is the override -- and its own
   components follow it correctly. PlatformNavGroups.tsx uses P3's classes
   (text-ink-4, hover:bg-surface-glass-1), so the rail's ITEMS were never the
   problem. What did not follow were the two things that read these tokens:

       P3/frontend/src/components/layout/BootRail.tsx   ~20 --sx-* references,
                                                        the entire boot rail
       platform/shell/html/_platform/feedback.js        23 --sx-* references,
                                                        on all four surfaces

   Measured with data-theme flipped to dark on the running app:

       --sidebar-bg        #ffffff -> #191919     (P3's own, follows)
       --sx-rail-bg        #FFFFFF -> #FFFFFF     (did not)
       --sx-ink            #0F172A -> #0F172A     (did not)
       --sx-item-active-bg #E6F4F2 -> #E6F4F2     (did not)

   So a white boot rail in a near-black app, and #0F172A ink on #191919, which
   is effectively invisible.

   ONE BLOCK, ALL FOUR RENDERERS, which is what the note above asked for: this
   file is the single definition, so every consumer switches together and none
   can be dark while another is light. Only COLOUR is overridden here --
   geometry, sizes and weights are identical in both themes, because a rail
   that changes shape with the theme is a different component.

   Values track Governance's dark palette so the shared rail sits inside it
   rather than beside it: #191919 ground, white-alpha borders at .09/.13, text
   at 1/.81/.60/.44, and its mint (#14b8a6) as the accent instead of the light
   theme's #0F766E, which is far too dark to read on #191919.

   WHO THIS ACTUALLY REACHES: NOTHING, AS OF 16 SEP 2026. This block is
   DORMANT. It used to say "only surfaces that set data-theme='dark';
   Governance does" -- both halves are now false. Governance removed dark mode
   (globals.css, App.tsx), and the platform home removed the Theme toggle that
   was the last writer of the attribute, because tmc.theme had no other reader:
   switching it darkened the landing page while all three applications stayed
   light, which is the seam the shell's own comment says the platform exists to
   hide. `grep -rn 'data-theme' platform/ P2/frontend/src P3/frontend/src`
   returns no writer -- only this block, the shell's copy of it, and comments.

   It is kept rather than deleted because it is a complete, considered palette
   and re-deriving it costs more than carrying it. Reviving it means giving all
   four rails a writer IN ONE CHANGE -- one rail switching alone reintroduces
   exactly the seam that got the toggle removed. Until then, treat every
   declaration below as documentation, and do not add to it expecting it to
   render: the two dark values added with the rail-fit work (--sx-rail-shadow,
   --sx-scrim-bg) have never been painted.

   The historical note, for whoever revives it: the UAT Portal and Delivery
   Solution never set the attribute at all, so both stayed light and this block
   was inert
   for them -- it cannot make one surface dark on its own, which was the whole
   failure mode being guarded against.

   The shell used to, through a Theme toggle on the landing page. That toggle is
   DELETED, along with the tmc.theme key it was the only reader and writer of,
   because one dark surface handing off to three light ones is that same failure
   mode with a button on it. shell/html/index.html keeps its dark palette and
   marks it DORMANT. The right order for a real platform theme is: a palette on
   all four surfaces, then ONE storage key (today the shell's dead tmc.theme and
   Governance's own `theme` are two keys that never saw each other), then a
   bootstrap that stamps the attribute everywhere before first paint -- and only
   then a control. A control first is what was just removed.

   NOT `color-scheme: light dark` on :root, despite the note above suggesting
   it. That keyword hands the choice back to the OS whenever no explicit theme
   is set, which is exactly the bug that was removed from the shell and the
   login page -- light pages with dark scrollbars and dark dropdowns on a
   dark-mode machine. Explicit light on :root and explicit dark here keeps the
   decision in our CSS, where a toggle can reach it. */
:root[data-theme="dark"] {
  color-scheme: dark;

  --sx-rail-bg:           #191919;
  --sx-rail-border:       rgba(255, 255, 255, .09);

  --sx-item-idle-fg:      rgba(255, 255, 255, .60);
  --sx-item-hover-bg:     rgba(255, 255, 255, .06);
  --sx-item-hover-fg:     #FFFFFF;
  --sx-item-active-bg:    rgba(20, 184, 166, .14);
  --sx-item-active-fg:    #2DD4BF;
  --sx-item-active-hover-bg: rgba(20, 184, 166, .20);

  --sx-group-fg:          rgba(255, 255, 255, .44);

  --sx-mark-bg:           #14B8A6;
  --sx-mark-fg:           #0A1F1D;
  --sx-wordmark-fg:       #FFFFFF;
  --sx-subtitle-fg:       rgba(255, 255, 255, .44);

  --sx-accent:            #2DD4BF;
  --sx-accent-hover:      #5EEAD4;
  --sx-accent-soft:       rgba(20, 184, 166, .14);
  --sx-accent-fg:         #0A1F1D;

  --sx-page-bg:           #141414;
  --sx-surface:           #202020;
  --sx-surface-2:         #252525;
  --sx-ink:               #FFFFFF;
  --sx-ink-2:             rgba(255, 255, 255, .81);
  --sx-muted:             rgba(255, 255, 255, .60);
  --sx-faint:             rgba(255, 255, 255, .44);
  --sx-border:            rgba(255, 255, 255, .13);
  --sx-border-soft:       rgba(255, 255, 255, .09);

  /* Semantic colours lightened to clear 4.5:1 on #191919. The light theme's
     #15803D / #A65B12 / #B0271F are all under 3:1 there. */
  --sx-ok:                #4ADE80;
  --sx-warn:              #FBBF24;
  --sx-danger:            #F87171;
  --sx-focus-ring:        #2DD4BF;

  /* Shadows carry no weight on a dark ground unless they are much stronger. */
  --sx-shadow-card:       0 1px 2px rgba(0, 0, 0, .40),
                          0 8px 24px -16px rgba(0, 0, 0, .70);
  --sx-shadow-pop:        0 1px 2px rgba(0, 0, 0, .50),
                          0 18px 44px -20px rgba(0, 0, 0, .85);
  --sx-rail-shadow:       0 0 48px -10px rgba(0, 0, 0, .80);
  /* Pure black, not slate: the light theme's #0F172A over a #141414 page is a
     blue-grey film rather than a dimmer, and the drawer it is meant to separate
     from the page is #191919 -- lighter than the scrim's own hue. */
  --sx-scrim-bg:          rgba(0, 0, 0, .58);
}

/* ════ THE ONE ICON RULE ════════════════════════════════════════════════════
   The rule that makes --sx-icon-size, --sx-icon-stroke and --sx-icon-opacity
   mean something. Lives here rather than in nav.css because nav.css reaches
   only Delivery Solution and /admin, while all four surfaces load this file.

   WHY IT WORKS, and the one way to break it: an SVG's width, height and
   stroke-width set as ATTRIBUTES are presentation attributes, which sit below
   every author CSS declaration in the cascade -- so this rule wins over
   width="15" or strokeWidth={1.6} without !important and without the renderers
   agreeing on a number. An INLINE STYLE does not: style="width:16px" or
   style={{ width: 16 }} beats this rule and the glyph silently stops
   following the token. Attributes, never inline styles.

   THE CONTRACT, both idioms:

     vanilla JS / HTML   <svg class="sx-icon" width="14" height="14"
                              viewBox="0 0 24 24" fill="none"
                              stroke="currentColor" stroke-width="1.6"
                              stroke-linecap="round" stroke-linejoin="round"
                              aria-hidden="true" focusable="false">

     React / lucide      <Icon className="sx-icon" size={14} strokeWidth={1.6}
                               aria-hidden="true" />

   The 14 and the 1.6 on the element are the no-stylesheet FALLBACK and nothing
   else -- if tokens.css 404s, this rule is gone too and the attribute is all
   that is left. The rendered value always comes from the tokens.

   flex-basis is in here because every rail row is a flex container and a flex
   item with only width set still shrinks when the label is long. `flex:0 0`
   plus the token is what stops "Resource & WP Allocation" squashing its glyph.

   NOT SET HERE: colour. Icons inherit currentColor from the row, which is what
   makes them go teal on the active item and dark on hover for free. */
.sx-icon {
  width:        var(--sx-icon-size, 14px);
  height:       var(--sx-icon-size, 14px);
  flex:  0 0    var(--sx-icon-size, 14px);
  stroke-width: var(--sx-icon-stroke, 1.6);
  opacity:      var(--sx-icon-opacity, .9);
}

/* ── The user-collapsed rail ──────────────────────────────────────────────────
   ONE class on <html> retargets ONE variable, and every surface follows —
   because --sx-rail-offset is DERIVED from --sx-rail-w rather than restated, so
   the feedback button and Matrix's copilot launcher move with the rail without
   knowing it collapsed.

   Persisted as localStorage["tmc.rail"] = "collapsed" | "open". That is the key
   the shell's landing page has used since it gained a collapse button, so
   reusing it means the choice CARRIES between the platform home and the three
   apps instead of each surface remembering its own. A second key would have
   been the same mistake as the theme seam, where the shell wrote "tmc.theme"
   and Governance wrote "theme" and neither saw the other. (The shell's half of
   that seam is gone now -- the Theme toggle and its key were deleted -- but the
   lesson is the whole reason this key is shared rather than per-surface, and it
   is the first thing to settle if theming ever comes back.)

   Stamp it BEFORE first paint. Applied late it produces exactly the 232px
   content jump that P1/frontend-runtime/inject-config.js was written to kill —
   only in reverse and 168px wide.

   Geometry ONLY. Each surface still supplies its own rule for what an icon-only
   rail looks like, because the four render different markup: nav.css for Matrix
   and the shared panel, the shell's own .collapsed block, and a Tailwind rule
   each in P2 and P3. What they must agree on is the width, and now they do.

   WRAPPED IN THE BREAKPOINT ON PURPOSE, and this is not defensive noise. Every
   surface gates its icon-only PRESENTATION at >=900px, because below that the
   rail is a drawer over the content rather than a column beside it. If the
   width were retargeted unconditionally, a collapsed preference carried onto a
   narrow screen would produce a 64px drawer still trying to render full text
   labels — narrower than the labels inside it, with the geometry collapsed and
   the presentation not. Gating both at the same width keeps them from ever
   disagreeing, whatever the stored preference says. */
@media (min-width: 900px) {
  html.sx-rail-collapsed { --sx-rail-w: var(--sx-rail-w-collapsed); }
}

/* Narrow screens: no rail, so nothing needs clearing it. The button also goes
   icon-only (40px circle) and lifts clear of the UAT Portal's fixed bottom nav
   bar, so the stack offset grows to match. */
@media (max-width: 899px) {
  :root { --sx-rail-offset: 0px; }
}
@media (max-width: 520px) {
  :root {
    --sx-fab-inset: 16px;
    --sx-fab-h:     40px;
  }
}

/* ════ THE SOLVIX WORDMARK ══════════════════════════════════════════════════
   Ported from Delivery Solution, which has drawn its name this way since
   before the platform existed (P1/public/index.html:86, .svx-wm): "Sol" and
   "x" in a grey vertical gradient, a green chevron standing in for the V, and
   a green "i". Every other surface spelled "Solvix" as plain bold text, so the
   one app users had seen for months looked like the odd one out.

   TWO DELIBERATE DEVIATIONS FROM THE ORIGINAL.

   1. THE CHEVRON USES currentColor, not two hardcoded gradients. Matrix draws
      it with <linearGradient id="svvHdr">, and an SVG gradient needs a
      document-unique id. This mark now renders in four rails and two React
      trees, several of which can appear on one page or be re-rendered, so ids
      would collide and every instance after the first would silently take the
      first one's fill. currentColor also gives the on-dark variant below for
      free. The gradient it replaces spanned .6em -- about 8px in a rail -- so
      the difference is not visible at the sizes this is used at.

   2. IT HAS AN ACCESSIBLE NAME. role="img" plus aria-label="Solvix" on the
      wrapper, because the text nodes alone spell "Sol", "i", "x" around a
      graphic and a screen reader read that out as three fragments.

   The font stack keeps 'Outfit' first even though nothing here loads it: it IS
   loaded on Delivery Solution's pages, so the rail matches its host there, and
   everywhere else it falls through to the system stack. Adding a Google Fonts
   request to the sign-in path for one word was not worth the dependency.

   USAGE. Wrap in .svx-wm and set font-size on that element; everything scales
   from it. Add .svx-on-dark on a dark surface. */
.svx-wm {
  font-family: 'Outfit', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  font-weight: 600;
  letter-spacing: -.02em;
  display: inline-flex;
  align-items: baseline;
  line-height: 1;
  white-space: nowrap;
}
.svx-g   { color: #6b7174; }
.svx-grn { color: #6f9f33; }
/* The gradient is the enhancement, and the flat colours above are the
   fallback: background-clip:text with -webkit-text-fill-color:transparent
   renders INVISIBLE text where it is unsupported, so it is gated. */
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .svx-g {
    background: linear-gradient(180deg, #B0B0B0, #606060);
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent; color: transparent;
  }
  .svx-grn {
    background: linear-gradient(180deg, #88C040, #689038);
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent; color: transparent;
  }
}
.svx-v {
  display: inline-block; flex: 0 0 auto;
  height: .60em; width: .52em;
  vertical-align: -.02em; margin: 0 .005em;
  color: #7BAE3A;
}
/* On a dark surface the grey gradient is close to unreadable, which is why
   this variant exists rather than the mark being dropped in unchanged. The
   greens lighten too: #7BAE3A on the identity panel's dark teal is under 3:1. */
.svx-wm.svx-on-dark .svx-g {
  background: none; color: #FFFFFF; -webkit-text-fill-color: #FFFFFF;
}
.svx-wm.svx-on-dark .svx-grn {
  background: none; color: #B7E07C; -webkit-text-fill-color: #B7E07C;
}
.svx-wm.svx-on-dark .svx-v { color: #B7E07C; }

/* THE SHELL'S DARK THEME GETS THE SAME TREATMENT WITHOUT A MARKUP CHANGE.
   DORMANT, like the block it belongs to: the landing page's Theme button and
   the tmc.theme key it drove are deleted, so nothing stamps data-theme there
   any more and this rule cannot currently fire. Kept for the same reason --
   it is the wordmark's half of a palette that is finished and contrast-checked,
   waiting on a platform-wide theme rather than on a control.

   What it is FOR, when that day comes: the landing page stamps
   data-theme="dark" and sets --rail to #101817. Its mark is static HTML so
   it cannot add .svx-on-dark by itself -- and the grey gradient's lower stop
   (#606060) lands around 3:1 on that rail, below the 4.5:1 text floor, where
   the plain bold "Solvix" it replaced inherited a light ink at roughly 15:1.
   The letters faded out from the waist down.

   Keyed off the attribute rather than the class so no HTML has to know. P1, P2
   and P3 are unaffected: P3's dark mode was removed, P2 is light-only, and
   nothing stamps data-theme on Matrix. */
:root[data-theme="dark"] .svx-wm .svx-g {
  background: none;
  color: var(--sx-wordmark-fg, #FFFFFF);
  -webkit-text-fill-color: var(--sx-wordmark-fg, #FFFFFF);
}
:root[data-theme="dark"] .svx-wm .svx-grn,
:root[data-theme="dark"] .svx-wm .svx-v {
  background: none;
  color: #B7E07C;
  -webkit-text-fill-color: #B7E07C;
}

/* PRINT. background-clip:text over a transparent fill has nothing to clip once
   the browser drops background graphics, which Chrome and Firefox both do by
   default -- so the brand printed as a bare chevron and an "i". Inherited from
   Matrix, where it has shipped for months, but new to these four surfaces and
   to the sign-in page, so it is closed here. */
@media print {
  .svx-g, .svx-grn {
    background: none;
    color: #3A3A3A;
    -webkit-text-fill-color: #3A3A3A;
  }
}

/* The rail toggle's transform is suppressed for these users on every surface;
   the new per-group chevron rotates the same way and was missed. */
@media (prefers-reduced-motion: reduce) {
  .sxg-chev { transition: none; }
}
