/* =========================================================================
 * theme-modern.css - ALTERNATE COLOR THEME ("modern"), light + dark.
 *
 * PULSAR: copied verbatim from ElitaPro (ElitaPro.Web/wwwroot/css/theme-modern.css) on
 *   2026-10-22 [developer-directed: "colours similar to ElitaPro"]. The notes below are
 *   ElitaPro's own history; the tokens are the same names in Pulsar's app.css.
 *
 * WHAT THIS FILE IS
 *   A pure TOKEN layer. It re-points the design tokens declared in app.css §01
 *   (:root) and §22 (body.app-dark) and NOTHING else - no component rule, no
 *   layout, no selector the app does not already own. Every visual change below
 *   therefore reaches the whole app through the tokens the components already read.
 *
 * HOW TO ROLL BACK
 *   Delete (or comment out) the single <link> to this file in wwwroot/index.html.
 *   app.css is untouched, so the previous theme returns exactly as it was.
 *
 * HOW TO ADD A THIRD THEME
 *   Copy this file, retune, and swap the <link>. Never edit app.css's own tokens.
 *
 * WHY IT WINS THE CASCADE
 *   Same specificity as app.css's own blocks (:root / body.app-dark / .app-sidebar),
 *   loaded AFTER app.css, so it wins on source order. Nothing here needs !important.
 *
 * ⚠️ ALIASES ARE NOT REPEATED. A custom property whose value is var(--other)
 *   resolves against the COMPUTED value on the element it is declared on. The
 *   aliases (--app-color-form-box-*, --app-color-filter-*, --app-field-label-*)
 *   are already declared by app.css on BOTH :root and body.app-dark, so they pick
 *   up the base tokens re-pointed here on their own. Re-declaring them would be
 *   dead weight - but a NEW base token added below must still be declared in both
 *   blocks if dark needs its own value.
 *
 * THE PALETTE, IN ONE LINE
 *   One brand hue in both themes - indigo (#3e63dd light / #849dff dark) - over
 *   near-neutral cool greys, so the color in the app is carried by MEANING (brand,
 *   good, bad, warn) and never by the furniture. Softer corners (10px surfaces,
 *   6px controls), quieter borders, and a layered two-step shadow in place of the
 *   old single blur, so a card reads as lifted rather than as outlined.
 * ========================================================================= */

/* -------------------------------------------------------------------------
 * LIGHT
 * ---------------------------------------------------------------------- */
:root {
    /* --- Ground & surfaces ------------------------------------------------
       NOTHING IS PURE WHITE ANY MORE [developer-directed 2026-09-06]. The card, the
       sidebar and the topbar were all #ffffff on a near-white ground, so light mode
       read as one sheet of white; the ask was "a little bit less white/bright". The
       whole ladder came down a notch and the RELATIONSHIPS are untouched, which is
       what keeps this a retune rather than a redesign: a box (an input, a dropdown
       panel, the preferences card) sits a step ABOVE the card, the card a clear step
       above the ground, and every subtle / hover / active fill a step below the card.
       The card-over-ground step actually GREW (3.5% -> ~4.5%), so a card reads as
       lifted more plainly than it did while it was white.
       ⚠️ --app-surface-raised is the highest surface in the app and is deliberately
       the one that stays near-white: it paints the boxes you TYPE IN. Taking it
       further down either merges it into the card under it or collides with the
       read-out / disabled shades below it (see "Locked boxes"), so a genuinely grey
       field means INVERTING the ladder (a sunken field) — a different design, not a
       darker value. */
    --app-bg: #eaedf4;
    --app-card: #f6f7fb;
    --app-surface-raised: #fbfcfe;
    --app-surface-subtle: #f1f3f8;
    --app-surface-accent: #ecf0fb;
    --app-surface-hover: #edeff5;
    --app-surface-active: #e2e6ef;
    --app-surface-inset: #eef0f6;

    /* --- Borders ----------------------------------------------------------
       Quieter than before: the rule between two rows is a hint, not a frame.
       --app-border draws every table/calendar/card rule, --app-border-muted the
       few places that must read as a real division.
       ⚠️ Both came down one step WITH the surfaces [2026-09-06]: the same hairline
       reads fainter on a grey card than it did on a white one, so holding the old
       values would have quietly softened every table rule at the same time. */
    --app-border: #e2e5ee;
    --app-border-muted: #ccd1dd;

    /* --- Control borders — THE SWITCH --------------------------------------
       ON. One token, one line: every control that opts in (the toolbar buttons §07, the
       row-action icon buttons §11, the dialog/form buttons §11, the status pill §16) draws a
       1px edge in ITS OWN colour. Set it to 0% and every edge disappears with NOTHING else
       moving — the width is permanent and each control's padding is pre-compensated for it, so
       the two states are the same size to a quarter of a pixel. Anything between is a softened
       edge (55% is a quiet outline) and needs no new colour token.

       ⚠️ IT IS A PERCENTAGE, NOT A COLOUR, and that is not a style choice — a colour here is
       silently broken. The full reasoning is on the token in app.css §01; the short version is
       that a var() inside a custom property resolves where it is DECLARED, so a :root switch
       naming --control-accent computes to invalid and every edge falls back to currentColor.

       WHY EACH CONTROL'S OWN COLOUR AND NOT A GREY: at full strength it is the only option
       that PASSES WCAG 1.4.11 (a control's boundary needs 3:1). Against this theme's own card,
       light / dark: its own accent 5.21 / 6.92, var(--app-muted) 5.31 / 6.86, but
       var(--app-border-muted) only 1.45 / 1.69 and var(--app-border) 1.20 / 1.29.
       (With NO border the boundary is the soft fill alone: 1.15 / 1.31.)

       ⚠️ THE STRENGTH BELOW IS A LOOKS-VS-CONTRAST TRADE, and it is set to the quieter end
       [developer-directed]. The edge is the accent at <strength> alpha over the card, so the
       ratio falls with it. Measured, light / dark:
                              100%        85%        70%        55%
         toolbar (indigo)   5.21/6.92  3.94/5.35  2.97/4.04  2.29/2.99
         add + submit       3.17/9.19  2.65/6.94  2.23/5.09  1.86/3.62
         row delete (red)   3.91/4.51  3.26/3.57  2.64/2.78  2.13/2.14
         row edit (amber)   2.15/8.21  1.91/6.22  1.72/4.61  1.52/3.31
         cancel (muted)     5.31/6.86  3.88/5.28  2.92/4.01  2.22/2.96
       So: 100% is the only value that clears 3:1 broadly; 85% still clears it for indigo, red
       and muted in light and for everything in dark; 55% clears it almost nowhere. Raise this
       to 100% if the edge must carry the control's boundary rather than merely decorate it.
       ⚠️ Amber-on-white (row edit) fails 3:1 at EVERY strength — that is the colour, not the
       token; without a border its boundary was the fill alone at ~1.1, so any edge is a gain.

       A FIXED colour is still available where one control must differ: declare
       --control-border-color on that part (the status pill does — see app.css §16). */
    --app-control-border-strength: 55%;

    /* --- Text ------------------------------------------------------------- */
    --app-text: #171a23;
    --app-muted: #656b7d;
    --app-text-disabled: #7b8296;
    --app-icon-muted: #99a0b0;
    --app-placeholder: #a2a8b8;
    /* The ink a SOLID control wears (the picked form tab, the brand/green/danger buttons,
       the sidebar's active row, the tooltip). Softened off pure white with the surfaces
       [developer-directed 2026-09-06] — it is the one "white" in this pass that is TEXT and
       not a surface, so it is the one that costs contrast: on the brand fill 5.21 -> 4.85
       (still past the 4.5 floor), on danger 3.91 -> 3.65 and on green 3.17 -> 2.96 (both
       were already under it before this change — that is the fill, not this token).
       ⚠️ LIGHT ONLY: this token has exactly one declaration in app.css (:root) and none on
       body.app-dark, so a value set here would otherwise reach the dark theme too. The dark
       block below restates #ffffff for that reason — delete that line only if dark's solid
       controls should soften as well. */
    --app-on-primary: #f5f7fb;
    /* Declared by no block in app.css, yet read by .report-filter-note__label and
       .page-title-fact__label - both of which are muted labels that until now simply
       inherited their parent's ink. Defining it here is the whole fix; drop this one
       line if the two labels should go back to inheriting. */
    --app-text-muted: var(--app-muted);

    /* --- Brand ------------------------------------------------------------ */
    --app-primary: #3e63dd;
    --app-primary-soft: #eaeffd;
    /* The brand at a CONTROL’s depth: the fill a solid control wears (the status toggles’
       saturation), as against --app-primary, which is the brand as TEXT/edge and is lifted in
       dark so it can be read on a dark card. In light the two are the same colour — it already
       carries white ink; only dark needs its own value, or a solid pill paints washed-out.
       ⚠️ Restated in every dark block: a token composed from another token bakes the value it
       saw where it was DECLARED, so inheriting this one into dark would carry the LIGHT brand. */
    --app-primary-solid: var(--app-primary);
    /* The resting EDGE of a control in the solid-brand family: --app-primary-solid at 29%
       [developer-directed 2026-09-05]. A neutral --app-border-muted edge made an unpicked form tab
       read as unrelated furniture beside the picked one; a brand edge at low alpha says "same
       control, not chosen" without becoming a second signal.
       ⚠️ RESTATED in every dark block, exactly like the token it is mixed from: a composed token
       substitutes its var()s on the element that DECLARES it, so a :root-only declaration would
       carry the LIGHT brand under body.app-dark. */
    --app-primary-edge: color-mix(in srgb, var(--app-primary-solid) 29%, transparent);
    --app-primary-tint-subtle: rgba(62, 99, 221, 0.06);
    --app-primary-tint: rgba(62, 99, 221, 0.13);
    --app-primary-tint-strong: rgba(62, 99, 221, 0.18);

    /* --- Sidebar — WHITE IN LIGHT MODE [developer-directed] ----------------
       ⚠️ This REVERSES a standing design decision. app.css §01 states that the sidebar
       is dark in BOTH themes, and that is why its row states were "declared once here
       and never reassigned on body.app-dark". They now ARE per-theme, so every token in
       this group is restated in the dark block below — leave one out and dark inherits
       a light value (a dark-alpha hover is invisible on navy).

       Three consequences of the inversion, each handled rather than inherited:
       - The GLASS values flip sign. Hover/divider/active-bg were white-alpha lifts on
         navy; on white they must be dark-alpha, or they paint nothing at all.
       - The two ink roles SWAP direction but keep their meaning. app.css's rule is that
         a page (child) wears the "lifted" tone and its group header is brand-TINTED
         rather than merely dimmer, "because a header darker than its own children reads
         as disabled". On navy, lifted = near-white; on white, lifted = near-black. So
         the child goes to the strongest ink and the parent to a desaturated indigo.
       - The current page's label can no longer be mint. #4ddbb4 on white measures ~1.7:1
         — illegible — so it becomes the brand indigo. That overrides the developer's own
         mint-in-light choice [2026-08-13], which existed *because* the surface was navy;
         it is a consequence of going white, not a second opinion about the colour. */
    /* ⚠️ = --app-card's value, deliberately, and for the same reason --app-sidebar-divider
       is = --app-border: the menu, the topbar and the page cards are one continuous light
       surface in this theme, so they must move together. They came off pure white with
       everything else [developer-directed 2026-09-06]. */
    --app-sidebar: #f6f7fb;
    --app-sidebar-deep: #eef0f6;
    --app-sidebar-text: #2b3141;
    --app-sidebar-muted: #7b8296;
    --app-sidebar-text-parent: #6a78c8;
    --app-sidebar-text-child: #1f2430;
    /* ⚠️ = --app-border, deliberately, not a colour of its own. The brand row's border-bottom and
       the topbar's border-bottom are the two halves of ONE rule across the top of the app, and
       the topbar's half is --app-border. While the menu was navy the two halves sat on different
       surfaces so nobody could compare them; on a white menu they are end to end on the same
       white, and two near-but-unequal greys read as a tone step exactly at the seam. */
    --app-sidebar-divider: var(--app-border);
    --app-sidebar-hover-bg: rgba(23, 26, 35, 0.055);
    --app-sidebar-hover-subtle: rgba(23, 26, 35, 0.03);
    --app-sidebar-active-bg: rgba(62, 99, 221, 0.10);
    --app-sidebar-active-color: #3e63dd;
    --app-sidebar-accent: var(--app-primary);
    --app-sidebar-accent-text: var(--app-on-primary);
    /* The two the bright menu exists to prove. Both were hand-written CSS rules in this file
       until the menu contract landed; they are values now, which is the whole point. */
    --app-sidebar-edge: 1px solid var(--app-border);
    /* One owner for that edge: the drawer border above draws it full height. Restated here
       even though app.css already defaults it, per contract rule 2. */
    --app-sidebar-brand-edge: none;
    --app-sidebar-logo-filter: none;
    /* ⚠️ --app-sidebar-tree-line / -path are deliberately NOT restated, in EITHER block. They are
       declared in app.css's contract as a color-mix of --app-sidebar-accent against
       --app-sidebar, and substitution uses the computed value of those two ON THE SAME ELEMENT.
       app.css declares them once on :root and once on body.app-dark — the very two elements this
       file re-points --app-sidebar on — so each copy re-resolves against the surface set here by
       itself. This is the contract paying for itself: a skin changes the surface and the tree
       follows. Restate them only to give the tree a colour of its own. */

    /* --- Topbar ----------------------------------------------------------- */
    --app-topbar-bg: var(--app-card);
    --app-topbar-muted: #6a7185;
    --app-topbar-muted-dark: #8b93a8;

    /* --- Links ------------------------------------------------------------ */
    --app-link: #3355cc;
    --app-link-hover: #2544b8;
    --app-link-tint: rgba(51, 85, 204, 0.10);

    /* --- Semantics: bad --------------------------------------------------- */
    --app-danger: #e5484d;
    --app-danger-soft: #fdecec;
    --app-danger-hover-bg: rgba(229, 72, 77, 0.18);
    --app-danger-tint-subtle: rgba(229, 72, 77, 0.07);
    --app-danger-tint: rgba(229, 72, 77, 0.12);

    /* --- Semantics: warn -------------------------------------------------- */
    --app-warning: #f59e0b;
    --app-warning-soft: #fef4e2;
    --app-warning-tint: rgba(245, 158, 11, 0.16);
    /* ⚠️ Was #fffcf5 — a near-white warm wash, which on the grey card would have been the
       BRIGHTEST thing on the page [2026-09-06]. Same amber, one step down. */
    --app-banner-warning-bg: #fdf7ea;
    --app-amber-tint: rgba(180, 118, 20, 0.08);
    --app-amber-tint-strong: rgba(180, 118, 20, 0.34);

    /* --- Semantics: good -------------------------------------------------- */
    --app-green: #10a56d;
    --app-green-soft: rgba(16, 165, 109, 0.14);
    --app-green-tint-toolbar: rgba(16, 165, 109, 0.20);
    --status-on: #16a06a;
    --status-off: #e0484d;

    /* --- Semantics: info -------------------------------------------------- */
    --app-info: #0ea5e9;
    --app-info-soft: rgba(14, 165, 233, 0.10);
    --app-alert-info: #2f6fd0;
    --app-alert-info-soft: rgba(47, 111, 208, 0.10);

    /* --- Accents ---------------------------------------------------------- */
    --app-mint: #0eb39c;
    --app-mint-soft: rgba(14, 179, 156, 0.14);
    --app-mint-muted: rgba(14, 179, 156, 0.85);
    --app-indigo: #4f4bd8;
    --app-indigo-soft: rgba(79, 75, 216, 0.12);
    --app-neutral-soft: rgba(140, 148, 165, 0.20);
    --app-brand-gradient-start: #6366f1;
    --app-brand-gradient-end: #22d3ee;

    /* --- Export menu: the three formats keep their own recognisable colors,
       with PDF and print pulled onto the palette's red and brand blue. */
    --app-export-excel: #107c41;
    --app-export-pdf: #e5484d;
    --app-export-print: #3e63dd;

    /* --- Tables -----------------------------------------------------------
       The head is a whisper off the card and its ink is a strong grey, not black:
       a column NAME is a label, and 21 of them in near-black shout over the data.
       The zebra stripe goes NEUTRAL - a blue stripe under a blue hover made every
       other row look half-hovered. */
    /* ⚠️ A whisper BELOW the card, never above it. Its old #f7f8fc was a step down from a
       white card; against the grey card it would have been a step UP, i.e. a head lighter
       than the rows under it [2026-09-06]. */
    --app-table-head: #eef1f8;
    /* --app-table-column-name is NOT re-pointed here: the column-name ink is the one token
       the developer edits directly, so it lives once per theme in app.css §01/§22 and this
       file leaves it alone [developer-directed 2026-09-02]. Its values there are the ones
       this theme used to set. */
    --app-table-row-hover: rgba(62, 99, 221, 0.055);
    --app-table-row-stripe: rgba(23, 26, 35, 0.022);
    --app-table-sort-active: #3e63dd;
    --app-scrollbar-thumb: rgba(100, 108, 130, 0.32);
    --app-scrollbar-thumb-hover: rgba(100, 108, 130, 0.55);
    /* The DARK thumb is declared at :root on purpose - see app.css §01: the page scrollbar is
       painted from <html>, which cannot see the body.app-dark palette. The dark block below
       only aliases the generic token to it. A solid navy step off the dark surfaces, not the
       white wash it replaced [developer-directed 2026-09-02]. */
    --app-scrollbar-dark-thumb: #272e4c;
    --app-scrollbar-dark-thumb-hover: #384268;

    /* --- Corners ----------------------------------------------------------
       Surfaces soften (8 -> 10px), controls firm up on their own smaller step
       (4 -> 6px). Two radii, one relationship: a box inside a card is always the
       tighter of the two, so nothing looks nested by accident. */
    --app-radius: 8.75px;
    --app-radius-form-box: 5.25px;
    --app-radius-button: 5.25px;
    --app-radius-icon-button: 5.25px;

    /* --- Elevation --------------------------------------------------------
       A single wide blur reads as haze; two stacked shadows - a 1px contact
       shadow plus a wide soft one - read as a real lift. Same ink for both
       shadow families so nothing on the page casts a differently-tinted shadow. */
    --app-shadow-ink: 17, 24, 45;
    --app-shadow-ink-alt: 17, 24, 45;
    --app-shadow: 0 1px 2px rgba(var(--app-shadow-ink), 0.04), 0 5.25px 17.5px rgba(var(--app-shadow-ink), 0.06);
    --app-shadow-popover: 0 2px 5.25px rgba(var(--app-shadow-ink), 0.06), 0 12.25px 29.75px rgba(var(--app-shadow-ink), 0.11);
    --app-shadow-dropdown: 0 2px 5.25px rgba(var(--app-shadow-ink), 0.05), 0 8.75px 24.5px rgba(var(--app-shadow-ink), 0.12);
    --app-shadow-menu: 0 3.5px 8.75px rgba(var(--app-shadow-ink), 0.06), 0 15.75px 42px rgba(var(--app-shadow-ink), 0.16);
    --app-shadow-dialog: 0 5.25px 12.25px rgba(var(--app-shadow-ink), 0.08), 0 24.5px 56px rgba(var(--app-shadow-ink), 0.22);
    --app-shadow-login-card: 0 2px 5.25px rgba(var(--app-shadow-ink), 0.05), 0 14px 38.5px rgba(var(--app-shadow-ink), 0.12);
    --app-shadow-topbar-user: 0 5.25px 15.75px rgba(62, 99, 221, 0.12);
    --app-shadow-brand: 0 8.75px 19.25px rgba(62, 99, 221, 0.28);
    --app-shadow-avatar-hover: 0 5.25px 14px rgba(62, 99, 221, 0.28);

    /* --- Overlays --------------------------------------------------------- */
    --app-overlay-scrim: rgba(17, 24, 45, 0.48);
    --app-overlay-dialog: rgba(17, 24, 45, 0.52);
    --app-overlay-drawer: rgba(17, 24, 45, 0.30);

    /* --- Locked boxes -----------------------------------------------------
       Two distinct sunken shades kept distinct: --disabled = a rule shut this box
       for now, --readout = you can never type here.
       ⚠️ Both stepped down with the card [2026-09-06]: the read-out shade was #f6f7f9,
       which against the new #f6f7fb card would have been the SAME colour — a box you can
       never type into, painted exactly like the card behind it. They are also what the two
       calendars paint a neighbouring month's day with, so they must stay readable as "out
       of reach" on a grey card, not merely on a white one. */
    --app-input-disabled-bg: #e7eaf1;
    --app-input-disabled-text: #98a0b0;
    --app-input-readout-bg: #eff1f7;
    --app-disabled-bg: rgba(23, 26, 35, 0.10);
    --app-disabled-text: rgba(23, 26, 35, 0.36);

    /* --- Form controls ----------------------------------------------------
       ⚠️ The focus state is a real 3px RING — the old value spent its ring on
       zero alpha and carried a 12px glow instead, which on a dense grid read as a
       smudge around the cell rather than as "the caret is here". The ring's EDGE
       fades out softly [developer-directed 2026-09-06]: the 2.625px SPREAD is still
       the ring, and a 5.25px blur lets its outer edge melt into the card instead of
       stopping on a hard line. The blur stays this small on purpose — a wider one
       is the smudge the ring replaced. The ring animates in and out through the
       shared 0.2s box-shadow transition in app.css §19. */
    --form-control-border: #d5d9e4;
    --form-control-focus-border: #3e63dd;
    --form-control-focus-shadow: 0 0 5.25px 2.625px rgba(62, 99, 221, 0.16);
    --form-control-invalid-border: #e5484d;
    --form-control-invalid-shadow: 0 0 0 2.625px rgba(229, 72, 77, 0.15);
    --app-shadow-invalid-ring: 0 0 0 2.625px rgba(229, 72, 77, 0.14);

    /* --- Tooltip: a slate chip rather than the framework's flat grey. */
    --app-tooltip-bg: #2a2f3d;
    --app-tooltip-radius: 5.25px;

    /* --- Icon glyphs baked into data: URIs. These carry their color INSIDE the
       SVG, so they cannot follow a token and must be restated per theme. */
    --dropdown-caret-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M0 0l6 8 6-8z' fill='%2399a0b0'/%3E%3C/svg%3E");
    --dropdown-clear-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M1.5 1.5l9 9M10.5 1.5l-9 9' stroke='%2399a0b0' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
    --dropdown-clear-hover-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M1.5 1.5l9 9M10.5 1.5l-9 9' stroke='%23e5484d' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
}

/* -------------------------------------------------------------------------
 * MudBlazor's own palette — LIGHT.
 *
 * ⚠️ `:root:root`, and it is NOT a hack. MudThemeProvider INJECTS a <style> block at
 *   runtime holding `:root { --mud-palette-*: … }`, built from AppTheme.cs (whose
 *   Primary is still the OLD #4895ef / #7c83ff). That block is unlayered and lands
 *   AFTER this file in the document, so a plain `:root` here ties on specificity and
 *   LOSES on source order. Measured in the running app with this theme loaded and a
 *   plain `:root`: --mud-palette-primary-rgb still read back «72,149,239», i.e. the
 *   old brand, which is what Mud tints its own ripples and hovers with. Doubling the
 *   pseudo-class takes specificity to (0,2,0) and wins wherever the injected block
 *   sits — the identical remedy app.css already applies to --mud-default-borderradius.
 *   The cascade-layer trick that tames the rest of MudBlazor does not help: the
 *   injected style is not in the layer.
 *
 * ⚠️ EVERY token set here MUST be restated in the dark block below. `:root:root`
 *   matches in BOTH themes (html never carries `app-dark`), so anything left
 *   unmirrored would leak its light value into dark.
 *
 * ⚠️ The rgb triplets are Mud's own rgba() source and are NOT derived from
 *   --mud-palette-primary, so they must be restated by hand whenever the brand moves.
 * ---------------------------------------------------------------------- */
:root:root {
    --mud-palette-primary: var(--app-primary);
    --mud-palette-primary-rgb: 62, 99, 221;
    --mud-palette-primary-text: var(--app-on-primary);
    --mud-palette-secondary: var(--app-primary);
    --mud-palette-secondary-rgb: 62, 99, 221;
    --mud-palette-secondary-text: var(--app-on-primary);

    /* Mud's own SURFACES. app.css §22 says it out loud - "Dialogs (Mud surface comes
       from the dark palette; just align the frame)" - so a dialog, a menu and a popover
       are painted by MudBlazor, not by --app-card. Left alone, the dark dialog kept
       AppTheme.cs's old #151d31 while the page behind it had moved (measured). Mapping
       Mud's palette onto the app tokens is what makes the theme reach those surfaces.
       In LIGHT nearly every line below re-states Mud's own default, so this block is
       close to a no-op here and does the real work in the dark block. */
    --mud-palette-surface: var(--app-card);
    --mud-palette-background: var(--app-card);
    --mud-palette-background-gray: var(--app-bg);
    --mud-palette-drawer-background: var(--app-sidebar);
    --mud-palette-drawer-text: var(--app-sidebar-text);
    --mud-palette-drawer-icon: var(--app-sidebar-text);
    --mud-palette-appbar-background: var(--app-topbar-bg);
    --mud-palette-appbar-text: var(--app-text);
    --mud-palette-text-primary: var(--app-text);
    --mud-palette-text-secondary: var(--app-muted);
    --mud-palette-text-disabled: var(--app-text-disabled);
    --mud-palette-action-default: var(--app-icon-muted);
    --mud-palette-action-default-hover: var(--app-surface-hover);
    --mud-palette-action-disabled: var(--app-disabled-text);
    --mud-palette-action-disabled-background: var(--app-disabled-bg);
    --mud-palette-divider: var(--app-border);
    --mud-palette-divider-light: var(--app-border);
    --mud-palette-lines-default: var(--app-border);
    --mud-palette-lines-inputs: var(--form-control-border);
    --mud-palette-table-lines: var(--app-border);
    --mud-palette-table-striped: var(--app-table-row-stripe);
    --mud-palette-table-hover: var(--app-table-row-hover);
    --mud-palette-gray-default: var(--app-muted);
    --mud-palette-gray-light: var(--app-surface-hover);
    --mud-palette-gray-lighter: var(--app-surface-subtle);
    --mud-palette-error: var(--app-danger);
    --mud-palette-error-rgb: 229, 72, 77;
    --mud-palette-warning: var(--app-warning);
    --mud-palette-success: var(--app-green);
    --mud-palette-info: var(--app-info);
}

/* The sidebar re-declares Mud's primary triplet locally (app.css §04) so the drawer's
   own ripples/selection use the brand. It is a literal there, so it is restated here
   for the same reason the triplets above are. A declaration on a DESCENDANT wins for
   that subtree whatever the root's specificity, so this needs no doubling. */
.app-sidebar {
    --mud-palette-primary-rgb: 62, 99, 221;
}

/* -------------------------------------------------------------------------
 * DARK
 * Only the tokens whose dark value must DIFFER are listed. Everything else -
 * radii, the invalid ring, the gradient, the export colors - inherits from the
 * :root block above, exactly as app.css's own dark block relies on.
 * ---------------------------------------------------------------------- */
body.app-dark {
    /* The dark half of the :root:root block above — see the three ⚠️ there. This
       selector is (0,2,0) AND later in the file, so it beats both the injected
       `:root` and this file's own light Mud palette. */
    --mud-palette-primary: var(--app-primary);
    --mud-palette-primary-rgb: 132, 157, 255;
    --mud-palette-primary-text: #0b1020;
    --mud-palette-secondary: var(--app-primary);
    --mud-palette-secondary-rgb: 132, 157, 255;
    --mud-palette-secondary-text: #0b1020;
    /* ⚠️ Every surface line of the :root:root block restated, or its LIGHT value would
       leak into dark - which is exactly how the dialog kept the old navy. These var()s
       resolve on BODY, so they pick up the dark tokens declared right below. */
    --mud-palette-surface: var(--app-card);
    --mud-palette-background: var(--app-bg);
    --mud-palette-background-gray: var(--app-bg);
    --mud-palette-drawer-background: var(--app-sidebar);
    --mud-palette-drawer-text: var(--app-sidebar-text);
    --mud-palette-drawer-icon: var(--app-sidebar-text);
    --mud-palette-appbar-background: var(--app-topbar-bg);
    --mud-palette-appbar-text: var(--app-text);
    --mud-palette-text-primary: var(--app-text);
    --mud-palette-text-secondary: var(--app-muted);
    --mud-palette-text-disabled: var(--app-text-disabled);
    --mud-palette-action-default: var(--app-icon-muted);
    --mud-palette-action-default-hover: var(--app-surface-hover);
    --mud-palette-action-disabled: var(--app-disabled-text);
    --mud-palette-action-disabled-background: var(--app-disabled-bg);
    --mud-palette-divider: var(--app-border);
    --mud-palette-divider-light: var(--app-border);
    --mud-palette-lines-default: var(--app-border);
    --mud-palette-lines-inputs: var(--form-control-border);
    --mud-palette-table-lines: var(--app-border);
    --mud-palette-table-striped: var(--app-table-row-stripe);
    --mud-palette-table-hover: var(--app-table-row-hover);
    --mud-palette-gray-default: var(--app-muted);
    --mud-palette-gray-light: var(--app-surface-raised);
    --mud-palette-gray-lighter: var(--app-surface-inset);
    --mud-palette-black: #05070f;
    --mud-palette-error: var(--app-danger);
    --mud-palette-error-rgb: 255, 99, 105;
    --mud-palette-warning: var(--app-warning);
    --mud-palette-success: var(--app-green);
    --mud-palette-info: var(--app-info);

    /* Navy-indigo near-black, three steps: page -> card -> raised box. */
    --app-bg: #0c0f1a;
    --app-card: #141828;
    --app-surface-raised: #1b2036;
    --app-surface-subtle: rgba(132, 157, 255, 0.07);
    --app-surface-accent: rgba(132, 157, 255, 0.10);
    --app-surface-hover: rgba(255, 255, 255, 0.045);
    --app-surface-active: rgba(132, 157, 255, 0.13);
    --app-surface-inset: #10131f;

    --app-border: rgba(255, 255, 255, 0.09);
    --app-border-muted: rgba(255, 255, 255, 0.17);

    /* --- Control borders ---------------------------------------------------
       NOTHING IS NEEDED HERE. The switch is a percentage, so it inherits cleanly from the
       light block and each control mixes its OWN accent — which is already per-theme. Declare
       something here only to give DARK a different strength (a heavier outline on the dark
       surfaces, say). */

    --app-text: #e8ebf4;
    --app-muted: #98a1bd;
    /* Re-resolve against the DARK --app-muted: the :root alias substituted the LIGHT one when
       it was declared, so without this line .report-filter-note__label and
       .page-title-fact__label wear #656b7d on the dark card [audit 2026-09-02]. */
    --app-text-muted: var(--app-muted);
    --app-text-disabled: rgba(232, 235, 244, 0.32);
    --app-icon-muted: #858ea9;
    --app-placeholder: #858ea9;
    /* ⚠️ Restated PURE WHITE on purpose: the softening above is LIGHT-mode only
       [developer-directed 2026-09-06], and --app-on-primary has no body.app-dark
       declaration anywhere in the estate — so without this line the light value would
       reach the dark theme's solid controls through :root. */
    --app-on-primary: #ffffff;

    --app-primary: #849dff;
    --app-primary-soft: rgba(132, 157, 255, 0.16);
    --app-primary-tint-subtle: rgba(132, 157, 255, 0.07);
    --app-primary-tint: rgba(132, 157, 255, 0.16);
    --app-primary-tint-strong: rgba(132, 157, 255, 0.28);
    /* = this theme’s own LIGHT brand blue: the same hue at full strength, white ink at 5.2:1. */
    --app-primary-solid: #3e63dd;
    --app-primary-edge: color-mix(in srgb, var(--app-primary-solid) 29%, transparent);

    /* The sidebar stays DARK here — see the ⚠️ in the light block. Every one of its
       tokens must be restated, the four glass values included, because light now
       declares dark-alpha lifts that would paint nothing on this navy. */
    --app-sidebar: #0e1120;
    --app-sidebar-deep: #080a13;
    --app-sidebar-text: #a2abc6;
    --app-sidebar-muted: #7d86a2;
    --app-sidebar-text-parent: #a9b6ff;
    --app-sidebar-text-child: #d9dff2;
    /* = --app-border, for the reason the light block spells out: this draws the brand row's
       border-bottom AND (via app.css §22) the topbar's, and it now meets the drawer edge above,
       which is also --app-border. Three segments of one corner, one token, no tone step. */
    --app-sidebar-divider: var(--app-border);
    --app-sidebar-hover-bg: rgba(255, 255, 255, 0.08);
    --app-sidebar-hover-subtle: rgba(255, 255, 255, 0.035);
    --app-sidebar-active-bg: rgba(255, 255, 255, 0.08);
    /* Violet-in-dark is the developer's own [2026-08-13] and is kept; only light's
       half of that pair had to change (it could not stay mint on white). */
    --app-sidebar-active-color: #b48cfa;
    --app-sidebar-accent: var(--app-primary);
    --app-sidebar-accent-text: var(--app-on-primary);
    /* ⚠️ Dark gets the edge TOO [developer-reported]. It shipped at 0 on the reasoning that a
       dark menu separates itself from the page - which is false here: the menu is #0e1120 and
       the page #0c0f1a, a two-unit difference, so the boundary simply was not drawn. It is also
       needed higher up for a second reason: --app-topbar-bg IS --app-sidebar in this theme, so
       without it the topbar and the brand row are one unbroken band with nothing marking where
       the menu begins. Same token as light, so the two themes now say it the same way. */
    --app-sidebar-edge: 1px solid var(--app-border);
    /* Restated per contract rule 2. `none` here too: the shadow this token replaced painted the
       VIEWPORT's outer edge, never the topbar-to-menu seam - see the note in app.css. */
    --app-sidebar-brand-edge: none;
    --app-sidebar-logo-filter: invert(1) hue-rotate(180deg);

    --app-topbar-bg: var(--app-sidebar);
    --app-topbar-muted: #aab4d2;
    --app-topbar-muted-dark: #858ea9;

    --app-link: #7fb6ff;
    --app-link-hover: #a8cfff;
    --app-link-tint: rgba(127, 182, 255, 0.13);

    --app-danger: #ff6369;
    --app-danger-hover-bg: rgba(255, 99, 105, 0.22);
    --app-danger-tint-subtle: rgba(255, 99, 105, 0.08);
    --app-danger-tint: rgba(255, 99, 105, 0.14);
    --app-danger-tint-strong: var(--app-danger-hover-bg);
    --app-danger-soft: var(--app-danger-tint-strong);

    --app-warning: #ffb224;
    --app-warning-tint: rgba(255, 178, 36, 0.18);
    --app-warning-soft: var(--app-warning-tint);
    --app-banner-warning-bg: rgba(255, 178, 36, 0.07);
    --app-amber-tint: rgba(255, 178, 36, 0.10);
    --app-amber-tint-strong: rgba(255, 178, 36, 0.32);

    --app-green: #37d399;
    --app-green-tint-toolbar: rgba(55, 211, 153, 0.22);
    --app-green-soft: var(--app-green-tint-toolbar);

    --app-info: #4cc3fb;
    --app-info-soft: rgba(76, 195, 251, 0.12);
    --app-alert-info: #7abdff;
    --app-alert-info-soft: rgba(122, 189, 255, 0.16);

    --app-mint: #2ee0c2;
    --app-mint-soft: rgba(46, 224, 194, 0.18);
    --app-mint-muted: rgba(46, 224, 194, 0.85);
    --app-indigo: #7c7bff;
    --app-indigo-soft: rgba(124, 123, 255, 0.26);
    --app-neutral-soft: rgba(152, 161, 189, 0.20);

    --app-table-head: #10131f;
    /* --app-table-column-name is NOT re-pointed here: the column-name ink is the one token
       the developer edits directly, so it lives once per theme in app.css §01/§22 and this
       file leaves it alone [developer-directed 2026-09-02]. Its values there are the ones
       this theme used to set. */
    --app-table-row-hover: rgba(132, 157, 255, 0.09);
    --app-table-row-stripe: rgba(255, 255, 255, 0.028);
    --app-table-sort-active: #849dff;
    /* Scrollbars: the value is up in this file's :root (a dark-only token, because <html>
       cannot read this block); these lines only point the generic tokens at it. */
    --app-scrollbar-thumb: var(--app-scrollbar-dark-thumb);
    --app-scrollbar-thumb-hover: var(--app-scrollbar-dark-thumb-hover);
    --app-scrollbar-track: transparent;

    /* Dark casts pure black, and keeps the same two-step recipe as light. */
    --app-shadow-ink: 0, 0, 0;
    --app-shadow-ink-alt: 0, 0, 0;
    /* The two toggle shadows (tracks + knobs — their only consumers) are restated per dark
       block rather than riding the ink: the composed tokens bake the light ink at :root
       (resolves-where-declared), and black needs a higher alpha than navy-on-light to read
       on the dark tracks. Mirrors app.css §22 [developer-reported 2026-09-02]. */
    --app-shadow-inset-deep: var(--app-shadow-inset-deep-geometry) rgba(0, 0, 0, 0.5);
    --app-shadow-inset-sunken: inset 3px 3px 7px rgba(0, 0, 0, 0.62);
    --app-shadow-elevation-sm: 0 2px 4.375px rgba(0, 0, 0, 0.55);
    --app-shadow: 0 1px 2px rgba(0, 0, 0, 0.30), 0 7px 22.75px rgba(0, 0, 0, 0.40);
    --app-shadow-popover: 0 2px 5.25px rgba(0, 0, 0, 0.32), 0 12.25px 29.75px rgba(0, 0, 0, 0.42);
    --app-shadow-dropdown: 0 2px 5.25px rgba(0, 0, 0, 0.34), 0 8.75px 24.5px rgba(0, 0, 0, 0.48);
    --app-shadow-menu: 0 3.5px 8.75px rgba(0, 0, 0, 0.36), 0 15.75px 42px rgba(0, 0, 0, 0.55);
    --app-shadow-dialog: 0 5.25px 12.25px rgba(0, 0, 0, 0.40), 0 24.5px 56px rgba(0, 0, 0, 0.60);
    --app-shadow-login-card: 0 2px 5.25px rgba(0, 0, 0, 0.34), 0 14px 38.5px rgba(0, 0, 0, 0.50);
    --app-shadow-topbar-user: 0 5.25px 15.75px rgba(0, 0, 0, 0.38);

    --app-input-disabled-bg: #0f1322;
    --app-input-disabled-text: rgba(232, 235, 244, 0.42);
    --app-input-readout-bg: var(--app-input-disabled-bg);
    --app-disabled-bg: rgba(255, 255, 255, 0.07);
    --app-disabled-text: rgba(232, 235, 244, 0.36);

    --form-control-border: rgba(255, 255, 255, 0.16);
    --form-control-focus-border: #849dff;
    /* The same soft-edged ring as light (see the light block); the alpha is a hair
       higher because the blur dilutes the peak and the dark card swallows a faint one. */
    --form-control-focus-shadow: 0 0 5.25px 2.625px rgba(132, 157, 255, 0.24);

    --dropdown-caret-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M0 0l6 8 6-8z' fill='%23858ea9'/%3E%3C/svg%3E");
    --dropdown-clear-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M1.5 1.5l9 9M10.5 1.5l-9 9' stroke='%23858ea9' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
    --dropdown-clear-hover-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M1.5 1.5l9 9M10.5 1.5l-9 9' stroke='%23ff6369' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
}

body.app-dark .app-sidebar {
    --mud-palette-primary-rgb: 132, 157, 255;
}

/* This file is now 100% TOKENS again. It briefly carried two hand-written rules - one
   cancelling the logo invert on a bright menu, one giving the drawer its edge back -
   because app.css hard-coded both. They are --app-sidebar-logo-filter and
   --app-sidebar-edge in the menu contract now, set as values in the two blocks above. */
