/* wretches-and-kings — stone-blue ground + red-fox accent
   Palette built by method (OKLCH, computed WCAG), 2026-08-22:
   .planning/research/2026-08-22-color-palette-method.md — regenerate with
   .planning/research/2026-08-22-design-sweep/palette_gen.py. Neutral = H250
   (Stone blue deep), accent = H46 (Red fox, the fur colour; the surname tie-in
   stays unspoken). Change a token here only by re-running the generator.

   Type built by method the same day: .planning/research/2026-08-22-typography-method.md.
   Bitter (slab) = body, UI, numbers; Onest = h1–h3 + the voice surfaces. Seven rem
   steps, three weights (400/500/600), one uppercase tracking, no mono face. The
   invariant test (web/test/invariant-type-ladder.test.ts) fails on any px
   font-size outside the landing block — sizes are tokens, not numbers. */

/* ── Web fonts (self-hosted, OFL; built from the google/fonts variable TTFs by
   .planning/research/2026-08-22-design-sweep/build_fonts.py — wght instanced to
   400–600, latin subset, woff2). NOT Astro's Fonts API: its <Font /> emits an
   inline <style> that the prod CSP (style-src 'self') blocks. The *Fallback
   faces are metric-matched (size-adjust measured in-browser, ascent/descent
   from the font's hhea table) so the swap does not shift layout. */
@font-face {
  font-family: 'Bitter';
  src: url('/fonts/bitter-400-600.woff2') format('woff2');
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Bitter Fallback';
  src: local('Georgia');
  size-adjust: 102.82%;
  ascent-override: 90.94%;
  descent-override: 25.77%;
  line-gap-override: 0%;
}
@font-face {
  font-family: 'Onest';
  src: url('/fonts/onest-400-600.woff2') format('woff2');
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Onest Fallback';
  src: local('Arial');
  size-adjust: 105.19%;
  ascent-override: 92.21%;
  descent-override: 29.0%;
  line-gap-override: 0%;
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

:root {
  color-scheme: light;

  /* ── Palette ─────────────────────────────────────────────────────────── */
  --bg:           #E8F1FB;   /* neutral step 2 — page ground */
  --surface:      #F9FCFF;   /* neutral step 1 — cards, nav, inputs */
  --border:       #BBCDDF;   /* neutral step 6 */
  --border-ui:    #E0EDFA;   /* neutral step 3 — hover wash, row dividers */
  --text:         #10171E;   /* neutral step 12 */
  --ink-2:        #3B4958;   /* neutral step 11 */
  --muted:        #576D83;   /* neutral step 9 — 4.7:1 on --bg */

  --accent:       #C25614;   /* accent step 9 — Red fox; white text 4.53:1 */
  --accent-dark:  #AA4703;   /* accent step 10 — hover for filled fox */
  --accent-soft:  #FFDCCC;   /* accent step 3 */
  --accent-soft-dark: #FECAB2; /* accent step 4 — hover step for accent-soft surfaces (profile lanes) */
  --accent-text:  #8E4218;   /* accent step 11 — links; 6.3:1 on --bg */
  --on-accent:    #FFFFFF;

  --secondary:      #2D6C9C; /* info blue, built at the accent's L/C */
  --secondary-dark: #235A85;
  --secondary-soft: #D8E9F8;

  --danger:       #A43A3D;   /* true red, 24° and 0.08 L from the fox; white text 6.4:1 */
  --danger-soft:  #FBD3D1;

  --success:      #377342;
  --success-soft: #DBECDC;

  --warn:         #845F11;
  --warn-soft:    #F0E5D3;

  /* ── Shape / motion ──────────────────────────────────────────────────── */
  --radius:       12px;
  --radius-sm:    8px;
  --shadow-card:  0 1px 0 rgba(16,23,30,0.04), 0 18px 40px -24px rgba(16,23,30,0.18);
  --shadow-pop:   0 24px 60px -24px rgba(16,23,30,0.35);
  /* Motion built by method (round 4, 2026-08-23 — .planning/design-rebrand-exploration.md § 11).
     Three durations, one curve; every transition/animation in the app takes
     one (web/test/invariant-motion-tokens.test.ts fails on a literal number).
     --dur-1 reacts to the hand: press, chip rise, popover open.
     --dur-2 appears or settles: toast, saved tick, empty state, wizard step.
     --dur-3 travels: a card leaving, a row moving; count-up runs 1.3× it,
     and the verdict word settles over it. */
  --dur-1: 120ms;
  --dur-2: 180ms;
  --dur-3: 380ms;
  --ease-out: cubic-bezier(.2,.85,.3,1);

  /* ── Type ────────────────────────────────────────────────────────────── */
  --font:         'Bitter', 'Bitter Fallback', Georgia, 'Times New Roman', serif;
  --font-heading: 'Onest', 'Onest Fallback', Arial, ui-sans-serif, system-ui, sans-serif;
  --mono:         ui-monospace, 'SF Mono', Menlo, monospace; /* landing-page furniture only; the app has no mono face */

  /* The seven steps. Each carries its own line-height (unitless) and tracking
     (em). Sizes are rem so the browser's default-font-size preference scales
     them; html keeps the UA default so 1rem is the user's choice. The four
     steps above body step down one rung under 768px (the only type
     breakpoint); body and below never change, which also keeps every text
     field at 1rem (16px at the UA default = Apple's zoom-on-focus threshold). */
  --t-xs:  0.75rem;  --lh-xs:  1.33; --ls-xs:  0.02em;  --ls-caps: 0.08em; /* 12px: chips, eyebrows, th, hints */
  --t-sm:  0.875rem; --lh-sm:  1.43; --ls-sm:  0.01em;                     /* 14px: table cells, metadata, secondary copy */
  --t-md:  1rem;     --lh-md:  1.5;  --ls-md:  0;                          /* 16px: body, inputs, buttons, nav */
  --t-lg:  1.25rem;  --lh-lg:  1.3;  --ls-lg:  -0.01em;                    /* 20px: h3, card + dialog titles */
  --t-xl:  1.5rem;   --lh-xl:  1.25; --ls-xl:  -0.015em;                   /* 24px: h2 */
  --t-2xl: 1.875rem; --lh-2xl: 1.2;  --ls-2xl: -0.02em;                    /* 30px: h1 */
  --t-3xl: 2.5rem;   --lh-3xl: 1.1;  --ls-3xl: -0.025em;                   /* 40px: greeting, big numbers, the verdict word */

  /* ── Layout ──────────────────────────────────────────────────────────── */
  --nav-w:        240px;
  --page-max:      1200px;   /* .page: hubs, detail and reading surfaces */
  --page-max-wide: 1600px;   /* .page--wide: data surfaces (tables, result lists) */
  --grain-opacity: 0.025;
}

@media (max-width: 767px) {
  :root { --t-lg: 1.125rem; --t-xl: 1.25rem; --t-2xl: 1.5rem; --t-3xl: 1.875rem; }
}

/* Reduced motion: faster and simpler, never frozen. Zeroing the durations
   makes every tokenised transition instant in one place; the spinner and the
   indeterminate sweeps keep their own slow-not-stop rules on purpose. */
@media (prefers-reduced-motion: reduce) { :root { --dur-1: 0ms; --dur-2: 0ms; --dur-3: 0ms; } }

html, body { height: 100%; }

body {
  font-family: var(--font);
  font-size: var(--t-md);
  line-height: var(--lh-md);
  letter-spacing: var(--ls-md);
  font-optical-sizing: auto;
  color: var(--text);
  background: var(--bg);
  display: flex;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Numbers that sit in a column or update in place: tabular lining figures in
   the body face. Display-size numbers (.stat-style) are proportional — a
   tabular "1" at 40px leaves a gap each side. No mono face anywhere. */
.num { font-variant-numeric: lining-nums tabular-nums; }

/* ── Grain overlay ───────────────────────────────────────────────────── */

body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
  opacity: var(--grain-opacity);
  filter: url(#grain-filter);
}

/* ── Typography ──────────────────────────────────────────────────────── */

h1, h2, h3 { font-family: var(--font-heading); font-weight: 600; text-wrap: balance; }
h1 { font-size: var(--t-2xl); line-height: var(--lh-2xl); letter-spacing: var(--ls-2xl); margin-bottom: 18px; }
h2 { font-size: var(--t-xl);  line-height: var(--lh-xl);  letter-spacing: var(--ls-xl);  margin-bottom: 12px; }
h3 { font-size: var(--t-lg);  line-height: var(--lh-lg);  letter-spacing: var(--ls-lg);  margin-bottom: 8px; }

/* The voice: Onest at 500 on the greeting, empty states and the job-card
   title — the surfaces that carry Will's register. Never in forms or tables. */
.voice { font-family: var(--font-heading); font-weight: 500; }

/* Links: fox text (accent step 11), hover = underline only. The one hover law
   (see § Buttons) — colour never changes on hover, the cue is the underline. */
a { color: var(--accent-text); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 3px; }

/* Links embedded inline within a paragraph of body text need a non-color
   cue at rest too (WCAG 1.4.1 / axe link-in-text-block) — the default `a`
   rule relies on color alone, which is fine for nav/button-shaped links but
   not for a link surrounded by plain-text prose. */
.inline-text-link {
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ── Focus ───────────────────────────────────────────────────────────────
   One ring, keyboard only. `:focus-visible` never matches a mouse click on a
   control, so the ring means "you got here by keyboard". Two deliberate
   exceptions:
   1. Text inputs show their own focus cue on EVERY focus (border + soft halo,
      below) — the caret alone is too small a signal, and that is the norm.
   2. Regions that are focused by SCRIPT as a screen-reader landing point —
      `<main tabindex="-1">`, the auth `role="status"` panels, the sections
      `jobs/[id]` and `resume` focus after a state change — get NO ring.
      `tabindex="-1"` makes a region click-focusable, and a click followed by
      any keypress (or a bfcache back/forward restore) flips `:focus-visible`
      on, painting a fox rectangle around the whole region. That was the
      "ring that shows up when it shouldn't" (Will, 2026-08-22). Interactive
      elements with a roving `tabindex="-1"` (chips, menu items) are NOT in the
      list and keep their ring. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
:is(main, section, article, div, h1, h2, h3, p)[tabindex="-1"]:focus,
:is(main, section, article, div, h1, h2, h3, p)[tabindex="-1"]:focus-visible {
  outline: none;
}

/* ── Nav / sidebar ──────────────────────────────────────────────────── */

nav {
  width: var(--nav-w);
  min-height: 100vh;
  padding: 24px 16px;
  background: var(--surface);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex-shrink: 0;
  position: relative;
}

.nav-brand-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0;
}

.nav-burger { display: none; }

.nav-tray {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  gap: 2px;
}

.nav-backdrop { display: none; }

nav .logo {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 8px 20px;
  margin-bottom: 8px;
  color: var(--text);
  text-decoration: none;
  font-family: var(--font-heading);
  font-size: var(--t-md);
  font-weight: 600;
  letter-spacing: var(--ls-md);
}

nav .logo::before {
  content: 'W';
  width: 26px; height: 26px;
  flex-shrink: 0;
  border-radius: 6px;
  background: var(--accent);
  color: var(--on-accent);
  display: grid; place-items: center;
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: var(--t-sm);
  line-height: 1;
}

nav a {
  display: flex;
  align-items: center;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  color: var(--ink-2);
  text-decoration: none;
  font-size: var(--t-md);
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  transition: background var(--dur-1), color var(--dur-1);
}

nav a:hover {
  background: var(--border-ui);
  color: var(--text);
}

/* --on-accent on --accent = 4.60:1, clears AA at nav's 13.5px/500. */
nav a.active {
  background: var(--accent);
  color: var(--on-accent);
}

nav .sidebar-footer {
  margin-top: auto;
  padding: 12px 8px 0;
  border-top: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 10px;
}

/* --on-accent on --accent = 4.60:1, clears AA at this weight/size. */
nav .avatar {
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-accent);
  display: grid; place-items: center;
  font-weight: 600;
  font-size: var(--t-xs);
  font-family: var(--font-heading);
  letter-spacing: 0;
  flex-shrink: 0;
  text-transform: uppercase;
}

nav .who {
  font-size: var(--t-sm);
  font-weight: 500;
  line-height: 1.2;
  min-width: 0;
  flex: 1;
}
nav .who small {
  display: block;
  color: var(--muted);
  font-size: var(--t-xs);
  font-weight: 400;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

nav .logout-btn {
  all: unset;
  color: var(--muted);
  cursor: pointer;
  padding: 4px;
  border-radius: var(--radius-sm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--t-sm);
  white-space: nowrap;
  transition: color var(--dur-1), background var(--dur-1);
}
nav .logout-btn:hover { color: var(--text); background: var(--border-ui); }
nav .logout-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm); }

/* ── Main content ────────────────────────────────────────────────────── */

main {
  flex: 1;
  /* min-width:0 overrides the flex min-width:auto floor — without it a wide
     table's min-content width propagates through main and the whole PAGE
     scrolls horizontally on mobile instead of the .table-scroll container. */
  min-width: 0;
  padding: 32px 40px;
  overflow-y: auto;
}

/* ── Page containers ─────────────────────────────────────────────────── */

/* main has no cap of its own: each Base-shell page's ROOT picks one, and the
   cap is CENTRED once the pane outgrows it. A left-pinned cap beside the fixed
   sidebar reads as a phone layout on a wide monitor (Home at 2000px: 40px on
   the left, 600px dead on the right). Two caps, chosen by what the page IS:
   .page (1200px) for hubs, detail and reading surfaces; .page--wide (1600px)
   for data surfaces that earn the width (tables, result lists). Settings keeps
   its own 960px form cap below. A cap is inert until the pane exceeds it
   (viewport ≈ cap + 240px nav + 80px padding: ~1520px for .page, ~1280px for
   Settings), so a 1440px laptop only sees Settings move. */
.page { max-width: var(--page-max); margin-inline: auto; }
.page--wide { max-width: var(--page-max-wide); }

/* ── Tables ──────────────────────────────────────────────────────────── */

/* position:relative makes the scroll container the containing block for
   absolutely-positioned descendants — the .sr-only (position:absolute) boxes
   inside th/td otherwise resolve their containing block OUTSIDE the clip
   (.search-page or the viewport), park at their static position inside the
   wide table, and hand main/the page a phantom horizontal scroll on mobile. */
.table-scroll { overflow-x: auto; position: relative; }

table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--t-sm);
}

thead th {
  text-align: left;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  color: var(--muted);
  font-weight: 500;
  font-size: var(--t-xs);
  text-transform: uppercase;
  letter-spacing: var(--ls-caps);
  white-space: nowrap;
}

tbody td {
  padding: 11px 12px;
  border-bottom: 1px solid var(--border-ui);
  vertical-align: middle;
}

tbody tr:hover td { background: var(--border-ui); }

tbody tr:last-child td { border-bottom: none; }

/* ── Status badges (outlined pills) ───────────────────────────────────── */

.badge {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  border: 1px solid currentColor;
  font-size: var(--t-xs);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--ls-caps);
  background: transparent;
  text-align: center;
}

.badge-saved      { color: var(--success); }

/* ── Buttons ─────────────────────────────────────────────────────────
   One hierarchy, one hover law (2026-08-22, bench:
   .planning/research/2026-08-22-color-palette-method.md § Status):
     primary    — fox fill, white text. THE default <button>. One per screen.
     secondary  — outlined: surface fill, ink text, border.
     ghost      — borderless, ink-2 text (Undo, Skip, Dismiss).
     danger     — danger-soft fill → danger fill.
   Hover law: a FILLED thing goes one step darker at the same hue; an UNFILLED
   thing takes the neutral wash (--border-ui) and its text goes to ink; links
   underline. Nothing lifts. Active = 1px push. Focus = the global fox ring.
   The old ink-black default button is gone on purpose — it read as lazy and
   never popped (Will). `.accent` / `.btn-accent` are kept as aliases of the
   default so existing markup keeps working. */

button, .btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px;
  border-radius: var(--radius-sm);
  font-size: var(--t-md);
  font-weight: 500;
  font-family: var(--font);
  cursor: pointer;
  border: 1px solid transparent;
  background: var(--accent);
  color: var(--on-accent);
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--dur-1), color var(--dur-1), border-color var(--dur-1), transform var(--dur-1);
}

button:hover, .btn:hover {
  background: var(--accent-dark);
  text-decoration: none;
}

button:active, .btn:active { transform: translateY(1px); }

button:disabled, .btn[aria-disabled="true"] { opacity: 0.45; cursor: not-allowed; }

button.accent, .btn-accent { background: var(--accent); color: var(--on-accent); }
button.accent:hover, .btn-accent:hover { background: var(--accent-dark); }

button.secondary, .btn-secondary {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
}

button.secondary:hover, .btn-secondary:hover {
  background: var(--border-ui);
  border-color: var(--ink-2);
  color: var(--text);
}

button.ghost, .btn-ghost {
  background: transparent;
  color: var(--ink-2);
  border: 1px solid transparent;
}
/* The wash alone is one palette step off the page ground (--border-ui vs --bg)
   and vanished there (the feed's Refresh; reported on Settings' Change email,
   since moved to .btn-secondary) — reuse the whole-card hover's --ink-2 border
   move so the stroke clears 3:1 on both grounds. */
button.ghost:hover, .btn-ghost:hover { background: var(--border-ui); color: var(--text); border-color: var(--ink-2); }

button.danger {
  background: var(--danger-soft);
  color: var(--danger);
  border: 1px solid var(--danger-soft);
}
button.danger:hover { background: var(--danger); color: var(--on-accent); border-color: var(--danger); }

button.ghost-danger, .btn-ghost-danger {
  background: transparent;
  color: var(--danger);
  border: 1px solid transparent;
}
button.ghost-danger:hover, .btn-ghost-danger:hover {
  background: var(--danger-soft);
  color: var(--danger);
  border-color: transparent;
}

button.sm, .btn-sm { padding: 6px 12px; font-size: var(--t-sm); }

/* `<label class="btn">` is used as a file-input proxy. The form `label { ... }`
   rule below sets uppercase / block / extra letter-spacing for field labels;
   override those four properties when the label is acting as a button. */
label.btn {
  display: inline-flex;
  margin-bottom: 0;
  text-transform: none;
  letter-spacing: 0;
}

/* ── Busy spinner (shared) ───────────────────────────────────────────── */
/* Every user-visible wait shows motion (phone QA 2026-07-19): text-only
   status reads as stalled. Inline next to text at 14px; add .spinner--lg
   for standalone/centered uses. */
.spinner {
  display: inline-block;
  width: 14px; height: 14px;
  vertical-align: -2px;
  border: 2px solid var(--accent-soft);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
/* .spinner's display beats the UA's [hidden]{display:none} (author origin
   wins) — without this override a hidden spinner still renders and spins. */
.spinner[hidden] { display: none; }
.spinner--lg { width: 28px; height: 28px; border-width: 3px; }
button .spinner, .btn .spinner { margin-right: 8px; }
@keyframes spin { to { transform: rotate(360deg); } }
/* Keep the motion (it IS the busy signal) but slow it well below the
   vestibular-trigger range instead of freezing it. */
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 2.4s; } }

/* ── Indeterminate sweep (shared) ───────────────────────────────────────
   An honest "we're working, no percentage to show" bar. Donor: the
   onboarding finish step; reused by the /jobs scoring state. Reduced motion
   keeps a static half-opacity bar — the state must still read as busy. */
.progress-track {
  height: 4px; border-radius: 2px; background: var(--accent-soft);
  overflow: hidden; margin: 20px 0 4px; position: relative;
}
.progress-track--inline { width: min(240px, 100%); margin: 0 auto 16px; }
.progress-glow {
  position: absolute; top: 0; left: -30%; width: 30%; height: 100%;
  border-radius: 2px; background: var(--accent);
  animation: ob-sweep 1.8s ease-in-out infinite;
}
@keyframes ob-sweep { 0% { left: -30%; } 100% { left: 100%; } }
@media (prefers-reduced-motion: reduce) { .progress-glow { animation: none; left: 0; width: 100%; opacity: 0.5; } }

/* ── Forms ───────────────────────────────────────────────────────────── */

/* .form-stack = a field group that can't be a <form> (settings' change-email and
   delete-account panels — see the markup comments) but stacks like one. */
form, .form-stack { display: flex; flex-direction: column; gap: 14px; max-width: 480px; }
.form-stack[hidden] { display: none; }

/* Submit + secondary + saved-tick/error on one line, wrapping on narrow widths. */
.form-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }

/* Buttons size to their content (label + padding), not the form's full width.
   The column-flex form's default align-items:stretch would otherwise blow
   submit buttons out to 480px — a mobile-app pattern that reads as heavy on a
   desktop form. Inputs/selects still stretch. */
form > button,
form > .btn,
form > label.btn { align-self: flex-start; }

label {
  font-size: var(--t-sm);
  font-weight: 500;
  color: var(--ink-2);
  display: block;
  margin-bottom: 4px;
  letter-spacing: var(--ls-sm);
}

/* Checkbox ROW labels — a <label> wrapping a control plus body text (settings
   remote-only/notification toggles, role-family grid). Opts out of the
   form-caption treatment above; .geo-picker-option carries the same resets. */
.check-row {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-size: var(--t-md);
  text-transform: none;
  letter-spacing: normal;
  color: var(--text);
  font-weight: 400;
  margin-bottom: 0;
}

input[type="text"],
input[type="search"],
input[type="email"],
input[type="password"],
input[type="url"],
input[type="date"],
select,
textarea {
  width: 100%;
  padding: 9px 12px;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: var(--t-md);
  font-family: var(--font);
  transition: border-color var(--dur-1), box-shadow var(--dur-1);
}

/* Without an appearance reset, iOS/WebKit keeps the native inset-pill search
   chrome and ignores most of the field styling above. */
input[type="search"] {
  appearance: none;
}

select { padding-right: 28px; }

input[type="checkbox"] { accent-color: var(--accent); }

::placeholder { color: var(--muted); opacity: 0.7; }

input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* Readonly = display-only (e.g. the settings email field, changed via its own
   verified flow, not in place). Without this it looks identical to an
   editable field. Focus ring left untouched — readonly still tabs into it
   (unlike disabled) and someone tabbing here almost certainly wants to
   select/copy the value, so it needs its usual focus indicator (WCAG 2.4.7). */
input[readonly] {
  background: var(--bg);
  color: var(--muted);
  cursor: default;
}

textarea {
  min-height: 120px;
  resize: vertical;
}

/* ── Tag input ───────────────────────────────────────────────────────── */

.tag-input {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  cursor: text;
  transition: border-color var(--dur-1), box-shadow var(--dur-1);
}

.tag-input:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.tag-input .tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 6px;
  cursor: default;
}

.tag-input .tag-remove,
.geo-picker .tag-remove {
  /* Reset the global `button, .btn` rule — this is a real <button> for
     keyboard a11y (A11Y-10), but must stay a bare × inside the chip, not
     balloon into a full app button. */
  appearance: none;
  background: none;
  border: none;
  padding: 0;
  color: inherit;
  cursor: pointer;
  opacity: 0.5;
  font-size: var(--t-sm);
  line-height: 1;
  transition: opacity var(--dur-1);
}

.tag-input .tag-remove:hover,
.geo-picker .tag-remove:hover { opacity: 1; }
.tag-input .tag-remove:focus-visible,
.geo-picker .tag-remove:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; opacity: 1; }

.tag-input input {
  border: none !important;
  outline: none !important;
  box-shadow: none !important;
  padding: 2px 4px !important;
  font-size: var(--t-md);
  background: transparent;
  flex: 1;
  min-width: 120px;
}


/* Function-profile curation chips (onboarding step 3 + Settings § Role profile).
   Toggle chips: surface-bordered off, accent-filled on — hovers per the
   design-system sibling rules (surface→border-ui keeping text; accent→accent-dark
   keeping white). */
.profile-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 16px 0; }
/* Tag-sized (--t-sm), not button-sized: they sit under --t-sm intro copy and
   a dozen of them render at once. Selected = the lane cards' quiet state
   (soft tint + accent border, ink text) — a solid fox fill on 12 of 12 was a
   wall of orange (owner, 2026-08-28). Unselected keeps a visible --border
   hairline so the two states never depend on colour alone. */
.profile-chip {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 12px;
  font-size: var(--t-sm);
}
.profile-chip:hover { background: var(--border-ui); color: var(--text); }
.profile-chip[aria-pressed="true"] {
  background: var(--accent-soft);
  color: var(--text);
  border-color: var(--accent);
}
.profile-chip[aria-pressed="true"]:hover { background: var(--accent-soft-dark); color: var(--text); }
.profile-chips-added { margin: 0 0 8px; }
.profile-chips-count { margin: 0 0 16px; font-size: var(--t-sm); }

/* S2 lane cards — the preview-enriched candidate chips. Selected state is
   deliberately QUIET (owner, 2026-08-10: "can't be a wall of orange" — every
   candidate seeds selected, so an accent fill painted the whole step): soft
   tint + accent border + ✓, ink text throughout, title carrying the weight.
   Hover contract: every state sets background AND color (web-frontend rule). */
.profile-lanes { flex-direction: column; align-items: stretch; }
.profile-lane {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  text-align: left;
  /* The global `button` rule nowraps — sample lines must wrap or the card's
     intrinsic width blows past its container (caught in the 1280 screenshot). */
  white-space: normal;
  width: 100%;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border-ui);
  border-radius: var(--radius-sm);
  padding: 10px 14px;
  font-size: var(--t-md);
}
.profile-lane:hover { background: var(--border-ui); color: var(--text); }
.profile-lane[aria-pressed="true"] {
  background: var(--accent-soft);
  color: var(--text);
  border-color: var(--accent);
}
.profile-lane[aria-pressed="true"]:hover { background: var(--accent-soft-dark); color: var(--text); }
.profile-lane-title { font-weight: 600; font-size: var(--t-md); }
/* No check indicator (owner, 2026-08-10) — tint + accent border carry the
   selected state on their own. */
.profile-lane-meta { font-size: var(--t-sm); opacity: 0.8; }
.profile-lane-count { font-variant-numeric: lining-nums tabular-nums; }
.profile-lane-samples { font-size: var(--t-sm); opacity: 0.7; }
.profile-lanes-note { margin: 0 0 12px; font-size: var(--t-sm); }
.profile-preview-line { margin: 4px 0 8px; font-size: var(--t-sm); }
.profile-preview-line:empty { display: none; }

/* ── Geo picker (country / state multi-select) ───────────────────────── */

.geo-picker {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.geo-picker-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  min-height: 22px;
}

.geo-picker-search {
  width: 100%;
  font-size: var(--t-md);
}

.geo-picker-list {
  max-height: 220px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  padding: 4px;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.geo-picker-option {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 8px;
  border-radius: var(--radius-sm);
  font-size: var(--t-md);
  cursor: pointer;
  /* These rows are <label> elements, so they inherit the global form-caption
     `label` treatment (uppercase, letterspaced, muted) — reset to body text. */
  text-transform: none;
  letter-spacing: normal;
  color: var(--text);
  font-weight: 400;
  margin-bottom: 0;
}
.geo-picker-option:hover { background: var(--bg); }
.geo-picker-option input { margin: 0; flex: 0 0 auto; }
/* `.geo-picker-option` sets display:flex, which beats the UA `[hidden]` rule;
   this higher-specificity rule restores hiding for the search filter. */
.geo-picker-option[hidden] { display: none; }

.geo-picker-empty {
  padding: 5px 8px;
  font-size: var(--t-md);
  color: var(--muted);
}
.geo-picker-empty[hidden] { display: none; }

@keyframes shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

select:not(form select) { width: auto; }

/* ── Utility ─────────────────────────────────────────────────────────── */

.muted   { color: var(--muted); }
.score   { font-variant-numeric: lining-nums tabular-nums; font-weight: 500; color: var(--text); }
.empty   { text-align: center; padding: 48px; color: var(--muted); }
.text-sm { font-size: var(--t-sm); }

/* Keyboard hint chip (#14): <span class="kbd">→</span> beside an action that
   has a key. Hidden on touch-only devices — a key hint with no keyboard is
   noise. */
.kbd {
  font-size: var(--t-xs);
  font-variant-numeric: lining-nums tabular-nums;
  padding: 2px 6px;
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--muted);
  background: var(--bg);
  line-height: 1.4;
}
@media (hover: none) and (pointer: coarse) { .kbd { display: none; } }

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.skip-link:focus,
.skip-link:focus-visible {
  position: fixed;
  top: 12px;
  left: 12px;
  width: auto;
  height: auto;
  margin: 0;
  padding: 10px 16px;
  overflow: visible;
  clip: auto;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-card);
  text-decoration: none;
  z-index: 10000;
}

/* ─── Score breakdown (Layer 2+3) ─────────────────────────────────── */

.score-breakdown {
  margin-bottom: 20px;
  padding: 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.score-dimensions {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.dimension-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.dimension-label {
  font-size: var(--t-sm);
  font-weight: 500;
  width: 90px;
  flex-shrink: 0;
  color: var(--ink-2);
}

.dimension-bar-track {
  flex: 1;
  height: 6px;
  background: var(--border-ui);
  border-radius: 3px;
  overflow: hidden;
}

/* Fills draw in once, staggered 60ms per row, when the breakdown scrolls into
   view (jobs/[id].astro → revealOnce). Width stays the value; scaleX is the
   animation so the number beside the bar is the source of truth throughout. */
.dimension-bar-fill {
  height: 100%;
  border-radius: 3px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform calc(var(--dur-3) * 1.3) var(--ease-out);
  transition-delay: var(--stagger, 0ms);
}
.score-dimensions.in .dimension-bar-fill { transform: scaleX(1); }

.dimension-value {
  font-variant-numeric: lining-nums tabular-nums;
  font-size: var(--t-sm);
  font-weight: 500;
  color: var(--text);
  width: 32px;
  text-align: right;
  flex-shrink: 0;
}

.confidence-badge {
  flex-shrink: 0;
  min-width: 78px; /* widest label (MEDIUM) in .badge's padding — measured, keeps the bar tracks equal */
}

/* HIGH is the default; keep the slot (equal bar tracks) and the text for AT, hide only the paint. */
.confidence-badge.confidence-high { color: transparent; border-color: transparent; }
.confidence-medium { color: var(--warn); }
.confidence-low    { color: var(--muted); }

.gap-factors {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--border-ui);
}

.gap-factors-title {
  display: block;
  font-size: var(--t-xs);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--ls-caps);
  color: var(--muted);
  margin-bottom: 6px;
}

.gap-factor-item {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--t-sm);
  padding: 3px 0;
  color: var(--ink-2);
}

.gap-factor-impact {
  flex-shrink: 0;
  min-width: 78px; /* same slot as .confidence-badge — keeps the impact labels aligned down the list */
}

.gap-impact-high   { color: var(--danger); }
.gap-impact-medium { color: var(--warn); }
.gap-impact-low    { color: var(--muted); }

/* ── Optimize panel ──────────────────────────────────────────────────── */

.optimize-panel {
  margin-top: 32px;
  padding: 24px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
}

/* The panel's three mutually-exclusive states (empty/stale/results) each
   build an <h2> as their own primary heading — not h3, this is the panel's
   top-level heading, sibling to "Match Analysis"/"Role summary" at the page
   outline level. Selector was stale against the actual DOM (audit Issue 15). */
.optimize-panel h2 {
  font-family: var(--font-heading);
  font-size: var(--t-lg);
  font-weight: 600;
  letter-spacing: var(--ls-lg);
  margin: 0 0 12px;
}

.opt-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 4px;
}

.opt-header h2 { margin-bottom: 0; }

.budget-chip {
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--muted);
  font-size: var(--t-sm);
}

.opt-empty-desc {
  color: var(--muted);
  font-size: var(--t-md);
  margin-bottom: 16px;
  max-width: 560px;
}

.optimize-panel details {
  margin-top: 16px;
  border-top: 1px solid var(--border-ui);
  padding-top: 12px;
}

.optimize-panel summary {
  cursor: pointer;
  font-weight: 500;
  color: var(--ink-2);
  padding: 4px 0;
  user-select: none;
}

/* Keyword pills */

.opt-pills-wrap {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 10px;
}

.kw-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: var(--t-sm);
}

/* Importance is an opportunity, not a fault: no --danger here (only .gap-impact-high keeps red). */
.kw-importance.high   { color: var(--accent-text); }
.kw-importance.medium { color: var(--ink-2); }
.kw-importance.low    { color: var(--muted); }

/* Gaps */

.opt-gaps-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 10px;
}

.opt-gap-item {
  padding: 10px 12px;
  font-size: var(--t-sm);
}

.opt-gap-top {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}

.opt-gap-action {
  color: var(--ink-2);
  font-size: var(--t-sm);
}

.opt-impact-chip {
  flex-shrink: 0;
}

/* A high-impact suggestion is good news: no --danger here (only .gap-impact-high keeps red). */
.opt-impact-high   { color: var(--accent-text); }
.opt-impact-medium { color: var(--ink-2); }
.opt-impact-low    { color: var(--muted); }

/* Formatting flags */

.opt-flags-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 10px;
}

.opt-flag-item {
  padding: 10px 12px;
  font-size: var(--t-sm);
}

.opt-flag-issue {
  font-weight: 500;
  color: var(--text);
}

.opt-flag-fix {
  color: var(--ink-2);
  font-size: var(--t-sm);
  margin-top: 2px;
}

/* Assessment */

.opt-assessment {
  margin-top: 10px;
  font-size: var(--t-md);
  color: var(--ink-2);
  line-height: 1.6;
}

/* Section blurb (intro line under each <summary>) */

.opt-section-blurb {
  margin: 6px 0 4px;
  font-size: var(--t-sm);
  color: var(--muted);
  line-height: 1.45;
}

/* Format-check category badge */

.opt-fc-cat {
  flex-shrink: 0;
}
.opt-fc-cat-parsing      { color: var(--ink-2); }
.opt-fc-cat-bias_trigger { color: var(--warn); }
.opt-fc-cat-structure    { color: var(--accent-text); }

/* Density block */

.opt-density {
  margin-top: 10px;
  font-size: var(--t-sm);
  color: var(--ink-2);
  line-height: 1.5;
}

.opt-density-msg {
  color: var(--muted);
}

.tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: var(--t-sm);
  letter-spacing: var(--ls-lg);
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--ink-2);
}

/* ── Login / auth pages ──────────────────────────────────────────────── */

.login-wrap {
  min-height: 100vh;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px 16px;
  background: var(--bg);
}

.login-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 36px 32px;
  width: 100%;
  max-width: 380px;
  box-shadow: var(--shadow-card);
}

.login-card h1 {
  font-size: var(--t-2xl);
  letter-spacing: var(--ls-2xl);
  margin-bottom: 4px;
}

.login-card p {
  color: var(--muted);
  margin-bottom: 24px;
  font-size: var(--t-md);
}

/* Brand eyebrow above the purpose <h1> on every auth page (A11Y-18). */
.login-card .brand-line {
  color: var(--accent-text);
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: var(--t-md);
  margin-bottom: 4px;
}

.error-msg { color: var(--danger); font-size: var(--t-sm); margin-top: 4px; }

/* ── Landing page — the viewport IS the broadcast ─────────────────────────
   Public `/` only (a session renders HomeStatus instead). `body.landing` is a
   fixed, non-scrolling column: header on top, one full-bleed broadcast filling
   everything under it. The CRT stopped being an object ON the page — the page
   IS the tube, so the scanlines, curvature vignette and glass sheen run edge to
   edge and the bezel controls became a floating remote.

   Palette note: this whole section is deliberately off-palette dark, for the
   same reason the old CRT housing was — it is a foreign object on the warm
   canvas, not a themed component. Don't re-skin it when the site palette moves.

   Stacking inside `.crt` (which isolates, so these stay local):
     1 .crt-tube      picture, then INSIDE it: scrims (1) → vignette (2) →
                      LIVE flag + chyron (3), so the broadcast graphics sit on
                      top of the tube falloff instead of under it
     2 .crt::before   scanlines — the one layer that covers everything in the tube
     4 .crt-glass     reflection sheen (and the power-off dying scanline)
     5 .broadcast-copy  h1 + CTA — above the scanlines, so it stays crisp
     6 .crt-remote    PWR + CH
   The h1, CTA and remote sit OUTSIDE `.crt-tube` on purpose: powering the set
   off must not take the primary CTA — or the button that powers it back on —
   with it. */

body.landing {
  position: relative;
  flex-direction: column;
  height: 100dvh;
  min-height: 0;
  overflow: hidden;
  background: #05070c;
}

/* The ident floats OVER the picture on its own scrim — no bar, so the broadcast
   starts at y=0. z-index 10 puts it above the scanline layer: the one control an
   existing user needs (Log In) stays crisp. */
.landing-header {
  position: absolute;
  z-index: 10;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: clamp(14px, 2.4vh, 26px) clamp(20px, 3.4vw, 56px);
  background: linear-gradient(to bottom, rgba(3,6,12,0.82) 0%, rgba(3,6,12,0.4) 55%, transparent 100%);
  pointer-events: none;
}
.landing-header a { pointer-events: auto; }

.landing-ident {
  display: inline-flex;
  align-items: center;
  gap: clamp(8px, 0.9vw, 13px);
  text-decoration: none;
}
.landing-ident:hover { text-decoration: none; }

.landing-ident-tile {
  display: grid;
  place-items: center;
  width: clamp(26px, 2.1vw, 36px);
  aspect-ratio: 1;
  border-radius: 6px;
  background: #f7f2e6;
  color: #15130f;
  font-family: var(--mono);
  font-weight: 600;
  font-size: clamp(13px, 1.1vw, 18px);
}

.landing-ident-name {
  font-family: var(--font-heading);
  font-size: clamp(14px, 1.5vw, 25px);
  font-weight: 600;
  line-height: 1;
  color: #fff;
  text-shadow: 0 2px 10px rgba(0,0,0,0.6);
}
/* The ident takes the channel's colour with it. The channel class lives on
   `.crt`; :has() reads it from a sibling rather than duplicating that state onto
   <body> and having two sources of truth for which channel is on. */
.landing-ident-name b {
  font-weight: 600;
  padding: 0 0.18em;
  background: #b8342a;
}
body:has(.crt-fine) .landing-ident-name b { background: #2c7a4b; }

.landing-meta {
  display: flex;
  align-items: center;
  gap: clamp(10px, 1.4vw, 20px);
}

.landing-clock {
  font-family: var(--mono);
  font-size: clamp(11px, 0.85vw, 14px);
  letter-spacing: 0.1em;
  /* #b9b3a6 on the scrimmed picture clears AA; it is decorative either way. */
  color: #b9b3a6;
}

.landing-login {
  font-family: var(--font);
  font-size: clamp(12px, 0.92vw, 14px);
  font-weight: 500;
  letter-spacing: 0;
  color: #f7f2e6;
  text-decoration: none;
  border: 1px solid rgba(247,242,230,0.42);
  padding: clamp(7px, 0.9vh, 11px) clamp(14px, 1.3vw, 22px);
  border-radius: var(--radius-sm);
  background: rgba(12,16,24,0.4);
  backdrop-filter: blur(6px);
  transition: border-color 0.12s, background 0.12s, color 0.12s;
}
.landing-login:hover {
  background: rgba(247,242,230,0.94);
  color: #15130f;
  border-color: rgba(247,242,230,0.94);
  text-decoration: none;
}

body.landing main { display: contents; }

/* ── The stage ─────────────────────────────────────────────────────────── */

.crt {
  position: relative;
  flex: 1;
  min-height: 0;
  overflow: hidden;
  isolation: isolate;
  background: #05070c;
}

/* Scanlines. They cover the picture and the chyron — broadcast content — but
   not the h1/CTA, which sit above them and stay crisp. */
.crt::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: repeating-linear-gradient(
    to bottom,
    transparent 0px,
    transparent 2px,
    rgba(0,0,0,0.16) 2px,
    rgba(0,0,0,0.16) 4px
  );
  animation: crt-flicker 0.08s infinite alternate;
}

/* Tube curvature: the picture falling away into the corners. It lives INSIDE the
   tube, below the chyron and the LIVE flag — when it sat above them it was
   costing the bar ~35% of its brightness and the CH number was unreadable in the
   corner it darkens hardest. The scanlines still run over everything. */
.crt-tube::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: radial-gradient(ellipse 82% 76% at 50% 48%, transparent 55%, rgba(0,0,0,0.62) 100%);
  box-shadow: inset 0 0 clamp(60px, 9vw, 160px) clamp(20px, 3vw, 60px) rgba(0,0,0,0.46);
}

.crt-tube {
  position: absolute;
  inset: 0;
  z-index: 1;
  transform-origin: center;
}

.crt-plate {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Above centre — keeps the anchor's face out of the chyron on tall viewports. */
  object-position: 50% 38%;
}

/* CH 01 — the same shot, same framing, lights down. It stacks on top of the CH 00
   plate and fades, so the flip is one opacity change and never shows a gap.
   0.22s lands inside the 320ms signal glitch that fires with it. */
.crt-plate-real {
  opacity: 1;
  transition: opacity 0.22s ease;
}
.crt-fine .crt-plate-real { opacity: 0; }

/* The ghost: CH 01 punching through the break in the signal, twice, hard-cut.
   `steps(1, end)` and no fade on purpose — a crossfade reads as a transition,
   and this has to read as a fault. It is the discovery cue that there is another
   channel at all, so it is the PICTURE that blips; swapping the little CH label
   alone was invisible. */
.crt-fine.crt-ghost .crt-plate-real { animation: crt-ghost-blip 0.26s steps(1, end) 1; }
.crt-fine.crt-ghost .crt-tube { animation: crt-ghost-shift 0.26s steps(1, end) 1; }
@keyframes crt-ghost-blip {
  0%, 100%  { opacity: 0; }
  8%, 26%   { opacity: 1; }
  27%, 57%  { opacity: 0; }
  58%, 74%  { opacity: 1; }
}
/* The tube tears sideways on the same frames — a clean cut between two takes of
   the same shot reads as an edit; a torn one reads as interference. */
@keyframes crt-ghost-shift {
  0%, 100%  { transform: none; }
  8%, 26%   { transform: translateX(-5px) scaleY(1.006); }
  27%, 57%  { transform: none; }
  58%, 74%  { transform: translateX(3px); }
}

/* The tally light stops being reassuring too. */
.crt:not(.crt-fine) .crt-led {
  background: #cc6600;
  box-shadow: 0 0 6px rgba(204,102,0,0.7), 0 0 12px rgba(204,102,0,0.3);
}
.crt:not(.crt-fine) .crt-tube::after {
  background: radial-gradient(ellipse 80% 74% at 50% 48%, transparent 46%, rgba(0,0,0,0.78) 100%);
}

/* Legibility scrims: one raking in from the left under the headline, one rising
   from the bottom under the chyron. Both live here rather than on the <img>,
   which is a replaced element and renders no pseudos. */
.crt-tube::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(100deg, rgba(3,6,12,0.93) 0%, rgba(3,6,12,0.74) 24%, rgba(3,6,12,0.14) 50%, transparent 64%),
    linear-gradient(to top, rgba(3,6,12,0.88) 0%, rgba(3,6,12,0.34) 14%, transparent 28%);
}

/* Soft reflection across the glass, plus (::after) the dying scanline on power-off. */
.crt-glass {
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  background:
    linear-gradient(118deg, rgba(255,255,255,0.07) 0%, rgba(255,255,255,0.03) 20%, transparent 40%),
    linear-gradient(200deg, rgba(180,205,255,0.04) 0%, transparent 28%);
}
.crt-glass::after {
  content: '';
  position: absolute;
  left: 4%;
  right: 4%;
  top: 50%;
  height: 3px;
  border-radius: 3px;
  opacity: 0;
  background: #efe6d4;
  box-shadow: 0 0 26px 6px rgba(239,230,212,0.75);
  transform: translateY(-50%) scaleX(0.04);
}

/* ── Broadcast furniture ───────────────────────────────────────────────── */

.crt-flag {
  position: absolute;
  /* Clears the ident bar, which now floats over the top of the picture. */
  top: clamp(78px, 11vh, 122px);
  left: clamp(20px, 3.4vw, 56px);
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 9px;
  border-radius: 2px;
  /* #d13a2c is as bright as this red goes and still clears AA on white (4.8:1).
     The bloom does the rest of the work the brightness can't. */
  background: #d13a2c;
  color: #fff;
  box-shadow: 0 0 22px rgba(209,58,44,0.55);
  font-family: var(--mono);
  font-size: clamp(9px, 0.82vw, 13px);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
.crt-live-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #fff;
  animation: crt-live 1.6s infinite;
}


/* ── Lower third ───────────────────────────────────────────────────────── */

.crt-chyron {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3;
  padding: clamp(11px, 1.5vh, 18px) clamp(20px, 3.4vw, 56px) clamp(13px, 1.8vh, 22px);
  border-top: 2px solid #d8c489;
  /* Near-opaque the whole way across. The bar is under the scanlines AND under
     the vignette's inset shadow, which is heaviest at exactly this edge — a bar
     that faded out to the right left the CH number unreadable there. */
  background: linear-gradient(90deg, rgba(4,7,13,0.97) 0%, rgba(4,7,13,0.9) 100%);
}

.crt-kicker {
  display: inline-block;
  padding: 3px 8px;
  border-radius: 2px;
  background: #d13a2c;
  color: #fff;
  box-shadow: 0 0 18px rgba(209,58,44,0.45);
  font-family: var(--mono);
  font-size: clamp(9px, 0.82vw, 13px);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.crt-text {
  font-family: var(--font);
  font-size: clamp(16px, 1.85vw, 30px);
  font-weight: 500;
  line-height: 1.24;
  letter-spacing: -0.015em;
  color: #fdf8ec;
  /* One line reserved. The bar grows upward for a second, which only ever happens
     BETWEEN headlines — mid-scramble the character count is constant, so a
     resolving frame never reflows it. */
  min-height: 1.24em;
  text-shadow: 0 0 10px rgba(232,220,190,0.4), 0 0 26px rgba(200,180,120,0.25);
  animation: crt-phosphor 5.5s infinite;
}

.crt-byline {
  display: flex;
  align-items: center;
  gap: clamp(8px, 1vw, 14px);
  margin-bottom: clamp(7px, 1vh, 11px);
}
.crt-ch { margin-left: auto; }

.crt-source {
  padding: 3px 8px;
  border: 1px solid rgba(232,216,160,0.62);
  border-radius: 2px;
  font-family: var(--mono);
  font-size: clamp(9px, 0.78vw, 12px);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #e8d8a0;
}

.crt-ch {
  flex-shrink: 0;
  font-family: var(--mono);
  font-size: clamp(9px, 0.78vw, 12px);
  font-weight: 500;
  letter-spacing: 0.12em;
  /* Furthest into the vignette's dark corner of anything on the bar, so it needs
     the most headroom: #9a9488 read as unlit there. */
  color: #d5cfc2;
  transition: color 0.1s;
}
/* The other channel bleeding through the break in the signal — a fault, not a
   label. 150ms inside the 320ms glitch, so it reads as part of the same break. */
.crt-ch-ghost {
  color: #fff;
  text-shadow: 1.5px 0 rgba(255,70,60,0.8), -1.5px 0 rgba(70,200,255,0.7);
}

/* CH 00 — the network's own broadcast. Same furniture, opposite mood. */
.crt-fine .crt-kicker { background: #2c7a4b; box-shadow: 0 0 18px rgba(44,122,75,0.5); }
.crt-fine .crt-chyron { border-top-color: #5ee08c; }
.crt-fine .crt-source { border-color: rgba(150,236,190,0.62); color: #a9ecc4; }

/* ── Headline + CTA ────────────────────────────────────────────────────── */

.broadcast-copy {
  position: absolute;
  z-index: 5;
  left: clamp(20px, 3.4vw, 56px);
  top: 50%;
  translate: 0 -50%;
  width: min(46%, 620px);
  /* Sits above the chyron's reserved height on short viewports. */
  margin-top: clamp(-90px, -7vh, -40px);
}

.hero-headline {
  font-family: var(--font-heading);
  font-size: clamp(34px, 5.4vw, 88px);
  font-weight: 500;
  line-height: 1.02;
  letter-spacing: -0.03em;
  margin-bottom: clamp(18px, 2.6vh, 34px);
  color: #f7f2e6;
  text-wrap: balance;
  text-shadow: 0 2px 30px rgba(0,0,0,0.75);
}

.hero-cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font);
  font-size: clamp(14px, 1.05vw, 17px);
  font-weight: 500;
  letter-spacing: 0;
  /* Pure #fff (not the off-white #fbf8f1) on --accent = 4.60:1, clears AA. */
  color: #fff;
  background: var(--accent);
  padding: clamp(12px, 1.5vh, 18px) clamp(22px, 2vw, 34px);
  border-radius: var(--radius-sm);
  text-decoration: none;
  transition: background 0.12s, transform 0.12s;
}
.hero-cta:hover {
  background: var(--accent-dark);
  text-decoration: none;
}

/* ── The remote ────────────────────────────────────────────────────────── */

.crt-remote {
  position: absolute;
  z-index: 6;
  right: clamp(16px, 2.4vw, 40px);
  top: 50%;
  translate: 0 -50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 14px 10px 12px;
  border-radius: 12px;
  border: 1px solid rgba(255,255,255,0.09);
  background: linear-gradient(180deg, rgba(32,32,38,0.92), rgba(20,20,25,0.92));
  box-shadow: 0 14px 40px rgba(0,0,0,0.55), inset 0 1px 0 rgba(255,255,255,0.08);
  backdrop-filter: blur(6px);
}

.crt-panel-label {
  font-family: var(--mono);
  font-size: 7px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  /* #777 was 3.5:1 on the panel (failed AA); #a0a0a0 = ~6.3:1. */
  color: #a0a0a0;
  margin-top: 3px;
}

.crt-knob {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: radial-gradient(circle at 38% 35%, #3a3a3e 0%, #252528 50%, #161618 100%);
  box-shadow: 0 2px 4px rgba(0,0,0,0.6), inset 0 1px 1px rgba(255,255,255,0.06);
  position: relative;
  flex-shrink: 0;
}
.crt-knob::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 50%;
  transform: translateX(-50%);
  width: 2px; height: 7px;
  background: rgba(255,255,255,0.15);
  border-radius: 1px;
}

.crt-pwr {
  appearance: none;
  padding: 0;
  border: none;
  cursor: pointer;
  min-width: 28px;
  min-height: 28px;
  transition: box-shadow 0.2s;
}
.crt-pwr:hover {
  /* re-assert the knob gradient — generic `button:hover { background: var(--ink-2) }`
     outranks `.crt-knob` on hover and would flatten the gradient to solid ink */
  background: radial-gradient(circle at 38% 35%, #3a3a3e 0%, #252528 50%, #161618 100%);
  box-shadow: 0 2px 4px rgba(0,0,0,0.5), inset 0 1px 1px rgba(255,255,255,0.12), 0 0 8px rgba(0,204,68,0.3);
}
.crt-pwr:focus-visible,
.crt-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* Detent points up when powered, rotated when off. */
.crt-pwr::after { transform: translateX(-50%) rotate(0deg); transform-origin: center 11px; }
.crt-off .crt-pwr::after { transform: translateX(-50%) rotate(135deg); }

.crt-ch-pad {
  display: flex;
  gap: 5px;
  flex-shrink: 0;
}
.crt-btn {
  appearance: none;
  min-width: 26px;
  min-height: 26px;
  padding: 0;
  border: 1px solid #3a3a3e;
  border-radius: 5px;
  background: #26262a;
  color: #d8d4cc;
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1;
  justify-content: center;
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.06), 0 1px 2px rgba(0,0,0,0.5);
}
/* Both halves restated — the global `button:hover` sets background only, and this
   button overrides `color`, so inheriting that hover would strand the label. */
.crt-btn:hover {
  background: #3f3f45;
  color: #fff;
}

/* The dial twitches on the dare's beat. `.crt-nudge` is re-added with a reflow
   between, so it replays; there is no removal timer to keep in sync. */
.crt-nudge .crt-btn { animation: crt-nudge 1.15s ease-out 1; }
@keyframes crt-nudge {
  0%, 62%, 100% { transform: none; border-color: #3a3a3e; box-shadow: inset 0 1px 0 rgba(255,255,255,0.06), 0 1px 2px rgba(0,0,0,0.5); }
  14%           { transform: translateY(-2px); border-color: #7a7a84; box-shadow: 0 0 0 3px rgba(226,220,205,0.16), 0 0 14px rgba(226,220,205,0.28); }
  34%           { transform: translateY(0);    border-color: #5c5c66; box-shadow: 0 0 0 6px rgba(226,220,205,0); }
}

/* ← → under the pad. It is the only written instruction on the page, so it stays
   at the size of a set's silkscreen and never louder than the PWR/CH labels. */
.crt-keyhint {
  font-family: var(--mono);
  font-size: 9px;
  line-height: 1;
  letter-spacing: 0.12em;
  color: #8f8f95;
  margin-top: 5px;
}
/* Nothing to press on a touch device. */
@media (hover: none) {
  .crt-keyhint { display: none; }
}

.crt-led {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: #00cc44;
  box-shadow: 0 0 6px rgba(0,204,68,0.7), 0 0 12px rgba(0,204,68,0.3);
  margin-top: 6px;
  flex-shrink: 0;
  transition: background 0.3s, box-shadow 0.3s;
}

/* ── Power on ──────────────────────────────────────────────────────────────
   A real set does not snap on. The beam strikes as a bright horizontal line, the
   raster unfolds vertically out of it, and the phosphors overshoot and settle
   over the next half second — colour arrives last, because the tube is still
   warming. `crt-on` is added on power-up and removed when the run is over. */
.crt-on .crt-tube {
  animation: crt-warmup 0.86s cubic-bezier(0.22, 0.7, 0.2, 1) both;
}
.crt-on .crt-glass::after { animation: crt-strike 0.4s ease-out; }
.crt-on::before { animation: crt-flicker 0.05s 14 alternate; }

/* ── Power off ─────────────────────────────────────────────────────────────
   The whole viewport collapses to a scanline, then a dot. The copy and the
   remote survive: they are outside the tube. */
.crt-off .crt-tube {
  opacity: 0;
  animation: crt-collapse 0.42s cubic-bezier(0.3, 0, 0.2, 1) forwards;
}
.crt-off .crt-glass::after { animation: crt-dying-line 0.42s ease-out forwards; }
.crt-off::before { animation: none; opacity: 0; }
.crt-off .crt-led { background: #4a1a12; box-shadow: none; }

/* ── Keyframes ─────────────────────────────────────────────────────────── */

/* Chroma split on the headline while the signal is breaking up. */
@keyframes crt-split {
  0%   { transform: translateX(-3px); text-shadow: 3px 0 rgba(255,70,60,0.6), -3px 0 rgba(70,200,255,0.5); }
  50%  { transform: translateX(3px);  text-shadow: -4px 0 rgba(255,70,60,0.6), 4px 0 rgba(70,200,255,0.5); }
  100% { transform: translateX(0);    text-shadow: 0 0 10px rgba(232,220,190,0.4), 0 0 26px rgba(200,180,120,0.25); }
}
.crt-glitch .crt-text { animation: crt-split 0.32s steps(2, end) 1; }

/* Phosphor micro-flicker — the tube never sits perfectly still. */
@keyframes crt-phosphor {
  0%, 46%, 56%, 100% { opacity: 1; }
  48%                { opacity: 0.94; }
  50%                { opacity: 0.99; }
  52%                { opacity: 0.96; }
}

@keyframes crt-live {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.25; }
}

@keyframes crt-collapse {
  0%   { transform: scale(1, 1);        opacity: 1; }
  40%  { transform: scale(1, 0.012);    opacity: 1; }
  70%  { transform: scale(0.55, 0.006); opacity: 1; }
  100% { transform: scale(0, 0.004);    opacity: 0; }
}
/* Struck line → raster unfold → phosphor bloom → settle. The brightness and
   saturation curve is the part that reads as "warming up"; the scale alone just
   looks like a wipe. */
@keyframes crt-warmup {
  0%   { transform: scale(0.03, 0.004); opacity: 0; filter: brightness(7) contrast(0.35) saturate(0); }
  10%  { transform: scale(1, 0.006);    opacity: 1; filter: brightness(6) contrast(0.4) saturate(0); }
  26%  { transform: scale(1, 0.02);                 filter: brightness(4.5) contrast(0.55) saturate(0.1); }
  46%  { transform: scale(1, 0.34);                 filter: brightness(2.6) contrast(0.8) saturate(0.4); }
  62%  { transform: scale(1, 1);                    filter: brightness(1.7) contrast(0.95) saturate(0.72); }
  74%  { filter: brightness(0.86) saturate(0.9); }
  86%  { filter: brightness(1.14) saturate(1); }
  100% { transform: scale(1, 1); opacity: 1; filter: none; }
}
/* The beam striking, before there is a picture to show. */
@keyframes crt-strike {
  0%   { opacity: 0;    transform: translateY(-50%) scaleX(0.04); }
  18%  { opacity: 1;    transform: translateY(-50%) scaleX(1); }
  55%  { opacity: 0.7;  transform: translateY(-50%) scaleX(1); }
  100% { opacity: 0;    transform: translateY(-50%) scaleX(1); }
}

@keyframes crt-dying-line {
  0%   { opacity: 0;   transform: translateY(-50%) scaleX(1); }
  38%  { opacity: 0.9; transform: translateY(-50%) scaleX(1); }
  72%  { opacity: 1;   transform: translateY(-50%) scaleX(0.5); }
  100% { opacity: 0;   transform: translateY(-50%) scaleX(0.04); }
}

@keyframes crt-flicker {
  from { opacity: 0.97; }
  to   { opacity: 1; }
}

/* ── Narrow landscape ──────────────────────────────────────────────────────
   A phone on its side, or a small laptop. There IS still a left column, so the
   desktop composition holds — it just needs to be smaller. Splitting on
   ORIENTATION rather than width is what keeps a landscape tablet on the layout
   that suits it instead of the phone one. */
@media (orientation: landscape) and (max-width: 1100px) {
  .broadcast-copy { width: min(54%, 520px); }
  .hero-headline { font-size: clamp(28px, 5vw, 54px); }
  .hero-cta { font-size: 14px; padding: 11px 20px; }
  .crt-text { font-size: clamp(14px, 2.1vw, 22px); }
  .crt-remote { padding: 11px 8px 9px; gap: 5px; }
}

/* ── Portrait ──────────────────────────────────────────────────────────────
   Any portrait viewport — phone, tablet, or a tall narrow desktop window. There
   is no left column to rake a scrim into, so the composition rotates instead of
   collapsing: the anchor takes the top two thirds, the copy sits at the bottom
   of the picture on the rise, the chyron keeps the floor.

   The copy used to sit at the TOP, crammed under the ident with an empty middle
   below it, and the remote turned into a horizontal bar that matched nothing
   else on the page. Both are gone. The remote stays vertical at every size — it
   is the same object — and moves to the top-right corner under the ident, which
   is the one region the bottom-stacked copy can never reach.

   `--chyron-h` is the shared floor height: the chyron is pinned to at least it,
   and the copy is offset by it. Both used to guess separately, at different
   numbers. */
@media (orientation: portrait) {
  .crt { --chyron-h: clamp(102px, 15.5vh, 152px); }

  .crt-tube::before {
    background:
      linear-gradient(to bottom, rgba(3,6,12,0.8) 0%, rgba(3,6,12,0.3) 16%, transparent 32%),
      linear-gradient(to top, rgba(3,6,12,0.97) 0%, rgba(3,6,12,0.9) 30%, rgba(3,6,12,0.45) 48%, transparent 68%);
  }

  .crt-chyron {
    min-height: var(--chyron-h);
    box-sizing: border-box;
  }

  .broadcast-copy {
    left: clamp(18px, 5vw, 40px);
    right: clamp(18px, 5vw, 40px);
    top: auto;
    bottom: calc(var(--chyron-h) + clamp(18px, 2.6vh, 34px));
    width: auto;
    translate: none;
    margin-top: 0;
  }
  .hero-headline {
    font-size: clamp(30px, 7.6vw, 56px);
    margin-bottom: clamp(14px, 2vh, 24px);
  }
  .hero-cta { font-size: 14px; padding: 12px 22px; }

  /* Face in the upper third, above the copy. */
  .crt-plate { object-position: 50% 30%; }

  .crt-remote {
    top: clamp(74px, 10vh, 118px);
    bottom: auto;
    translate: none;
    padding: 11px 8px 9px;
    gap: 5px;
  }

  .crt-text { font-size: clamp(15px, 4.2vw, 24px); min-height: 2.48em; }
}

/* A phone in landscape has no room for a centred copy block AND a chyron; pull
   the copy up off the bar and shrink the remote so it clears both. */
@media (orientation: landscape) and (max-height: 480px) {
  /* The flag's normal band is where the headline starts at this height. */
  .crt-flag { top: 62px; }
  .broadcast-copy { margin-top: clamp(-64px, -9vh, -34px); }
  .hero-headline { font-size: clamp(24px, 4.4vw, 38px); margin-bottom: 12px; }
  .hero-cta { font-size: 13px; padding: 9px 17px; }
  .crt-remote { padding: 8px 7px 7px; gap: 4px; }
  .crt-knob { width: 22px; height: 22px; min-width: 22px; min-height: 22px; }
  .crt-btn { min-width: 22px; min-height: 22px; font-size: 12px; }
  .crt-text { font-size: clamp(13px, 2.2vw, 18px); }
}

/* ── Feedback entry points + dialog ──────────────────────────────────────
   The desktop tab is a fixed sibling of <main>, NOT a descendant of any
   overflow scroller — that placement is what avoids the iOS WebKit
   position:fixed clipping that bit the /search chip overlays (PR #243).

   z-index 150 sits above ambient page content and below the mobile nav tray
   (1000). The dialog itself needs NO z-index: showModal() renders it in the
   browser top layer, above every stacking context on the page. */
.feedback-tab {
  position: fixed;
  right: 0;
  top: 50%;
  /* Vertical centering uses the `translate` property, NOT `transform`. The
     global `button:active { transform: translateY(1px) }` above REPLACES a
     transform rather than composing with it, so centering via `transform`
     made the tab jump down half its own height (~45px, it is vertical text)
     on mousedown — out from under the cursor, so mouseup landed elsewhere and
     no click event ever fired. The dialog opened only when the pointer
     happened to stay inside the moved rect. `translate` is a separate
     property, so the :active nudge now composes on top of it. */
  translate: 0 -50%;
  z-index: 150;
  writing-mode: vertical-rl;
  padding: 14px 7px;
  border: 1px solid var(--border);
  border-right: 0;
  border-radius: 8px 0 0 8px;
  background: var(--surface);
  color: var(--ink-2);
  font-size: var(--t-sm);
  letter-spacing: 0.02em;
  cursor: pointer;
  box-shadow: -1px 1px 3px rgb(0 0 0 / 6%);
}
.feedback-tab:hover { background: var(--border-ui); color: var(--text); }

/* Mobile-only control; the desktop tab covers >720px. */
.feedback-open-mobile { display: none; }

.feedback-dialog {
  width: min(520px, calc(100vw - 32px));
  /* A modal <dialog> is centered by the UA stylesheet's `margin: auto` against
     `inset: 0`. This sheet's `*, *::before, *::after { margin: 0 }` reset wipes
     that, pinning the dialog to the top-left corner in every browser. Restore
     it explicitly — this is the codebase's first <dialog>, so nothing else was
     already compensating. */
  margin: auto;
  padding: 20px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  color: var(--text);
}
.feedback-dialog::backdrop { background: rgb(0 0 0 / 40%); }
.feedback-dialog h2 { margin: 0 0 6px; font-size: var(--t-lg); }
.feedback-intro { margin: 0 0 16px; color: var(--muted); font-size: var(--t-sm); }
.feedback-dialog label {
  display: block;
  margin: 12px 0 4px;
  font-size: var(--t-sm);
  font-weight: 500;
}
.feedback-dialog select,
.feedback-dialog textarea {
  width: 100%;
  box-sizing: border-box;
  padding: 8px 10px;
  border: 1px solid var(--border-ui);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  font: inherit;
}
.feedback-dialog textarea { resize: vertical; }
.feedback-status { min-height: 18px; margin: 10px 0 0; font-size: var(--t-sm); }
.feedback-status:empty { display: none; }
.feedback-status[data-state="error"] { color: var(--danger); }

/* Success confirmation after the dialog closes. Same ink-fill, bottom-centre
   treatment as the page-level toasts on /search, /jobs and the resume review —
   but under its own class rather than a global `.toast`, because those three
   define their own page-scoped `.toast` rules and a global one would reach
   their elements for every property they happen not to set.

   Sits BELOW the mobile nav tray (1000) and above ambient content, matching the
   z-index scale documented in docs/design-system.md. */
.feedback-toast {
  position: fixed;
  left: 50%;
  bottom: 24px;
  translate: -50% 0;
  z-index: 200;
  max-width: calc(100vw - 32px);
  padding: 10px 16px;
  border-radius: var(--radius-sm);
  background: var(--text);
  color: var(--surface);
  box-shadow: var(--shadow-pop);
  font-size: var(--t-sm);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-2), translate var(--dur-2);
}
.feedback-toast.show { opacity: 1; translate: -50% -6px; }

/* ── Saved tick (shared) ────────────────────────────────────────────────
   The one confirmed-state language: web/src/lib/saved-tick.ts writes the
   tick + text and toggles .show; the element stays in flow (opacity, not
   display) so the row never jumps. Errors: same element, no tick, danger. */
.saved-tick {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--t-md);
  color: var(--success);
  opacity: 0;
  transform: translateY(2px);
  transition: opacity var(--dur-2), transform var(--dur-2) var(--ease-out);
  pointer-events: none;
}
.saved-tick.show { opacity: 1; transform: none; }
.saved-tick[data-kind="error"] { color: var(--danger); }
.saved-tick svg {
  width: 16px; height: 16px; padding: 3px;
  border-radius: 50%;
  background: var(--success-soft);
  fill: none; stroke: currentColor; stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round;
}

/* Tracker row hand-off flash (#7): one fade from accent-soft to nothing.
   Reduced motion (durations 0) would end the fade instantly, so there the
   tint is static and tracker.astro clears it on the next interaction. */
.row-highlight { animation: row-flash calc(var(--dur-3) * 2) var(--ease-out) both; outline: none; }
.row-highlight:focus-visible { outline: 2px solid var(--secondary); outline-offset: -2px; }
@keyframes row-flash { from { background: var(--accent-soft); } to { background: transparent; } }
@media (prefers-reduced-motion: reduce) { .row-highlight { animation: none; background: var(--accent-soft); } }
.feedback-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 16px;
}

/* ── Responsive ──────────────────────────────────────────────────────── */

@media (max-width: 640px) {
  main { padding: 20px 16px; }

  .dimension-label { width: 64px; }

}

/* No auto-advance, no scramble, no idle motion — the CH buttons still work, so the
   headlines stay reachable on demand. The JS reads the same query. */
@media (prefers-reduced-motion: reduce) {
  .crt::before { animation: none; opacity: 0.98; }
  .crt-text { animation: none; }
  .crt-live-dot { animation: none; }
  .crt-off .crt-tube { animation: none; }
  .crt-off .crt-glass::after { animation: none; }
}

/* ── Sidebar collapse → top bar + hamburger tray (mobile) ─────────── */

@media (max-width: 720px) {
  body { flex-direction: column; }
  nav {
    width: 100%;
    min-height: 0;
    padding: 0;
    border-right: 0;
    border-bottom: 1px solid var(--border);
    flex-direction: column;
    gap: 0;
    /* Site-wide invariant: the open mobile menu outranks every ambient
       z-index (page content that escapes to the root stacking context —
       toasts sit at 200, search overlays at 60). Pages should still trap
       their own stacks (isolation: isolate) rather than lean on this. */
    z-index: 1000;
  }
  .nav-brand-row {
    width: 100%;
    padding: 12px 16px;
    height: 56px;
    box-sizing: border-box;
  }
  /* margin-right: auto on .logo keeps the burger — and the feedback button
     on the breakpoints where it shares this row — flush right. */
  nav .logo { padding: 0; margin: 0; margin-right: auto; }
  .nav-burger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    padding: 0;
    background: transparent;
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    cursor: pointer;
  }
  .nav-burger:hover { background: var(--border-ui); border-color: var(--ink-2); }
  .nav-burger .ic-close { display: none; }
  nav[data-mobile-open="1"] .nav-burger .ic-open { display: none; }
  nav[data-mobile-open="1"] .nav-burger .ic-close { display: inline-block; }

  .nav-tray {
    position: absolute;
    top: 56px;
    left: 0;
    right: 0;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    box-shadow: 0 12px 24px rgba(16,23,30,0.06);
    padding: 12px 16px 16px;
    max-height: 0;
    overflow: hidden;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: max-height var(--dur-2), opacity var(--dur-2), padding var(--dur-2), visibility 0s linear var(--dur-2);
    gap: 4px;
    z-index: 5;
  }
  nav[data-mobile-open="1"] .nav-tray {
    max-height: 80vh;
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    overflow: auto;
    transition: max-height var(--dur-2), opacity var(--dur-2), padding var(--dur-2), visibility 0s linear 0s;
  }
  .nav-tray .nav-link {
    padding: 12px 12px;
    font-size: var(--t-md);
  }
  .nav-tray .sidebar-footer {
    margin-top: 8px;
    padding: 12px 4px 4px;
    border-top: 1px solid var(--border);
  }

  .nav-backdrop {
    position: fixed;
    inset: 56px 0 0 0;
    background: rgba(16,23,30,0.18);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--dur-2);
    z-index: 4;
  }
  nav[data-mobile-open="1"] .nav-backdrop {
    display: block;
    opacity: 1;
    pointer-events: auto;
  }

  /* Below the nav breakpoint the sidebar becomes a top bar, so the right-edge
     tab is replaced by a labelled control in the brand row. The tab is hidden
     rather than repositioned: on a phone the right edge is exactly where the
     /jobs group-swipe gesture terminates. */
  .feedback-tab { display: none; }
  .feedback-open-mobile {
    display: inline-flex;
    align-items: center;
    margin-right: 4px;
    padding: 5px 8px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface);
    color: var(--ink-2);
    font-size: var(--t-xs);
    cursor: pointer;
  }
  /* REQUIRED, not decorative. A button that overrides `background`/`color` must
     define its own :hover — the global `button:hover` sets ONLY the background
     (to --ink-2) and leaves the overridden text color alone. Here that painted
     #3A362E text on a #3A362E fill: 1:1 contrast, the label simply vanished.
     Matches the .nav-burger treatment two rules up, which is the sibling control
     in this same brand row. Pinned by invariant-button-hover-contrast.test.ts. */
  .feedback-open-mobile:hover { background: var(--border-ui); border-color: var(--ink-2); }
}

/* ── Home (Status surface) ────────────────────────────────────────── */

.home-status {
  display: flex;
  flex-direction: column;
}

.home-greet-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 2px;
}

.home-h1 {
  font-family: var(--font-heading);
  font-size: var(--t-3xl);
  font-weight: 500;
  letter-spacing: var(--ls-3xl);
  line-height: var(--lh-3xl);
  margin: 0;
  color: var(--text);
}

.home-stamp {
  font-size: var(--t-xs);
  color: var(--muted);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  white-space: nowrap;
}

.home-sub {
  font-size: var(--t-md);
  color: var(--muted);
  margin: 0 0 28px;
  max-width: 620px;
}

/* Urgency lozenge */

.home-loz {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 20px 24px;
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  margin-bottom: 28px;
  text-decoration: none;
  transition: border-color var(--dur-1);
}
.home-loz:hover { text-decoration: none; border-color: var(--ink-2); }

.home-loz-num {
  font-family: var(--font-heading);
  font-size: var(--t-3xl);
  font-weight: 600;
  letter-spacing: var(--ls-3xl);
  line-height: 1;
  min-width: 80px;
  text-align: center;
  color: var(--text);
}

.home-loz-body { flex: 1; min-width: 0; }

.home-loz-title {
  font-size: var(--t-lg);
  font-weight: 600;
  letter-spacing: var(--ls-lg);
  line-height: 1.25;
  margin: 0;
}

.home-loz-meta {
  font-size: var(--t-sm);
  color: var(--muted);
}

.home-loz-cta-wrap { flex-shrink: 0; }

.home-loz-cta {
  display: inline-block;
  color: var(--accent-text);
  font-size: var(--t-sm);
  font-weight: 500;
  white-space: nowrap;
}

/* Dispatcher cards */

.home-cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  margin-bottom: 32px;
}

.home-card {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px 18px;
  text-decoration: none;
  color: inherit;
  transition: border-color var(--dur-1);
}
.home-card:hover { border-color: var(--ink-2); text-decoration: none; }

.home-card-head {
  margin-bottom: 10px;
}

.home-card-title {
  font-size: var(--t-md);
  font-weight: 600;
  color: var(--text);
}

.home-card-kpi-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 2px;
}

.home-card-kpi {
  font-family: var(--font-heading);
  font-size: var(--t-2xl);
  font-weight: 600;
  letter-spacing: var(--ls-2xl);
  line-height: 1;
  color: var(--text);
}

.home-card-kpi-label { font-size: var(--t-xs); color: var(--muted); }

.home-card-sub {
  font-size: var(--t-sm);
  color: var(--muted);
  margin-bottom: 12px;
  min-height: 14px;
}

.home-card-list {
  border-top: 1px solid var(--border);
  padding-top: 6px;
  display: flex;
  flex-direction: column;
}

.home-card-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 6px 0;
  font-size: var(--t-sm);
  border-top: 1px solid var(--border);
}
.home-card-row:first-child { border-top: 0; }

.home-card-row-co {
  font-weight: 600;
  color: var(--text);
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.home-card-row-tag {
  font-size: var(--t-xs);
  color: var(--muted);
  margin-left: auto;
  white-space: nowrap;
}

.home-card-row-value {
  margin-left: auto;
  color: var(--ink-2);
  font-size: var(--t-xs);
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.home-card-empty {
  font-size: var(--t-sm);
  color: var(--muted);
  padding: 6px 0;
}

.home-card-cta {
  margin-top: 12px;
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--accent-text);
}

@media (max-width: 760px) {
  .home-greet-row { flex-direction: column; align-items: flex-start; gap: 4px; }
  .home-sub { margin-bottom: 22px; }
  .home-loz {
    padding: 16px 16px;
    gap: 14px;
    flex-wrap: wrap;
  }
  .home-loz-num { min-width: 56px; text-align: left; }
  .home-loz-cta-wrap {
    flex-basis: 100%;
    padding-top: 8px;
    margin-top: 4px;
  }
  .home-cards { grid-template-columns: 1fr; gap: 12px; margin-bottom: 24px; }
}

/* ── Settings ─────────────────────────────────────────────────────── */

.settings { max-width: 960px; margin-inline: auto; }

/* Every section is a head (h2 + intro copy) and a body (the controls). They
   stack below 1200px; at desktop they sit side by side so the page reads as a
   form page, not a phone column pinned to the left edge. 1200 is where the body
   column (viewport - 240px nav - 80px main padding - head - gap) first clears
   the 480px form width; below it two columns would be narrower than one. */
.settings-section { padding: 32px 0; border-top: 1px solid var(--border); }
.settings-section:first-of-type { padding-top: 0; border-top: 0; }
.settings-head { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
.settings-head h2, .settings-head p { margin: 0; }
.settings-body { min-width: 0; } /* grid item's min-width:auto would let a wide child overflow the track */
.settings-danger { padding: 20px; border: 1px solid var(--danger-soft); border-radius: var(--radius); }

@media (min-width: 1200px) {
  .settings-section { display: grid; grid-template-columns: 280px minmax(0, 1fr); column-gap: 48px; }
  .settings-head { margin-bottom: 0; }
}
