/* ===================================================================================================
   PreemptiveScheduler design tokens (feature K) — the single styling source of truth.
   Components and utilities consume var(--ps-*) ONLY; raw hex/px values live in this file alone.
   Colors are light-dark() pairs (Baseline 2024): the browser resolves them through color-scheme,
   which the three-state theme narrows via html[data-theme] (stamped by App.razor from the ps-theme
   cookie, flipped live by site.js setTheme). Defining tokens is render-inert: nothing changes until
   a rule consumes them.
   =================================================================================================== */

:root {
    color-scheme: light dark;

    /* --- Breakpoints — min-width tiers, rem-based so full-page zoom reflows into the smaller tiers
       (WCAG 1.4.10). The CSS spec forbids var() inside @media/@container conditions, so these
       declarations are the documented literals shell media queries must repeat, and the parse source
       for the C# mirror (Components/Ui/Breakpoints.cs; BreakpointsTokensSyncTests guards the drift).
       sm 640px large phone · md 768px tablet/icon rail · lg 1024px laptop/sidebar · xl 1280px desktop
       · 2xl 1536px full desktop (1920@125%) · 3xl 1920px multi-pane · 4xl 2560px wall display. */
    --ps-breakpoint-sm: 40rem;
    --ps-breakpoint-md: 48rem;
    --ps-breakpoint-lg: 64rem;
    --ps-breakpoint-xl: 80rem;
    --ps-breakpoint-2xl: 96rem;
    --ps-breakpoint-3xl: 120rem;
    --ps-breakpoint-4xl: 160rem;

    /* --- Color: neutrals. Surfaces step raised > surface > sunken; every text token holds ≥4.5:1
       (WCAG AA) against ALL three surfaces in both schemes except -faint (placeholder/disabled tier,
       ≥3:1 against raised). */
    --ps-color-surface: light-dark(#f6f7f9, #16181d);
    --ps-color-surface-raised: light-dark(#ffffff, #1d2026);
    --ps-color-surface-sunken: light-dark(#eceef2, #101216);
    --ps-color-text: light-dark(#1a1d23, #e7e9ee);
    --ps-color-text-muted: light-dark(#525a66, #a3abb8);
    --ps-color-text-faint: light-dark(#6f7886, #8b93a1);
    --ps-color-border: light-dark(#d4d9e0, #343943);
    --ps-color-border-strong: light-dark(#b3bac4, #4c525e);

    /* --- Usage stacked-area bands: the non-user bands of a usage lane. "Others" is a mid gray DISTINCT from the
       fainter "Idle" headroom, so a merged/unselected-user band never reads as unused capacity. */
    --ps-usage-others: light-dark(#9aa3b0, #6b7280);
    --ps-usage-idle: light-dark(#eef1f5, #262a31);
    /* The FIXED low-presence color of the "root" account, the third member of the trio that bypasses the per-user
       palette: idle is the track, system is a neutral gray, and root is a GREY WITH A COOL CAST — the machine's own
       housekeeping, present on every host and never the answer to "who is using this". Its chroma is deliberately
       tiny (LCh C* ≈ 7, half of the first slate's ≈ 14, which read as a person's color at a glance and was ruled out
       for exactly that): the swatch must land in the grey family first and only then be told apart from the other
       greys. LIGHTNESS is what tells it apart, so the value is tuned per ground rather than shared: on light it sits
       BELOW every neutral in the bar (they run pale, L* 50-95), on dark it sits ABOVE the dark ones and BELOW the
       pale ones (they run L* 5-61), which is why the dark one is the lighter of the pair. What is left of the hue is
       still the CYAN side of cool, because the neutrals root shares a bar with — system, others, idle, the surfaces
       — are all blue-VIOLET (hue ≈ 263-278), so the small cast it keeps pushes away from them rather than into them;
       on dark that cast is load-bearing, worth ΔE00 +2.3 against the "others" band a pure neutral of the same
       lightness would sit 8.5 from. Measured, not asserted: nearest-neighbour ΔE00 ≈ 14 (light) / 11 (dark) against
       all twenty palette slots, the overflow hue ring and every grey that can FILL beside it in the same card, and
       ≈ 28 / 27 from the warm user slot #9D7660 the retired bronze sat 11 from and was mistaken for. */
    --ps-usage-root: light-dark(#4e5f60, #57696a);

    /* --- Color: accent (interactive). */
    --ps-color-accent: light-dark(#0a5cb8, #6cb2ff);
    --ps-color-accent-emphasis: light-dark(#084a93, #8ec4ff);
    --ps-color-accent-soft: light-dark(#e3effc, #14233a);
    --ps-color-on-accent: light-dark(#ffffff, #0c1116);
    --ps-color-focus: light-dark(#0a5cb8, #6cb2ff);

    /* --- Color: status. Strong value for text/icons (AA on every surface), -soft for fills behind
       the strong value (AA as a pair). */
    --ps-color-success: light-dark(#177235, #4fc56a);
    --ps-color-success-soft: light-dark(#def7e4, #15281b);
    --ps-color-warning: light-dark(#8a5c00, #d9a83e);
    --ps-color-warning-soft: light-dark(#fdf0d3, #2a2113);
    --ps-color-danger: light-dark(#c52431, #ff7b81);
    --ps-color-danger-soft: light-dark(#fde8e9, #321b1d);
    --ps-color-on-danger: light-dark(#ffffff, #0c1116);
    --ps-color-info: light-dark(#0a5cb8, #6cb2ff);
    --ps-color-info-soft: light-dark(#e3effc, #14233a);
    --ps-color-backdrop: light-dark(rgb(0 0 0 / 0.45), rgb(0 0 0 / 0.6));

    /* --- Elevation. */
    --ps-shadow-1: 0 1px 2px light-dark(rgb(16 24 40 / 0.08), rgb(0 0 0 / 0.45));
    --ps-shadow-2: 0 2px 8px light-dark(rgb(16 24 40 / 0.10), rgb(0 0 0 / 0.50));
    --ps-shadow-3: 0 8px 24px light-dark(rgb(16 24 40 / 0.14), rgb(0 0 0 / 0.55));

    /* --- Spacing — 4px base scale (token n = n × 0.25rem). */
    --ps-space-1: 0.25rem;
    --ps-space-2: 0.5rem;
    --ps-space-3: 0.75rem;
    --ps-space-4: 1rem;
    --ps-space-5: 1.25rem;
    --ps-space-6: 1.5rem;
    --ps-space-8: 2rem;
    --ps-space-10: 2.5rem;
    --ps-space-12: 3rem;
    --ps-space-16: 4rem;

    /* --- Type — gently fluid (≈±8%), rem-anchored clamp() with max ≤ 1.25 × min so 200% text
       resize is never defeated (WCAG 1.4.4); body anchor stays exactly 1rem. */
    --ps-font-sans: system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --ps-font-mono: ui-monospace, "Cascadia Mono", Consolas, "SF Mono", Menlo, monospace;
    --ps-text-xs: clamp(0.75rem, 0.72rem + 0.10vw, 0.8125rem);
    --ps-text-sm: clamp(0.875rem, 0.85rem + 0.10vw, 0.9375rem);
    --ps-text-md: 1rem;
    --ps-text-lg: clamp(1.125rem, 1.05rem + 0.30vw, 1.25rem);
    --ps-text-xl: clamp(1.375rem, 1.25rem + 0.50vw, 1.625rem);
    --ps-text-2xl: clamp(1.75rem, 1.55rem + 0.80vw, 2.125rem);
    --ps-line-body: 1.5;
    --ps-line-dense: 1.35;
    --ps-line-heading: 1.25;
    --ps-weight-normal: 400;
    --ps-weight-medium: 500;
    --ps-weight-semibold: 600;
    --ps-weight-bold: 700;
    --ps-measure: 70ch;

    /* --- Density — pointer-driven, never viewport-driven (the coarse-pointer override below).
       --ps-target-min is the WCAG 2.5.8 AA floor: no interactive target renders under 24px. */
    --ps-control-height: 2rem;
    --ps-row-height: 2.25rem;
    --ps-target-min: 1.5rem;

    /* --- Icon sizes (Ui/Icon) — text-size tracking plus three token steps. */
    --ps-icon-size-text: 1em;
    --ps-icon-size-sm: 1rem;
    --ps-icon-size-md: 1.25rem;
    --ps-icon-size-lg: 1.5rem;

    /* --- Radius. */
    --ps-radius-sm: 0.25rem;
    --ps-radius-md: 0.375rem;
    --ps-radius-lg: 0.75rem;
    --ps-radius-full: 999px;

    /* --- Component grid bases (RAM pattern minimums). */
    --ps-tile-min: 15rem;
    --ps-card-min: 22rem;
    --ps-field-min: 16rem;
    /* The legibility floor of ONE node block in the job form's device candidate picker: a hostname + its
       "Whole node" tri-state on the head row, and a row of GPU chips (dot + label + co-domain + live SM badge)
       beneath it. Below this a block would wrap its own head row, so the picker stays single-column instead. */
    --ps-picker-node-min: 20rem;

    /* --- WideGrid (the 4xl page-composition primitive). At the 4xl wall-display tier WideGrid reflows a page's
       stacked sections into TWO equal columns (full-span rows excepted), so the horizontal surplus REDUCES vertical
       scrolling instead of opening a margin. --ps-wide-grid-gap is the inter-column/row gap; --ps-wide-col-min is the
       readable floor a side-by-side column must clear before the two-column tier is worth engaging (a column narrower
       than this would crowd its content). Consumed only inside the scoped 160rem WideGrid tier; below it WideGrid is a
       plain vertical stack, so these are inert at ≤1920. */
    --ps-wide-grid-gap: var(--ps-space-8);
    --ps-wide-col-min: 34rem;
    /* The LEGIBILITY floor of a cluster-grid node card, and the only thing that decides how many cards a row holds
       (tracks stretch from here — no ceiling and no centering at any width). It is measured from the card's densest
       line: a GPU row carrying two segmented mini-bars still wide enough that a small share reads as a segment, plus
       the occupant chip printed WHOLE rather than ellipsized, on ONE line. Cards per row is the CONSEQUENCE of this
       floor, never the goal — where density and legibility disagree, the wider card wins. (Measured, not guessed: at
       38rem the chip lost its trailing ordinal at the exact floor width, so the floor moved to where it does not.) */
    --ps-cluster-card-min: 40rem;
    /* The measure a cluster-summary METER stops growing at. A bar stretched across a wall-width row turns a small
       segment into a stripe the eye reads as a boundary rather than a share; the card's TABLE is deliberately
       uncapped, since a table only gets easier to read as it widens. */
    --ps-cluster-summary-meter-max: 56rem;

    /* --- Pattern dimensions (Ui/Patterns). --ps-font-size-input keeps focused inputs at or above the
       16px iOS zoom threshold while still growing with rem-based zoom. */
    --ps-modal-width-sm: 26rem;
    --ps-modal-width-md: 36rem;
    --ps-modal-width-lg: 52rem;
    --ps-drawer-width: 28rem;
    --ps-detail-rail-width: 22rem;
    /* The readable width of ONE standalone labeled control row (a select/input that is NOT inside a FormSection
       RAM grid). Without it a lone FormField's control fills its whole column, so a single dropdown stretches to
       wall width at 4xl; capping it keeps a single-control row at a readable measure on every tier. */
    --ps-field-max: 28rem;
    /* 4xl (wall-display) widths — the cap relaxes and the rail/form column widen so the fluid grids pack
       more columns instead of opening a margin gutter. Consumed only inside the scoped 160rem tiers. */
    --ps-detail-rail-width-wide: 26rem;
    --ps-form-max: 64rem;
    /* The readable max for a DETAIL page (masthead + main column + docked rail together) at the 4xl tier: without it
       the main column stretches to the full fluid shell (~3000px KV rows at 4K). Equals --ps-table-max so detail
       tables and output panes stay as generous as list pages; the page CENTERS in the shell column per the
       large-screen composition rule (MainLayout.razor.css). Consumed only inside the scoped 160rem
       .ps-content--detail utility tier (utilities.css). */
    --ps-detail-max: 120rem;
    --ps-font-size-input: max(1rem, 16px);

    /* --- Shell dimensions. */
    --ps-shell-topbar-height: 3.5rem;
    --ps-shell-rail-width: 5rem;
    --ps-shell-nav-width: 16.5rem;
    --ps-content-max: 90rem;
    /* The 4xl (wall-display) content max is a generous upper BOUND, not the working width: at the 4xl tier the shell
       fills the main area MINUS a symmetric gutter pair (min(calc(100% - 2*gutter), this)), so on a true 4K (3840px)
       screen the content reaches ~(viewport − sidebar − both gutters) ≈ 3450px rather than stopping at a fixed cap that
       leaves a wide right void; this 220rem ceiling only binds on a viewport wider than ~4K, keeping width-hungry
       surfaces from running truly unbounded. Consumed only inside the scoped 160rem shell tier. */
    --ps-content-max-wide: 220rem;
    /* The deliberate PER-SIDE gutter at the 4xl tier: the centered shell keeps one of these on EACH edge (the
       large-screen composition rule — MainLayout.razor.css), so the fluid fill reads as composed, balanced breathing
       room rather than an accidental edge-to-edge bleed. Consumed only in the scoped 160rem shell tier. */
    --ps-content-gutter-wide: 4rem;
    /* The readable max for LIST/TABLE content inside the wide shell at the 4xl tier (≈1920px). A list/table page FILLS
       the fluid column UP TO this cap (inline-size:100% bounded by max-inline-size) and CENTERS it, so on a
       true-4K (3840px) screen the table sits at a readable ~1920px width in TABLE mode (wide rows/columns — well above
       the DataTable container-query card-collapse threshold of 28rem), the surplus splitting into two symmetric
       gutters (the centered composition rule). This REPLACES the v3 fit-content sizing, which over-shrank a few-column table below the card-mode
       breakpoint and forced phone-style card mode on a 4K display. Width-hungry surfaces (the dashboard cluster grid,
       usage lanes, detail main column) do NOT carry the list class and fill the full fluid column. The dashboard
       header also caps here so its toggle does not escape to the wall edge. Consumed only inside the scoped 160rem
       tiers; reading-prose caps (settings note, key-value value column) use --ps-measure instead. */
    --ps-table-max: 120rem;
    /* The same readable cap for a LOW-COLUMN list page, worn through utilities' .ps-content--list-narrow. A table
       with few columns does NOT get more readable as it widens: auto table layout hands the whole wall-display
       surplus to the same handful of cells, so at 2560+ a four-column row reads as four islands separated by dead
       field, and the eye loses the row it is tracking between them. Which cap a page wears is decided by its column
       DENSITY, never by its identity — the same jobs page wears the wide cap for the administrator's nine-column
       operator table and this one for the normal user's four-column occupancy view. The value is MEASURED, not
       derived: shot at 2560 and 3840 against a ladder of candidates (120 / 100 / 90 / 84 / 78rem), this is the one
       at which a four-column row stops reading as separate cells and starts reading as a line. It sits deliberately
       BELOW --ps-content-max (90rem), so a low-column table is a little narrower at 2560 than at 1920 — accepted
       rather than overlooked: 160rem is where the shell stops capping and starts composing, and the only way to keep
       the width monotonic is to keep the spread this token exists to remove. Consumed only inside the scoped 160rem
       tier. */
    --ps-table-max-narrow: 78rem;

    /* --- Z-index scale. A single owned ladder (sticky < shell < drawer < modal < popover < toast);
       Popover API / <dialog> surfaces ride the top layer and need no z-index at all. */
    --ps-z-sticky: 1020;
    --ps-z-shell: 1030;
    --ps-z-drawer: 1045;
    --ps-z-modal: 1055;
    --ps-z-popover: 1070;
    --ps-z-toast: 1090;

    /* --- Motion — no-motion-first: zero by default, real durations only when the user allows
       motion (the no-preference override below), so reduced-motion needs no per-rule queries. */
    --ps-motion-fast: 0ms;
    --ps-motion-medium: 0ms;
    --ps-motion-slow: 0ms;
    --ps-ease: cubic-bezier(0.2, 0.6, 0.2, 1);
}

/* The three-state theme: auto (default) keeps both schemes live so the OS preference decides;
   an explicit choice narrows to one scheme, and `only` additionally opts out of forced darkening. */
:root[data-theme="light"] {
    color-scheme: only light;
}

:root[data-theme="dark"] {
    color-scheme: only dark;
}

@media (pointer: coarse) {
    :root {
        --ps-control-height: 2.75rem;
        --ps-row-height: 2.75rem;
        --ps-target-min: 2.75rem;
    }
}

@media (prefers-reduced-motion: no-preference) {
    :root {
        --ps-motion-fast: 120ms;
        --ps-motion-medium: 200ms;
        --ps-motion-slow: 320ms;
    }
}
