/* ============================================================================
   DESIGN TOKENS
   The single source of truth for colour, surface, border, text and control
   values. Dark is the default and lives in :root; light redefines the same
   names under html[data-theme="light"] so the ~860 var(--…) references across
   the other sheets flip automatically.

   Only custom properties belong here. A rule that styles a selector belongs
   in the sheet for that component, consuming a token from this file.

   Custom properties resolve at computed-value time, so load order relative to
   the other sheets does not affect correctness — this file is linked first
   because it reads as the vocabulary the rest of the CSS speaks.
   ============================================================================ */

:root {
    /* Dark chrome base (body, topbar, sidebar): hue ~220 keeps the UI's
       blue depth but stops short of the vendor's violet-leaning #171920,
       which reads as a purple tint. */
    --chrome-base: #15181e;

    /* Accent palette. The RGB triplet is the source of truth and the hex is
       derived from it, because CSS can split neither a hex into components
       nor a colour back out of rgb(). Roughly a third of the app consumes an
       accent as rgba(var(--*-rgb), alpha) for tints rather than as a solid,
       so a hand-maintained second copy of each colour would drift silently:
       solid fills would move and every tinted surface would stay behind.
       Retuning a hue means editing one triplet. */
    --color-blue-rgb: 74, 158, 255;
    --color-blue-hover-rgb: 107, 178, 255;
    --color-green-rgb: 52, 211, 153;
    --color-orange-rgb: 251, 146, 60;
    --color-red-rgb: 239, 68, 68;
    --color-purple-rgb: 168, 85, 247;
    --color-gray-rgb: 148, 163, 184;
    --color-yellow-rgb: 234, 179, 8;
    --color-teal-rgb: 20, 184, 166;
    --color-pink-rgb: 244, 114, 182;
    --color-rose-rgb: 251, 113, 133;
    --color-lime-rgb: 163, 230, 53;
    --color-indigo-rgb: 129, 140, 248;
    --color-cyan-rgb: 34, 211, 238;
    --color-brown-rgb: 200, 162, 124;

    /* Fill rung. An accent has two jobs that pull in opposite directions: it
       has to be dark enough to carry as text on the page, and bright enough
       to read as a live control when it IS the surface. On dark those are the
       same colour and every -fill triplet just forwards the text one. On
       light they are not: an accent deep enough for 4.5:1 body text is a
       muddy slab as a button, which is what made the light theme's buttons
       read bland. Only the six semantic hues get the rung, because only they
       are ever painted solid (the .btn-* variants). */
    --color-blue-fill-rgb: var(--color-blue-rgb);
    --color-green-fill-rgb: var(--color-green-rgb);
    --color-orange-fill-rgb: var(--color-orange-rgb);
    --color-red-fill-rgb: var(--color-red-rgb);
    --color-purple-fill-rgb: var(--color-purple-rgb);
    --color-gray-fill-rgb: var(--color-gray-rgb);

    --color-blue: rgb(var(--color-blue-rgb));
    --color-blue-hover: rgb(var(--color-blue-hover-rgb));
    --color-green: rgb(var(--color-green-rgb));
    --color-orange: rgb(var(--color-orange-rgb));
    --color-red: rgb(var(--color-red-rgb));
    --color-purple: rgb(var(--color-purple-rgb));
    --color-gray: rgb(var(--color-gray-rgb));
    --color-yellow: rgb(var(--color-yellow-rgb));
    --color-teal: rgb(var(--color-teal-rgb));
    --color-pink: rgb(var(--color-pink-rgb));
    --color-rose: rgb(var(--color-rose-rgb));
    --color-lime: rgb(var(--color-lime-rgb));
    --color-indigo: rgb(var(--color-indigo-rgb));
    --color-cyan: rgb(var(--color-cyan-rgb));
    --color-brown: rgb(var(--color-brown-rgb));

    /* Pre-alpha'd accents, kept as their own names because they are used as
       finished colours rather than as a base to tint further. */
    --color-gold: rgba(var(--color-orange-rgb), 0.6);
    --color-deep-blue: rgba(var(--color-blue-rgb), 0.4);
    --surface-1: rgba(255, 255, 255, 0.02);
    --surface-2: rgba(255, 255, 255, 0.05);
    --surface-3: rgba(255, 255, 255, 0.08);

    /* --- Surface system -------------------------------------------------
       Flat, opaque fills assigned by what a component IS, never by how deep
       it sits. Depth is not an input, so nothing accumulates and nesting has
       no limit: a card inside a card inside a card is still `--bg-surface`,
       separated by its border. See docs/frontend/adr/.

       The container roles descend, because a real screen nests
       card > section > well anyway:

           --bg-surface   a card or panel
           --bg-mid       a section inside one          (one step down)
           --bg-inset     a well: code, log, editor     (two steps down)

       `--bg-raise` is the one upward move, for grouping components and for
       repeated peers. "Lift" means one step toward the sheet rather than one
       fixed colour, so rows on a card take --bg-raise while rows on a section
       return to --bg-surface. Both read as lighter in either theme.

       --bg-mid is derived so every theme gets a correct middle rung free. */
    --bg-canvas: var(--chrome-base);
    --bg-surface: #1a1d24;
    --bg-raise: #1f232b;
    --bg-inset: #0d1117;
    --bg-overlay: #232730;
    --bg-mid: color-mix(in srgb, var(--bg-surface) 45%, var(--bg-inset));

    --border-subtle: rgba(255, 255, 255, 0.05);
    --border-normal: rgba(255, 255, 255, 0.1);
    --border-emphasis: rgba(255, 255, 255, 0.15);

    /* Scrollbars inside panels (select2 results, the destination picker).
       Tinted from the surface rather than a fixed grey, so the bar reads
       as part of the panel; both themes must define these or the bar
       disappears into a same-colored surface. */
    --scrollbar-thumb: rgba(255, 255, 255, 0.15);
    --scrollbar-thumb-hover: rgba(255, 255, 255, 0.25);
    --scrollbar-track: rgba(255, 255, 255, 0.03);

    /* Floating menus/panels (dropdowns, select2, destination picker,
       popovers): one shared elevated surface so every popup reads
       the same. */
    --menu-bg: #0d1117;
    --menu-border: #4a505a;
    --menu-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
    /* For popups anchored at the bottom of the viewport, where --menu-shadow's
       16px downward throw lands off-screen and leaves the top and side edges
       undrawn. Low offset, mostly ambient: a tight contact shadow to cut the
       edge, then two soft throws that surround the panel instead of pooling
       under it. */
    --menu-shadow-lifted:
        0 1px 2px rgba(0, 0, 0, 0.45),
        0 4px 10px rgba(0, 0, 0, 0.28),
        0 12px 24px rgba(0, 0, 0, 0.22);
    --pop-bg: var(--menu-bg);
    /* Selected row in a floating menu — the theme's own blue sunk into the
       panel surface. --color-blue at full strength is a bright tint meant
       for text and edges; as a fill it reads baby blue, so it's mixed back
       toward --menu-bg until it can carry white label text. */
    --menu-item-active-bg: color-mix(in srgb, var(--color-blue) 70%, var(--menu-bg));
    --menu-item-active-bg-hover: color-mix(in srgb, var(--color-blue) 85%, var(--menu-bg));

    /*--text-primary: rgba(255, 255, 255, 0.95);*/
    /*--text-secondary: rgba(255, 255, 255, 0.8);*/
    /*--text-tertiary: rgba(255, 255, 255, 0.6);*/
    /*--text-muted: rgba(255, 255, 255, 0.4);*/

    --text-primary-rgb: 240, 240, 245;
    --text-primary: rgb(var(--text-primary-rgb));
    --text-secondary: #c9cbd4;
    --text-tertiary: #9497a8;
    --text-tertiary-rgb: 148, 151, 168;
    --text-muted: #6b6e7e;
    --text-muted-rgb: 107, 110, 126;

    /* alerts: an even low-opacity tint over a solid base reads as a
       designed surface, not a flat translucent wash over the page */
    --alert-base: var(--chrome-base);
    --alert-tint: 0.1;
    /* notifications: one neutral surface for every variant — colour rides on
       the left accent bar, the icon puck and the title, never the slab */
    --notify-surface: rgba(26, 30, 38, 0.97);

    /* Fields have no fill of their own — the border alone defines them, so
       a field sits on whatever surface it lands on and reads the same on a
       card, in a modal or inside a zebra row. */
    --input-bg: transparent;
    /* Same as resting — the border change alone signals focus */
    --input-bg-focus: var(--input-bg);
    /* The one field state that carries a fill, so an inert control reads
       as filled-in where a live one reads as an empty box awaiting input.
       Alpha rather than a hex, for the same reason the border is: it has
       to sit on cards, modals and zebra rows alike, and a fixed dark fill
       would punch a hole in any surface lighter than the page. Sits
       between --surface-1 and --surface-2 on that scale. */
    --input-bg-disabled: rgba(255, 255, 255, 0.04);
    /* The single source of truth for every input surface's border:
       inputs, input groups, selects, select2, uppy dropzones, the TTS
       audio widget. Change --input-border and the whole app follows;
       nothing else may hardcode an input border color. Color stays
       reserved for content cues (validation, selection fills), so the
       hover/focus states raise the same white's opacity rather than
       shifting hue. Alpha, not a hex: the field's fill is transparent, so
       the edge has to composite over whatever surface it lands on — card,
       modal, zebra row — the way the light theme's border already does. */
    --input-border: rgba(255, 255, 255, 0.14);
    --input-border-hover: color-mix(in srgb, var(--input-border) 80%, #fff);
    /* Focus pushes the edge well past hover, so the two states are told
       apart by how solid the line is, not by a shift in color. */
    --input-border-focus: color-mix(in srgb, var(--input-border) 62%, #fff);
    /* The same white edge at half strength: still visible, but lower
       contrast than a live field's, so the control reads as backed off
       rather than outlined. Mixing toward the fill would darken it into a
       line louder than a live field's, so the alpha drops instead. */
    --input-border-disabled: rgba(255, 255, 255, 0.07);
    /* No inner shading: a field is an outline, not a recess, so nothing
       should suggest depth inside its edge. Kept as a no-op shadow rather
       than dropped, because --input-focus-ring composes it into a shadow
       list where `none` would be invalid — and because it stays the one
       place to reintroduce inner shading app-wide. */
    --input-inset: 0 0 #0000;
    /* A faint halo just outside the border. The border brightening carries
       the state change; the halo widens its reach so focus is findable at
       a glance in a dense form. */
    --input-focus-ring: var(--input-inset), 0 0 0 3px rgba(255, 255, 255, 0.05);

    /* The one field that brings its own fill: a code editor, painted by its
       syntax theme (monokai). The fill is the theme's and fixed on both pages,
       so the edge around it has to be fixed too — --input-border composites
       over whatever it lands on, and on the light theme that's a dark ink the
       editor's dark fill would swallow. Fixed white, at the input ramp's
       strengths: the theme says "code", the edge still says "field". */
    --code-well-border: rgba(255, 255, 255, 0.14);
    --code-well-border-hover: rgba(255, 255, 255, 0.24);
    --code-well-border-focus: rgba(255, 255, 255, 0.42);
    --code-well-focus-ring: 0 0 0 3px rgba(255, 255, 255, 0.05);

    /* Select option backgrounds */
    /* A select's dropdown is its input continued, not a floating menu —
       it keeps the field's border so the two read as one control. But a
       transparent field has no fill to hand down, so the panel takes the
       surface the field is showing through — the card — and the two match
       again. Its border and drop shadow do the separating, the same way an
       outline rather than a fill is what defines the field. Not --menu-bg:
       that belongs to panels floating free of any control, and it reads as
       a different object bolted to the field. select2's dropdown, the
       native option list, the polymorphic destination panel, the pickers
       and the webrtc menus all resolve here. */
    --select-dropdown-bg: var(--chrome-base);
    --select-option-bg: var(--select-dropdown-bg);
    /* Hover and selection lift off --select-dropdown-bg by the same step
       they always have. */
    --select-option-bg-hover: #1d2330;
    --select-option-bg-active: #232a3a;
    --select-option-border-active: rgba(var(--bs-primary-rgb), 0.45);
    --select-arrow-color: rgba(255, 255, 255, 0.45);

    /* Bootstrap-style aliases. Both halves point at the accent palette above,
       so a semantic role and its tints can never disagree. These are aliases
       only — change a hue at --color-*-rgb, not here. */
    --bs-primary: var(--color-blue);
    --bs-primary-rgb: var(--color-blue-rgb);
    --bs-success: var(--color-green);
    --bs-success-rgb: var(--color-green-rgb);
    --bs-warning: var(--color-orange);
    --bs-warning-rgb: var(--color-orange-rgb);
    --bs-danger: var(--color-red);
    --bs-danger-rgb: var(--color-red-rgb);
    --bs-info: var(--color-purple);
    --bs-info-rgb: var(--color-purple-rgb);
    --bs-secondary: var(--color-gray);
    --bs-secondary-rgb: var(--color-gray-rgb);

    /* The solid-fill twin of each semantic alias. buttons.css reads these for
       the slab and keeps --bs-*-rgb for the label, so a variant's fill and
       its text can sit on different rungs without the two ever disagreeing
       about the hue. */
    --bs-primary-fill-rgb: var(--color-blue-fill-rgb);
    --bs-success-fill-rgb: var(--color-green-fill-rgb);
    --bs-warning-fill-rgb: var(--color-orange-fill-rgb);
    --bs-danger-fill-rgb: var(--color-red-fill-rgb);
    --bs-info-fill-rgb: var(--color-purple-fill-rgb);
    --bs-secondary-fill-rgb: var(--color-gray-fill-rgb);

    /* `light` and `dark` are the two neutral variants, and neither survives
       being a fixed colour: a near-black `dark` vanishes on this theme's page
       and a near-white `light` vanishes on the other one. They are emphasis
       levels, so they read off the text ramp and flip with it — `light` is the
       high-emphasis neutral, `dark` the low-emphasis one. Each solid is the
       full-strength form of its own -rgb twin, so rgba(var(--bs-light-rgb), a)
       and var(--bs-light) are finally the same colour at different alphas. */
    --bs-light: var(--text-primary);
    --bs-light-rgb: var(--text-primary-rgb);
    /* `dark` is a well, not a weight on the text ramp: one dark surface that
       every *-dark variant paints solid — badge, button, alert — so they
       are all the same colour. Dark on both themes, or the word means nothing.
       A shade off the old near-black, which read as a hole. */
    --bs-dark: #101319;
    --bs-dark-rgb: 16, 19, 25;
    --bs-dark-fg: #d7dae2;
    --bs-dark-fg-rgb: 215, 218, 226;
    /* The well's edge is darker than the well, not lighter: it reads as a
       recess on the dark page and as the well's silhouette on the light one.
       A pale edge instead glowed on dark and vanished on light. */
    --bs-dark-border: rgba(0, 0, 0, 0.4);
    /* The paintable form of the well, for any *-dark variant that has to
       read as an object sitting ON the page (badge, button, alert, .bg-dark)
       rather than the page itself. Raw --bs-dark lands below the card and its
       black edge blurs into it, so the pair reads as a hole punched in the
       sheet. The fill lifts one rung above the card and a step toward the
       well's own text colour, and the edge swaps for a hairline of it. */
    --bs-dark-well: color-mix(in srgb, #191d25 90%, var(--bs-dark-fg));
    --btn-dark-border: rgba(var(--bs-dark-fg-rgb), 0.14);
    /* The pale counterpart of the well, for the same reason: --bs-light is the
       theme's ink ramp (a wash colour, and near-black on the light theme), so
       it cannot double as a pale surface. Fixed on both themes, like the well. */
    --bs-light-well: #e9edf3;
    --bs-light-well-fg: #10141a;
    /* An outlined `dark` has no well to sit on. Drawing it in the well's own
       colours put it within a hair of .btn-outline-light, so the outlined pair
       separates by weight instead: light takes the strong end of the neutral
       ramp, dark the quiet end. Both flip with the theme, so the two stay
       distinguishable on either page. */
    --bs-dark-edge: var(--text-tertiary);
    --bs-dark-edge-rgb: var(--text-tertiary-rgb);

    --surface-0: linear-gradient(
            135deg,
            rgba(255, 255, 255, 0.03) 0%,
            rgba(255, 255, 255, 0.01) 100%
    );

    /* --- Page chrome ---------------------------------------------------
       --chrome-surface is the solid colour of the frame (top bar, logo
       block, sidebar) and is what a cut-out ring on that frame has to
       match. --chrome-panel is the same surface as a *background* value:
       dark layers the gradient over it, light is flat. --page-bg is the
       canvas the frame sits on. */
    --page-bg: var(--chrome-base);
    --chrome-surface: var(--chrome-base);
    --chrome-panel: var(--surface-0), var(--chrome-surface);
    /* Body copy inside .main-panel. Its own rung: a touch softer than
       --text-secondary on dark, full strength on light. */
    --panel-text: rgba(169, 175, 187, 0.82);

    /* Two text rungs that do not sit on the same step in both themes.
       Dark text carries less weight per unit of lightness, so a label
       that reads as "quiet" on dark is one rung lighter than on light. */
    --text-label: var(--text-primary);
    --text-quiet: var(--text-tertiary);

    /* Text rendering. Pale type on a dark page blooms, so dark thins it
       with grayscale antialiasing; see base.css. */
    --font-smoothing: antialiased;
    --font-smoothing-moz: grayscale;

    /* --- Typography -----------------------------------------------------
       Lato ships 100/300/400/700/900 and nothing between 400 and 700, so
       every rung below names a weight the file actually contains. Ask for
       500 or 600 and the browser silently rounds to one of the real ones,
       which is how a "medium" rung ends up rendering identically to body
       copy on one element and identically to bold on the next.

       The rungs are themed for the same reason the smoothing is: a pale
       glyph on a dark page renders heavier than the identical glyph
       inverted, so light steps up to land in the same place. With no middle
       weight to step to, only --font-weight-control moves, and only where
       the extra stem is wanted (buttons, tabs) rather than everywhere.

       Tracking moves the other way. Dark keeps the wider setting so pale
       stems stay apart; light closes almost to 0, because loose tracking is
       what makes dark-on-white type read spindly. */
    --font-family-base: 'Lato', sans-serif;
    /* Extra ink on the glyph outline itself, because Lato's weight axis
       cannot supply it: 400 -> 700 widens a string by 1.9% where a normal
       family gives 6-7%, so asking for a heavier weight is a no-op at UI
       sizes. Stroking the outline thickens every stem by a real fraction of
       a pixel instead, which is the only lever that actually moves. Dark
       stays at 0 — pale type on a dark page already blooms, and stroking it
       there closes up the counters. */
    --text-stroke: 0;
    --font-weight-body: 400;
    /* Type that labels a control rather than being read as prose. */
    --font-weight-control: 400;
    --font-weight-strong: 700;
    --font-weight-heading: 700;
    --body-tracking: 0.05em;
    /* Headings previously inherited body tracking; at heading sizes that
       reads as gappy rather than airy. */
    --heading-tracking: normal;

    /* UA-drawn control chrome (date/time/number pickers and spinners). */
    --control-scheme: dark;
    /* Unchecked custom-checkbox/switch track. Bootstrap's own values. */
    --switch-track-bg: #ffffff;
    --switch-track-border: #adb5bd;

    /* Sidebar search field: same rung as every other input on dark. */
    --sidebar-search-border: var(--input-border);

    /* Keyboard-shortcut hint chip (the sidebar's Alt+S badge). */
    --kbd-bg: var(--surface-3);
    --kbd-border: var(--border-normal);
    --kbd-fg: inherit;
    --kbd-opacity: 0.35;

    /* Fieldsets take their fill from the surface system by context, so only
       the edge and shadow are named here. */
    --fieldset-border: var(--border-normal);
    --fieldset-border-hover: var(--border-emphasis);
    --fieldset-shadow: none;

    /* Cards */
    --card-border: var(--border-subtle);
    --card-shadow:
        0 2px 8px rgba(0, 0, 0, 0.2),
        inset 0 1px 0 rgba(255, 255, 255, 0.05);
    /* A card lifted off the page (hover on an interactive tile). */
    --card-hover-shadow: 0 8px 20px rgba(0, 0, 0, 0.35);
    /* Strong end of a stat tile's accent hairline. A pastel accent on a
       near-black card can run near-opaque before it shouts. */
    --stat-accent-alpha: 0.85;
    --card-light2-bg: #24252a9c;
    --card-dark-bg: linear-gradient(
        135deg,
        rgba(30, 30, 30, 0.95),
        rgba(25, 25, 25, 0.95)
    );
    --card-hover-bg: linear-gradient(
        135deg,
        rgba(255, 255, 255, 0.05) 0%,
        rgba(255, 255, 255, 0.02) 100%
    );

    /* Modals */
    --modal-bg: linear-gradient(
        135deg,
        rgba(26, 28, 34, 0.98),
        rgba(22, 24, 30, 0.98)
    );
    /* A faint brand tint on the edge, matching the swal2 popup the app's
       confirms use — the dialog and the confirm read as the same object.
       Low enough alpha that it lifts the edge off the backdrop without
       reading as a coloured frame. */
    --modal-border: rgba(var(--bs-primary-rgb), 0.2);
    /* A 1px inset top highlight: on a dark theme it's what sells the panel
       as catching light from above. Light needs none — a white dialog on a
       slate scrim already separates — so it flattens to transparent. */
    --modal-highlight: rgba(255, 255, 255, 0.07);
    /* One soft pool rather than a tight contact shadow plus a wide one: the
       dialog floats well clear of the page instead of sitting just above it. */
    --modal-shadow: 0 20px 60px rgba(0, 0, 0, 0.8);
    /* The header and footer carry no fill — one unbroken dialog surface, so
       the only thing separating them from the body is this hairline. It sits
       two steps below the outer edge, and so reads quieter than the dialog's
       own border rather than banding the dialog into three slabs. */
    --modal-bar-border: var(--border-subtle);
    --modal-backdrop-bg: rgba(0, 0, 0, 0.78);

    /* Tooltips. On dark the tip is just the shared floating surface; on
       light it inverts to a dark slate panel, because a white tip
       disappeared into the white cards and navbar. */
    --tooltip-bg: var(--pop-bg);
    --tooltip-edge: var(--border-emphasis);
    --tooltip-fg: var(--text-primary);
    --tooltip-shadow: 0 6px 20px rgba(0, 0, 0, 0.5);

    /* The page's own scrollbars (layout.css), distinct from the in-panel
       bars above. Muted thumb on a track a step lighter than the page so
       the scrollbar column reads as its own strip; disabled is the state
       while a drawer/modal scroll lock is active. */
    --page-scrollbar-thumb: rgba(255, 255, 255, 0.38);
    --page-scrollbar-thumb-hover: rgba(255, 255, 255, 0.52);
    --page-scrollbar-thumb-disabled: rgba(255, 255, 255, 0.12);
    --page-scrollbar-track: #1c212b;

    /* Label colour for the `light` badge/button variant. --bs-light
       flips to a near-transparent black on light, so it cannot double as
       a text colour there. */
    --tag-light-fg: var(--bs-light);
    /* How far a badge label lifts toward white. The raw hues read a shade
       dull as small text on this theme's tint fills; the lift keeps them
       luminous. Light theme zeroes it — there the raw hue IS the readable
       form. */
    --tag-label-lift: 22%;

    /* The .btn-* colour ladder (buttons.css composes these with the
       variant's own hue). Dark is a translucent tint that composites over
       whatever surface the button lands on, and deepens by raising alpha;
       the scrim it would otherwise darken with stays transparent. */
    --btn-fill-a: 0.15;
    --btn-fill-b: 0.08;
    --btn-fill-hover-a: 0.25;
    --btn-fill-hover-b: 0.15;
    --btn-fill-active-a: 0.35;
    --btn-fill-active-b: 0.25;
    --btn-fill-disabled-a: 0.08;
    --btn-fill-disabled-b: 0.04;
    --btn-shade-hover: transparent;
    --btn-shade-active: transparent;
    --btn-edge-a: 0.4;
    --btn-edge-hover-a: 0.6;
    --btn-edge-active-a: 0.7;
    --btn-edge-disabled-a: 0.2;
    /* Depth. A translucent tint on a dark page is already legible as a
       raised thing, so resting is flat and only the hover lift throws a
       shadow. --btn-hover-fade is the vendor's opacity dip: on dark it
       softens the tint, on light it would bleach a solid fill toward the
       white behind it, so light holds at 1 and lets the scrim do the work. */
    --btn-shadow: none;
    --btn-shadow-hover: 0 2px 8px rgba(0, 0, 0, 0.3);
    --btn-shadow-hover-sm: 0 1px 4px rgba(0, 0, 0, 0.3);
    --btn-hover-fade: 0.9;


    /* Topbar user avatar chip. */
    --avatar-fg: rgba(255, 255, 255, 0.9);
    --avatar-bg: #3a4358;
    --avatar-bg-hover: #48536d;
    --avatar-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18),
        0 0 0 1px rgba(255, 255, 255, 0.16), 0 2px 4px rgba(0, 0, 0, 0.45);
    --avatar-shadow-hover: inset 0 1px 0 rgba(255, 255, 255, 0.25),
        0 0 0 1px rgba(255, 255, 255, 0.28), 0 2px 5px rgba(0, 0, 0, 0.5);

    /* Text sitting on a .bg-* tint (avatar initials). The tint is pale on
       light, so white type would vanish. */
    --on-tint-fg: #ffffff;

    /* Slide-over drawer. */
    --drawer-bg: #1e1f25;
    --drawer-shadow: 0 0 40px rgba(0, 0, 0, 0.7);

    /* The neutral that contrasts with the page: white on dark, black on
       light. Anything built as a wash of "not the background" — skeleton
       shimmers, pill fills and edges — reads this triplet and keeps its
       own alpha ladder, so one token themes them all. */
    --ink-rgb: 255, 255, 255;
    /* The dots pill is a white lozenge in both themes; on light it needs a
       hairline ring to separate from the card under it. */
    --dots-overlay-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);

    /* Active sidebar row: an inset pill. Dark fills it with a neutral lift
       off the rail rather than a blue tint — the label and icon already
       carry the accent, and a coloured slab under coloured text muddies
       both. --nav-active-marker is the bullet on nested rows, where there
       is no icon to carry the accent. */
    --nav-active-bg: rgba(255, 255, 255, 0.08);
    --nav-active-marker: var(--nav-active-fg);

    /* Label and icon on an active row. Deeper and more saturated than the
       accent a tab chip uses: the chip is a solid slab of colour, this is
       16px of glyph stroke, and the same hex antialiases toward white at
       that weight — it reads a full step paler unless the chroma is
       pushed back in. */
    --nav-active-fg: #2b7fff;

    /* Single seam colour for every chrome boundary: header bottom,
       logo-block edge, sidebar edge. One colour, one geometry.
       0.12 white over the chrome base matches the old #30333e seam. */
    --chrome-seam: rgba(255, 255, 255, 0.12);
    /* The sidebar sliding out over the page. A 0.45 black smear reads as
       depth on a dark page and as a bruise on a light one. */
    --sidebar-slideout-shadow: 10px 0 30px rgba(0, 0, 0, 0.45);

    /* Darkening at the bottom edge of a scrollable nav, so the list reads as
       running under the edge rather than ending there. It sits over the
       chrome, which is already near-black, so it has to go most of the way to
       opaque before it registers as depth at all. */
    --scroll-shadow: rgba(0, 0, 0, 0.85);

    /* Segmented .nav-tabs: a recessed track with a raised active pill.
       Dark fills the pill with the accent; light makes it a white chip
       lifted out of a grey track, so the two need different fills. */
    --tabs-track-bg: rgba(255, 255, 255, 0.03);
    --tab-active-bg: var(--color-blue);
    --tab-active-fg: #08111f;
    --tab-active-shadow: none;

    /* The sticky table head in a scroll pane, which has to stay opaque over
       the rows passing under it. */
    --table-sticky-header-bg: #171a1f;

    /* Poly-ref chip. The chip's colours are mixed in poly-ref.css from the
       per-object hue it carries inline as --poly-ref-h; a token here can't
       do that mixing, since a custom property is substituted on the element
       that declares it and :root has no hue. So these are the theme's
       lightness/alpha rungs, and the component feeds them the hue.
       Dark lifts the type label well above mid to read on a near-black row;
       light has to sink it below mid instead. */
    --poly-ref-border-s: 30%;
    --poly-ref-border-l: 45%;
    --poly-ref-border-a: 0.45;
    --poly-ref-type-l: 78%;
    --poly-ref-type-bg-a: 0.14;
    /* Deliberately not --text-primary: the chip's name sits slightly below
       the page's brightest text so the type label reads as the accent. */
    --poly-ref-name-fg: #d9dce4;
    --poly-ref-name-fg-hover: #ffffff;

}

/* ============================================================================
   LIGHT THEME
   One neutral (slate, hue 215) carries every surface, border and text rung, so
   nothing reads as a different grey bolted on. Neutral alphas composite the
   slate ink --- rgba(15, 23, 42, a) --- rather than pure black, which turns
   muddy-warm over a cool white and is the main reason a light theme looks
   dirty.

   Depth is three flat steps, not translucency: the canvas is the darkest, the
   chrome and the cards are white, and a card is told from the canvas by its
   hairline border plus a 1px contact shadow. Elevation on light comes from the
   shadow; on dark it comes from the fill.
   ============================================================================ */
html[data-theme="light"] {
    color-scheme: light;

    /* Accent palette: the dark theme uses the pastel -400 tier (tuned for
       dark backgrounds); on white those read washed-out. Drop every accent
       to the deeper -600 tier — same hues, more ink, and every one clears
       4.5:1 on white so an accent may carry text.

       Only the triplets are restated. `--color-blue` and the --bs-* aliases
       are declared in :root as rgb(var(--color-blue-rgb)), and both blocks
       target the same <html> element, so those declarations re-resolve
       against whichever triplet wins the cascade here. */
    --color-blue-rgb: 37, 99, 235;
    --color-blue-hover-rgb: 29, 78, 216;
    --color-green-rgb: 4, 120, 87;
    --color-orange-rgb: 180, 83, 9;
    --color-red-rgb: 220, 38, 38;
    --color-purple-rgb: 147, 51, 234;
    --color-gray-rgb: 71, 85, 105;
    --color-yellow-rgb: 161, 98, 7;
    --color-teal-rgb: 13, 148, 136;
    --color-pink-rgb: 219, 39, 119;
    --color-rose-rgb: 225, 29, 72;
    --color-lime-rgb: 77, 124, 15;
    --color-indigo-rgb: 79, 70, 229;
    --color-cyan-rgb: 14, 116, 144;
    --color-brown-rgb: 133, 100, 63;

    /* Fill rung (see :root). Only the hues whose text tier is a -700 are
       restated: at that depth green is a pine and orange is a brown, and a
       button painted in one reads as a disabled control rather than a thing
       to press. The fills are the -600 tier, which holds white type at
       3.5:1 or better — past the 3:1 WCAG asks of a UI component's own
       label, where the text rung's 4.5:1 is the bar for prose. The other
       four hues are already at -600 and forward the text triplet. */
    --color-green-fill-rgb: 5, 150, 105;
    --color-orange-fill-rgb: 234, 88, 12;

    /* Slate ink. Every neutral wash and edge below is this triplet at an
       alpha, so the greys stay one family instead of drifting warm. */
    --ink-rgb: 15, 23, 42;

    /* Surfaces. --surface-0 is the card fill: flat white, because a gradient
       over white only shows up as a smear. 1/2/3 are washes that ride on top
       of whatever they land on (zebra rows, card headers, wells). */
    --surface-0: #ffffff;
    --surface-1: rgba(15, 23, 42, 0.035);
    --surface-2: rgba(15, 23, 42, 0.06);
    --surface-3: rgba(15, 23, 42, 0.095);

    /* --- Surface system -------------------------------------------------
       Same roles as the dark block; only the values differ. The ramp here is
       deliberately wider than it looks like it should be: white is a hard
       ceiling, so where dark can spend range in both directions from the
       sheet, light has to fit the whole ladder underneath it. A tight light
       ramp is exactly why nesting stops reading.

       --bg-raise still resolves lighter than what it lands on, because it is
       only ever used over --bg-mid or --bg-canvas, never over the sheet. */
    --bg-canvas: #eef1f6;
    --bg-surface: #ffffff;
    --bg-raise: #f7f9fc;
    --bg-inset: #e3e8ef;
    --bg-overlay: #ffffff;

    /* The frame is white and the canvas beside it is grey, so the tone
       step already draws the boundary; a seam on top of it reads as a
       stray rule down the page rather than an edge. Barely there. */
    --chrome-seam: rgba(15, 23, 42, 0.05);

    /* Borders. On white a hairline needs more alpha than the dark theme's
       white-on-black equivalents to read at the same strength. */
    --border-subtle: rgba(15, 23, 42, 0.07);
    --border-normal: rgba(15, 23, 42, 0.11);
    --border-emphasis: rgba(15, 23, 42, 0.18);

    /* Black-on-white counterparts: the dark theme's white-alpha bars are
       invisible on a white panel. */
    --scrollbar-thumb: rgba(15, 23, 42, 0.32);
    --scrollbar-thumb-hover: rgba(15, 23, 42, 0.46);
    --scrollbar-track: rgba(15, 23, 42, 0.06);

    /* Text. Solid slate rather than black-alpha, so type keeps its colour on
       tinted rows instead of half-dissolving into them. Monotonic (primary
       darkest → muted lightest); primary 17:1 on white, secondary 10.4:1,
       tertiary 6.1:1 — all well past AA. Muted lands at 4.5:1 rather than
       under it, because on a white page the rung gets used for real content
       (secondary lines in a table cell, field help) far more often than the
       dark theme's does, and a decorative-only grey is unreadable there. */
    --text-primary-rgb: 15, 23, 42;
    --text-primary: rgb(var(--text-primary-rgb));
    --text-secondary: #334155;
    --text-tertiary: #55637a;
    --text-tertiary-rgb: 85, 99, 122;
    --text-muted: #6b7889;
    --text-muted-rgb: 107, 120, 137;

    /* Alerts: faint tint over a near-white base */
    --alert-base: #f8fafc;
    --alert-tint: 0.08;
    --notify-surface: rgba(255, 255, 255, 0.98);

    /* Form controls. A field is white and the surfaces around it are white,
       so the border alone has to say "you can type here" — hence a firmer
       resting edge than the dark theme's. */
    --input-bg: #ffffff;
    --input-bg-focus: #ffffff;
    /* A grey fill, so an inert control reads as filled-in where a live one
       reads as an empty white box awaiting input. */
    --input-bg-disabled: #eef1f5;
    --input-border: rgba(15, 23, 42, 0.16);
    --input-border-hover: rgba(15, 23, 42, 0.30);
    --input-border-focus: rgb(var(--bs-primary-rgb));
    /* Override the dark :root rung, which brightens toward white — on the
       light disabled fill that erases the edge instead of saving it. Same
       goal, opposite direction: a firmer border against the grey fill. */
    --input-border-disabled: rgba(15, 23, 42, 0.13);
    /* Override the dark :root inset, which is tuned to carve a recess out
       of a near-black field and reads as a hard smudge on white. On light
       the field is already brighter than the card, so it only needs a
       hairline of shadow under the top edge. */
    --input-inset: inset 0 1px 2px rgba(15, 23, 42, 0.05);
    /* Override the dark :root ring — primary tint to match the blue focus
       border. Needs a much higher alpha than the dark ring: that one is
       white over near-black, this one is blue over white, where the same
       alpha all but disappears. Keeps the inset, as the dark ring does. */
    --input-focus-ring: var(--input-inset),
                        0 0 0 3px rgba(var(--bs-primary-rgb), 0.18);

    /* Selects. Hover/active are blue washes rather than greys, so the
       highlighted row reads as a selection and not as a dead strip. */
    --select-dropdown-bg: var(--input-bg);
    --select-option-bg: var(--select-dropdown-bg);
    --select-option-bg-hover: #eff6ff;
    --select-option-bg-active: #dbeafe;
    --select-option-border-active: rgba(var(--bs-primary-rgb), 0.45);
    --select-arrow-color: rgba(15, 23, 42, 0.45);

    /* Bootstrap light/dark helper tokens flip meaning on a light page */
    --bs-light: var(--text-primary);
    --bs-light-rgb: var(--text-primary-rgb);


    /* Tokens defined in per-app/extra stylesheets */
    --pop-bg: #ffffff;
    --routing-base: #f1f5f9;

    /* Floating menus/panels (dropdowns, popovers, destination
       picker) — light variants of theme-override's popup tokens. */
    --menu-bg: #ffffff;
    --menu-border: rgba(15, 23, 42, 0.12);
    /* White-on-white pages: the shadow IS the contrast. Tight dark
       contact shadow for the edge + wide soft throw for depth. */
    --menu-shadow:
        0 1px 2px rgba(15, 23, 42, 0.10),
        0 8px 16px rgba(15, 23, 42, 0.10),
        0 24px 48px rgba(15, 23, 42, 0.14);
    --menu-shadow-lifted:
        0 1px 2px rgba(15, 23, 42, 0.10),
        0 4px 10px rgba(15, 23, 42, 0.09),
        0 12px 24px rgba(15, 23, 42, 0.10);
    /* Light theme's --color-blue is already the deep end of the ramp, so the
       fill is the token itself; hover sinks toward the text slab instead of
       the white panel, which would wash it out. */
    --menu-item-active-bg: var(--color-blue);
    --menu-item-active-bg-hover: color-mix(in srgb, var(--color-blue) 85%, #000);

    /* --- Page chrome ---------------------------------------------------
       Inverted from dark: there the frame is the same slab as the page and
       the cards lift off it; here the frame is white and the content canvas
       is the grey it sits on, so the rail and the top bar read as one solid
       panel and every card is a white sheet on grey. */
    --page-bg: #e8ecf3;
    --chrome-surface: #ffffff;
    --chrome-panel: var(--chrome-surface);
    --panel-text: var(--text-primary);

    --text-label: var(--text-secondary);
    --text-quiet: var(--text-tertiary);

    /* Dark type on white needs every bit of stem it has. */
    --font-smoothing: auto;
    --font-smoothing-moz: auto;

    /* See the :root block. Control type takes Lato's next real weight so a
       button label on white carries the stem the dark theme gets free from
       its glow; body copy stays 400, because the only step available is
       700 and a page of it is shouting, not reading. */
    /* Dark-on-white has nothing to bloom into, so the stems have to be put
       there. 0.2px is the most that reads as a denser face rather than as
       a smudge — past ~0.3 the counters in `e` and `a` start to fill. */
    --text-stroke: 0.2px;
    --font-weight-control: 700;
    /* `normal`, not a small em value. Light renders type with subpixel
       antialiasing, so a fractional track (0.01em is 0.14px at 14px) lands
       every glyph on a different subpixel phase and each one picks up its
       own colour fringe — the text reads unevenly spaced and smeared. Dark
       gets away with a fractional track only because grayscale smoothing
       hides the phase. Zero hands spacing back to the font's own metrics,
       which is also the tighter setting light wanted. */
    /*--body-tracking: 0.01em;*/
    --body-tracking: normal;

    --control-scheme: light;
    /* Bootstrap's white track is invisible on a white card. */
    --switch-track-bg: #e2e8f0;
    --switch-track-border: rgba(15, 23, 42, 0.22);

    /* One rung firmer than a normal field: the rail's white surface
       swallows the resting border. */
    --sidebar-search-border: var(--input-border-hover);

    /* The dark chip is a white-alpha wash at 0.35 opacity — on white it
       reads as an empty faint box, so light gives it a real fill and
       drops the dimming. */
    --kbd-bg: #f1f5f9;
    --kbd-border: var(--border-normal);
    --kbd-fg: var(--text-tertiary);
    --kbd-opacity: 1;

    /* Fieldsets take their fill from the surface system by context, so only
       the edge and shadow are named here. */
    --fieldset-border: rgba(15, 23, 42, 0.10);
    --fieldset-border-hover: rgba(15, 23, 42, 0.18);
    --fieldset-shadow: none;

    /* Cards: white sheet, hairline edge, and a two-part shadow — a tight
       contact shadow that draws the edge plus a soft throw for depth. */
    --card-border: rgba(15, 23, 42, 0.08);
    --card-shadow:
        0 1px 2px rgba(15, 23, 42, 0.06),
        0 1px 3px rgba(15, 23, 42, 0.10);
    /* Light gets its depth from the shadow, so the lift is a longer throw
       rather than a darker one — a heavy black on white reads as grime. */
    --card-hover-shadow:
        0 2px 4px rgba(15, 23, 42, 0.08),
        0 10px 24px rgba(15, 23, 42, 0.12);
    /* Half the dark theme's: the -600 accents are ink-dense, and on a white
       sheet a near-opaque bar of one reads as a stripe of highlighter. */
    --stat-accent-alpha: 0.45;
    --card-light2-bg: #f8fafc;
    --card-dark-bg: linear-gradient(135deg, #f8fafc, #eef2f7);
    --card-hover-bg: linear-gradient(
        135deg,
        rgba(15, 23, 42, 0.035) 0%,
        rgba(15, 23, 42, 0.012) 100%
    );

    /* Modals */
    --modal-bg: #ffffff;
    /* Same brand-tinted edge as dark, one alpha step up: the tint has to
       carry against a white dialog rather than a near-black one. */
    --modal-border: rgba(var(--bs-primary-rgb), 0.28);
    --modal-highlight: transparent;
    --modal-shadow: 0 20px 60px rgba(15, 23, 42, 0.28);
    --modal-bar-border: var(--border-subtle);
    /* The scrim stays a dark wash even on a light page — slate rather than
       black, so the page tints instead of going grey. */
    --modal-backdrop-bg: rgba(15, 23, 42, 0.55);

    /* Deliberately inverted: a dark slate tip stands out against the
       white cards and navbar. The edge matches the fill so Bootstrap's
       border-drawn arrow reads as solid. */
    --tooltip-bg: #1e293b;
    --tooltip-edge: #1e293b;
    --tooltip-fg: #ffffff;
    --tooltip-shadow: 0 4px 14px rgba(15, 23, 42, 0.25);

    --page-scrollbar-thumb: rgba(15, 23, 42, 0.38);
    --page-scrollbar-thumb-hover: rgba(15, 23, 42, 0.52);
    --page-scrollbar-thumb-disabled: rgba(15, 23, 42, 0.16);
    --page-scrollbar-track: #eef1f5;

    --tag-light-fg: var(--text-secondary);
    --tag-label-lift: 0%;

    /* Solid buttons. A translucent hue over white is a pastel with a
       pastel edge — it reads as a disabled control, not a thing to press.
       Light fills every accent variant at full opacity with a white label,
       so a button is the most solid object on the page, and deepens on
       hover/active with an ink scrim rather than with more alpha. The
       resting edge is the fill itself, one step darker via the scrim's
       neighbour, so the shape stays crisp on a white card. */
    --btn-fill-a: 1;
    --btn-fill-b: 1;
    --btn-fill-hover-a: 1;
    --btn-fill-hover-b: 1;
    --btn-fill-active-a: 1;
    --btn-fill-active-b: 1;
    --btn-fill-disabled-a: 0.28;
    --btn-fill-disabled-b: 0.22;
    --btn-shade-hover: rgba(15, 23, 42, 0.14);
    --btn-shade-active: rgba(15, 23, 42, 0.24);
    --btn-edge-a: 1;
    --btn-edge-hover-a: 1;
    --btn-edge-active-a: 1;
    --btn-edge-disabled-a: 0.3;
    --btn-fg-on-fill: #ffffff;
    /* Where dark gets "raised" for free from a tint sitting on a darker
       page, light has to draw it: a contact shadow to lift the slab off the
       white card, plus a hairline of white along the top edge so the fill
       reads as a curved surface catching light rather than a flat rectangle
       of colour, and a matching shade along the bottom so the slab is lit
       from above rather than being one flat wash of hue. The two insets are
       what stop a solid fill reading as a coloured rectangle: the hue is
       already as saturated as a white label allows, so the remaining
       "blandness" is the absence of modelling, not the absence of chroma.
       Hover throws further in slate, not black — black on white is grime. */
    --btn-shadow: 0 1px 2px rgba(15, 23, 42, 0.12),
        inset 0 1px 0 rgba(255, 255, 255, 0.22),
        inset 0 -1px 0 rgba(15, 23, 42, 0.20);
    --btn-shadow-hover: 0 4px 10px rgba(15, 23, 42, 0.18),
        inset 0 1px 0 rgba(255, 255, 255, 0.22),
        inset 0 -1px 0 rgba(15, 23, 42, 0.20);
    --btn-shadow-hover-sm: 0 2px 5px rgba(15, 23, 42, 0.16),
        inset 0 1px 0 rgba(255, 255, 255, 0.22),
        inset 0 -1px 0 rgba(15, 23, 42, 0.20);
    /* Solid fills have nothing behind them but white, so the vendor's
       opacity dip bleaches the button on hover instead of softening it. */
    --btn-hover-fade: 1;
    /* A pure near-black slab is a heavyweight among the pastel tints on a
       white page, so every *-dark well softens toward slate here — still
       comfortably the most solid object on the page. */
    --bs-dark-well: color-mix(in srgb, var(--bs-dark) 78%, var(--bs-dark-fg));
    --btn-dark-border: var(--bs-dark-border);


    /* Pale slate chip instead of a dark one; the status dot's cut-out
       ring follows --chrome-surface automatically. */
    --avatar-fg: #334155;
    --avatar-bg: #e2e8f0;
    --avatar-bg-hover: #cbd5e1;
    --avatar-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8),
        0 0 0 1px rgba(15, 23, 42, 0.10), 0 1px 2px rgba(15, 23, 42, 0.12);
    --avatar-shadow-hover: inset 0 1px 0 rgba(255, 255, 255, 0.9),
        0 0 0 1px rgba(15, 23, 42, 0.16), 0 2px 4px rgba(15, 23, 42, 0.16);

    --on-tint-fg: var(--text-primary);

    --drawer-bg: #ffffff;
    --drawer-shadow: -8px 0 32px rgba(15, 23, 42, 0.16);

    /* The dots pill is a white lozenge in both themes; on light it needs a
       hairline ring to separate from the card under it. */
    --dots-overlay-shadow: 0 2px 10px rgba(15, 23, 42, 0.14),
        0 0 0 1px rgba(15, 23, 42, 0.08);

    /* Neutral slate lift, mirroring dark: the accent lives on the label
       and icon, not under them. One rung above --surface-2 so the active
       pill still outranks a hovered one. */
    --nav-active-bg: rgba(15, 23, 42, 0.09);
    --nav-active-marker: var(--nav-active-fg);

    /* Light antialiases glyphs toward the white behind them, which darkens
       rather than pales them, so the accent needs no correction here. */
    --nav-active-fg: var(--color-blue);

    --sidebar-slideout-shadow: 10px 0 30px rgba(15, 23, 42, 0.16);

    --scroll-shadow: rgba(15, 23, 42, 0.28);

    /* A recessed grey track with a white chip lifted out of it, where dark
       fills the chip with the accent. */
    --tabs-track-bg: #e6eaf0;
    --tab-active-bg: #ffffff;
    --tab-active-fg: var(--text-primary);
    --tab-active-shadow: 0 1px 2px rgba(15, 23, 42, 0.14),
        0 0 0 1px rgba(15, 23, 42, 0.06);

    --table-sticky-header-bg: #ffffff;

    /* On white the chip has to sink instead of glow: the type label drops
       to a deep tint that clears 4.5:1 on its own pale fill, the name takes
       the page's ink, and the border darkens so the outline survives
       against the card. */
    --poly-ref-border-s: 40%;
    --poly-ref-border-l: 50%;
    --poly-ref-border-a: 0.50;
    --poly-ref-type-l: 32%;
    --poly-ref-type-bg-a: 0.12;
    --poly-ref-name-fg: var(--text-primary);
    --poly-ref-name-fg-hover: var(--text-primary);
}
