/* findingmyjob — design tokens. Mirrors docs/design/mockups.html.
 *
 * Every text/background pair below is measured to clear WCAG AA (4.5:1) against
 * BOTH surfaces it can land on — --surface and --ground differ enough that a
 * value passing on one can fail on the other. Ratios in comments are computed,
 * not estimated; tests/test_contrast.py re-derives them and fails the build if
 * an edit drops one below the line.
 *
 * The old --ink-3 (#79838F) sat at 3.85:1 on cards, which put every date, salary
 * and meta line in the app below AA. --accent (#A66A24) was 4.46:1 both as link
 * text and as the ground under white button text, so the primary action failed
 * too.
 */
:root {
  /* Neutrals carry a faint violet bias toward the accent rather than sitting on
     pure grey — a mid-grey reads as unconsidered, this reads as chosen. */
  --ground:#F6F5FA; --surface:#FFFFFF; --surface-2:#EEEDF5; --line:#DEDCE9;
  --line-strong:#C4C1D4; --ink:#17161F; --ink-2:#4A4860;
  --ink-3:#66637E;              /* 5.75 on surface · 5.30 on ground */
  --accent:#5B4FD6;             /* 5.94 as text on white AND white text on it */
  --accent-soft:#E7E4FB;
  --good:#1F6B45; --good-soft:#DCEFE4;   /* 5.39 */
  --warn:#7D5A0F; --warn-soft:#F6EBD1;   /* 5.30 */
  --bad:#A33A28; --bad-soft:#F7DEDA;     /* 5.14 */
  --dead:#5A5870; --dead-soft:#E9E8F0;   /* 5.63 */
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
  /* One text face, used at every size. The old stack added a CONDENSED family
     for headings, buttons, labels, pills and nav — 21 uppercase treatments in
     288 lines — which is what made the app read as an industrial control panel
     rather than a product. Sentence case in a single family carries hierarchy
     through size and weight instead. */
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI Variable Text", "Segoe UI",
          Roboto, "Helvetica Neue", Arial, sans-serif;
  /* Retained only for the micro-labels where uppercase genuinely aids scanning
     at 11px: stat keys and table headers. Everything else is now --sans. */
  --cond: var(--sans);

  /* Type scale. Was ten sizes including 14.5 and 12.5 — half-pixel steps that
     read as drift rather than hierarchy. Six steps, each a real jump. */
  --t-xs:11px; --t-sm:12px; --t-md:13px; --t-base:15px; --t-lg:17px; --t-xl:27px;

  /* Spacing. Was eighteen values with no common ratio (7, 5, 1, 14, 9, 13, 3…).
     A 4px scale: everything is a multiple, so rhythm holds without thinking. */
  --s-1:4px; --s-2:8px; --s-3:12px; --s-4:16px; --s-5:20px; --s-6:24px;
  --s-7:32px; --s-8:40px;

  /* Radii. Was six (3, 5, 6, 8, 9, 10). Three: chips, controls, containers. */
  --r-sm:3px; --r-md:6px; --r-lg:9px;

  /* Ink for text sitting ON the accent (primary buttons). Light theme: white on
     a deep indigo. Dark theme: the accent is a LIGHT indigo, so white would give
     roughly 1.6:1 — the primary action would fail AA in exactly the theme most
     users see. Dark ink there instead. */
  --on-accent:#FFFFFF;

  /* Elevation. Flat cards on a flat ground gave the app no depth at all; a
     considered shadow is most of the difference between "web page" and
     "application". Kept subtle — two stacked shadows, not one heavy blur. */
  --shadow-1:0 1px 2px rgba(23,22,31,.04), 0 1px 3px rgba(23,22,31,.06);
  --shadow-2:0 2px 4px rgba(23,22,31,.04), 0 8px 24px -8px rgba(23,22,31,.14);

  /* Minimum comfortable touch target. WCAG 2.5.5 asks for 44px. */
  --tap:44px;
}
@media (prefers-color-scheme: dark) {
  :root {
    /* Near-black with a violet cast rather than the old blue-grey slate, which
       read as a generic dark IDE. */
    --ground:#0E0D14; --surface:#16151F; --surface-2:#1F1E2A; --line:#2A2836;
    --line-strong:#3D3A4D; --ink:#EDECF5; --ink-2:#B0ADC2;
    --ink-3:#918EA6;            /* 5.70 on surface · 6.10 on ground */
    --accent:#9B8CFF;           /* 6.54 on surface · 6.99 on ground */
    --accent-soft:#241F3D80;
    --good:#5FBF8B; --good-soft:#12291d99;   /* 7.36 */
    --warn:#D4AC55; --warn-soft:#2b241099;   /* 7.75 */
    --bad:#E2705C; --bad-soft:#2c161399;     /* 5.59 */
    --dead:#9490A8; --dead-soft:#1f1e2999;   /* 5.58 */
    --on-accent:#11101A;        /* 6.82 on the accent */
    --shadow-1:0 1px 2px rgba(0,0,0,.4), 0 1px 3px rgba(0,0,0,.3);
    --shadow-2:0 2px 4px rgba(0,0,0,.4), 0 8px 24px -8px rgba(0,0,0,.6);
  }
}
* { box-sizing: border-box; }

/* One focus rule for everything focusable. Previously only .btn and form fields
   had one, which left the whole nav, every dropdown item and every in-page link
   invisible to a keyboard user. :focus-visible rather than :focus so a mouse
   click does not leave a ring behind. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline:2px solid var(--accent);
  outline-offset:2px;
  border-radius:var(--r-sm);
}

/* Keyboard users need to reach the content without tabbing the whole nav. */
.skip {
  position:absolute; left:-9999px; top:0; z-index:200;
  background:var(--surface); color:var(--ink); padding:var(--s-3) var(--s-4);
  border:1px solid var(--accent); border-radius:0 0 var(--r-md) 0;
  font-family:var(--cond); font-size:var(--t-md); font-weight:700;
 text-decoration:none;
}
.skip:focus { left:0; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration:.01ms !important; animation-iteration-count:1 !important;
    transition-duration:.01ms !important; scroll-behavior:auto !important;
  }
}
body {
  margin:0; background:var(--ground); color:var(--ink); font-family:var(--sans);
  font-size:var(--t-base); line-height:1.55; -webkit-font-smoothing:antialiased;
}
a { color: var(--accent); }
.bar {
  display:flex; align-items:center; gap:var(--s-4); padding:0 var(--s-4); height:46px;
  background:var(--surface-2); border-bottom:1px solid var(--line);
}
/* The wordmark is the one place a distinct treatment earns its keep: it is a
   name, not a label. Tightened rather than tracked out, and lowercase, which is
   how the product is written everywhere else in the copy. */
.brand { font-weight:650; font-size:var(--t-lg); letter-spacing:-.03em;
  text-decoration:none; color:var(--ink); }
.brand span { color:var(--accent); }
/* Five grouped menus, not nineteen flat links. The old bar listed every feature
   in one row, which read as a wall and gave no clue where to start. */
.bar nav { display:flex; gap:var(--s-1); margin-left:var(--s-2); }
.navgroup { position:relative; }
.navgroup-t { font-size:var(--t-md);
 color:var(--ink-3); padding:var(--s-1) var(--s-3); border-radius:var(--r-md);
  font-weight:600; background:none; border:0; cursor:pointer; }
.navgroup-t:hover { color:var(--ink); }
.navgroup-t.on { color:var(--ink); background:var(--surface);
  box-shadow:inset 0 0 0 1px var(--line); }
.navgroup-t::after { content:""; display:inline-block; margin-left:var(--s-1);
  border:3px solid transparent; border-top-color:currentColor;
  transform:translateY(2px); opacity:.55; }
.navmenu { display:none; position:absolute; top:calc(100% + 4px); left:0; z-index:60;
  min-width:190px; padding:var(--s-1); background:var(--surface-2);
  border:1px solid var(--line); border-radius:var(--r-lg);
  box-shadow:0 8px 24px rgba(0,0,0,.35); }
.navgroup.open .navmenu { display:block; }
.navmenu a { display:block; font-size:var(--t-md); color:var(--ink-2); text-decoration:none;
  padding:var(--s-2) var(--s-3); border-radius:var(--r-md); }
.navmenu a:hover { background:var(--surface); color:var(--ink); }
.navmenu a.on { color:var(--accent); font-weight:600; }

/* First-run checklist. Disappears the moment setup is complete — a permanent
   banner would become furniture and stop being read. */
.setup { border:1px solid var(--accent); border-radius:var(--r-lg); padding:var(--s-5) var(--s-5);
  margin-bottom:var(--s-6); background:var(--surface); }
.setup h2 { font-size:var(--t-lg); font-weight:600; letter-spacing:-.012em;
  color:var(--ink); margin:var(--s-7) 0 var(--s-3); padding-bottom:var(--s-2);
  border-bottom:1px solid var(--line); }
.setup-steps { list-style:none; margin:var(--s-4) 0 0; padding:0; }
.setup-step { display:flex; gap:var(--s-3); padding:var(--s-3) 0; border-top:1px solid var(--line); }
.setup-step:first-child { border-top:0; }
.setup-num { flex:0 0 22px; height:22px; border-radius:50%; font-size:var(--t-xs);
  display:flex; align-items:center; justify-content:center; font-weight:700;
  background:var(--surface-2); color:var(--ink-3); box-shadow:inset 0 0 0 1px var(--line); }
.setup-step.done .setup-num { background:var(--good); color:#04150b; box-shadow:none; }
.setup-step.now .setup-num { background:var(--accent); color:#1a1004; box-shadow:none; }
.setup-step.done .setup-t { color:var(--ink-3); text-decoration:line-through; }
.setup-t { font-weight:600; font-size:var(--t-base); }
.setup-why { color:var(--ink-2); font-size:var(--t-md); margin-top:var(--s-1); }
.setup-detail { color:var(--ink-3); font-size:var(--t-md); margin-top:var(--s-1); line-height:1.5; }
.bar .right { margin-left:auto; font-family:var(--mono); font-size:var(--t-xs); color:var(--ink-3); }
main { max-width:1120px; margin:0 auto; padding:var(--s-6) var(--s-5) 80px; }
h1 { font-size:var(--t-xl); font-weight:650; letter-spacing:-.022em;
  margin:0 0 var(--s-1); text-wrap:balance; line-height:1.15; }
/* h2 was an 11px uppercase micro-label doing section-heading duty, which is
   why every section read as a form field group rather than a part of the page. */
h2 { font-size:var(--t-lg); font-weight:600; letter-spacing:-.012em;
  color:var(--ink); margin:var(--s-7) 0 var(--s-3); padding-bottom:var(--s-2);
  border-bottom:1px solid var(--line); }
.sub { color:var(--ink-2); font-size:var(--t-base); margin:0 0 var(--s-5); }
.card { background:var(--surface); border:1px solid var(--line);
  border-radius:var(--r-lg); overflow:hidden; box-shadow:var(--shadow-1); }
.stats { display:grid; grid-template-columns:repeat(auto-fit,minmax(130px,1fr)); }
.stat { padding:var(--s-4) var(--s-4); border-right:1px solid var(--line); }
.stat:last-child { border-right:0; }
.stat-k { font-family:var(--cond); font-size:var(--t-xs); text-transform:uppercase;
  letter-spacing:.1em; color:var(--ink-3); font-weight:600; }
.stat-v { font-family:var(--mono); font-size:25px; font-variant-numeric:tabular-nums;
  letter-spacing:-.03em; margin-top:var(--s-1); line-height:1; }
.row { display:grid; grid-template-columns:44px 1fr auto; gap:var(--s-4); align-items:start;
  padding:var(--s-3) var(--s-4); border-bottom:1px solid var(--line); }
.row:last-child { border-bottom:0; }
.row:hover { background:var(--surface-2); }
.row a.t { font-weight:600; font-size:var(--t-base); color:var(--ink); text-decoration:none; }
.row .co { color:var(--ink-2); font-size:var(--t-md); }
.row .meta { display:flex; flex-wrap:wrap; gap:var(--s-2) var(--s-3); margin-top:var(--s-1);
  font-family:var(--mono); font-size:var(--t-xs); color:var(--ink-3); }
.row .fit { font-size:var(--t-md); color:var(--ink-2); margin-top:var(--s-2); max-width:70ch;
  border-left:2px solid var(--line-strong); padding-left:var(--s-3); }
.score { font-family:var(--mono); font-size:var(--t-lg); font-weight:600;
  font-variant-numeric:tabular-nums; width:44px; height:34px; display:flex;
  align-items:center; justify-content:center; border-radius:var(--r-md); }
.s-hi { background:var(--good-soft); color:var(--good); }
.s-md { background:var(--warn-soft); color:var(--warn); }
.s-lo { background:var(--dead-soft); color:var(--dead); }
.pill { display:inline-flex; align-items:center; font-family:var(--sans);
  font-size:var(--t-xs); font-weight:600; padding:2px var(--s-2);
  border-radius:var(--r-sm); letter-spacing:0; white-space:nowrap; }
.p-good{background:var(--good-soft);color:var(--good)} .p-warn{background:var(--warn-soft);color:var(--warn)}
.p-bad{background:var(--bad-soft);color:var(--bad)} .p-dead{background:var(--dead-soft);color:var(--dead)}
.p-acc{background:var(--accent-soft);color:var(--accent)}
.btn { font-family:var(--sans); font-size:var(--t-md); font-weight:550;
  padding:var(--s-2) var(--s-4); border-radius:var(--r-md);
  border:1px solid var(--line-strong); background:var(--surface); color:var(--ink);
  cursor:pointer; text-decoration:none; display:inline-flex; align-items:center;
  justify-content:center; gap:var(--s-2); box-shadow:var(--shadow-1);
  transition:border-color .12s ease, background .12s ease, box-shadow .12s ease; }
.btn:hover { border-color:var(--line-strong); background:var(--surface-2);
  box-shadow:var(--shadow-2); }
.btn-p { background:var(--accent); border-color:var(--accent); color:var(--on-accent);
  font-weight:600; }
.btn-p:hover { background:var(--accent); border-color:var(--accent);
  filter:brightness(1.08); }
.btn:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
label { display:block; font-family:var(--cond); font-size:var(--t-xs); text-transform:uppercase;
  letter-spacing:.1em; color:var(--ink-3); font-weight:700; margin:var(--s-4) 0 var(--s-1); }
input, textarea, select { width:100%; background:var(--surface); color:var(--ink);
  border:1px solid var(--line-strong); border-radius:var(--r-md); padding:var(--s-2) var(--s-3);
  font-family:var(--sans); font-size:var(--t-base); }
textarea { min-height:220px; resize:vertical; font-family:var(--mono); font-size:var(--t-md); line-height:1.6; }
input:focus, textarea:focus, select:focus { outline:2px solid var(--accent); outline-offset:1px; border-color:var(--accent); }
.split { display:grid; grid-template-columns:1fr 1fr; }
@media (max-width:900px){ .split{grid-template-columns:1fr} }

/* The contact page.
 *
 * Two columns of equal weight was wrong twice. As .split it put the history —
 * the thing you open a contact to read — in a narrow column beside a tall
 * stack of two forms. Widening history and reordering just moved the empty
 * space to the other side, because a contact you have spoken to once has one
 * line of history and nothing fills a column.
 *
 * So the columns are sized by what they hold rather than split evenly, and
 * they stop stretching: `align-items:start` lets each end where its content
 * does instead of both running to the height of the taller one. The page is
 * short when the history is short, which is the honest shape for it. */
.contact-cols { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,420px);
                align-items:start; }
.contact-cols > :first-child { order:2; }
.contact-cols > :last-child  { order:1; }
@media (max-width:1100px){
  .contact-cols { grid-template-columns:1fr; }
  /* Stacked, the log form comes first: on a phone you are far more likely to
     be recording a call you just had than reading back through one. */
  .contact-cols > :first-child { order:1; }
  .contact-cols > :last-child  { order:2; }
}

/* Three options, three columns. The positioning page always renders exactly
   three - aim, reframe, accept - and .split's two columns dropped the third
   onto its own row, which read as an afterthought rather than the equal
   choice it is. */
.three { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); align-items:start; }
@media (max-width:1000px){ .three{grid-template-columns:1fr} }

.pane { padding:var(--s-4); }
.pane + .pane { border-left:1px solid var(--line); }
@media (max-width:900px){ .pane + .pane { border-left:0; border-top:1px solid var(--line);} }
.pane-h { font-family:var(--cond); font-size:var(--t-xs); text-transform:uppercase;
  letter-spacing:.1em; color:var(--ink-3); font-weight:700; margin-bottom:var(--s-3); }
.posting { font-size:var(--t-md); color:var(--ink-2); line-height:1.62; white-space:pre-wrap;
  max-height:420px; overflow-y:auto; }
.qa { border:1px solid var(--line); border-radius:var(--r-md); margin-bottom:var(--s-2); overflow:hidden; }
.qa-q { background:var(--surface-2); padding:var(--s-2) var(--s-3); font-family:var(--cond); font-size:var(--t-md);
  font-weight:700; color:var(--ink-2);
  display:flex; justify-content:space-between; align-items:center; gap:var(--s-3); }
.qa-a { padding:var(--s-3) var(--s-3); font-size:var(--t-md); color:var(--ink-2); line-height:1.6; white-space:pre-wrap; }
.copy { font-family:var(--cond); font-size:var(--t-xs); letter-spacing:.06em; color:var(--accent);
  cursor:pointer; font-weight:700; background:none; border:0; padding:0; }
.tl { position:relative; padding-left:var(--s-6); }
.tl::before { content:""; position:absolute; left:5px; top:5px; bottom:5px; width:1px; background:var(--line-strong); }
.tl-i { position:relative; padding-bottom:var(--s-4); }
.tl-i::before { content:""; position:absolute; left:-21px; top:5px; width:9px; height:9px;
  border-radius:50%; background:var(--accent); }
.tl-t { font-family:var(--cond); font-size:var(--t-md); font-weight:700; }
.tl-d { font-family:var(--mono); font-size:var(--t-xs); color:var(--ink-3); }
.tl-n { font-size:var(--t-md); color:var(--ink-2); margin-top:var(--s-1); }
.note { border-left:2px solid var(--accent); background:var(--accent-soft); padding:var(--s-3) var(--s-4);
  border-radius:0 6px 6px 0; font-size:var(--t-md); color:var(--ink-2); margin:var(--s-4) 0; }
.note.bad { border-left-color:var(--bad); background:var(--bad-soft); }
.note b { color:var(--ink); }
.empty { text-align:center; padding:52px var(--s-5); color:var(--ink-3); }
.stat-d { font-size:var(--t-xs); color:var(--ink-3); margin-top:var(--s-1); }
.stat-v small { font-size:var(--t-md); color:var(--ink-3); letter-spacing:0; }
.fstep { display:grid; grid-template-columns:150px 1fr 82px; gap:var(--s-3); align-items:center;
  margin-bottom:var(--s-2); }
.fname { font-size:var(--t-md); color:var(--ink-2); font-weight:500; }
.fbar { height:22px; border-radius:var(--r-sm); background:var(--accent); opacity:.82; min-width:2px; }
.fnum { font-family:var(--mono); font-size:var(--t-sm); color:var(--ink-3);
  font-variant-numeric:tabular-nums; text-align:right; }
.fnum b { color:var(--ink); font-weight:600; }
code { font-family:var(--mono); font-size:.9em; background:var(--surface-2);
  padding:1px var(--s-1); border-radius:var(--r-sm); }

/* nav wraps rather than overflowing once there are many sections */
.bar { flex-wrap:wrap; height:auto; min-height:46px; padding-top:var(--s-1); padding-bottom:var(--s-1); }
.bar nav { flex-wrap:wrap; }

/* keyboard nav + bulk selection */
.approw { cursor:default; }
.approw.cursor { box-shadow:inset 3px 0 0 var(--accent); background:var(--surface-2); }
.approw.selected { background:var(--accent-soft); }
.rowcheck { width:15px; height:15px; accent-color:var(--accent); cursor:pointer; margin:0; }
.bulkbar {
  display:flex; align-items:center; gap:var(--s-3); flex-wrap:wrap;
  background:var(--surface-2); border:1px solid var(--line-strong);
  border-radius:var(--r-lg); padding:var(--s-3) var(--s-4); margin-bottom:var(--s-3);
}
.bulkbar select { width:auto; min-width:170px; }
.bulkcount { font-family:var(--cond); font-size:var(--t-sm);
  letter-spacing:.08em; color:var(--ink-2); font-weight:700; }
.bulkcount b { color:var(--accent); font-size:var(--t-base); }
.bulknote { font-size:var(--t-xs); color:var(--ink-3); }
kbd {
  font-family:var(--mono); font-size:var(--t-xs); background:var(--surface-2);
  border:1px solid var(--line-strong); border-bottom-width:2px;
  border-radius:var(--r-sm); padding:1px var(--s-1); color:var(--ink-2);
}
#keyhelp {
  position:fixed; inset:0; background:rgba(0,0,0,.45); z-index:900;
  display:flex; align-items:center; justify-content:center;
}
#keyhelp[hidden] { display:none; }
.keyhelp-box {
  background:var(--surface); border:1px solid var(--line-strong); border-radius:var(--r-lg);
  padding:var(--s-5) var(--s-6); min-width:280px; box-shadow:0 10px 34px rgba(0,0,0,.32);
}
.keyhelp-head { font-family:var(--cond); font-size:var(--t-sm);
  letter-spacing:.12em; color:var(--ink-3); font-weight:700; margin-bottom:var(--s-3); }
.keyhelp-box dl { display:grid; grid-template-columns:auto 1fr; gap:var(--s-2) var(--s-4); margin:0; }
.keyhelp-box dt { font-family:var(--mono); font-size:var(--t-sm); color:var(--accent); }
.keyhelp-box dd { margin:0; font-size:var(--t-md); color:var(--ink-2); }
mark { background:var(--accent-soft); color:var(--accent); padding:0 var(--s-1); border-radius:var(--r-sm); }

/* analytics */
table.data { width:100%; border-collapse:collapse; font-size:var(--t-md); }
table.data th { font-family:var(--cond); font-size:var(--t-xs);
  letter-spacing:.09em; color:var(--ink-3); text-align:left; padding:var(--s-2) var(--s-3);
  border-bottom:1px solid var(--line-strong); font-weight:700; white-space:nowrap; }
table.data td { padding:var(--s-2) var(--s-3); border-bottom:1px solid var(--line); color:var(--ink-2); }
table.data tr:last-child td { border-bottom:0; }
table.data td.num, table.data th.num { font-family:var(--mono);
  font-variant-numeric:tabular-nums; text-align:right; color:var(--ink); }
table.data th.num { font-family:var(--cond); color:var(--ink-3); }
.tscroll { overflow-x:auto; }
.thin-flag { font-family:var(--cond); font-size:var(--t-xs); font-weight:700;
  text-transform:uppercase; letter-spacing:.08em; color:var(--warn);
  background:var(--warn-soft); padding:1px var(--s-1); border-radius:var(--r-sm);
  margin-left:var(--s-1); vertical-align:1px; white-space:nowrap; }

/* code + diff */
.code { font-family:var(--mono); font-size:var(--t-xs); line-height:1.62; color:var(--ink-2);
  background:var(--surface-2); border:1px solid var(--line); border-radius:var(--r-md);
  padding:var(--s-3); overflow-x:auto; white-space:pre; max-height:340px; overflow-y:auto; }
.code.diff { padding:0; }
.diff span { display:block; padding:0 var(--s-3); }
.diff .d-add  { background:var(--good-soft); color:var(--good); }
.diff .d-del  { background:var(--bad-soft);  color:var(--bad); }
.diff .d-ctx  { color:var(--ink-3); }
.diff .d-hunk { background:var(--surface); color:var(--ink-3); font-size:var(--t-xs);
  border-top:1px solid var(--line); border-bottom:1px solid var(--line);
  padding-top:var(--s-1); padding-bottom:var(--s-1); margin:var(--s-1) 0; }

/* ingest + uplift */
.ing-steps { display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr)); gap:var(--s-3); }
.ing-step { display:flex; gap:var(--s-3); align-items:flex-start; padding:var(--s-3) var(--s-3);
  border:1px solid var(--line); border-radius:var(--r-md); background:var(--surface-2); }
.ing-step.done { border-color:var(--good); background:var(--good-soft); }
.ing-step.active { border-color:var(--accent); background:var(--accent-soft); }
.ing-num { flex:0 0 auto; width:20px; height:20px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  font-family:var(--mono); font-size:var(--t-xs); font-weight:600;
  background:var(--line-strong); color:var(--ink-2); }
.ing-step.done .ing-num { background:var(--good); color:var(--surface); }
.ing-step.active .ing-num { background:var(--accent); color:var(--surface); }
.ing-t { font-family:var(--cond); font-size:var(--t-md); font-weight:700;
 }
.ing-d { font-size:var(--t-xs); color:var(--ink-3); margin-top:var(--s-1); line-height:1.45; }
.ing-gap { margin-bottom:var(--s-3); }
.ing-why { font-size:var(--t-xs); color:var(--ink-3); margin-top:var(--s-1); line-height:1.5; }
.ing-ask { border-top:1px solid var(--line); padding-top:var(--s-3); }
.ing-ask label { color:var(--accent); text-transform:none; letter-spacing:0;
  font-family:var(--sans); font-size:var(--t-md); font-weight:600; }
input[type="file"] { padding:var(--s-2); font-family:var(--sans); font-size:var(--t-md); }

/* integrity verdicts */
.verdict { border:1px solid var(--line-strong); border-left-width:3px;
  border-radius:0 7px 7px 0; padding:var(--s-3) var(--s-4); margin-bottom:var(--s-3); background:var(--surface); }
.v-trap  { border-left-color:var(--warn); }
.v-inj   { border-left-color:var(--bad); }
.v-stuff { border-left-color:var(--dead); }
.v-ok    { border-left-color:var(--good); }
.v-head  { display:flex; align-items:center; gap:var(--s-2); flex-wrap:wrap; margin-bottom:var(--s-2); }
.v-tech  { font-family:var(--mono); font-size:var(--t-xs); color:var(--ink-3); }
.v-quote { font-family:var(--mono); font-size:var(--t-sm); color:var(--ink);
  background:var(--surface-2); border-left:2px solid var(--line-strong);
  padding:var(--s-2) var(--s-3); margin:var(--s-2) 0; border-radius:0 4px 4px 0; white-space:pre-wrap; }
.v-why   { font-size:var(--t-md); color:var(--ink-2); line-height:1.58; }

/* settings tabs — plain links, so each is a real URL */
.tabs { display:flex; gap:var(--s-1); flex-wrap:wrap; margin:var(--s-5) 0 var(--s-6);
  border-bottom:1px solid var(--line); }
.tab { display:block; padding:var(--s-2) var(--s-4) var(--s-3); text-decoration:none;
  color:var(--ink-3); font-size:var(--t-md); font-weight:600; line-height:1.35;
  border-bottom:2px solid transparent; margin-bottom:-1px; }
.tab:hover { color:var(--ink); background:var(--surface-2);
  border-radius:5px 5px 0 0; }
.tab.on { color:var(--accent); border-bottom-color:var(--accent); }
.tab-b { display:block; font-size:var(--t-xs); font-weight:400; color:var(--ink-3);
  text-transform:none; letter-spacing:0; margin-top:var(--s-1); }
.tab.on .tab-b { color:var(--ink-2); }

/* the sentence under a heading that says what the section is for */
.lead { font-size:var(--t-md); color:var(--ink-2); line-height:1.6; max-width:74ch;
  margin:0 0 var(--s-4); }

/* error pages — the nav bar still renders above this, so a 404 leaves you
   inside the application rather than on a bare JSON body */
.errpage { max-width:60ch; padding:var(--s-8) 0 var(--s-7); }
.errcode { font-family:var(--mono); font-size:52px; line-height:1;
  font-variant-numeric:tabular-nums; letter-spacing:-.04em; color:var(--line-strong);
  margin-bottom:var(--s-3); }
.errpage h1 { margin-bottom:var(--s-2); }
.errdo { display:flex; gap:var(--s-2); flex-wrap:wrap; margin-top:var(--s-5); }

/* ── Toasts ─────────────────────────────────────────────────────────────────
   Confirmation without a page-shifting banner. Bottom-right on desktop, full
   width at the bottom on mobile where a corner toast is easy to miss. */
#toasts { position:fixed; z-index:1000; bottom:var(--s-5); right:var(--s-5);
  display:flex; flex-direction:column; gap:var(--s-2); pointer-events:none;
  max-width:min(380px, calc(100vw - var(--s-8))); }
.toast {
  pointer-events:auto; cursor:pointer;
  background:var(--surface); color:var(--ink);
  border:1px solid var(--line-strong); border-left:3px solid var(--ink-3);
  border-radius:var(--r-md); padding:var(--s-3) var(--s-4);
  font-size:var(--t-md); line-height:1.5;
  box-shadow:0 6px 22px rgba(0,0,0,.28);
  opacity:0; transform:translateY(8px);
  transition:opacity .18s ease, transform .18s ease;
}
.toast.in { opacity:1; transform:none; }
.toast-ok  { border-left-color:var(--good); }
.toast-bad { border-left-color:var(--bad); }

/* ── Pending state ────────────────────────────────────────────────────────── */
.is-pending { position:relative; color:transparent !important; pointer-events:none; }
.is-pending::after {
  content:""; position:absolute; inset:0; margin:auto;
  width:14px; height:14px; border-radius:50%;
  border:2px solid currentColor; border-top-color:transparent;
  color:var(--ink-2);
  animation:fmjspin .6s linear infinite;
}
.btn-p.is-pending::after { color:var(--on-accent); }
@keyframes fmjspin { to { transform:rotate(360deg); } }
/* Reduced motion still needs a visible busy signal, just not a spinning one. */
@media (prefers-reduced-motion: reduce) {
  .is-pending { color:var(--ink-3) !important; opacity:.65; }
  .is-pending::after { display:none; }
}
.htmx-request.is-htmx { opacity:.55; transition:opacity .12s ease; }

/* ── Mobile ─────────────────────────────────────────────────────────────────
   The stylesheet previously had two media queries in 288 lines, both for one
   grid. The bar wrapped to 131px on a 375px screen and 25 of 42 tap targets
   were under 44px. */
.navtoggle { display:none; }

@media (max-width:767px) {
  .bar { flex-wrap:nowrap; height:auto; min-height:52px; padding:var(--s-2) var(--s-3); }
  .navtoggle {
    display:flex; flex-direction:column; justify-content:center; gap:var(--s-1);
    margin-left:auto; order:3;
    width:var(--tap); height:var(--tap); padding:0 var(--s-3);
    background:none; border:1px solid var(--line-strong); border-radius:var(--r-md);
    cursor:pointer;
  }
  .navtoggle span { display:block; height:2px; background:var(--ink-2); border-radius:1px; }
  .bar nav {
    display:none; order:4; flex-basis:100%; flex-direction:column;
    gap:var(--s-1); margin:var(--s-3) 0 var(--s-1);
  }
  .bar.nav-open nav { display:flex; }
  .navgroup { position:static; }
  /* On a phone there is no room for a hover menu — show the whole group. */
  .navgroup-t { display:block; width:100%; text-align:left;
    min-height:var(--tap); padding:var(--s-3); font-size:var(--t-md); }
  .navgroup-t::after { display:none; }
  .navmenu {
    display:block; position:static; border:0; box-shadow:none; padding:0 0 var(--s-2);
    background:none; min-width:0;
  }
  .navmenu a { min-height:var(--tap); display:flex; align-items:center;
    padding:var(--s-2) var(--s-4); font-size:var(--t-base); }
  .bar .right { order:2; margin-left:0; }

  main { padding:var(--s-4) var(--s-3) var(--s-8); }
  h1 { font-size:23px; }

  /* Comfortable targets everywhere, not just in the nav. */
  .btn, .tab, button { min-height:var(--tap); }
  /* Inline text links inside rows should not become 44px blocks - that would
     look broken. Padding on the vertical axis gives the finger a real target
     while the text keeps its size, which is what 2.5.5 is actually asking for. */
  .row a.t, .row .co a, .meta a { padding:var(--s-2) 0; display:inline-block; }
  .brand { padding:var(--s-2) 0; }
  .btn { padding:var(--s-3) var(--s-4); }
  .tab { display:flex; flex-direction:column; justify-content:center; }
  .rowcheck { width:22px; height:22px; }

  /* Stat strips read as columns on a phone, not a squeezed row. */
  .stats { grid-template-columns:repeat(2,1fr); }
  .stat { border-right:0; border-bottom:1px solid var(--line); }

  /* List rows become cards: the 44px score column and the right-hand action
     stack instead of fighting for width. */
  .row { grid-template-columns:auto 1fr; gap:var(--s-2) var(--s-3);
    padding:var(--s-4) var(--s-3); }
  .row > :last-child { grid-column:1/-1; }
  .row .meta { gap:var(--s-1) var(--s-3); }
  .row .fit { max-width:none; }

  #toasts { left:var(--s-3); right:var(--s-3); bottom:var(--s-3); max-width:none; }
  .fstep { grid-template-columns:1fr; gap:var(--s-1); }
  .errcode { font-size:40px; }
}

/* Between phone and desktop the nav fits, but the stat strip still wants to
   wrap rather than squeeze six cells onto one line. */
@media (min-width:768px) and (max-width:1024px) {
  .stats { grid-template-columns:repeat(3,1fr); }
  main { padding-left:var(--s-4); padding-right:var(--s-4); }
}

/* ── Utilities ──────────────────────────────────────────────────────────────
   Replaces the most-repeated inline declarations. There were 419 style="…"
   attributes across the templates, and font sizes like 12.5px in them are where
   the type scale drifted out of the stylesheet in the first place. These cover
   the common cases; genuinely one-off layout stays inline, which is fine — the
   problem was never inline styles as such, it was the same six declarations
   copied 400 times with slightly different numbers. */
.f      { display:flex; }
.f-col  { display:flex; flex-direction:column; }
.f-wrap { flex-wrap:wrap; }
.f-mid  { align-items:center; }
.f-end  { align-items:flex-end; }
.f-top  { align-items:flex-start; }
.f-btw  { justify-content:space-between; }
.f-1    { flex:1; }
.g-1 { gap:var(--s-1); } .g-2 { gap:var(--s-2); } .g-3 { gap:var(--s-3); }
.g-4 { gap:var(--s-4); }
.mt-1 { margin-top:var(--s-1); } .mt-2 { margin-top:var(--s-2); }
.mt-3 { margin-top:var(--s-3); } .mt-4 { margin-top:var(--s-4); }
.mb-1 { margin-bottom:var(--s-1); } .mb-2 { margin-bottom:var(--s-2); }
.mb-3 { margin-bottom:var(--s-3); } .mb-4 { margin-bottom:var(--s-4); }
.ml-1 { margin-left:var(--s-1); } .ml-2 { margin-left:var(--s-2); }
.m-0  { margin:0; }
.p-3 { padding:var(--s-3); } .p-4 { padding:var(--s-4); }
.dim  { color:var(--ink-3); }
.dim-2 { color:var(--ink-2); }
.mono { font-family:var(--mono); }
.t-xs { font-size:var(--t-xs); } .t-sm { font-size:var(--t-sm); }
.t-md { font-size:var(--t-md); }
.pre  { white-space:pre-wrap; }
.w-full { width:100%; }
.grid-1a { display:grid; grid-template-columns:1fr auto; }
.note-item { border-top:1px solid var(--line); padding:var(--s-3) 0; }
.note-item:first-child { border-top:0; }

/* ══ Application page ═══════════════════════════════════════════════════════
   Decision at the top, material in the middle, record-keeping folded away. */

.backlink { display:inline-block; font-size:var(--t-md); color:var(--ink-3);
  text-decoration:none; margin-bottom:var(--s-3); }
.backlink:hover { color:var(--accent); }

.apphead { display:grid; grid-template-columns:1fr auto; gap:var(--s-6);
  align-items:start; padding-bottom:var(--s-5); margin-bottom:var(--s-5);
  border-bottom:1px solid var(--line); }
.apphead-co { color:var(--ink-2); font-size:var(--t-base); margin-top:var(--s-1); }
.apphead-co b { color:var(--ink); font-variant-numeric:tabular-nums; }
.apphead-do { display:flex; flex-direction:column; gap:var(--s-2);
  min-width:230px; align-items:stretch; }
.apphead-note { font-size:var(--t-xs); color:var(--ink-3); margin:0; text-align:center; }

/* The score is the headline number on this page, not a chip in a list. */
.score-lg { width:62px; height:52px; font-size:26px; border-radius:var(--r-md); }

/* The sentence that decides the application. It was 13px grey at the bottom of
   a card; it is the reason the page exists. */
.verdict-line { font-size:var(--t-lg); line-height:1.5; color:var(--ink);
  max-width:62ch; margin:var(--s-4) 0 0; }

.btn-lg { padding:var(--s-3) var(--s-5); font-size:var(--t-md); text-align:center; }

.workspace { display:grid; grid-template-columns:1fr 1fr; gap:var(--s-6);
  align-items:start; margin-bottom:var(--s-6); }
.workspace h2 { margin-top:0; }
.empty-inline { padding:var(--s-4); }

/* Record-keeping: four blocks, each showing content or a one-line empty state.
   The add form is behind a <details> so an empty section costs one line, not a
   screenful of inputs. */
.records { display:grid; grid-template-columns:repeat(auto-fit,minmax(260px,1fr));
  gap:var(--s-4); align-items:start; }
/* Four blocks in a three-up grid leaves one orphan and a large dead zone. Two
   columns keeps the pairs balanced at every width that fits them. */
@media (min-width:900px) { .records { grid-template-columns:repeat(2,1fr); } }
@media (min-width:1300px) { .records { grid-template-columns:repeat(4,1fr); } }
.rec { background:var(--surface); border:1px solid var(--line);
  border-radius:var(--r-lg); padding:var(--s-4); }
.rec-h { display:flex; align-items:center; justify-content:space-between;
  gap:var(--s-2); margin-bottom:var(--s-3); }
.rec-h h3 { margin:0; font-family:var(--cond); font-size:var(--t-sm);
 color:var(--ink-3); font-weight:700; }
.rec-n { font-family:var(--mono); font-size:var(--t-xs); color:var(--ink-3);
  background:var(--surface-2); border-radius:var(--r-sm); padding:1px var(--s-2);
  font-variant-numeric:tabular-nums; }
.rec-row { border-top:1px solid var(--line); padding:var(--s-2) 0; font-size:var(--t-md); }
.rec-row:first-of-type { border-top:0; }
.rec-row.is-done { opacity:.5; }

.adder { margin-top:var(--s-3); border-top:1px solid var(--line); padding-top:var(--s-2); }
.adder > summary { cursor:pointer; font-family:var(--cond); font-size:var(--t-sm);
 color:var(--accent);
  font-weight:700; list-style:none; padding:var(--s-1) 0; }
.adder > summary::-webkit-details-marker { display:none; }
.adder > summary::before { content:"+ "; }
.adder[open] > summary::before { content:"− "; }
.adder-form { display:grid; grid-template-columns:1fr 1fr; gap:var(--s-2); margin-top:var(--s-2); }
.adder-form .span-all { grid-column:1/-1; }
.lbl-hint { text-transform:none; letter-spacing:0; font-weight:400; }

/* ══ Rows ═══════════════════════════════════════════════════════════════════
   The whole row is the link now. No per-row button pair. */
a.row.approw { text-decoration:none; color:inherit; grid-template-columns:auto 1fr auto; }
a.row.approw:hover { background:var(--surface-2); }
a.row.approw:hover .row-go { color:var(--accent); transform:translateX(2px); }
.row-body { min-width:0; }
.row-top { display:flex; align-items:center; gap:var(--s-2); flex-wrap:wrap; }
.row-go { color:var(--line-strong); font-size:20px; align-self:center;
  transition:color .12s ease, transform .12s ease; }
.meta-warn { color:var(--warn); }
.row .fit { font-size:var(--t-md); color:var(--ink-2); margin-top:var(--s-2);
  max-width:78ch; border-left:2px solid var(--line-strong); padding-left:var(--s-3); }

@media (max-width:900px) {
  .apphead { grid-template-columns:1fr; gap:var(--s-4); }
  .apphead-do { min-width:0; }
  .workspace { grid-template-columns:1fr; gap:var(--s-5); }
}
@media (max-width:767px) {
  .verdict-line { font-size:var(--t-base); }
  .score-lg { width:52px; height:44px; font-size:21px; }
  .adder-form { grid-template-columns:1fr; }
  a.row.approw { grid-template-columns:auto 1fr; }
  .row-go { display:none; }
}

/* ══ Page and section headers ═══════════════════════════════════════════════ */
.pagehead { display:flex; align-items:flex-start; justify-content:space-between;
  gap:var(--s-4); flex-wrap:wrap; }
.pagehead h1 { margin-bottom:var(--s-1); }

/* A section heading with its own context line, so the h2 does not have to carry
   the explanation as a longer label. */
.sec-h { display:flex; align-items:baseline; justify-content:space-between;
  gap:var(--s-3); flex-wrap:wrap; margin:var(--s-7) 0 var(--s-3);
  padding-bottom:var(--s-2); border-bottom:1px solid var(--line); }
.sec-h h2 { margin:0; padding:0; border:0; }
.sec-note { font-size:var(--t-sm); color:var(--ink-3); }
.sec-link { font-size:var(--t-sm); text-decoration:none; }
.sec-link:hover { text-decoration:underline; }

/* Funnel: a track behind every bar so zero reads as "none of them" rather than
   as a missing row. */
.ftrack { background:var(--surface-2); border-radius:var(--r-sm); overflow:hidden; }
.fbar.is-zero { background:var(--line-strong); min-width:3px; }

.empty { text-align:center; padding:var(--s-6) var(--s-5); color:var(--ink-2); }
.empty a { font-weight:600; }
/* A one-line "nothing here" does not need the full empty-state treatment. It
   was rendering 200px tall on Security, where two of them made the page look
   emptier than it is. */
.empty-slim { padding:var(--s-4) var(--s-5); text-align:left; color:var(--ink-3);
  font-size:var(--t-md); }

/* Small external-link affordance on list rows, replacing a full OPEN FORM
   button per row. Sized to the 44px minimum on touch. */
.row-ext { display:flex; align-items:center; justify-content:center;
  min-width:var(--tap); min-height:var(--tap); align-self:center;
  color:var(--ink-3); text-decoration:none; font-size:var(--t-lg);
  border-radius:var(--r-md); }
.row-ext:hover { color:var(--accent); background:var(--surface-2); }

/* Checkbox + score stack: centre them on each other so the column reads as one
   control rather than two things that happen to be near each other. */
.row .selcol { display:flex; flex-direction:column; align-items:center;
  gap:var(--s-2); padding-top:2px; }

/* keys.js toggles the bulk bar's `hidden` attribute as rows are selected.
   .bulkbar sets display:flex, which would override `hidden` — so restate it. */
.bulkbar[hidden] { display:none; }

/* A key for status pills, where the words alone do not carry the meaning. */
.legend { display:flex; gap:var(--s-5); flex-wrap:wrap; margin-bottom:var(--s-4);
  font-size:var(--t-md); color:var(--ink-2); }
.legend > span { display:flex; align-items:center; gap:var(--s-2); }
.meta-bad { color:var(--bad); }

/* Row-level actions that are not the row's own link: quieter than a button,
   still a real 44px target. */
.rowacts { display:flex; flex-direction:column; gap:var(--s-1); align-items:stretch; }
.rowact { display:flex; align-items:center; justify-content:center;
  min-height:36px; padding:0 var(--s-3); border-radius:var(--r-md);
  font-family:var(--cond); font-size:var(--t-sm); font-weight:600;
  color:var(--ink-2); text-decoration:none; background:none;
  border:1px solid var(--line); cursor:pointer; width:100%; }
.rowact:hover { border-color:var(--line-strong); color:var(--ink); background:var(--surface-2); }
.rowact-bad:hover { border-color:var(--bad); color:var(--bad); background:var(--bad-soft); }
@media (max-width:767px) { .rowact { min-height:var(--tap); } }

/* Destructive actions read as destructive without shouting: an outline, not a
   filled red block competing with the primary action. */
.btn-danger { border-color:var(--bad); color:var(--bad); }
.btn-danger:hover { background:var(--bad-soft); border-color:var(--bad); color:var(--bad); }

/* ══ Prompt editor ══════════════════════════════════════════════════════════ */
.prompt { background:var(--surface); border:1px solid var(--line);
  border-radius:var(--r-lg); padding:var(--s-5); margin-bottom:var(--s-5);
  box-shadow:var(--shadow-1); }
.prompt-h { display:flex; align-items:flex-start; justify-content:space-between;
  gap:var(--s-3); flex-wrap:wrap; }
.prompt-h h3 { margin:0; font-size:var(--t-lg); font-weight:600; letter-spacing:-.01em; }
.prompt-meta { display:flex; align-items:center; gap:var(--s-2); flex-wrap:wrap;
  margin-top:var(--s-1); font-size:var(--t-sm); color:var(--ink-3); }
.prompt-what { font-size:var(--t-md); color:var(--ink-2); max-width:76ch;
  margin:var(--s-3) 0; line-height:1.55; }
.prompt-body { min-height:200px; font-family:var(--mono); font-size:var(--t-md);
  line-height:1.6; }
.prompt-foot { display:flex; align-items:center; justify-content:space-between;
  gap:var(--s-4); flex-wrap:wrap; margin-top:var(--s-3); }
.prompt-fields { font-size:var(--t-sm); color:var(--ink-2); }
.prompt-fields code { margin:0 var(--s-1); }
.guard { white-space:pre-wrap; max-height:none; margin-top:var(--s-2); }

/* Visually hidden but read by screen readers. */
.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; }

/* Multi-select document picker. Checkboxes rather than a multi-select list:
   ctrl-clicking a <select multiple> is a skill, not an affordance. */
.picklist { display:grid; grid-template-columns:repeat(auto-fill,minmax(240px,1fr));
  gap:var(--s-2); }
.pick { display:flex; align-items:center; gap:var(--s-2); margin:0;
  padding:var(--s-2) var(--s-3); border:1px solid var(--line);
  border-radius:var(--r-md); background:var(--surface); cursor:pointer;
  font-family:var(--sans); font-size:var(--t-md); font-weight:400;
  text-transform:none; letter-spacing:0; color:var(--ink-2);
  min-height:var(--tap); transition:border-color .12s ease, background .12s ease; }
.pick:hover { border-color:var(--line-strong); background:var(--surface-2); }
.pick input { width:16px; height:16px; margin:0; accent-color:var(--accent); flex:0 0 auto; }
.pick:has(input:checked) { border-color:var(--accent); background:var(--accent-soft);
  color:var(--ink); }
.pick span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* ------------------------------------------------------------------ */
/* CV builder interview                                               */
/*                                                                    */
/* One question per screen. This was a page of stacked forms — every  */
/* finding rendered at once with its own input — which reads as a     */
/* fourteen-field survey. The engine underneath was always            */
/* conversational; only the interface disagreed.                      */
/* ------------------------------------------------------------------ */

/* Mode choice. Cards rather than a radio list: the difference between a
   five-minute pass and a twenty-minute one is the whole decision, so the
   time cost is stated on the card and not left to be discovered. */
.modes { display:grid; grid-template-columns:repeat(auto-fit,minmax(260px,1fr));
  gap:var(--s-3); }
.mode { display:block; margin:0; padding:var(--s-4); cursor:pointer;
  border:1px solid var(--line); border-radius:var(--r-md);
  background:var(--surface); text-transform:none; letter-spacing:0;
  font-family:var(--sans); font-weight:400;
  transition:border-color .12s ease, background .12s ease; }
.mode:hover { border-color:var(--line-strong); background:var(--surface-2); }
.mode:has(input:checked) { border-color:var(--accent); background:var(--accent-soft); }
.mode input { position:absolute; opacity:0; pointer-events:none; }
.mode:has(input:focus-visible) { outline:2px solid var(--accent); outline-offset:2px; }
.mode-body { display:block; }
.mode-t { display:block; font-family:var(--cond); font-size:var(--t-lg);
  font-weight:600; color:var(--ink); margin-bottom:var(--s-1); }
.mode-b { display:block; font-size:var(--t-md); color:var(--ink-2); }
.mode-d { display:block; font-size:var(--t-xs); color:var(--ink-3); margin-top:var(--s-2); }

/* The interview is a reading task, so it is held to a readable measure rather
   than stretched to the viewport. A question and a one-line answer set across
   1600px is uncomfortable in a way that is easy to miss on a wide monitor. */
.iv-wrap { max-width:760px; }
.iv-head { margin-bottom:var(--s-5); }
.iv-head h1 { margin-bottom:var(--s-2); }
.iv-head .sub { margin:0; }

/* Progress. Shown as a bar and a count because "3 / 11" answers a different
   question than a bar does: how much longer, versus how far in. */
.iv-top { display:flex; align-items:center; justify-content:space-between;
  gap:var(--s-4); flex-wrap:wrap; margin-bottom:var(--s-4); }
.iv-prog { display:flex; align-items:center; gap:var(--s-3); flex:1; min-width:180px; }
.iv-bar { flex:1; height:6px; background:var(--surface-2);
  border:1px solid var(--line-strong); border-radius:999px; overflow:hidden; }
/* A min-width so the first question does not present an empty trough that
   reads as a divider rule rather than as progress. */
.iv-fill { height:100%; min-width:6px; background:var(--accent);
  border-radius:999px; transition:width .3s ease; }
.iv-count { font-size:var(--t-xs); color:var(--ink-3);
  font-variant-numeric:tabular-nums; flex:0 0 auto; }
.iv-mode { display:flex; align-items:center; gap:var(--s-2); flex-wrap:wrap; }

/* The question itself. Deliberately the only thing on screen. */
.iv-card { border:1px solid var(--line); border-radius:var(--r-lg);
  background:var(--surface); padding:var(--s-5); box-shadow:var(--shadow-1); }
.iv-meta { display:flex; align-items:center; gap:var(--s-2);
  flex-wrap:wrap; margin-bottom:var(--s-4); }

/* The line under review, quoted verbatim. Reading it back is what makes the
   question answerable — "what was the team size" is meaningless without it. */
.iv-quote { font-family:var(--mono); font-size:var(--t-md); line-height:1.6;
  color:var(--ink); border-left:2px solid var(--line-strong);
  padding:var(--s-2) 0 var(--s-2) var(--s-4); margin-bottom:var(--s-3); }
.iv-why { font-size:var(--t-md); color:var(--ink-2); margin:0 0 var(--s-5); max-width:64ch; }

/* A follow-up shows the first answer above it: the follow-up asks for the
   missing half, not a redo, and hiding what they already said implies otherwise. */
.iv-said { display:flex; gap:var(--s-3); align-items:baseline;
  background:var(--surface-2); border-radius:var(--r-md);
  padding:var(--s-3) var(--s-4); margin-bottom:var(--s-4);
  font-size:var(--t-md); color:var(--ink-2); }
.iv-said-k { font-family:var(--cond); font-size:var(--t-xs); text-transform:uppercase;
  letter-spacing:.06em; color:var(--ink-3); flex:0 0 auto; }

.iv-form label { font-size:var(--t-base); text-transform:none; letter-spacing:0;
  font-family:var(--sans); font-weight:550; color:var(--ink);
  line-height:1.45; margin-bottom:var(--s-3); max-width:60ch; }
/* The global textarea is 220px — sized for editing a document, not for
   answering one question. */
.iv-form textarea { min-height:78px; font-family:var(--sans);
  font-size:var(--t-base); line-height:1.55; }
.iv-acts { display:flex; align-items:center; gap:var(--s-3);
  flex-wrap:wrap; margin-top:var(--s-4); }

.iv-nav { display:flex; align-items:center; justify-content:space-between;
  gap:var(--s-3); margin-top:var(--s-4); }
.iv-foot { font-size:var(--t-sm); color:var(--ink-3); max-width:70ch;
  margin:var(--s-5) 0 0; }
.iv-row { grid-template-columns:1fr auto; padding:var(--s-4); }

/* A button that reads as a link. Used where the action is navigation rather
   than commitment — back, skip ahead, switch mode. */
.linkbtn { background:none; border:0; padding:var(--s-1) 0; cursor:pointer;
  font-family:var(--sans); font-size:var(--t-md); color:var(--accent);
  text-decoration:underline; text-underline-offset:2px; }
.linkbtn:hover { color:var(--ink); }
.linkbtn:focus-visible { outline:2px solid var(--accent); outline-offset:2px;
  border-radius:var(--r-sm); }

/* Utilities used by the builder. */
.lbl-opt { text-transform:none; letter-spacing:0; font-family:var(--sans);
  font-weight:400; color:var(--ink-3); }
.dim-3 { color:var(--dead); }
/* `.row .fit` sets its own left border, so this needs matching specificity to
   take effect — a bare `.ok-l` loses and renders grey. */
.row .fit.ok-l, .ok-l { border-left:2px solid var(--good); padding-left:var(--s-3); }
.scroll-md { max-height:460px; overflow:auto; }
.bl-1 { border-left:1px solid var(--line); }
.pl-5 { padding-left:var(--s-5); }
.pr-5 { padding-right:var(--s-5); }

@media (max-width:640px) {
  .iv-card { padding:var(--s-4); }
  .iv-acts .btn { flex:1 1 auto; }
  .bl-1 { border-left:0; border-top:1px solid var(--line); }
  .pl-5, .pr-5 { padding-left:0; padding-right:0; }
}

/* ------------------------------------------------------------------ */
/* The setup conversation                                             */
/*                                                                    */
/* This replaced a five-step wizard: progress bar, stage pills,       */
/* Continue and Skip, "1 / 5" in the corner. Every screen was a form  */
/* with good copy. There is no step counter here on purpose — the     */
/* assistant decides what comes next from what the person said, so a  */
/* meter counting toward a fixed total would be a lie.                */
/* ------------------------------------------------------------------ */

.chat { max-width:780px; margin:0 auto; }
.thread { display:flex; flex-direction:column; gap:var(--s-5);
  padding:var(--s-4) 0 var(--s-5); }

/* Consecutive turns from the same side are one utterance broken over two
   messages, not two exchanges. Tightening the gap is what makes the thread
   read in speaker blocks rather than as an evenly spaced list. */
.msg + .msg:not(.msg-me) { margin-top:calc(var(--s-4) - var(--s-5)); }
.msg-me + .msg-me { margin-top:calc(var(--s-4) - var(--s-5)); }

.msg { max-width:88%; }
.msg-body { white-space:pre-wrap; font-size:var(--t-base); line-height:1.6;
  color:var(--ink); }

/* The user's own turns are right-aligned and boxed. The assistant's are not:
   an assistant bubble makes long explanatory text harder to read, and the
   assistant does most of the talking here. */
.msg-me { margin-left:auto; max-width:76%; }
.msg-me .msg-body { background:var(--accent-soft); color:var(--ink);
  border-radius:var(--r-lg); padding:var(--s-3) var(--s-4); text-align:left; }
/* Attachments and notes belong to the message, so they align with it rather
   than with the thread — a chip flush left under a right-aligned bubble reads
   as a separate turn. */
.msg-me .msg-attach { justify-content:flex-end; }

/* Where the last message ends. #thread-end is scrolled to after a send and
   after a poll swap the user was already following; without the margin the
   composer — position:sticky, so it is painted over the thread, not below it —
   covers the newest turn and the user scrolls a click to find it.

   The real value is measured in start.html from the composer's offsetHeight,
   because the composer grows with the textarea and with the upload row under
   it. This is only the pre-script fallback for first paint. It was 200px and
   static, which stopped the view a fixed distance short of the bottom no
   matter what was actually there — the user could not reach the end of the
   page, and every scroll attempt snapped back to the same wrong spot. */
#thread-end { scroll-margin-bottom:120px; }

/* Newly swapped turns fade in. The poll replaces the whole thread, so without
   this the reply appears as an instantaneous relayout with no indication that
   anything was added. Reduced motion kills the duration globally at line 105. */
#chat-live.htmx-added .msg:last-of-type { animation:msg-in .22s ease-out; }
@keyframes msg-in { from { opacity:0; transform:translateY(4px); } }

.msg-attach { display:flex; flex-wrap:wrap; gap:var(--s-2); margin-top:var(--s-2); }
.chip { font-family:var(--mono); font-size:var(--t-xs); color:var(--ink-2);
  background:var(--surface-2); border:1px solid var(--line);
  border-radius:var(--r-md); padding:var(--s-1) var(--s-3); }

.msg-note { margin-top:var(--s-3); padding:var(--s-3) var(--s-4);
  border-left:2px solid var(--accent); background:var(--accent-soft);
  border-radius:0 var(--r-md) var(--r-md) 0;
  font-size:var(--t-md); color:var(--ink-2); }

.msg-form { margin-top:var(--s-4); }
.msg-form textarea { min-height:220px; }

/* A proposed role, with its evidence. Cards rather than a plain checkbox list
   because the evidence is the point — a title on its own is a horoscope. */
.pickrole { display:flex; gap:var(--s-3); align-items:flex-start; margin:0 0 var(--s-2);
  padding:var(--s-3) var(--s-4); cursor:pointer;
  border:1px solid var(--line); border-radius:var(--r-md); background:var(--surface);
  font-family:var(--sans); font-weight:400; text-transform:none; letter-spacing:0;
  transition:border-color .12s ease, background .12s ease; }
.pickrole:hover { border-color:var(--line-strong); background:var(--surface-2); }
.pickrole:has(input:checked) { border-color:var(--accent); background:var(--accent-soft); }
.pickrole input { width:16px; height:16px; margin:3px 0 0; accent-color:var(--accent);
  flex:0 0 auto; }
.pickrole > span { display:block; }
.pickrole-t { font-weight:600; color:var(--ink); margin-right:var(--s-2); }
.pickrole-w { display:block; font-size:var(--t-md); color:var(--ink-2); margin-top:var(--s-2); }
.pickrole-e { display:block; font-family:var(--mono); font-size:var(--t-xs);
  color:var(--ink-3); margin-top:var(--s-2); padding-left:var(--s-3);
  border-left:2px solid var(--line-strong); }
.pickrole-g { display:block; font-size:var(--t-sm); color:var(--warn); margin-top:var(--s-2); }

/* The composer sticks to the bottom: in a conversation the input is where you
   look, and hunting for it after a long assistant turn is friction. */
.composer { position:sticky; bottom:0; background:var(--ground);
  border-top:1px solid var(--line); padding:var(--s-4) 0 var(--s-3); }
/* The thread runs under the composer as it scrolls. Without a fade the text
   passing behind the sticky edge is cut mid-line, which reads as a rendering
   fault rather than as scrolling. */
.composer::before { content:""; position:absolute; left:0; right:0;
  bottom:100%; height:var(--s-6); pointer-events:none;
  background:linear-gradient(to top, var(--ground), transparent); }
.composer-row { display:flex; gap:var(--s-3); align-items:flex-end; }
.composer-row textarea { min-height:52px; max-height:240px; font-family:var(--sans);
  font-size:var(--t-base); line-height:1.5; resize:none; overflow-y:auto; }
/* The send button is the height of a single-line composer, and stays there as
   the textarea grows — a button stretching to match six lines of typed text is
   a wall, not a control. */
.composer-row .btn { flex:0 0 auto; min-height:52px; }
.composer-files { display:flex; align-items:center; gap:var(--s-3);
  flex-wrap:wrap; margin-top:var(--s-3); }

.filebtn { display:inline-flex; align-items:center; margin:0; cursor:pointer;
  font-family:var(--sans); font-size:var(--t-md); font-weight:500;
  text-transform:none; letter-spacing:0; color:var(--accent);
  text-decoration:underline; text-underline-offset:2px; }
.filebtn input { position:absolute; width:1px; height:1px; opacity:0; }
.filebtn:hover { color:var(--ink); }
.filebtn:has(input:focus-visible) { outline:2px solid var(--accent); outline-offset:2px; }

.chat-foot { display:flex; align-items:center; justify-content:space-between;
  gap:var(--s-3); padding:var(--s-3) 0 var(--s-5); }

@media (max-width:640px) {
  .msg { max-width:100%; }
  .composer-row { flex-wrap:wrap; }
  .composer-row textarea { width:100%; }
  .composer-row .btn { width:100%; }
}

/* Interview prep. Gaps and certainties are visually distinct from the rest:
   limited prep time should go to what you are weakest on, and a list where
   everything looks equally urgent is not advice. */
.prep-row { grid-template-columns:1fr; padding:var(--s-4); }
.prep-q { font-size:var(--t-base); font-weight:550; color:var(--ink);
  line-height:1.5; max-width:66ch; }

/* Pending state while a reply generates in the background. The model call is
   20-40s; before this the request blocked the app's single worker until
   Traefik pulled the backend and served "no available server". */
.thinking { display:flex; align-items:center; gap:var(--s-2);
  padding:var(--s-3) 0; }
.thinking .dot { width:6px; height:6px; border-radius:50%;
  background:var(--ink-3); animation:think 1.4s ease-in-out infinite; }
.thinking .dot:nth-child(2) { animation-delay:.2s; }
.thinking .dot:nth-child(3) { animation-delay:.4s; margin-right:var(--s-2); }
@keyframes think { 0%,60%,100% { opacity:.25 } 30% { opacity:1 } }
.composer-row textarea:disabled, .composer-row .btn:disabled {
  opacity:.5; cursor:not-allowed; }

/* Threshold preview. The generate-at dial was a number between 0 and 10 with
   no observable meaning: you moved it, waited a night, and counted what turned
   up. This shows what each value would produce from scores already computed. */
.thresh td { padding:var(--s-1) var(--s-3); vertical-align:middle; }
.thresh tr.on { background:var(--accent-soft); }
.thresh tr.on td:first-child { font-weight:700; color:var(--accent); }
/* NO min-width. A zero-width bar is the honest rendering of zero
   applications; a 2px stub made "nothing would generate" look identical to
   one, which is the distinction the whole table exists to show. */
.thresh .bar { display:block; height:8px; border-radius:999px;
  background:var(--line-strong); }
.thresh tr.on .bar { background:var(--accent); }

/* ── the queue ───────────────────────────────────────────────────────────────
 * One packet at a time. A sidebar-and-detail layout was the obvious shape and
 * the wrong one: that is a file browser, and a queue is a sequence. Everything
 * here is sized so a packet can be decided without scrolling.
 */

/* Segmented progress. "3 of 6" has to be parsed; this is read. Deliberately
   thin — it orients, it is not the content. */
.qrail { display:flex; gap:5px; }
.qrail i { height:3px; flex:1; border-radius:999px; background:var(--line); }
.qrail i.now { background:var(--accent); }

.qrole { font-size:var(--t-xl); letter-spacing:-.02em; margin:0 0 var(--s-1);
  line-height:1.2; }
.qmeta { color:var(--ink-3); font-size:var(--t-md); margin:0; }
.qmeta b { color:var(--ink-2); }

.qscore { text-align:right; }
.qscore b { display:block; font-family:var(--mono); font-size:var(--t-xl);
  color:var(--good); line-height:1; letter-spacing:-.03em; }
.qscore span { font-size:var(--t-xs); color:var(--ink-3); }

/* Materials on ONE line. Four chips advertising finished work is what made an
   earlier draft read as a checklist of things to review rather than a packet
   ready to send. */
.qmats { font-size:var(--t-md); color:var(--ink-3);
  margin:var(--s-4) 0 var(--s-3); padding-bottom:var(--s-4);
  border-bottom:1px solid var(--line); }
.qmats b { color:var(--good); }

.qfit { font-size:var(--t-md); color:var(--ink-2); margin:0 0 var(--s-4); }

/* The one thing needing a human, so the only thing here carrying colour. */
.qask { border:1px solid var(--warn); background:var(--warn-soft);
  border-radius:var(--r-md); padding:var(--s-4); margin-bottom:var(--s-4); }
.qask-k { font-size:var(--t-xs); font-weight:700; text-transform:uppercase;
  letter-spacing:.06em; color:var(--warn); margin-bottom:var(--s-2); }
.qask-q { margin:0 0 var(--s-2); color:var(--ink-2); }
.qflags { margin:0 0 var(--s-3); padding-left:var(--s-5); }
.qflags li { margin-bottom:var(--s-1); color:var(--ink-2); }

.qgo { padding-top:var(--s-4); border-top:1px solid var(--line); }

/* ── trajectory readings ─────────────────────────────────────────────────────
 * A judgment, not a count, so it is visually separate from the stat strip
 * above it. Colour carries whether this is a problem or a confirmation: the
 * whole point of the module is distinguishing "nothing is reading it" from
 * "it reads fine and does not convert", and those should not look the same.
 */
.tread { border-left:2px solid var(--line-strong); padding:0 0 0 var(--s-4);
  margin-bottom:var(--s-4); }
.tread:last-of-type { margin-bottom:0; }
.tread b { display:block; margin-bottom:var(--s-1); }
.tread p { margin:0 0 var(--s-2); color:var(--ink-2); font-size:var(--t-md);
  max-width:70ch; }
.tread-good { border-left-color:var(--good); }
.tread-warn { border-left-color:var(--warn); }
.tread-note { border-left-color:var(--accent); }

/* ── the dossier ─────────────────────────────────────────────────────────────
 * What the app concluded about the user, as opposed to what they told it.
 * The read leads at size because if the page said nothing else, that is the
 * sentence worth having.
 */
.dread { font-size:var(--t-lg); line-height:1.5; margin:var(--s-2) 0 var(--s-3);
  max-width:66ch; }

/* Findings carry their severity as a rule rather than a word: "warn" has to be
   parsed, a colour is seen. */
.dfind { border-left:2px solid var(--line-strong); padding-left:var(--s-4);
  margin-bottom:var(--s-4); }
.dfind:last-child { margin-bottom:0; }
.dfind b { display:block; margin-bottom:var(--s-1); }
.dfind p { margin:0 0 var(--s-1); color:var(--ink-2); font-size:var(--t-md);
  max-width:70ch; }
.dfind-good { border-left-color:var(--good); }
.dfind-warn { border-left-color:var(--bad); }
.dfind-note { border-left-color:var(--accent); }
.dsrc { font-size:var(--t-xs); color:var(--ink-3); font-family:var(--mono); }

/* A literal window over the top of the CV. Rendering the actual first
   screenful reads as a simulation of what a recruiter sees; a score out of ten
   would not, and would invite arguing with the number instead of fixing the
   document. */
.dwindow { border:1px solid var(--line); border-radius:var(--r-md);
  background:var(--surface-2); padding:var(--s-4); font-size:var(--t-md);
  line-height:1.5; color:var(--ink-2); white-space:pre-wrap;
  max-height:220px; overflow:hidden; position:relative; }
.dverdict { font-size:var(--t-base); margin:var(--s-4) 0; max-width:66ch; }

/* ── changelog ───────────────────────────────────────────────────────────────
 * Commit messages in this repo are prose explaining what changed and why, so
 * the log IS the changelog. Collapsed by default: forty entries of full body
 * text is a wall, and the subject line is what someone scanning actually wants.
 */
.chentry { border-bottom:1px solid var(--line); }
.chentry:last-child { border-bottom:0; }
.chentry summary { display:flex; gap:var(--s-3); align-items:baseline;
  padding:var(--s-3) var(--s-4); cursor:pointer; list-style:none; }
.chentry summary::-webkit-details-marker { display:none; }
.chentry summary:hover { background:var(--surface-2); }
.chdate { font-size:var(--t-xs); color:var(--ink-3); white-space:nowrap; }
.chsub { flex:1; font-weight:600; font-size:var(--t-md); }
.chsha { font-size:var(--t-xs); color:var(--ink-3); }
/* pre-wrap, because the bodies are hard-wrapped prose and reflowing them
   would run sentences together across paragraph breaks. */
.chbody { padding:0 var(--s-4) var(--s-4) var(--s-4); font-size:var(--t-md);
  color:var(--ink-2); white-space:pre-wrap; max-width:78ch; }

/* ── feedback ────────────────────────────────────────────────────────────────
 * On every page, because a report written where the bug happened is worth
 * several written from memory afterwards. Anchored to the edge rather than
 * floating over content: a button that covers what someone is trying to
 * describe is a button that makes reporting harder.
 */
.fbtab { position:fixed; right:0; bottom:22%; z-index:70;
  transform-origin:100% 100%; transform:rotate(-90deg) translate(50%, 0);
  background:var(--surface); color:var(--ink-2); font:inherit;
  font-size:var(--t-xs); font-weight:700; letter-spacing:.06em;
  text-transform:uppercase; border:1px solid var(--line-strong);
  border-bottom:0; border-radius:var(--r-md) var(--r-md) 0 0;
  padding:var(--s-2) var(--s-4); cursor:pointer; box-shadow:var(--shadow-1); }
.fbtab:hover { color:var(--ink); border-color:var(--ink-3); }
/* Rotated tabs are a desktop affordance; on a phone it would sit under a
   thumb during scrolling. */
@media (max-width:768px) { .fbtab { display:none; } }

.fbpanel { position:fixed; right:var(--s-4); bottom:var(--s-4); z-index:80;
  width:min(440px, calc(100vw - var(--s-6))); max-height:82vh; overflow:auto;
  background:var(--surface); border:1px solid var(--line-strong);
  border-radius:var(--r-lg); box-shadow:var(--shadow-2); }
.fbform { padding:var(--s-5); }
.fbh { font-size:var(--t-lg); margin:0 0 var(--s-4); }
.fbkinds { display:flex; flex-direction:column; gap:var(--s-2);
  margin-bottom:var(--s-4); }
.fbform textarea { width:100%; }

/* What is attached, shown rather than merely collected. Attaching diagnostics
   the user cannot inspect is how a feedback form becomes one people stop
   trusting -- and this app holds their CV. */
.fbwhat { margin-top:var(--s-3); font-size:var(--t-md); }
.fbwhat summary { cursor:pointer; color:var(--ink-3); }
.fbwhat-b { padding:var(--s-3) 0 0; color:var(--ink-3); }
.fbwhat-b code { font-size:var(--t-xs); color:var(--ink-2);
  word-break:break-all; }
