/* Canonical design tokens — copied verbatim from design/Tokens.dc.html.
 * Do not hand-edit: change the artboard, re-run design/build.mjs, re-extract.
 *
 * Contrast floors: text/background pairs >= 4.5:1 (WCAG 1.4.3). Non-text tokens
 * that carry meaning — --border-strong (an interactive edge), --score-fill
 * against --score-track, --focus-ring — are >= 3:1 (1.4.11). The floors are
 * asserted, per theme, by `node scripts/contrast.mjs`; its exit code is the gate.
 *
 * NOT >= 3:1, deliberately: --border-subtle (~1.2:1 on --bg-surface) and
 * --score-track (~1.2:1) are a divider and a groove — decorative, and outside
 * 1.4.11's "graphical objects required to understand content". Raising them
 * would make every table read as a grid. The earlier blanket claim that all
 * non-text tokens clear 3:1 was false, and a dark mode must not inherit it.
 *
 * Themeable tokens are one `light-dark()` declaration each, not two hand-copied
 * blocks, so the two halves cannot drift. The raw scale below is NOT themed: it
 * is the palette the artboards draw from, and the semantic aliases are what the
 * app is allowed to use.
 */
:root {
  color-scheme: light dark;
  --slate-950: #12161c;
  --slate-900: #1c2431;
  --slate-800: #2c3a4f;
  --slate-700: #33415c;
  --slate-600: #5c6675;
  --slate-500: #77839a;
  --slate-300: #ccd3de;
  --slate-200: #e3e7ee;
  --slate-100: #f5f7fa;
  --slate-050: #eef0f4;
  --white: #ffffff;
  --accent-700: #23406e;
  --accent-600: #2c5282;
  --accent-100: #e4ecf6;
  --amber-700: #8a5200;
  --amber-100: #fbf0dc;
  --green-700: #1c5230;
  --green-300: #7fd3a4;
  --green-100: #e2f1e7;
  --red-600: #b3261e;
  --red-100: #fbe6e4;
  --bg-canvas: light-dark(#eef0f4, #0d1117);
  --bg-surface: light-dark(#ffffff, #161b22);
  --bg-sunken: light-dark(#f5f7fa, #1c222b);
  --bg-elevated: light-dark(#ffffff, #1c222b);
  /* Darker than --bg-canvas in BOTH themes. The rail/content split is the primary structural
     cue on every page; a rail that matches the canvas erases it. */
  --bg-nav: light-dark(#1c2431, #010409);
  --bg-nav-active: light-dark(#2c3a4f, #1f2733);
  --text-primary: light-dark(#12161c, #e6edf3);
  --text-secondary: light-dark(#374151, #a8b3cf);
  /* Dark-mode value is the dark canvas, not white: white on a light-blue --accent is 3.10:1. */
  --text-inverse: light-dark(#ffffff, #0d1117);
  /* The rail and status bar are dark in both themes, so these two are not themed. */
  --text-nav: #c8d0dc;
  --text-nav-dim: #a4aebf;
  --border-subtle: light-dark(#e3e7ee, #21262d);
  --border-default: light-dark(#ccd3de, #30363d);
  --border-strong: light-dark(#77839a, #6e7681);
  --accent: light-dark(#23406e, #79b8ff);
  --accent-strong: light-dark(#23406e, #a5d6ff);
  --accent-weak: light-dark(#e4ecf6, #14253c);
  --focus-ring: light-dark(#23406e, #79b8ff);
  --score-track: light-dark(#e3e7ee, #262c36);
  --score-fill: light-dark(#23406e, #79b8ff);
  --warn-text: light-dark(#6e4100, #e3b341);
  --warn-weak: light-dark(#fbf0dc, #2b2000);
  --ok-text: light-dark(#1c5230, #3fb950);
  --ok-weak: light-dark(#e2f1e7, #0c2618);
  --danger-text: light-dark(#961c15, #ff9b94);
  --danger-weak: light-dark(#fbe6e4, #2d0f0d);
  --overlay-scrim: light-dark(rgba(18, 22, 28, 0.4), rgba(1, 4, 9, 0.6));
  /* On the always-dark chrome (rail, status bar), where the light-theme --ok-text/--warn-text
     measure 2.4-3.7:1. Unthemed for the same reason --text-nav is. */
  --ok-text-on-dark: var(--green-300);
  --warn-text-on-dark: #e3b341;
  --font-ui: 'IBM Plex Sans', system-ui, sans-serif;
  --font-display: 'IBM Plex Serif', Georgia, serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, 'Courier New', monospace;
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --radius: 4px;
  --radius-pill: 999px;
  /* A shadow is a lighting model, and in dark mode there is no light to occlude:
     rgba(18,22,28,.06) over #0d1117 measures ~1.0:1, i.e. nothing. Dark elevation is a
     ring — the higher surface reads as nearer because its edge is brighter, not darker.
     light-dark() is a <color> function (CSS Color 5), so it wraps the shadow COLOURS, not
     the whole shadow: the geometry of both layers is always present and the layer that does
     not apply in a theme is `transparent`, which costs nothing to paint. */
  --elev-1: 0 1px 2px light-dark(rgba(18, 22, 28, 0.10), transparent), 0 0 0 1px light-dark(transparent, #30363d);
  --elev-2: 0 4px 12px light-dark(rgba(18, 22, 28, 0.16), transparent), 0 0 0 1px light-dark(transparent, #484f58);
  --dur-fast: 120ms;
  --dur-base: 180ms;
  --dur-slow: 260ms;
  --ease-out: cubic-bezier(0.2, 0, 0, 1);
  --ease-spring: cubic-bezier(0.22, 1, 0.36, 1);
}
/* An explicit choice, stamped on <html> server-side from the `theme` cookie. Flipping
   color-scheme is all it takes: every themed token is a light-dark() that resolves against it,
   and so are the form controls, checkboxes, scrollbars, autofill and selection colours that no
   custom property reaches. */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }
body { margin: 0; }
a { color: var(--accent); } a:hover { color: var(--accent-strong); }

/* ---------------------------------------------------------------------------
 * Repeated row treatments, hoisted out of `style="…"` attributes.
 *
 * Not from the artboard and not tokens — but they belong in THIS file rather
 * than in a `style="…"` attribute, because a class name is written once here
 * and the literal is written once PER ROW. /jobs renders PAGE_SIZE (50) table
 * rows plus two 25-row bands, so an inline literal is paid 50-126 times per
 * page: measured at 205KB with 80.9KB of style attributes against a 200KB
 * budget, the single worst literal (`font-size: 12px; color:
 * var(--text-secondary)`) appearing 126 times.
 *
 * NOT because this file is cached: serveStatic (web/server.ts) sends it with
 * `cache-control: no-store`, so it is re-fetched on every page load exactly
 * like render.ts's inline <style> block is re-sent. The saving is per-row
 * repetition, not per-page transfer.
 *
 * Only literals that repeat per row live here. A one-off layout style stays
 * inline where it is read, and anything computed per row (a score bar's width,
 * a status chip's colour) has to stay inline because it is not a constant.
 * ------------------------------------------------------------------------- */

/* Shared by the jobs table, the semantic band and the unresolved band. */
.rowtitle { font-size: 14px; font-weight: 500; line-height: 1.35; text-wrap: pretty; }
.rowsub { font-size: 12px; color: var(--text-secondary); }
.plain { text-decoration: none; color: inherit; }
.hint { font-size: 11px; color: var(--text-secondary); }
.cmd { background: var(--bg-sunken); border-radius: 3px; padding: 4px 8px; }

/* JobsTable: one rule per column, fixed geometry to prevent CLS. */
table[role="grid"] { table-layout: fixed; width: 100%; border-collapse: collapse; }
.jrow { border-bottom: 1px solid var(--border-subtle); }
.jrow td { height: 52px; box-sizing: border-box; vertical-align: middle; }
.jc-sel { padding: 11px 0 11px 14px; }
.jc-title { padding: 6px 8px; max-width: 480px; overflow: hidden; }
.rowtitle, .rowsub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.jc-score { padding: 11px 12px; text-align: right; vertical-align: middle; }
.jc-sal { padding: 11px 12px; text-align: right; font-size: 12px; }
.jc-age { padding: 11px 12px; font-size: 13px; color: var(--text-secondary); }
.jc-status { padding: 11px 18px; }

/* scoreCell (render.ts) — every page that shows a score. */
.scorenum { font-size: 16px; font-weight: 600; }
.scorebar { height: 4px; background: var(--score-track); border-radius: 2px; margin-top: 4px; }

/* SemanticBand and UnresolvedList rows. */
.bandrow { padding: 12px 18px; border-bottom: 1px solid var(--border-subtle); display: flex; justify-content: space-between; gap: 14px; align-items: center; }
.bandlink { min-width: 0; flex-grow: 1; text-decoration: none; color: inherit; }
.bandscore { font-size: 13px; color: var(--text-secondary); flex-shrink: 0; }
.ubandrow { padding: 12px 18px; border-bottom: 1px solid var(--border-subtle); display: flex; flex-direction: column; gap: 6px; }
.ubandlink { text-decoration: none; color: inherit; display: flex; justify-content: space-between; gap: 14px; align-items: baseline; }
.ubandscore { font-size: 13px; color: var(--text-secondary); flex-shrink: 0; white-space: nowrap; }

/* AppNav + StatusBar. Hand-written, not from the artboard — same bargain as the row
   treatments above: a class is written once here, an inline literal once per element per page. */
.app-shell { --rail-width: 220px; }
.nav { width: var(--rail-width, 220px); min-width: var(--rail-width, 220px); max-width: var(--rail-width, 220px); flex-shrink: 0; background: var(--bg-nav); color: var(--text-nav); padding: 24px 0 20px; display: flex; flex-direction: column; gap: 2px; }
.nav[data-collapsed="true"] { --rail-width: 56px; }
/* The rail is dark in the light theme, where --focus-ring (#2c5282) measures 1.96:1 on
   --bg-nav and 1.44:1 on --bg-nav-active — both failing WCAG 1.4.11 today. Scoping the token
   fixes it with no new rule: the global :focus-visible picks it up. */
.nav { --focus-ring: var(--white); }
.nav-brand { font-family: var(--font-mono); font-size: 15px; font-weight: 500; color: var(--white); padding: 0 20px 18px; }
.navitem { padding: 10px 16px 10px 20px; font-size: 14px; color: var(--text-nav); display: flex; justify-content: space-between; align-items: center; text-decoration: none; }
.navitem[aria-current="page"] { background: var(--bg-nav-active); color: var(--white); font-weight: 500; padding-left: 17px; border-left: 3px solid var(--white); }
.navsec { padding: 6px 16px 6px 32px; font-size: 13px; color: var(--text-nav); text-decoration: none; display: flex; justify-content: space-between; align-items: center; }
.navsec:hover { color: var(--white); }
/* The theme toggle is a <button> sitting in a column of <a>s: reset everything the UA gives a
   button so .navsec is the only thing styling it. */
.navsec-btn { font: inherit; font-size: 13px; background: none; border: 0; width: 100%; text-align: left; cursor: pointer; color: inherit; }
/* inset shadow, not colour alone: the secondary tier gets the same non-colour cue the pinned
   tier gets from its border, with no layout shift (WCAG 1.4.1). */
.navsec[aria-current="page"] { color: var(--white); font-weight: 500; box-shadow: inset 2px 0 var(--white); }
.navgroups { display: flex; flex-direction: column; gap: 1px; margin-top: 18px; padding-top: 14px; border-top: 1px solid rgba(255,255,255,0.08); }
.navgroup > summary { list-style: none; cursor: pointer; display: flex; align-items: center; gap: 6px; padding: 8px 20px 6px; font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-nav-dim); }
.navgroup > summary::-webkit-details-marker { display: none; }
.navgroup > summary:hover { color: var(--white); }
.navcaret { width: 8px; height: 8px; flex-shrink: 0; }
/* Unconditional: rotation carries open/closed state, not decoration. Only the interpolation
   between the two is gated on reduced motion. */
.navgroup[open] > summary .navcaret { transform: rotate(90deg); }
.nav-ico { width: 14px; height: 14px; flex-shrink: 0; }
.nav-label { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.nav-badge { font-family: var(--font-mono); font-size: 10px; color: var(--text-nav-dim); }
.nav-badge-pill { font-family: var(--font-mono); font-size: 11px; background: var(--red-600); color: var(--white); padding: 1px 7px; border-radius: var(--radius-pill); }
.nav-footer { margin-top: auto; padding-top: 12px; border-top: 1px solid rgba(255,255,255,0.08); }
.nav-tooltip { position: absolute; left: calc(100% + 10px); top: 50%; transform: translateY(-50%); background: var(--bg-elevated); color: var(--text-primary); border: 1px solid var(--border-subtle); border-radius: var(--radius); padding: 4px 8px; font-size: 12px; font-weight: 500; white-space: nowrap; pointer-events: none; opacity: 0; visibility: hidden; box-shadow: var(--elev-2); z-index: 100; display: flex; align-items: center; gap: 6px; }
.navitem-link:hover .nav-tooltip, .navitem-link:focus-visible .nav-tooltip { opacity: 1; visibility: visible; }

html {
  scroll-padding-bottom: 36px;
}

.skiplink {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}
.skiplink:focus, .skiplink:focus-visible {
  position: fixed;
  top: 12px;
  left: 12px;
  width: auto;
  height: auto;
  padding: 8px 16px;
  margin: 0;
  overflow: visible;
  clip: auto;
  white-space: normal;
  z-index: 100;
  background: var(--bg-surface);
  color: var(--text-primary);
  border: 2px solid var(--accent);
  box-shadow: var(--elev-2);
  border-radius: var(--radius);
}

/* Fixed, full width, across the rail and the content column. .app reserves the height so
   nothing renders underneath. */
.statusbar { position: fixed; bottom: 0; left: 0; right: 0; height: 30px; z-index: 10; display: flex; align-items: center; gap: 16px; padding: 0 14px; background: var(--bg-nav); color: var(--text-nav-dim); font-size: 11px; border-top: 1px solid rgba(255,255,255,0.10); }
.statusbar { --focus-ring: var(--white); }
.statsegs { display: flex; align-items: center; gap: 14px; min-width: 0; overflow-x: auto; white-space: nowrap; }
.statseg { color: inherit; text-decoration: none; }
.statseg:hover { color: var(--white); }
/* --warn-text, not --warn-text-on-dark, was 2.44:1 here: the bar's ground is --bg-nav in BOTH
   themes, so the light-theme amber never applied to it. Predates dark mode. */
.statalert { color: var(--warn-text-on-dark); font-weight: 600; }
.statspend { display: flex; gap: 12px; margin-left: auto; flex-shrink: 0; }
.statbuild { flex-shrink: 0; opacity: 0.75; }

/* Cross-document page transitions. Chrome/Edge 126+, Safari 18.2+; Firefox does not support
   this and drops the at-rule at parse time, navigating normally — graceful, no fallback needed.
   Kept at top level with reduced motion handled by killing the animations, rather than nesting
   @view-transition in @media: nesting IS valid (CSS View Transitions 2 §8.3.1) but a runtime
   check of it was inconclusive, and this form costs the same. */
@view-transition { navigation: auto; }

/* A matching name alone only stops the rail from MOVING — the old and new snapshots still
   cross-fade, and the rail's content differs per page (current item, badge counts), so the
   flicker the name was meant to remove would remain. These three rules are what "persists"
   actually requires. */
.nav { view-transition-name: nav; }
::view-transition-group(nav) { animation-duration: 0s; }
::view-transition-old(nav) { animation-name: none; opacity: 0; }
::view-transition-new(nav) { animation-name: none; opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation-name: none !important; }
}

@media (prefers-reduced-motion: no-preference) {
  .nav {
    transition: width var(--dur-fast) var(--ease-out),
                min-width var(--dur-fast) var(--ease-out),
                max-width var(--dur-fast) var(--ease-out);
  }
  .nav-tooltip {
    transition: opacity var(--dur-fast) var(--ease-out), visibility var(--dur-fast) var(--ease-out);
  }
  .navitem, .navsec, .navgroup > summary, .statseg {
    transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
  }
  .navcaret { transition: transform var(--dur-fast) var(--ease-out); }
  .row-link { transition: background var(--dur-fast) var(--ease-out); }
}
.row-link:hover { background: var(--bg-sunken); }
