/* The Matrix — a theme file. See themes/README.md for the contract.
   Loaded AFTER public/css/styles.css, so it wins on source order with no
   !important anywhere.

   BOTH VARIANTS. Dark is the obvious one; the light variant is at the foot of
   the token section and is NOT the dark one inverted — see "LIGHT" below.

   WHAT THIS THEME IS.
   The reference is the film: a true-black screen with code falling down it,
   phosphor green, bloom around anything bright, and the work sitting on solid
   console plates laid over the rain. Two earlier prohibitions have been lifted
   and both were wrong, so they are recorded here rather than quietly dropped:

     - "NO falling katakana, no overlay of any kind" produced a green terminal
       that the maintainer correctly said looked nothing like the film. The
       digital rain is now the signature of this theme and it is built, not
       gestured at. See ATMOSPHERE below for what it is, where it is allowed to
       be, and the four limits it lives inside.
     - "SEMANTIC COLOUR STAYS SEMANTIC — nothing that means available/overdue/
        error is recoloured" is superseded by the maintainer's decision of
       2026-08-29 that a theme MAY re-pitch status colour into its own palette.
       It is re-pitched here, into a console lamp row: see STATUS COLOUR below
       for the mapping, the reasoning, and every measured ratio.

   Two things it still refuses, because both are about six hours of data entry
   rather than about a screenshot:
     - NO #00FF00 on #000 for reading. Saturated green at full chroma vibrates
       against a black ground and is painful at paragraph length. Body ink is
       #d6e8da — a phosphor-tinted white, 16.41:1 on the ground — and the
       saturated green is spent on links, structure and the active nav item.
     - NO monospace for running text. The terminal faces carry headings, column
       heads, chips and figures (things that are labels or data); Karla carries
       every sentence, as it does in every other theme here.

   ONE ACCENT, AND WHAT HAPPENED TO THE AMBER RESERVATION.
   Phosphor green (--mx-phosphor) is structure: links, the active nav item, the
   section-heading prompt, focus-within borders. Amber (--mx-alert) used to be
   reserved for exactly two things, the focus ring and <mark>, so that its
   appearance always meant "deal with this".

   Spending amber on the caution status tier narrows that reservation rather
   than breaking it, and the narrowing is deliberate: amber still means "deal
   with this" — a caution status is exactly that — and the focus ring is still
   the only OUTLINE that is amber, which is the shape and the position the eye
   is actually looking for when it hunts for focus. The reservation survives in
   practice for a measurable reason: the caution tier is `pending` and `ordered`
   only, which live on /procurement (1 row in the current data), and the state
   that would have flooded the screen with amber — `checked_out`, 345 of 394
   assets — is deliberately NOT in that tier. If checked_out were amber, amber
   would be on 345 rows and the reservation would be dead. The two decisions
   hold each other up.

   HOW THE TWO VARIANTS ARE WIRED, because this is the part that is easy to get
   wrong and silent when it is. styles.css carries ~43 component rules selected
   by `:root[data-theme="dark"] .x` AND a mirror of each under
   `@media (prefers-color-scheme: dark) :root:not([data-theme="light"]) .x` —
   status chips, import states, chart bars, flash, audit flags. Together those
   two selectors mean "dark is in effect", including the case this theme must not
   get wrong: a two-variant theme leaves `data-theme` ABSENT until the user
   touches the switch (see applyPalette in public/js/themeToggle.js), so on a
   dark-OS machine the attribute is simply not there. The dark token blocks below
   are therefore written against the SAME two selectors, so the palette and those
   43 component rules can never disagree about which variant is showing. The
   light values are the palette's base block, matching the base stylesheet's own
   convention that light is the default and dark is the override.

   NOTHING BELOW DUPLICATES A COLOUR. The `--pico-*`, `--sidebar-*` and
   `--mx-st-*` mappings are written once, in the base block, against `--mx-*`
   role tokens; the dark blocks redefine only the raw `--mx-*` values, and
   var() re-resolves. That is the whole reason the roles are named. */

/* ===========================================================================
   TOKENS — LIGHT is the base (see the wiring note above)
   ===========================================================================

   LIGHT IS THE REAL DESIGN PROBLEM AND THIS IS THE ANSWER: GREENBAR.
   Green-on-white is a hospital chart, and inverting a phosphor palette gives
   you a smudge. So the light variant is not this theme in daylight — it is the
   same idea one step earlier in the pipeline: the PRINTOUT the terminal
   produced. Warm off-white continuous-feed stock (#f2f1e8), impact-printer ink
   in near-black green (#12291d), deep press green for structure (#0f6b3f), and
   the table zebra retinted to the pale green band that gave greenbar paper its
   name. That last part is not decoration: this app already stripes every table
   (see the zebra rule in styles.css), so the light variant's most distinctive
   feature costs one overridden declaration and lands on the exact element the
   metaphor is about. The sidebar stays a dark console rail in both variants —
   which is also what Street Culture and Cyberpunk do — so the phosphor idea is
   still on screen in daylight instead of being replaced by it.

   THE LIGHT VARIANT HAS NO RAIN AND NO SCANLINES, ON PURPOSE. Falling code on
   a sheet of continuous-feed paper is nonsense, and the technique the rain uses
   (an opaque black canvas dimmed by CSS opacity, which is only invisible
   because the page under it is true black) would grey the whole printout. Both
   atmosphere layers are switched off by a token in this block rather than by a
   second copy of every rule — see --mx-rain-display / --mx-overlay-display.

   The exception is the status ribbon: an impact printer really did have a
   two-colour ribbon, black and red, so the light variant spends red on the bad
   tier and a dark ochre on caution, which is the same lamp row in the medium
   the light variant is about.

   CONTRAST IS MEASURED, NOT EYEBALLED. Every ratio quoted below was computed
   (WCAG 2.1 relative luminance) against the surface the text actually sits on,
   not against the page ground it is nowhere near — the failure mode this
   project has already shipped once, where a light theme's sticky table header
   measured 4.04:1 because only the page ground had been checked. Every value in
   this file clears 4.5:1, including the small text: 0.68rem chip labels,
   0.72rem field captions and 0.75rem tile labels. */
:root[data-palette="matrix"] {
  /* OPTIONAL UI SOUND (X1, 2026-09-04): explicitly OFF, not merely undeclared.
     chrome.css's base :root now defaults every unscoped theme to sound on
     with the chime voice, so an atmospheric theme that wants to stay silent
     has to say so — this theme has its own glyph-rain canvas and reusing
     chime here would be a struck-bell sound over a terminal, which is a
     worse mismatch than no sound at all. A `terminal` voice (dry, clipped,
     no reverb) is real, separate creative work this round deliberately did
     not build. BUILT 2026-09-05: `terminal` is that voice — dry (reverb send 0
     on every cue, which is what separates it from everything else in the file),
     clipped short, an octave above chime, square for the machine edge. It keeps
     the four shared gestures, so switching to this theme changes the costume and
     not the meaning. See VOICES.terminal in public/js/uiSound.js. */
  --theme-sound: 1;
  /* The falling glyphs. See the MOTION section below. */
  --theme-motion: 1;
  --theme-voice: terminal;

  /* ---- Grounds and ink -------------------------------------------------
     --mx-band is the FIRMEST surface, and it exists because of the sticky
     table header: `thead th` in styles.css is `position: sticky` with no
     background of its own, and every theme is required to give it an opaque
     one or rows scroll through the column headings. It is also the topbar's
     ground, so the two pieces of chrome that pin content agree. */
  --mx-ground:     #f2f1e8;  /* warm printout stock */
  --mx-surface:    #ffffff;  /* cards, form fields — a fresh sheet */
  --mx-surface-2:  #e9ece0;  /* sectioning: card headers, code, wells */
  --mx-band:       #e2e6d8;  /* sticky thead + topbar */
  --mx-ink:        #12291d;  /* 13.61:1 on ground, 15.43:1 on surface, 12.16:1 on band */
  --mx-ink-2:      #4a5c4f;  /* muted: 6.31:1 on ground, 7.16:1 on surface, 5.64:1 on band */
  --mx-ink-3:      #5d6f62;  /* placeholders only: 5.36:1 on the white field */
  --mx-rule:       #d9dccc;  /* hairline grid between cells */
  --mx-rule-firm:  #b9c2ac;  /* component borders */

  /* ---- The one accent --------------------------------------------------
     #0f6b3f is press green, dark enough to read as ink: 5.80:1 on the stock,
     6.57:1 on white and — the value that actually set it — 5.18:1 on the
     #e2e6d8 band a sticky column heading sits on. A lighter green passed on
     paper and failed there, which is the whole reason that surface has a
     token of its own. */
  --mx-phosphor:       #0f6b3f;
  --mx-phosphor-hover: #0b5c34;  /* 7.14:1 on ground */
  --mx-on-accent:      #ffffff;  /* 6.57:1 on the #0f6b3f fill */

  /* ---- The alert colour: focus ring, <mark>, and now the caution tier --- */
  --mx-alert:    #7a4f00;  /* 6.29:1 on ground, 7.13:1 on white */
  --mx-mark-bg:  #f6e6bd;
  --mx-mark-ink: #4a3200;  /* 9.71:1 on its own wash */

  /* ---- STATUS: the console lamp row (see STATUS COLOUR below) -----------
     Ratios are ink-on-its-own-fill / ink-on-the-odd-row (#f2f1e8) /
     ink-on-the-greenbar-even-row (#e1e7db), which are the three surfaces a
     chip is ever seen against in this variant. */
  --mx-st-good-bg:   #d7eff3;  --mx-st-good-ink:   #08525c;  /* 7.39 / 7.81 / 7.02 */
  --mx-st-hold-bg:   #e8ebe1;  --mx-st-hold-ink:   #17512f;  /* 7.72 / 8.22 / 7.39 */
  --mx-st-warn-bg:   #fbe8c0;  --mx-st-warn-ink:   #6b4906;  /* 6.74 / 7.18 / 6.45 */
  --mx-st-bad-bg:    #f9d5cd;  --mx-st-bad-ink:    #93301f;  /* 5.75 / 6.90 / 6.21 */
  --mx-st-alarm-bg:  #f3bfb3;  --mx-st-alarm-ink:  #7a1d0e;  /* 6.43 / 9.22 / 8.29 */
  --mx-st-dead-bg:   #dfe0dd;  --mx-st-dead-ink:   #4a4d4b;  /* 6.71 / 7.55 / 6.79 */
  --mx-st-closed-bg: #dcdcd8;  --mx-st-closed-ink: #3d403e;  /* 7.63 / 9.26 / 8.32 */
  --mx-st-gone-bg:   #d2d2ce;  --mx-st-gone-ink:   #333634;  /* 8.06 / 10.78 / 9.69 */
  /* The overdue filter chip is an OUTLINE on the page ground, not a fill, so
     it needs an edge colour and a filled variant of its own. */
  --mx-st-alarm-edge:     #b23a20;
  --mx-st-alarm-fill:     #8f2a13;
  --mx-st-alarm-fill-ink: #ffffff;  /* 7.55:1 on that fill */

  /* The same lamp row as a solid bar. A bar is a FILL, not text, so the bar
     writes its own values instead of reusing the chip inks: styles.css states
     the requirement (~3:1 against the track, not 4.5:1 text contrast) and the
     inert steps in particular have to be picked for visibility rather than for
     recessiveness. Ratios below are against the light track,
     --pico-muted-border-color = #d9dccc. */
  --mx-bar-good:   #08525c;  /* 6.35 */
  --mx-bar-hold:   #1c5b38;  /* 5.79 — kept in step with the chip ink, 2026-09-14 */
  --mx-bar-warn:   #6b4906;  /* the caution tier has no chart today; kept in step */
  --mx-bar-bad:    #93301f;  /* 5.61 */
  --mx-bar-alarm:  #8f2a13;  /* 6.00 */
  --mx-bar-dead:   #6e7270;  /* 3.50 */
  --mx-bar-closed: #575b59;  /* 4.94 */
  --mx-bar-gone:   #3f4341;  /* 7.20 */

  /* Greenbar. Alpha rather than a hex so it darkens the stock and lightens the
     dark ground by the same relative amount, which is how the base zebra rule
     manages to serve both themes with one declaration. */
  --mx-stripe: rgba(15, 107, 63, 0.075);

  /* The CRT glow, as a colour rather than a rule, so one text-shadow
     declaration serves both variants: it is transparent in light, because a
     glow on paper is a printing error, and the light variant needs no override
     for it at all. */
  --mx-glow: transparent;
  /* The same trick for a bloom that has to be the FIGURE'S OWN colour rather
     than the theme's: a percentage in a custom property, substituted into
     color-mix, so `0%` is a fully transparent shadow and needs no light-variant
     rule either. It matters on the dashboard, where an amber tile blooming
     green would be a halo that disagrees with the number inside it. */
  --mx-glow-mix: 0%;

  --mx-scroll-shadow: rgba(18, 41, 29, 0.2);

  /* ---- Atmosphere switches ---------------------------------------------
     One token each, so the whole rain/scanline layer is turned off for the
     printout without a second copy of any rule. `display` accepts a custom
     property like any other value. */
  --mx-rain-display:    none;
  --mx-rain-opacity:    0;
  --mx-overlay-display: none;
  /* The chrome is opaque in light — there is nothing behind it to reveal. */
  --mx-rail-wash:    var(--sidebar-bg);
  --mx-topbar-wash:  var(--mx-band);
  --mx-login-wash:   transparent;
  --mx-login-edge:   transparent;
  --mx-plate:        transparent;
  --mx-plate-edge:   transparent;
  /* The console plate the work sits on. In light it is the page itself, so it
     is not inset and carries no edge; in dark it is inset and the rain runs
     around it. */
  --mx-content-inset: 0;
  --mx-content-edge:  transparent;

  /* ---- Stat-tile inks --------------------------------------------------
     The dashboard's attention row encodes a deadline in colour (salmon = a
     deadline has passed, amber = one is coming, teal = in hand) and that
     encoding is load-bearing, so the tiles keep their three hues and only stop
     being filled blocks. These are the ink values for the outlined form; all
     measured against the page ground the tile is now transparent against. */
  --mx-tile-teal:   #005b68;  /* 6.87:1 */
  --mx-tile-amber:  #664a07;  /* 7.27:1 */
  --mx-tile-salmon: #914131;  /* 6.17:1 */
  --mx-tile-grey:   #464648;  /* 8.31:1 */

  /* ---- Type ------------------------------------------------------------
     TWO terminal faces, and the split between them is the whole point.

     JetBrains Mono (already loaded for Cyberpunk, so it costs no request)
     carries DATA: column heads at 0.68rem, chip labels, numeric cells, the
     figures in a report. It is a screen face designed to be read small, and it
     stays.

     VT323 is this theme's own face, requested through the registry's `font:`
     entry, and it carries DISPLAY ONLY: h1, the section banners, the stat-tile
     figures, the sidebar's subtitle. It is a DEC VT220 screen font — the real
     thing rather than an impression of one — and at 2rem+ it is the single
     strongest signal in the theme. It is also close to unreadable at 0.68rem,
     which is exactly why --heading-font-family is NOT pointed at it: that token
     is read by the sidebar's group captions and the form legends as well as by
     headings, and switching it wholesale would put 11px labels on a bitmap
     face. Display gets VT323 by name, in five places, deliberately.

     The fallback chain ends at JetBrains Mono, so if the registry entry is ever
     removed the theme degrades to what it looked like before rather than to a
     system serif. */
  --mx-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
  --mx-display: 'VT323', 'JetBrains Mono', ui-monospace, monospace;
  --heading-font-family: var(--mx-mono);

  /* Rectilinear, at the token, so every component follows without being
     listed. The rounded corners are the single thing that would keep this
     reading as a web app rather than an instrument. */
  --pico-border-radius: 0;

  /* ---- The mappings. WRITTEN ONCE; the dark blocks below re-point only the
     raw --mx-* values above and every var() here re-resolves. ------------ */
  --pico-background-color: var(--mx-ground);
  --pico-color: var(--mx-ink);
  --pico-muted-color: var(--mx-ink-2);
  --pico-h1-color: var(--mx-ink);
  --pico-h2-color: var(--mx-ink);
  --pico-h3-color: var(--mx-ink);
  --pico-h4-color: var(--mx-ink);
  --pico-h5-color: var(--mx-ink);
  --pico-h6-color: var(--mx-ink);

  --pico-primary: var(--mx-phosphor);
  --pico-primary-background: var(--mx-phosphor);
  --pico-primary-underline: color-mix(in srgb, var(--mx-phosphor) 45%, transparent);
  --pico-primary-hover: var(--mx-phosphor-hover);
  --pico-primary-hover-background: var(--mx-phosphor-hover);
  --pico-primary-focus: color-mix(in srgb, var(--mx-phosphor) 30%, transparent);
  /* The rule, not a copied value: the fill is dark in light mode so its label
     is white, and bright in dark mode so its label is near-black. Both are
     stated once, in --mx-on-accent, and both measure over 6:1 — worth saying
     out loud because the sibling theme's primary button is dark ink on a dark
     fill at 2.62:1, which is what happens when a neon accent is used as a
     button ground without measuring the label on it. */
  --pico-primary-inverse: var(--mx-on-accent);

  --pico-secondary: var(--mx-ink-2);
  --pico-secondary-background: var(--mx-surface-2);
  --pico-secondary-hover: var(--mx-ink);
  --pico-secondary-hover-background: var(--mx-band);
  --pico-secondary-focus: color-mix(in srgb, var(--mx-ink-2) 25%, transparent);
  --pico-secondary-inverse: var(--mx-ink);

  --pico-mark-background-color: var(--mx-mark-bg);
  --pico-mark-color: var(--mx-mark-ink);

  --pico-card-background-color: var(--mx-surface);
  --pico-card-sectioning-background-color: var(--mx-surface-2);
  --pico-card-border-color: var(--mx-rule-firm);
  --pico-border-color: var(--mx-rule-firm);
  --pico-muted-border-color: var(--mx-rule);

  --pico-form-element-background-color: var(--mx-surface);
  --pico-form-element-selected-background-color: var(--mx-surface);
  --pico-form-element-border-color: var(--mx-rule-firm);
  --pico-form-element-color: var(--mx-ink);
  --pico-form-element-placeholder-color: var(--mx-ink-3);
  --pico-form-element-active-background-color: var(--mx-surface);
  --pico-form-element-active-border-color: var(--mx-phosphor);
  --pico-form-element-focus-color: var(--mx-phosphor);

  --pico-table-border-color: var(--mx-rule);
  --pico-table-row-stripped-background-color: var(--mx-stripe);

  --pico-code-background-color: var(--mx-surface-2);
  --pico-code-color: var(--mx-phosphor);
  --pico-blockquote-border-color: var(--mx-phosphor);
  --pico-dropdown-background-color: var(--mx-surface);
  --pico-dropdown-border-color: var(--mx-rule-firm);
  --pico-dropdown-hover-background-color: var(--mx-surface-2);
  --pico-switch-background-color: var(--mx-rule-firm);
  --pico-switch-checked-background-color: var(--mx-phosphor);
  --pico-range-thumb-color: var(--mx-phosphor);

  /* ---- The console rail -------------------------------------------------
     Identical in both variants and deliberately so: it is the one surface that
     keeps the phosphor idea on screen in daylight. Measured against #040d09 —
     link 12.05:1, muted caption 8.11:1, active label 8.06:1 on its own green.
     --sidebar-muted is a step brighter than it was (#7f9c88 -> #98b3a2) to buy
     headroom for the rain behind the rail in dark; see ATMOSPHERE. */
  --sidebar-bg: #040d09;
  --sidebar-ink: #d6e8da;
  --sidebar-muted: #98b3a2;
  --sidebar-link: #b9d0c1;
  --sidebar-link-active: #eafff2;
  --sidebar-hover-bg: #10241a;
  /* Also the light/dark switch's thumb (styles.css reads this token for it), so
     it has to be a colour that reads on the dark track — which a near-black
     "selected row" tint would not have been. */
  --sidebar-active-bg: #0f5c37;
  /* The rail's own edge. Not a --sidebar-* name: that namespace belongs to
     styles.css and nothing there reads this one. */
  --mx-rail-edge: #16291e;

  --scroll-shadow-color: var(--mx-scroll-shadow);
}

/* ---------------------------------------------------------------- DARK ----
   TRUE BLACK, and that is a change from #071310 with a reason attached: the
   rain's trails have to fade into the ground and there is nothing to fade into
   if the ground is a dark grey-green. It is also what makes the rain layer
   cheap — the canvas is drawn opaque black with the classic fade-rectangle
   technique and dimmed by CSS opacity, which is only invisible where it sits
   because the page beneath it is #000. The phosphor family has not gone
   anywhere; it moved onto the surfaces (#0c1c15 cards, #04100c band), which is
   where a tube's cast actually shows.

   TWO BLOCKS, AND THEY MUST STAY IDENTICAL. This first one covers "the OS is
   dark and the user has never touched the switch", i.e. `data-theme` absent —
   the case a two-variant theme hits by default. The second covers the explicit
   toggle. They mirror, exactly, the pair of selectors styles.css uses for its
   own ~43 dark component rules, which is what guarantees the palette and those
   rules can never disagree about which variant is on screen. Change one, change
   the other. */
@media (prefers-color-scheme: dark) {
  :root[data-palette="matrix"]:not([data-theme="light"]) {
    --mx-ground:     #000000;
    --mx-surface:    #0c1c15;
    --mx-surface-2:  #112a20;
    --mx-band:       #04100c;
    --mx-ink:        #d6e8da;
    --mx-ink-2:      #9fbfa9;
    --mx-ink-3:      #83a08e;
    --mx-rule:       #163023;
    --mx-rule-firm:  #26493a;

    --mx-phosphor:       #3ee07a;
    --mx-phosphor-hover: #74eea8;
    --mx-on-accent:      #000000;

    --mx-alert:    #ffb000;
    --mx-mark-bg:  #3a2a00;
    --mx-mark-ink: #ffdf9e;

    --mx-st-good-bg:   #04333c;  --mx-st-good-ink:   #7ce4f0;
    --mx-st-hold-bg:   #0c1f16;  --mx-st-hold-ink:   #74cf98;
    --mx-st-warn-bg:   #3d2c02;  --mx-st-warn-ink:   #ffc65a;
    --mx-st-bad-bg:    #3f0f0c;  --mx-st-bad-ink:    #ff9b8b;
    --mx-st-alarm-bg:  #7a1408;  --mx-st-alarm-ink:  #ffd0c4;
    --mx-st-dead-bg:   #1e2220;  --mx-st-dead-ink:   #b3bab6;
    --mx-st-closed-bg: #171a19;  --mx-st-closed-ink: #979e9a;
    --mx-st-gone-bg:   #121514;  --mx-st-gone-ink:   #868c89;
    --mx-st-alarm-edge:     #c8321c;
    --mx-st-alarm-fill:     #7a1408;
    --mx-st-alarm-fill-ink: #ffd0c4;

    --mx-bar-good:   #7ce4f0;
    --mx-bar-hold:   #74cf98;
    --mx-bar-warn:   #ffc65a;
    --mx-bar-bad:    #ff9b8b;
    --mx-bar-alarm:  #ff5a3c;
    --mx-bar-dead:   #b3bab6;
    --mx-bar-closed: #8d938f;
    --mx-bar-gone:   #767d79;

    --mx-stripe: rgba(62, 224, 122, 0.05);
    --mx-glow:   rgba(62, 224, 122, 0.3);
    --mx-glow-mix: 30%;
    --mx-scroll-shadow: rgba(214, 232, 218, 0.16);

    --mx-rain-display:    block;
    --mx-rain-opacity:    0.85;
    --mx-overlay-display: block;
    --mx-rail-wash:    rgba(4, 13, 9, 0.86);
    --mx-topbar-wash:  rgba(2, 9, 6, 0.18);
    --mx-login-wash:   rgba(0, 0, 0, 0.80);
    --mx-login-edge:   #26493a;
    --mx-plate:        #04100c;
    --mx-plate-edge:   #1b3a2c;
    --mx-content-inset: 1rem;
    --mx-content-edge:  #163023;

    --mx-tile-teal:   #99e1ec;
    --mx-tile-amber:  #ffe3a0;
    --mx-tile-salmon: #f9b6a9;
    --mx-tile-grey:   #cdcdce;
  }
}

/* Keep in step with the prefers-color-scheme:dark block above — this is the
   explicit-toggle half of the same pair. */
:root[data-palette="matrix"][data-theme="dark"] {
  --mx-ground:     #000000;  /* body ink 16.41:1, phosphor 12.15:1, amber 11.46:1 */
  --mx-surface:    #0c1c15;  /* body ink 13.76:1, muted 8.80:1, phosphor 10.19:1 */
  --mx-surface-2:  #112a20;  /* body ink 11.93:1, placeholder 5.37:1 */
  --mx-band:       #04100c;  /* body ink 15.13:1, column heading 9.68:1 */
  --mx-ink:        #d6e8da;
  --mx-ink-2:      #9fbfa9;
  --mx-ink-3:      #83a08e;
  --mx-rule:       #163023;
  --mx-rule-firm:  #26493a;

  /* Hue ~141°, not the film's #00ff41 and not #00FF00: the saturated original
     measures beautifully and still vibrates, because contrast is not the same
     quantity as legibility. This is the same green pulled back in chroma until
     a line of link text sits still, and it still measures 12.15:1 on the ground
     and 10.19:1 on a card. The film's brighter green is not lost — it is where
     it belongs, in the rain, whose leading glyph is brighter than anything the
     interface itself ever prints. */
  --mx-phosphor:       #3ee07a;
  --mx-phosphor-hover: #74eea8;
  --mx-on-accent:      #000000;  /* 12.15:1 as the label on the #3ee07a fill */

  --mx-alert:    #ffb000;  /* 11.46:1 on the ground — the focus ring is never lost */
  --mx-mark-bg:  #3a2a00;
  --mx-mark-ink: #ffdf9e;  /* 10.79:1 on its own wash */

  /* ---- STATUS in dark. Ratios are ink-on-its-own-fill /
     ink-on-the-odd-row (#000000) / ink-on-the-phosphor-striped-even-row
     (#030b06). All three are the surfaces a chip is actually seen against. */
  --mx-st-good-bg:   #04333c;  --mx-st-good-ink:   #7ce4f0;  /* 9.21 / 14.21 / 13.48 */
  --mx-st-hold-bg:   #0c1f16;  --mx-st-hold-ink:   #74cf98;  /* 9.09 / 11.12 / 10.55 */
  --mx-st-warn-bg:   #3d2c02;  --mx-st-warn-ink:   #ffc65a;  /* 8.64 / 13.48 / 12.78 */
  --mx-st-bad-bg:    #3f0f0c;  --mx-st-bad-ink:    #ff9b8b;  /* 8.03 / 10.31 / 9.78 */
  --mx-st-alarm-bg:  #7a1408;  --mx-st-alarm-ink:  #ffd0c4;  /* 7.80 / 15.07 / 14.29 */
  --mx-st-dead-bg:   #1e2220;  --mx-st-dead-ink:   #b3bab6;  /* 8.14 / 10.62 / 10.07 */
  --mx-st-closed-bg: #171a19;  --mx-st-closed-ink: #979e9a;  /* 6.40 / 7.67 / 7.28 */
  --mx-st-gone-bg:   #121514;  --mx-st-gone-ink:   #868c89;  /* 5.36 / 6.13 / 5.81 */
  --mx-st-alarm-edge:     #c8321c;
  --mx-st-alarm-fill:     #7a1408;
  --mx-st-alarm-fill-ink: #ffd0c4;

  /* Bars, against the dark track #163023: 9.60 / 7.06 / — / 6.96 / 4.58 /
     7.17 / 4.53 / 3.36. `lost` is a hotter red than its chip ink because a
     20px solid bar can carry a saturation the chip's label could not. */
  --mx-bar-good:   #7ce4f0;
  --mx-bar-hold:   #74cf98;
  --mx-bar-warn:   #ffc65a;
  --mx-bar-bad:    #ff9b8b;
  --mx-bar-alarm:  #ff5a3c;
  --mx-bar-dead:   #b3bab6;
  --mx-bar-closed: #8d938f;
  --mx-bar-gone:   #767d79;

  --mx-stripe: rgba(62, 224, 122, 0.05);
  --mx-glow:   rgba(62, 224, 122, 0.3);
  --mx-glow-mix: 30%;
  --mx-scroll-shadow: rgba(214, 232, 218, 0.16);

  /* Atmosphere. --mx-rain-opacity 0.85 is not a taste value: it is the number
     the rail, login and topbar contrast budgets were all solved against. See
     the contrast budget in ATMOSPHERE before changing it. */
  --mx-rain-display:    block;
  --mx-rain-opacity:    0.85;
  --mx-overlay-display: block;
  --mx-rail-wash:    rgba(4, 13, 9, 0.86);
  --mx-topbar-wash:  rgba(2, 9, 6, 0.18);
  --mx-login-wash:   rgba(0, 0, 0, 0.80);
  --mx-login-edge:   #26493a;
  --mx-plate:        #04100c;
  --mx-plate-edge:   #1b3a2c;
  --mx-content-inset: 1rem;
  --mx-content-edge:  #163023;

  /* Against #000000: 14.34 / 16.75 / 12.29 / 13.22. */
  --mx-tile-teal:   #99e1ec;
  --mx-tile-amber:  #ffe3a0;
  --mx-tile-salmon: #f9b6a9;
  --mx-tile-grey:   #cdcdce;
}

/* ===========================================================================
   ATMOSPHERE — the digital rain, the tube, and where they are allowed to be
   ===========================================================================

   THE CANVAS IS NOT DECLARED HERE AND CANNOT BE. A theme is CSS only and the
   DOM is theme-agnostic, so this file styles an element it does not create.
   The contract, which the companion script has to honour exactly:

     <canvas class="matrix-rain" aria-hidden="true"></canvas>

   as a direct child of <body>, created only while
   document.documentElement.dataset.palette === 'matrix', removed when the
   palette changes (themeToggle.js rewrites that attribute live), the drawing
   loop stopped while document.hidden and while
   matchMedia('(prefers-reduced-motion: reduce)').matches. Everything else about
   it — position, stacking, opacity, and whether it exists in the light variant
   — is decided by the rules below, so the script never has to know which
   variant is showing.

   WHERE THE RAIN IS ALLOWED TO BE, and why it is not simply everywhere.
   themes/README.md is explicit that decoration does not go behind a table of
   numbers, and it is right: at 394 rows the thing someone is doing all day is
   reading a grid. So the rain is a GROUND, and the work sits on plates laid
   over it. Four zones, in descending order of how much of the effect each one
   lets through:

     - THE TOPBAR is the signature band and the rain runs there at nearly full
       strength (a 0.18 wash). That is possible because everything the topbar
       contains — the search form and the signed-in readout — is given an
       OPAQUE PLATE of its own below, so no text is ever on the rain. This is
       the change that makes the effect visible on a working screen instead of
       only on the sign-in page: a 56px band of falling code across the top of
       every page, with the chrome sitting on plates in front of it.
     - THE BAND AROUND THE WORKING PLATE. main.content is opaque and inset by
       --mx-content-inset, so full-strength rain runs down both sides and along
       the bottom of the content, and — on any screen wider than about 1620px —
       in the gutter to the right of the 1400px column. Nothing moves behind a
       cell.
     - THE SIGNED-OUT PAGE (main.container WITHOUT .content — the login picker)
       has no table on it, so its panel is a 0.80 wash and the rain is strongly
       present behind the one screen in the app with nothing to read.
     - THE RAIL is a 0.86 wash: a faint shimmer rather than a display. It is the
       most constrained zone because nav labels, group captions and the brand
       all sit directly on it with no plate of their own, and plating each of
       them would turn the nav into a list of buttons.

   THE CONTRAST BUDGET, solved rather than guessed, because this is the half of
   an atmosphere layer that fails silently. The brightest pixel the script may
   emit is the leading glyph, taken as #e8ffee. Composited over the black page
   at --mx-rain-opacity 0.85 that is rgb(197,217,202) — bright enough to break
   any text on its own, which is why nothing sits directly on it anywhere.

     - Rail, 0.86 wash, worst case #1f2a24: body ink 11.60:1, nav link 9.09:1,
       muted caption 6.58:1, active label 14.19:1.
     - Login panel, 0.80 wash, worst case #272b28: body ink 11.22:1, muted
       7.18:1, placeholder ink 5.05:1, phosphor link 8.31:1.
     - Topbar, 0.18 wash, worst case #a2b4a7 — which would measure 1.71:1 for
       body ink and is exactly why the two things in the topbar are plated
       rather than washed.

   Those numbers are what set the wash alphas. Raising --mx-rain-opacity
   without re-solving all three is what would break this, and it would break it
   invisibly: the failure only shows on the frames where a leading glyph
   happens to pass behind a word.

   THE TUBE ITSELF is a second, static layer on body::after — scanlines and a
   vignette, one composited layer, no animation, no repaint. It is dark-only for
   a reason that is not taste: every overlay here DARKENS, and darkening a dark
   ground under light text raises contrast, while doing the same to the light
   variant's dark ink on cream would lower it. --mx-overlay-display is `none` in
   light.

   NO FLICKER. A slow opacity flutter on the tube layer is the obvious next
   thing and it is deliberately not here: this is a screen someone does data
   entry in for six hours, and a periodic brightness change across the whole
   viewport is the one atmosphere effect that would actually be felt. The rain
   moves; the tube does not. */

/* The rain layer. z-index: -1 is doing real work — it puts the canvas above
   the page's own background (which paints as the viewport canvas, below
   negative-z children) and below every in-flow background in the document, so
   NOTHING in the shell has to be re-stacked to sit on top of it. Giving it
   z-index: 0 instead would put it above all non-positioned content and hide
   the entire interface behind falling glyphs, which is the failure this line
   exists to avoid. */
:root[data-palette="matrix"] .matrix-rain {
  display: var(--mx-rain-display);
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
  pointer-events: none;
  opacity: var(--mx-rain-opacity);
}

/* OFF, not slower — themes/README.md is explicit, and so is the WCAG guidance
   behind it. The element selector is not decoration: it takes this rule to
   (0,2,1) so it beats the (0,2,0) rule above on specificity rather than on
   source order, which is the equal-specificity trap this stylesheet has been
   bitten by twice. The script must also stop its rAF loop; this only hides it. */
/* Two ways the rain is turned off, and they are deliberately separate.
   `prefers-reduced-motion` is the operating system's answer and has always been
   honoured. `data-motion="off"` is this person's answer for this app, set by the
   Appearance panel — added 2026-09-08 because the rain is the most noticeable
   motion in the app and "I like this theme but not today" had no answer.
   matrixRain.js checks both for the same reason it checked the media query: the
   CSS stops it being seen, the script stops it being computed. */
:root[data-palette="matrix"][data-motion="off"] canvas.matrix-rain {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  :root[data-palette="matrix"] canvas.matrix-rain {
    display: none;
  }
}

/* The tube: scanlines plus a vignette, above everything, inert to the pointer.
   z-index 2000 clears the mobile drawer (1000) and its backdrop (999) so the
   texture is continuous rather than stopping at the edge of an open drawer.
   0.11 alpha on one line in three is about a 4% average darkening — measured
   against the requirement that body ink stay above 4.5:1, which it does by a
   factor of three. */
:root[data-palette="matrix"] body::after {
  content: "";
  display: var(--mx-overlay-display);
  position: fixed;
  inset: 0;
  z-index: 2000;
  pointer-events: none;
  background:
    radial-gradient(ellipse 120% 100% at 50% 45%, transparent 55%, rgba(0, 0, 0, 0.35) 100%),
    repeating-linear-gradient(to bottom, rgba(0, 0, 0, 0.11) 0 1px, transparent 1px 3px);
}

/* ===========================================================================
   TYPOGRAPHY
   =========================================================================== */

/* h1 on VT323, which runs small on the em — hence 2.6rem rather than the
   1.85rem this heading wore on JetBrains Mono. Report titles like "Repair &
   maintenance cost by asset" are what set the size; VT323's narrow advance is
   what lets it grow without wrapping them. NOT uppercased: an asset page's h1
   is a device name, and shouting somebody's serial number at them is not a
   design. */
:root[data-palette="matrix"] h1 {
  font-family: var(--mx-display);
  font-size: 2.6rem;
  line-height: 1.1;
  letter-spacing: 0.01em;
  /* The CRT. --mx-glow is transparent in light, so this rule needs no variant
     of its own. */
  text-shadow: 0 0 24px var(--mx-glow);
}

/* A section heading is a console banner: prompt, label, rule. The `>` is drawn
   with ::before on an element that already exists — the DOM is theme-agnostic
   and this file adds nothing to it. Scoped to `main` so the sidebar's own
   captions and any heading in the chrome are left alone. */
:root[data-palette="matrix"] main h2 {
  font-family: var(--mx-display);
  font-size: 1.5rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding-bottom: 0.45rem;
  border-bottom: 1px solid var(--mx-rule-firm);
}

:root[data-palette="matrix"] main h2::before {
  content: ">";
  margin-right: 0.55ch;
  color: var(--mx-phosphor);
  text-shadow: 0 0 12px var(--mx-glow);
}

/* Every dashboard section heading is `<h2>Overdue check-outs <small><a>(view
   all)</a></small></h2>`. text-transform and font-family both inherit, so
   without this the quiet affordance beside the heading is shouted AND set in a
   bitmap face at 0.8rem — styles.css has already deliberately put that link
   back on the body face at a fixed size, which the inherited display face would
   undo. */
:root[data-palette="matrix"] main h2 > small {
  font-family: var(--pico-font-family);
  text-transform: none;
  letter-spacing: normal;
}

/* Figures line up in a column or they are not a column. These are the data
   faces — numeric report cells, the odometer/cost inputs, the chart counts,
   the "394 assets" line and the chip counts — and they stay on JetBrains Mono
   because they are read at 0.7–0.9rem. */
:root[data-palette="matrix"] .numeric-cell,
:root[data-palette="matrix"] .numeric-input,
:root[data-palette="matrix"] .chart-value,
:root[data-palette="matrix"] .table-count,
:root[data-palette="matrix"] .filter-chip-count,
:root[data-palette="matrix"] .nowrap {
  font-family: var(--mx-mono);
  font-variant-numeric: tabular-nums;
}

/* ===========================================================================
   THE GRID — tables read as an instrument panel
   =========================================================================== */

/* Column heads: mono, uppercase, letter-spaced, on the firm band. Muted ink
   rather than the accent, so the heading row frames the data instead of
   competing with the links inside it. */
:root[data-palette="matrix"] table thead th {
  font-family: var(--mx-mono);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--mx-ink-2);
  background: var(--mx-band);
}

/* The heading row's own rules are box-shadows, not borders, and that is a
   requirement rather than a preference — the same one styles.css documents on
   this element. With `border-collapse: collapse` a cell's border is painted by
   the TABLE, so it does not travel with a sticky cell: a border-bottom here
   stays behind while the heading scrolls, and a border-right would leave the
   pinned heading with no column separators over a body that has them. Both
   rules are drawn inset on the cell instead, where they stick with it. The
   bottom one is doubled in weight because it is now the line between the
   pinned chrome and the data. */
:root[data-palette="matrix"] thead th {
  box-shadow: inset -1px 0 0 var(--mx-rule), inset 0 -2px 0 var(--mx-rule-firm);
}

:root[data-palette="matrix"] thead th:last-child {
  box-shadow: inset 0 -2px 0 var(--mx-rule-firm);
}

/* Vertical hairlines between body cells. A console grid, kept at the rule
   colour rather than anything stronger — the point is that the eye can track
   along a row of a 394-row table, not that the table is caged. */
:root[data-palette="matrix"] tbody td:not(:last-child),
:root[data-palette="matrix"] tbody th:not(:last-child) {
  border-right: 1px solid var(--mx-rule);
}

/* GREENBAR. This overrides the sitewide zebra rule in styles.css, which is a
   warm brown wash; the selector has to carry that rule's :not() pair or it
   would re-tint the report subtotal and total rows the base deliberately
   excludes. In light this is the pale green band of continuous-feed paper; in
   dark it is the same declaration reading as a faint phosphor scan. */
:root[data-palette="matrix"] tbody tr:nth-child(even):not(.report-subtotal-row):not(.report-total-row) td,
:root[data-palette="matrix"] tbody tr:nth-child(even):not(.report-subtotal-row):not(.report-total-row) th {
  background-color: var(--mx-stripe);
}

/* ===========================================================================
   CHROME — the plate, the rail and the strip the rain shows through
   =========================================================================== */

/* THE WORKING PLATE. Opaque, so nothing moves behind a number, and inset so
   the rain runs around all three free edges of it (the fourth meets the
   topbar). The inset is a token: it is 0 in light, where there is nothing
   behind the page to reveal and a floating plate would just be a mystery
   rectangle.

   Specificity is deliberate, not incidental: .content.container in styles.css
   sets `margin: 0` at (0,2,0), so this has to reach (0,3,1) — which the `main`
   type selector supplies — rather than rely on source order. */
:root[data-palette="matrix"] main.content {
  background: var(--mx-ground);
  border: 1px solid var(--mx-content-edge);
  border-top: 0;
  margin: 0 var(--mx-content-inset) var(--mx-content-inset);
  /* `width: auto` IS THE FIX, not a tidy-up. Pico's .container sets
     `width: 100%`, and a 100%-wide block with a horizontal margin overflows its
     container by exactly the margin — measured: documentElement.scrollWidth
     1601 against clientWidth 1585 at a 1600px viewport, i.e. a page-level
     horizontal scrollbar on every screen, which the default theme does not
     have (measured at 1585/1585 on the same page). Overriding `margin` alone
     reproduces it. */
  width: auto;
}

/* The signed-out page — the login picker — is `main.container` with no
   `.content`, which is the one honest way to tell the two shells apart from
   CSS (the DOM carries no page identity and must not start to). It has no
   table on it, so it is a translucent console panel and the rain is fully
   present behind the only screen in the app with nothing to read. */
:root[data-palette="matrix"] main.container:not(.content) {
  background: var(--mx-login-wash);
  border: 1px solid var(--mx-login-edge);
  padding-top: 1.5rem;
  padding-bottom: 1.5rem;
}

/* THE TOPBAR IS THE RAIN'S BAND. In light it is the same firm band as the
   pinned column heads, so the two pieces of chrome that frame a scrolling table
   agree with each other. In dark it is almost clear (0.18) and the rain runs
   through it at close to full strength.

   --mx-band itself is NOT the token being made translucent: the sticky thead
   reads that token, and a translucent column heading lets rows scroll through
   the heading, which is the one thing every theme in this directory is required
   not to do. */
:root[data-palette="matrix"] .topbar {
  background: var(--mx-topbar-wash);
  border-bottom: 1px solid var(--mx-rule-firm);
}

/* …AND THE TWO THINGS IN IT GET PLATES, which is what buys the band above.
   The topbar holds exactly two blocks — the "Find an asset" form (absent on
   /assets, which has its own search) and the signed-in readout — and both are
   real elements with classes, so each can carry an opaque ground of its own
   instead of the strip carrying one for them. A search input already has an
   opaque field background; the "Find" button next to it is Pico's `.outline`
   variant, which is transparent, so the plate goes on the FORM rather than on
   the input. In light both tokens are `transparent` and these two rules render
   nothing at all. */
:root[data-palette="matrix"] .topbar-search,
:root[data-palette="matrix"] .topbar-user {
  background: var(--mx-plate);
  border: 1px solid var(--mx-plate-edge);
  padding: 0.2rem 0.45rem;
}

/* Same reason, other shell: the signed-out page's Appearance trigger is the one
   control that sits on the raw rain at full strength, because .pre-auth-topbar
   is a bare flex row with no ground of its own. It gets the plate rather than
   the row getting one, so the band of rain across the top of the sign-in screen
   stays full-bleed. */
:root[data-palette="matrix"] .pre-auth-topbar .appearance-trigger {
  background: var(--mx-plate);
  border: 1px solid var(--mx-plate-edge);
}

/* The rail is a wash over the rain rather than a solid, and this is the
   largest area of the effect anyone sees on a working screen. The wash alpha
   is solved against the rail's own text — see the contrast budget in
   ATMOSPHERE — not chosen by eye.

   --sidebar-bg is NOT the token being made translucent, deliberately:
   .sidebar-footer in styles.css also reads it, and its background exists to
   stop the scrolling nav showing through behind the Appearance control. A
   translucent footer would re-break exactly that. The footer therefore stays a
   solid plate at the foot of a washed rail, which is what a console looks like
   anyway. */
:root[data-palette="matrix"] .sidebar {
  background: var(--mx-rail-wash);
  border-right: 1px solid var(--mx-rail-edge);
}

/* The active nav item keeps its filled green pill (8.06:1 for the white label)
   and gains the phosphor bar a console selection has. The bar is an inset
   box-shadow and not a border-left ON PURPOSE: a border would add 2px to the
   active item's box and shift its label 2px right of every other item in the
   column, which is visible as a jitter every time you navigate. */
:root[data-palette="matrix"] .sidebar-nav a.active {
  box-shadow: inset 2px 0 0 var(--mx-phosphor);
  text-shadow: 0 0 10px var(--mx-glow);
}

:root[data-palette="matrix"] .sidebar-brand-subtitle {
  font-family: var(--mx-display);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 0.95rem;
  text-shadow: 0 0 12px var(--mx-glow);
}

/* Keyboard focus is the alert amber, never the accent: on this palette a green
   ring would sit inside a green interface and be lost against the very thing
   it is outlining — a focused link, a focused nav item, a focused primary
   button. Amber is still the only OUTLINE on screen that is amber, which is
   what the reservation now means (see the header comment). */
:root[data-palette="matrix"] :focus-visible {
  outline: 2px solid var(--mx-alert);
  outline-offset: 2px;
}

/* ===========================================================================
   STATUS COLOUR — the console lamp row
   ===========================================================================

   THE PROBLEM. A near-monochrome theme is the worst possible starting point
   for encoding state in hue, and the obvious fix — three greens — is the exact
   failure themes/README.md forbids: good, caution and bad converging into
   three shades of the accent. Worse, in THIS theme the accent IS green, so the
   one hue a "good" chip most wants is the one hue constraint 2 rules out.

   THE ANSWER is to stop treating the screen's phosphor as the status palette
   at all. On a real console the screen is green and the STATUS LAMPS beside it
   are a different set of colours entirely — and those colours are a real
   language, not a compromise: amber for caution and red for alarm are what
   phosphor terminals, aircraft annunciators and every console UI since have
   used. So structure is green; status is the lamp row; and green is spent on
   no status at all.

   THE LADDER, and it uses three axes rather than one, because hue alone is not
   enough to separate six tiers:

     lit, cool     good      cyan          available / approved / received
     standing      neutral   unlit phosphor  CHECKED OUT
     caution       warn      amber         pending / ordered
     alarm         bad       deep red      in maintenance / rejected
     alarm, hot    alarm     bright red    LOST
     dark lamp     inert     grey, 3 steps not working / disposed / donated /
                                           cancelled

   CHECKED_OUT IS THE WHOLE DESIGN PROBLEM AND IT IS AN UNLIT LAMP.
   345 of 394 assets carry it. Most of this inventory is issued indefinitely —
   staff laptops, phones, ID cards, fobs — so "checked out" is not a warning,
   it is the normal condition of the register, and the base palette painting it
   AMBER means the most common screen in the app is a wall of caution. Here it
   is the one status with no lamp colour at all: muted phosphor ink on a fill a
   hair above the row, which with the outline the chip already wears reads as a
   box that is present and not lit. It recedes into the table, which is what a
   state on 87% of rows should do, and it is separated from `available` on all
   three axes at once — hue (none vs cyan), luminance and chroma.

   This is the ONLY tier that moves from where styles.css puts it. Every other
   class keeps the base's tier and changes only its hue, so nothing means
   something new; `rejected` is still what `in_maintenance` is, `cancelled` is
   still what `disposed` is, and `lost` is still worse than both.

   SPECIFICITY, because getting it wrong here is silent. styles.css states each
   of these three times — a light rule at (0,1,0) and two dark rules at (0,3,0)
   — so a theme rule at (0,2,0) applies in light and is quietly overridden in
   dark, which is a re-pitch that half works and looks like a browser bug. The
   selectors below carry `.status-badge` as well as the status class, which
   reaches (0,4,0) and beats all three with ONE rule per tier instead of three.
   views/partials/statusBadge.ejs is the only thing in the app that emits a
   `.status-*` class and it always emits `.status-badge` beside it. */

/* Shape first, and unchanged: square, mono, letter-spaced, and outlined in a
   wash of their own text colour so the outline is automatically the right hue
   for the status with no rule per status. color-mix keeps that outline quieter
   than the label; where it is unsupported the declaration is dropped and the
   plain currentColor border on the line above stands, which is the same idea a
   shade louder. */
:root[data-palette="matrix"] .status-badge,
:root[data-palette="matrix"] .role-badge,
:root[data-palette="matrix"] .import-outcome,
:root[data-palette="matrix"] .import-state {
  border-radius: 0;
  font-family: var(--mx-mono);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  border: 1px solid currentColor;
  border-color: color-mix(in srgb, currentColor 40%, transparent);
}

/* GOOD — in hand, moving forward. Cyan is the one cool hue in reach that is
   not the accent, and it is what the base already uses for this tier, so the
   meaning survives the change of theme unchanged. */
:root[data-palette="matrix"] .status-badge.status-available,
:root[data-palette="matrix"] .status-badge.status-approved,
:root[data-palette="matrix"] .status-badge.status-received {
  background: var(--mx-st-good-bg);
  color: var(--mx-st-good-ink);
}

/* NEUTRAL — the unlit lamp. See the note above; this is the 345-row case.
   INK RE-PITCHED 2026-09-14, fill untouched. The note above describes this tier
   as "muted phosphor ink on a fill a hair above the row", and the ink was
   #37483d — H141 at 13% saturation, which is a grey with a rumour of green in
   it. Measured against the three inert greys it has to be told apart from
   (#4a4d4b, #3d403e, #333634) that is a distinction of a few points of
   chroma at 0.68rem in a mono uppercase chip, and on the real distribution it
   does not survive: screenshotted at 34 of 40 rows checked out, `NOT WORKING`
   and `DISPOSED` sit in the field of `CHECKED OUT` without separating from it,
   while the same view in `halloween` — which spends a hue on the broken tier —
   separates them at a glance.
   #17512f is the phosphor family the description already claimed: same
   luminance band (7.72 / 8.22 / 7.39 against 8.07 / 8.59 / 7.72, so no louder
   on any of the three surfaces a chip is seen against), same near-row fill, no
   lamp colour spent, and the outline follows for free because it is
   color-mix'd from currentColor. What changes is that "present, not lit" now
   reads as green rather than as a fourth grey — which is the distinction this
   tier was always documented to be making. Dark ink #9dbfa9 -> #74cf98 for the
   same reason and with the same restraint (9.09 vs 8.55 on its own fill).
   The four greys were raised by the T2 sweep as one of two "theme taste calls"
   (backlog), and the maintainer asked for it on 2026-09-14. The LOUD version of
   that fix — giving checked_out a lamp colour of its own — was rejected by this
   file's own argument, which the real-distribution screenshot confirmed: 87% of
   the register carries this status, so a lamp here is a wall of colour on the
   most-used screen in the app. */
:root[data-palette="matrix"] .status-badge.status-checked_out {
  background: var(--mx-st-hold-bg);
  color: var(--mx-st-hold-ink);
}

/* CAUTION — somebody is waiting on somebody. This is where the amber goes, and
   the reason it can go here without spending the reservation is that these two
   statuses live on /procurement and nowhere else. */
:root[data-palette="matrix"] .status-badge.status-pending,
:root[data-palette="matrix"] .status-badge.status-ordered {
  background: var(--mx-st-warn-bg);
  color: var(--mx-st-warn-ink);
}

/* BAD — out of service, or refused. */
:root[data-palette="matrix"] .status-badge.status-in_maintenance,
:root[data-palette="matrix"] .status-badge.status-rejected {
  background: var(--mx-st-bad-bg);
  color: var(--mx-st-bad-ink);
}

/* ALARM — the one status that means an asset walked out of the building. It
   gets the brightest fill of any chip in the theme (its fill measures 1.93:1
   against the row, against 1.2–1.5:1 for everything else), because being
   louder than the bad tier is the whole job. */
:root[data-palette="matrix"] .status-badge.status-lost {
  background: var(--mx-st-alarm-bg);
  color: var(--mx-st-alarm-ink);
}

/* INERT — dark lamps, in three descending steps, mirroring the base's own
   grey ladder (not_working brightest, then disposed/cancelled, then donated).
   Neutral grey rather than a dimmed green on purpose: no phosphor here is
   precisely what these states mean. */
:root[data-palette="matrix"] .status-badge.status-not_working {
  background: var(--mx-st-dead-bg);
  color: var(--mx-st-dead-ink);
}

:root[data-palette="matrix"] .status-badge.status-disposed,
:root[data-palette="matrix"] .status-badge.status-cancelled {
  background: var(--mx-st-closed-bg);
  color: var(--mx-st-closed-ink);
}

:root[data-palette="matrix"] .status-badge.status-donated {
  background: var(--mx-st-gone-bg);
  color: var(--mx-st-gone-ink);
}

/* THE BANNERS. `[role="alert"]` has no companion class to raise specificity
   with — it is on bare <p>/<div> elements as well as on the error flash — so
   the two-selector form does the job the `.status-badge` qualifier does above:
   the first selector wins in explicit light (where the base's dark rules do not
   apply at all), the second matches whenever dark is or may be in effect and
   reaches (0,4,0), which beats both of the base's (0,3,0) dark rules. The
   values come from the same tokens either way, so this costs a selector and
   duplicates no colour. */
:root[data-palette="matrix"] [role="alert"],
:root[data-palette="matrix"]:not([data-theme="light"]) [role="alert"] {
  background: var(--mx-st-bad-bg);
  color: var(--mx-st-bad-ink);
  border-color: color-mix(in srgb, var(--mx-st-bad-ink) 45%, transparent);
}

:root[data-palette="matrix"] .flash-success,
:root[data-palette="matrix"]:not([data-theme="light"]) .flash-success {
  background: var(--mx-st-good-bg);
  color: var(--mx-st-good-ink);
  border-color: color-mix(in srgb, var(--mx-st-good-ink) 45%, transparent);
}

/* Filter chips are a SELECTION state, not a status, so the accent is the right
   colour for the active one — unlike the chips above, "active" here means
   "this is the filter you are looking through", which is exactly what the
   theme's structural colour is for.
   `:not(.filter-chip-alert)` is load-bearing: styles.css gives an alert chip
   its own salmon active state at (0,2,0), and a bare `.filter-chip-active`
   under this palette is (0,3,0) — it would win and quietly repaint "Overdue 7"
   in the brand colour, which is the exact thing this theme's status work is
   about not doing. */
:root[data-palette="matrix"] .filter-chip {
  border-radius: 0;
  font-family: var(--mx-mono);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
}

:root[data-palette="matrix"] .filter-chip:hover,
:root[data-palette="matrix"] .filter-chip:focus-visible {
  border-color: var(--mx-phosphor);
}

:root[data-palette="matrix"] .filter-chip-active:not(.filter-chip-alert) {
  background: var(--mx-phosphor);
  border-color: var(--mx-phosphor);
  color: var(--mx-on-accent);
}

:root[data-palette="matrix"] .filter-chip-active:not(.filter-chip-alert) .filter-chip-count {
  color: var(--mx-on-accent);
}

/* "Overdue N" is the alarm tier, not the bad tier — it is the same fact as a
   lost asset in the making, and the view only applies this class when the
   count is non-zero. It is an OUTLINE chip on the page ground rather than a
   fill, so it takes the alarm ink at 10.31:1 (dark) / 6.90:1 (light) on the
   ground, with a mid-red edge. `.filter-chip` in the selector is the same
   specificity trick as `.status-badge`: it clears the base's (0,3,0) dark
   rule. */
:root[data-palette="matrix"] .filter-chip.filter-chip-alert {
  color: var(--mx-st-alarm-ink);
  border-color: var(--mx-st-alarm-edge);
}

:root[data-palette="matrix"] .filter-chip.filter-chip-alert.filter-chip-active {
  background: var(--mx-st-alarm-fill);
  border-color: var(--mx-st-alarm-fill);
  color: var(--mx-st-alarm-fill-ink);
}

/* ===========================================================================
   COMPONENTS
   =========================================================================== */

/* ---- The dashboard's attention row ------------------------------------
   Filled blocks become OUTLINED INSTRUMENT PANELS, which is the single change
   that makes this page read as a console rather than as a dashboard with a
   different palette — and it is a change of form, not of meaning: the three
   hues stay exactly where views/dashboard.ejs puts them (salmon = a deadline
   has passed, amber = one is coming, teal = in hand), they simply become the
   panel's ink and outline instead of its fill. Each is measured above against
   the page ground the tile is now transparent over.

   --mx-tile-ink is set by the colour class and read by the tile and all four
   of its children, so the fallback covers .stat-tile-quiet (which carries no
   colour class) without a second rule. */
:root[data-palette="matrix"] .stat-tile {
  background: transparent;
  border: 1px solid var(--mx-tile-ink, var(--mx-rule-firm));
  border-radius: 0;
  color: var(--mx-tile-ink, var(--mx-ink));
  transition: background-color 0.12s ease, border-color 0.12s ease;
}

:root[data-palette="matrix"] .stat-tile-teal   { --mx-tile-ink: var(--mx-tile-teal); }
:root[data-palette="matrix"] .stat-tile-amber  { --mx-tile-ink: var(--mx-tile-amber); }
:root[data-palette="matrix"] .stat-tile-salmon { --mx-tile-ink: var(--mx-tile-salmon); }
:root[data-palette="matrix"] .stat-tile-grey   { --mx-tile-ink: var(--mx-tile-grey); }

/* These three are `color: #fff` in styles.css, written for a filled tile. */
:root[data-palette="matrix"] .stat-tile-label,
:root[data-palette="matrix"] .stat-tile-value,
:root[data-palette="matrix"] .stat-tile-caveat,
:root[data-palette="matrix"] .stat-tile-caveat a {
  color: var(--mx-tile-ink, var(--mx-ink));
}

/* A quiet tile carries no colour, so its supporting text goes back to muted
   ink — the hierarchy the base intends — rather than to full body ink. */
:root[data-palette="matrix"] .stat-tile-quiet .stat-tile-label,
:root[data-palette="matrix"] .stat-tile-quiet .stat-tile-caveat,
:root[data-palette="matrix"] .stat-tile-quiet .stat-tile-caveat a {
  color: var(--mx-ink-2);
}

/* The big figure is the theme's other display moment: VT323 at size, blooming
   in ITS OWN colour rather than the theme's, via --mx-glow-mix. A fixed green
   halo would put a green ring around the amber "Renewals due 15" and the salmon
   overdue count, which is a light leaking from a lamp that is not there. */
:root[data-palette="matrix"] .stat-tile-value {
  font-family: var(--mx-display);
  font-size: 2.4rem;
  line-height: 1.05;
  text-shadow: 0 0 20px color-mix(in srgb, currentColor var(--mx-glow-mix), transparent);
  letter-spacing: 0;
}

/* An instrument panel does not float, so the base's lift-and-shadow hover is
   replaced by the panel lighting up: a wash of its own ink plus a full-strength
   outline. Specificity is deliberate — .stat-tile:hover in styles.css is
   (0,2,0) and sets `color: #fff`, so this has to out-specify it rather than
   rely on source order, which is the equal-specificity trap this stylesheet
   documents having been bitten by twice. */
:root[data-palette="matrix"] .stat-tile:hover {
  transform: none;
  box-shadow: none;
  color: var(--mx-tile-ink, var(--mx-ink));
  background: color-mix(in srgb, var(--mx-tile-ink, var(--mx-phosphor)) 12%, transparent);
}

/* Chart bars square up AND follow the lamp row, which is a deliberate addition
   to the set themes/README.md names. The reason is a contradiction that only
   appears once checked_out has been re-tiered: the dashboard prints "Checked
   Out 345" as a chip and as a bar, side by side on the same screen, and leaving
   the bar on the base palette put a bright amber bar next to a deliberately
   unlit chip for the same fact. A status that means two different things on one
   page is worse than either colour on its own.

   `.chart-bar` in the selector is the same specificity trick as `.status-badge`
   above (base states these three times, twice at (0,3,0)); the partial that
   emits them, views/partials/barChart.ejs, always writes it. */
:root[data-palette="matrix"] .chart-track,
:root[data-palette="matrix"] .chart-bar {
  border-radius: 0;
}

:root[data-palette="matrix"] .chart-bar.chart-bar-available      { background: var(--mx-bar-good); }
:root[data-palette="matrix"] .chart-bar.chart-bar-checked_out    { background: var(--mx-bar-hold); }
:root[data-palette="matrix"] .chart-bar.chart-bar-in_maintenance { background: var(--mx-bar-bad); }
:root[data-palette="matrix"] .chart-bar.chart-bar-lost           { background: var(--mx-bar-alarm); }
:root[data-palette="matrix"] .chart-bar.chart-bar-not_working    { background: var(--mx-bar-dead); }
:root[data-palette="matrix"] .chart-bar.chart-bar-disposed       { background: var(--mx-bar-closed); }
:root[data-palette="matrix"] .chart-bar.chart-bar-donated        { background: var(--mx-bar-gone); }

/* The one bar that is NOT a status: views/dashboard.ejs uses this class for the
   assets-by-category and assets-by-condition charts, which are counts of a
   taxonomy rather than states of an asset. The accent is the right colour for
   exactly that reason — README constraint 2 forbids a STATUS wearing the
   accent, and this is the case that shows why the rule is about status and not
   about the colour. */
:root[data-palette="matrix"] .chart-bar.chart-bar-category { background: var(--mx-phosphor); }

/* Square everything that Pico or styles.css rounds on its own. The token above
   covers components that read --pico-border-radius; these set their own. */
:root[data-palette="matrix"] .table-wrap,
:root[data-palette="matrix"] .stat-tiles-primary .stat-tile,
:root[data-palette="matrix"] [role="alert"],
:root[data-palette="matrix"] .flash-success,
:root[data-palette="matrix"] .audit-flag,
:root[data-palette="matrix"] .toast {
  border-radius: 0;
}

/* THE SEARCH BOX IS THE ONE CONTROL --pico-border-radius CANNOT SQUARE, and it
   took a measurement to find out why. Pico sets the token to 5rem on both
   `[role=search]` and `[type=search]`, so overriding the token there looks like
   the fix — and it is not, because the pill's left corners come from a rule
   with a LITERAL in it: `[role=search] > :first-child { border-top-left-radius:
   5rem; border-bottom-left-radius: 5rem }`. Measured with the token override in
   place: the custom property read `0` and the computed radius was still
   `80px 0px 0px 80px`, i.e. the left half of a lozenge sitting in a row of
   square controls. Zeroing the corners on the children is what actually works.

   BOTH SELECTORS ARE NEEDED. A search input inside a `[role=search]` group (the
   topbar box, the /assets toolbar) takes its corners from that literal rule; a
   bare `input[type=search]` with no group around it (the reports index's
   "Filter reports…", a report's "Filter rows…") takes them from the token, and
   was still a lozenge after the group rule was fixed.

   THREE SELECTORS, and the third one is backlog U2 (fixed 2026-09-03): THE
   GROUP ITSELF had to be squared too, not only its children. Pico's
   `[role=search] { --pico-border-radius: 5rem }` is a LOCAL redeclaration on
   that element, so it beats this theme's `:root` override of the same token no
   matter what — and `[role=group], [role=search] { border-radius:
   var(--pico-border-radius) }` then rounds the form. On its own that is
   invisible; it became visible when the topbar plate rule above gave
   `.topbar-search` a 1px border, because the border traces the 5rem radius.
   Measured on /checkouts in matrix dark: the form computed
   `border-radius: 80px` with a visible `rgb(27, 58, 44)` edge while the input
   and the Find button inside it computed `0px` with their own 1px edges — a
   rounded frame and a square frame drawn over each other, which is exactly
   what the maintainer's report described. In matrix LIGHT the plate's border
   colour is transparent, so the defect was dark-only.

   It has to be squared here rather than by dropping the plate's border: the
   plate is what keeps the topbar band from putting raw rain behind the search
   (see the plate rule above), and the square edge is this theme's intent. The
   same rule also squares the focus ring Pico paints as a rounded box-shadow on
   `[role=search]`, which had the same shape on every search group in the app. */
:root[data-palette="matrix"] [role="search"],
:root[data-palette="matrix"] [role="search"] > *,
:root[data-palette="matrix"] [type="search"] {
  border-radius: 0;
}

/* A card is a panel with a hairline, not a floating object — the app uses
   <article> for the check-out/repair blocks on an asset page and for the
   import panels, and with square corners and no shadow they need an edge to
   still read as separate regions. */
:root[data-palette="matrix"] article {
  border: 1px solid var(--mx-rule);
  box-shadow: none;
}

/* Bloom on hover rather than at rest, and that is a performance decision, not
   a taste one: /assets is 394 rows of linked asset names, and an always-on
   text-shadow on every one of them repaints the whole table on scroll. This
   costs nothing until the pointer is on a link. */
:root[data-palette="matrix"] main a:hover {
  text-shadow: 0 0 12px var(--mx-glow);
}

/* ===========================================================================
   THE NARROW BREAKPOINT
   ===========================================================================

   768px is this app's one narrow breakpoint (the sidebar becomes a drawer, the
   topbar search hides, and the column chooser drops its non-narrow columns).
   Nothing in this theme changes the desktop LAYOUT beyond insetting the content
   plate, so the drawer, the backdrop and the chooser behave exactly as they do
   in every other theme. Three things do have to be said here. */
@media (max-width: 768px) {
  /* The drawer slides OVER the content, so a washed rail would show the table
     through it. It goes solid at this width — the only place --sidebar-bg is
     used for .sidebar itself. */
  :root[data-palette="matrix"] .sidebar {
    background: var(--sidebar-bg);
  }

  /* The hamburger appears in the topbar at this width and it is three bare
     `.sidebar-toggle-bar` spans with no plate to sit on, so the strip itself
     has to go solid here. The band is the wrong effect on a phone anyway: at
     390px the topbar is mostly chrome. */
  :root[data-palette="matrix"] .topbar {
    background: var(--mx-band);
  }

  /* The plate stops floating: at phone width the 0.75rem band of rain around
     it is most of the horizontal room the content has. */
  :root[data-palette="matrix"] main.content {
    margin: 0;
  }

  /* A full-bleed rule under a 1.5rem uppercase banner is the heaviest thing on
     a phone screen. */
  :root[data-palette="matrix"] main h2 {
    font-size: 1.2rem;
    letter-spacing: 0.05em;
  }

  :root[data-palette="matrix"] h1 {
    font-size: 2rem;
  }
}

/* ===========================================================================
   THE COLOURED TILES KEEP THE BASE'S WHITE TEXT.

   `.stat-tile-teal/amber/salmon/grey` carry FIXED backgrounds from the app's
   own stylesheet, chosen so full white clears AA on each (6.03 / 6.05 / 6.99 /
   7.21:1), and they do not follow the theme. This file recolours the tile
   figure and its label to phosphor for the tiles that DO follow the theme, and
   that repainted these four as well — measured at 1.36:1 for BOTH the number
   and the label on the amber ground in light. Green on amber is invisible.
   Found 2026-09-05 while measuring the same defect in cyberpunk; this one had
   not been reported.
   =========================================================================== */
:root[data-palette="matrix"] .stat-tile-teal .stat-tile-value,
:root[data-palette="matrix"] .stat-tile-amber .stat-tile-value,
:root[data-palette="matrix"] .stat-tile-salmon .stat-tile-value,
:root[data-palette="matrix"] .stat-tile-grey .stat-tile-value,
:root[data-palette="matrix"] .stat-tile-teal .stat-tile-label,
:root[data-palette="matrix"] .stat-tile-amber .stat-tile-label,
:root[data-palette="matrix"] .stat-tile-salmon .stat-tile-label,
:root[data-palette="matrix"] .stat-tile-grey .stat-tile-label {
  color: #ffffff;
  text-shadow: none;
}
