/* jobfitr — the atmosphere theme. The whole UI is frosted glass over a live
   time-of-day sky (driven by atmosphere.js). Tokens below are the fallback values
   before the first paint; atmosphere.js overrides --ink/--sub/--card-* /--accent
   /--sky-* /--breath every minute from the browser's local clock. */

:root {
  --ink: #0b0f1a;
  --sub: rgba(11, 15, 26, 0.6);
  --card-bg: rgba(255, 255, 255, 0.34);
  --card-brd: rgba(255, 255, 255, 0.62);
  --field-bg: rgba(255, 255, 255, 0.28);
  --accent: #e6b985;
  --good: #2f8f5b;
  --breath: 5s;
  --sky-top: #a6d2f2;
  --sky-mid: #d6e8f5;
  --sky-bot: #eaf3ee;
  --glow: #ffffff;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --radius: 16px;
  --radius-sm: 11px;
  --maxw: 560px;

  /* ── the three value planes (the notan test: if it doesn't read in two values,
     it won't read in twenty). Plane 1 is the sky. Plane 2 is the glass surface —
     --card-bg's 0.34 alpha only gives body text ~2.9:1, so real reading surfaces
     use --plate instead. Plane 3 is --anchor: the single strongest value jump,
     RATIONED to the wordmark, the fit bar, the primary action, and an open card.
     --anchor derives from --ink so it inverts with the sky (atmosphere.js flips
     --ink light after dusk); a hardcoded dark would vanish at night. ────────── */
  /* The app's CHROME surface — the sticky header group. It has to be opaque (cards
     scroll under it) and it must NOT take the sky's colour: every version that mixed
     the sky in went muddy tan at sunset, exactly when the rest of the app is warmest
     and a dingy toolbar reads worst. atmosphere.js states --paper flatly per scene
     (cool near-white by day, deep navy at night); this is a fallback for first paint. */
  --paper: #f2f5f9;
  --chrome: var(--paper);
  --plate: color-mix(in srgb, var(--card-bg) 55%, rgba(255, 255, 255, 0.7));
  --plate-brd: color-mix(in srgb, var(--card-brd) 70%, rgba(255, 255, 255, 0.5));
  --anchor: color-mix(in srgb, var(--ink) 88%, var(--accent));
  /* the label ON the anchor tracks the SKY, not white — so it stays the light half
     of the pair by day and flips to the dark half at night, when --ink goes light. */
  --on-anchor: color-mix(in srgb, var(--sky-mid) 88%, #fff);

  /* ── a real modular type scale (1.25). The app previously ran ten sizes between
     10.5px and 15.5px — inside 5px, with no 16px body size at all. That is not a
     scale, and mush cannot express hierarchy. ─────────────────────────────────── */
  --t-micro: 11px;
  --t-small: 13px;
  --t-body: 16px;
  --t-lead: 20px;
  --t-head: 25px;
  --t-hero: 31px;

  /* The masthead is in normal flow above the front door, so a 100dvh section beneath
     it hangs exactly that far past the fold and its centre lands low. Measured 72px
     down to the 560 breakpoint and 68px below it (the applied rail's 44px toggle sets
     the height now that the nav links are gone). */
  --masthead: 72px;

  /* The front door's vertical rhythm. Every gap below was a fixed pixel, which made the
     whole page a constant ~828px tall on EVERY screen — so it overflowed any window
     shorter than that and left slack on taller ones. These scale continuously with the
     viewport instead, so there is no breakpoint tuned to any particular laptop. */
  --v-gap: clamp(18px, 4.5vh, 52px);   /* hero → bar: the separation actually asked for */
  --v-tight: clamp(10px, 1.6vh, 18px); /* small structural gaps */
  --v-rule: clamp(14px, 2.4vh, 26px);  /* above the explainer rule */
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  color: var(--ink);
  background: transparent;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  transition: color 1400ms ease;
}
[hidden] { display: none !important; }
h1, h2, h3, p { margin: 0; }
button { font: inherit; cursor: pointer; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
.skip-link { position: absolute; left: -999px; }
.skip-link:focus { left: 12px; top: 12px; z-index: 20; background: var(--card-bg); padding: 8px 12px; border-radius: 8px; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 6px; }

/* ── atmosphere layers ──────────────────────────────────────────────────────── */
#sky {
  position: fixed; inset: 0; z-index: -4;
  background: linear-gradient(180deg, var(--sky-top) 0%, var(--sky-mid) 52%, var(--sky-bot) 100%);
  transition: background 1400ms linear;
}
#glow {
  position: fixed; z-index: -3; left: var(--gx, 50%); top: var(--gy, 22%);
  width: 130vmax; height: 130vmax; margin: -65vmax 0 0 -65vmax;
  background: radial-gradient(circle, var(--glow) 0%, transparent 42%);
  opacity: var(--gA, 0.7); mix-blend-mode: screen; pointer-events: none;
  transition: opacity 1400ms linear, top 1800ms ease, background 1400ms linear;
  animation: drift 60s ease-in-out infinite alternate;
}
@keyframes drift { 0% { transform: translate(-4.5vw, 0) scale(1); } 100% { transform: translate(4.5vw, -3vh) scale(1.09); } }
/* two cloud layers drifting in different directions at different speeds — a slow,
   windy, alive movement in the color and space. Colors from --wash-* (set per minute). */
#wash { position: fixed; inset: -30%; z-index: -2; pointer-events: none; mix-blend-mode: screen; }
#wash::before, #wash::after {
  content: ""; position: absolute; inset: 0; transition: background 1600ms linear;
  will-change: transform;
}
#wash::before {
  opacity: 0.9;
  background: radial-gradient(46% 34% at 22% 24%, var(--wash-a, transparent), transparent 60%),
              radial-gradient(40% 38% at 80% 58%, var(--wash-b, transparent), transparent 62%),
              radial-gradient(44% 30% at 58% 10%, var(--wash-b, transparent), transparent 64%);
  animation: cloudA 48s ease-in-out infinite alternate;
}
#wash::after {
  opacity: 0.75;
  background: radial-gradient(52% 40% at 70% 14%, var(--wash-a, transparent), transparent 60%),
              radial-gradient(46% 36% at 15% 72%, var(--wash-b, transparent), transparent 62%),
              radial-gradient(40% 30% at 38% 6%, var(--wash-a, transparent), transparent 64%);
  animation: cloudB 70s ease-in-out infinite alternate;
}
@keyframes cloudA { 0% { transform: translate(-9%, 3%) scale(1.06); } 100% { transform: translate(9%, -6%) scale(1.24); } }
@keyframes cloudB { 0% { transform: translate(8%, -4%) scale(1.14); } 100% { transform: translate(-10%, 7%) scale(1.28); } }
#stars { position: fixed; inset: 0; z-index: -1; pointer-events: none; }

/* ── grain (silver) ─────────────────────────────────────────────────────────────
   Modelled on Capture One's silver film grain: LUMINANCE only, coarse, and actually
   present. Three things had to be right, and the first version got all three wrong.

   1. color-interpolation-filters='sRGB'. SVG filters default to linearRGB, so a
      turbulence centred on 0.5 renders at ~188 in sRGB — measured. An off-centre
      blend layer under `overlay` mostly just lightens, which is why it was invisible.
   2. feColorMatrix saturate 0. Raw feTurbulence varies R, G and B independently, so
      it is COLOUR speckle: chroma spread measured 37.6. Silver grain has none — this
      takes it to 0.0.
   3. A contrast ramp. Raw noise measured a spread of only 15.3, which at 5.5% opacity
      under overlay is arithmetically invisible. slope 2 with a centring intercept
      lifts the spread to ~50 while holding the mean at 128.

   baseFrequency 0.62 over 4 octaves gives the larger, clumpier structure that reads as
   silver rather than as fine digital speckle. Static, so reduced-motion needs no
   carve-out, and it stays one inline asset with no request and no JS. */
#grain {
  position: fixed; inset: 0; z-index: -1; pointer-events: none;
  opacity: 0.19; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='g' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.62' numOctaves='4' stitchTiles='stitch' result='t'/%3E%3CfeColorMatrix in='t' type='saturate' values='0' result='m'/%3E%3CfeComponentTransfer in='m'%3E%3CfeFuncR type='linear' slope='2' intercept='-0.5'/%3E%3CfeFuncG type='linear' slope='2' intercept='-0.5'/%3E%3CfeFuncB type='linear' slope='2' intercept='-0.5'/%3E%3CfeFuncA type='linear' slope='0' intercept='1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23g)'/%3E%3C/svg%3E");
  background-size: 200px 200px;
}
/* The same overlay bites harder on a dark sky — night keeps the texture, not the noise. */
@media (prefers-color-scheme: dark) { #grain { opacity: 0.15; } }
.tw { position: absolute; width: 2px; height: 2px; border-radius: 50%; background: #fff; box-shadow: 0 0 4px 1px rgba(255, 255, 255, 0.7); opacity: 0; }
/* --tw-peak is set per star so a dusk star is genuinely faint and a midnight one is
   bright, instead of every star firing at the same 0.85 regardless of the sky. */
@keyframes tw { 0% { opacity: 0; transform: scale(0.4); } 45% { opacity: var(--tw-peak, 0.85); transform: scale(1); } 100% { opacity: 0; transform: scale(0.6); } }
.tw.on { animation: tw 2.6s ease-in-out; }

/* ── glass ──────────────────────────────────────────────────────────────────── */
.glass {
  background: var(--card-bg);
  border: 1px solid var(--card-brd);
  border-radius: var(--radius);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.22) inset, 0 12px 44px rgba(15, 20, 38, 0.16);
  -webkit-backdrop-filter: blur(22px) saturate(1.4);
  backdrop-filter: blur(22px) saturate(1.4);
  transition: background 1400ms ease, border-color 1400ms ease;
}

/* ── header + stage ─────────────────────────────────────────────────────────── */
/* min-height, not just padding: the applied rail is hidden until something has been
   applied to, and without it the header collapses from 72px to 55px. Everything that
   positions against the header — the sticky boardbar, the lid that hides scrolling
   cards, the filter drawer — reads --masthead, so a header that silently changed height
   left the boardbar pinned 17px below its own flow position and the first result card
   slid underneath it. Fixing the height also removes the jank of the whole page shifting
   down the moment a user applies to their first job. */
.site-header {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; gap: 12px;
  max-width: 940px; margin: 0 auto; padding: 14px 22px;
  min-height: var(--masthead); box-sizing: border-box;
}
.brand { display: flex; align-items: center; gap: 9px; font-weight: 700; letter-spacing: -0.02em; color: var(--ink); text-decoration: none; }
/* plane 3 — the monogram is one of the four objects allowed the full ink jump */
.brand-mark {
  display: grid; place-items: center; width: 27px; height: 27px;
  border-radius: 8px; background: var(--anchor); color: var(--accent);
  font-size: var(--t-small); font-weight: 800; letter-spacing: -0.04em;
  transition: background 1400ms ease, color 1400ms ease;
}
.brand-name { font-size: var(--t-body); }
.site-header .spacer { flex: 1; }
.freshness { font-family: var(--mono); font-size: var(--t-micro); color: var(--sub); }
/* padding-bottom is small here on purpose: the 120px runway is for the BOARD, whose
   cards scroll, and on the front door it was pure dead space that forced a scrollbar
   onto a page that otherwise fits one screen. body.board sets its own. */
/* padding-bottom stays small: the 120px runway is for the BOARD, whose cards scroll, and
   body.board sets its own. On the front door it was dead space that forced a scrollbar
   onto a page that otherwise fits. */
.stage { max-width: var(--maxw); margin: 0 auto; padding: var(--v-tight) 20px 0; }

/* ── the front-door hero: what a cold visitor reads in three seconds ─────────── */
.hero { width: min(640px, calc(100% - 40px)); text-align: center; margin-bottom: 0; }
.hero-prop {
  font-size: var(--t-hero); font-weight: 680; letter-spacing: -0.025em;
  line-height: 1.15; text-wrap: balance; margin-bottom: 10px;
}
.hero-prop em { font-style: normal; color: var(--anchor); }
.hero-sub { font-size: var(--t-body); color: var(--sub); text-wrap: balance; max-width: 46ch; margin: 0 auto 14px; }
/* the proof line — real numbers from /api/meta, never invented ones */
.proof {
  display: inline-flex; align-items: center; gap: 9px; flex-wrap: wrap; justify-content: center;
  font-family: var(--mono); font-size: var(--t-micro); color: var(--sub); min-height: 1.2em;
}
.proof b { color: var(--ink); font-weight: 600; }
.proof .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--good); box-shadow: 0 0 0 3px color-mix(in srgb, var(--good) 22%, transparent); }
.proof .sep { opacity: 0.4; }
/* the interview progress dots — four questions, so show where you are */
.sayrow { display: flex; align-items: baseline; gap: 10px; width: 100%; }
.sayrow:has(.chat-say:empty) { display: none; } /* nothing said yet → no empty row */
.steps { display: inline-flex; gap: 5px; flex: none; transform: translateY(-3px); }
.steps:empty { display: none; }
.steps i { width: 6px; height: 6px; border-radius: 50%; display: block; background: color-mix(in srgb, var(--ink) 18%, transparent); transition: background 300ms ease; }
.steps i.on { background: var(--anchor); }

/* ── chat front door ────────────────────────────────────────────────────────── */
/* front door: the BAR is pinned dead-center; the transcript stacks upward above it,
   so answering a question never moves the bar. Cancels the stage's asymmetric padding. */
.chat-view {
  /* Deliberately NOT a full-height centring grid. Every version that claimed 100dvh
     pushed the starters and the explainer blocks past the fold and put the desktop
     into a scroll it does not need — the whole front door fits one screen. This is a
     plain column: hero, a real gap, the bar, and the rest flows under it. */
  display: flex; flex-direction: column; align-items: center;
}
/* the hero and the transcript share row 1 — hero on top, transcript hugging the bar */
/* The hero rides at the TOP of row 1, not the bottom of it. Those are two different
   things and only one of them moves the chat: shrinking the grid rows lifts the whole
   column including the input, whereas flipping this alignment lifts only the marketing
   block and leaves the bar centred in the viewport where it belongs — the input is the
   thing a returning user comes for, and it should sit in the same place every time. */
/* The separation between the proposition and the bar — the one thing that was
   actually asked for. A margin, not a layout system. */
.doorhead {
  width: 100%; margin-bottom: var(--v-gap);
  display: flex; flex-direction: column; align-items: center;
}
/* Sits INSIDE .convo now, immediately above the question — full width of the column,
   not its own centred box, so the transcript and the question share one left edge. */
.transcript {
  width: 100%;
  display: flex; flex-direction: column; justify-content: flex-end;
  gap: 10px; padding-bottom: var(--v-tight); overflow: hidden; /* no scroll — old lines clip/fade */
}
.transcript:has(.echo:empty) { display: none; } /* nothing answered yet → no empty box */
.echo { width: 100%; font-family: var(--mono); font-size: 12.5px; line-height: 1.55; color: var(--sub); }
.echo .row { transition: opacity 600ms ease; margin-bottom: 10px; }
.echo .row .q-asked { color: var(--ink); opacity: 0.72; }          /* the question, flush left */
/* the answer, indented beneath — a warm tint DARKENED toward the ink so it stays
   readable on the light sky (the raw accent is too pale to read). */
.echo .row .a-given { color: color-mix(in srgb, var(--accent) 20%, var(--ink)); padding-left: 18px; font-weight: 600; }
.halo {
  position: absolute; inset: -14px; border-radius: 26px; z-index: -1; pointer-events: none;
  background: radial-gradient(60% 120% at 50% 50%, color-mix(in srgb, var(--accent) 40%, transparent), transparent 70%);
  opacity: 0.5; animation: breathe var(--breath) ease-in-out infinite;
}
@keyframes breathe { 0%, 100% { transform: scale(0.99); opacity: 0.32; } 50% { transform: scale(1.04); opacity: 0.58; } }
/* the heartbeat: a faint, thick, barely-traceable pulse from the chat card */
.pulse { position: absolute; inset: 0; z-index: -1; pointer-events: none; }
.rip {
  position: absolute; inset: 0; border-radius: 16px; pointer-events: none;
  /* a faint heartbeat that swells to 1.06× the answer bar and fades. Color adapts:
     light on a bright sky, a dark shadow at night (--rip-col, set per-minute). */
  border: 3px solid var(--rip-col, rgba(15, 20, 38, 0.6));
  filter: blur(1.5px);
  opacity: 0; will-change: transform, opacity;
}
@keyframes ripOut {
  0% { transform: scale(1); opacity: 0; }
  26% { opacity: 0.12; }
  100% { transform: scale(1.06); opacity: 0; }
}
.rip.run { animation: ripOut 1.7s ease-out forwards; }
.rip.dub { animation-duration: 1.5s; }
/* the live conversation block: what was just said + the box + its chips, as one unit */
.doorextra { width: min(560px, calc(100% - 40px)); margin: var(--v-rule) auto 0; }
.convo { width: min(560px, calc(100% - 40px)); }
/* no outer window — the bar itself is the glass; halo + pulse hug the BAR, not the taller wrap */
.barwrap { position: relative; }
.chat-say { font-size: var(--t-lead); font-weight: 560; letter-spacing: -0.01em; line-height: 1.35; min-height: 1.35em; margin-bottom: 14px; padding: 0 4px; text-wrap: balance; }
/* collapse the assistant line when it has nothing to say — a clean single-line bar on load */
.chat-say:empty { display: none; }
.chat-say .cur { display: inline-block; width: 7px; height: 16px; vertical-align: -2px; background: var(--accent); margin-left: 2px; border-radius: 1px; animation: blink 1.1s steps(1) infinite; }
@keyframes blink { 50% { opacity: 0; } }
.chat-field {
  display: flex; align-items: center; gap: 10px; padding: 12px 10px 12px 18px; border-radius: 14px;
  /* a real reading surface (plane 2), not the 0.34-alpha whisper the rest of the app used */
  background: var(--plate); border: 1px solid var(--plate-brd);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.22) inset, 0 12px 44px rgba(15, 20, 38, 0.16);
  -webkit-backdrop-filter: blur(22px) saturate(1.4);
  backdrop-filter: blur(22px) saturate(1.4);
  transition: background 1400ms ease, border-color 1400ms ease;
}
.chat-field input { flex: 1; border: 0; background: transparent; color: var(--ink); font-size: var(--t-body); outline: none; }
.chat-field input::placeholder { color: var(--sub); }
/* Dim until there is something to send, then lit. Chips are multi-select — a tap adds
   to the box rather than committing — so this button is what tells you the turn is
   ready to go, instead of leaving Enter as a key you have to already know about. */
.chat-send {
  display: grid; place-items: center; width: 34px; height: 34px; flex: none;
  border: 0; border-radius: 50%; padding: 0; font-size: var(--t-small);
  background: color-mix(in srgb, var(--ink) 16%, transparent); color: var(--sub);
  transition: background 220ms ease, color 220ms ease, transform 220ms ease;
}
.chat-send.ready { background: var(--anchor); color: var(--on-anchor); transform: scale(1.06); }
.chips-hint {
  font-family: var(--mono); font-size: var(--t-micro); color: var(--sub);
  text-align: center; margin-top: 9px; opacity: 0.75;
}
/* When the server explains a question's mechanic ("Specific beats generic…") the line
   stops being a throwaway chip instruction and becomes the answer to a question the
   user would otherwise get wrong — so it reads as prose, not as UI chrome. */
.chips-hint-strong {
  font-family: inherit; font-size: var(--t-micro); opacity: 1;
  color: var(--ink); max-width: 54ch; margin-left: auto; margin-right: auto;
}
/* seeded starters — a bare input tells a first-time visitor nothing about what the
   assistant can do, so offer real searches they can run with one click. */
.starters { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 8px; }
.starters-lab { font-family: var(--mono); font-size: var(--t-micro); color: var(--sub); margin-bottom: 9px; text-align: center; }
.starter {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: var(--t-small); color: var(--sub);
  background: transparent; border: 1px dashed var(--card-brd); border-radius: 100px; padding: 7px 13px;
}
.starter:hover { color: var(--ink); border-style: solid; background: var(--card-bg); }
.starter .arw { opacity: 0.5; }
/* the two things a sceptical visitor actually wants to know before typing anything */
.doornotes { display: grid; gap: 20px; margin-top: var(--v-rule); padding-top: var(--v-rule); border-top: 1px solid var(--card-brd); }
.doornotes h2 { font-size: var(--t-small); font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--ink); margin-bottom: 6px; }
.doornotes p { font-size: var(--t-small); color: var(--sub); line-height: 1.5; }
@media (min-width: 620px) { .doornotes { grid-template-columns: 1fr 1fr; gap: 26px; } }
/* suggested-chips row — sits just under the pinned bar (grid row 3); hidden when empty.
   Populated later (fixed per-question or model-suggested); the front door stays bare for now. */
.chipwrap { margin-top: 14px; }
.chips { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.chips:empty { display: none; }
/* one tap = one answer. A chip used to only FILL the box, leaving the user to find
   Enter — a dead end dressed as a button. */
.chip { font-size: var(--t-small); color: var(--ink); background: var(--card-bg); border: 1px solid var(--card-brd); border-radius: 100px; padding: 7px 14px; cursor: pointer; -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); transition: transform 140ms ease, background 200ms ease, border-color 1400ms ease; }
.chip:hover { transform: translateY(-1px); background: var(--plate); }
.link-quiet { background: none; border: 0; color: var(--sub); font-size: var(--t-small); text-decoration: underline; text-underline-offset: 3px; }

/* ── results toolbar ────────────────────────────────────────────────────────── */
.toolbar { display: flex; align-items: center; gap: 9px; padding: 4px 2px 12px; flex-wrap: wrap; }
.tb-btn { font-size: var(--t-small); color: var(--ink); background: var(--plate); border: 1px solid var(--plate-brd); border-radius: 9px; padding: 7px 12px; -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px); }
.tb-sort select { font: inherit; font-size: var(--t-small); color: var(--ink); background: var(--plate); border: 1px solid var(--plate-brd); border-radius: 9px; padding: 7px 11px; }
.toolbar .spacer { flex: 1; }
.result-summary { font-family: var(--mono); font-size: var(--t-micro); color: var(--sub); }
.result-summary b { color: var(--good); }
/* fit is RELATIVE to your best match (the server normalizes to the top score), so say
   so once here rather than implying an absolute score with a "/100" on every row. */
.fit-note { font-family: var(--mono); font-size: var(--t-micro); color: var(--sub); opacity: 0.8; }
.tb-salary { display: flex; align-items: center; gap: 8px; }
.tb-salary-val { font-family: var(--mono); font-size: var(--t-micro); color: var(--sub); min-width: 48px; }
.tb-salary input[type="range"] { width: 120px; }

/* ── the job board ──────────────────────────────────────────────────────────────
   Each row is a three-column grid: the FIT GUTTER, the body, the expand affordance.

   Fit used to be drawn as a tinted wash BEHIND the row text — that is area plus
   color, the two channels the eye decodes least accurately (Cleveland & McGill),
   it doubled as a false hover state, and it dropped text contrast. Now fit gets
   its own aligned track: a number plus a bar on a common baseline, which is the
   most accurate encoding available in a list row. ──────────────────────────────── */
.carousel { display: flex; flex-direction: column; align-items: center; gap: 8px; width: 100%; }
/* The scroll lives HERE, not on the page. Cards are clipped by this box, so they can
   never appear above the refiner — which is what lets the header stay transparent
   instead of needing an opaque strip painted over them. */
body.board .carousel {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  padding-bottom: 56px;
  /* The list still scrolls — the bar itself is hidden. A visible track cut a hard
     grey edge through the glass on every screen; the cards fading under the refiner
     are the affordance instead. Same treatment as the criteria/toolbar rails. */
  scrollbar-width: none; -ms-overflow-style: none;
}
body.board .carousel::-webkit-scrollbar { display: none; }
/* Cards must NOT shrink. The carousel is a flex column with a constrained height now,
   and flex items shrink by default — so every card was squeezed to a ~32px sliver with
   its text clipped and overlapping the next one. Measurements all passed; only the
   screenshot showed it. */
body.board .carousel > * { flex: 0 0 auto; }

/* ── the fold ──────────────────────────────────────────────────────────────────
   What the hidden scrollbar gave up: the fact that there is more. A fade pinned to
   the bottom of the board, dissolving the last card into the sky instead of letting
   the window edge cut it in half. The gradient runs to --sky-bot — the same variable
   the page's own bottom is painted with, and the same one atmosphere.js retints
   through the day, so it never reads as a grey overlay at any hour.

   Hidden by opacity rather than [hidden] so it can fade in and out with the list. */
.fold {
  position: absolute; left: 0; right: 0; bottom: 0; height: 96px; z-index: 12;
  pointer-events: none; /* it must not eat clicks meant for the last card */
  opacity: 0; transition: opacity 260ms ease;
  background: linear-gradient(
    180deg,
    transparent 0%,
    color-mix(in srgb, var(--sky-bot) 55%, transparent) 45%,
    color-mix(in srgb, var(--sky-bot) 92%, transparent) 78%,
    var(--sky-bot) 100%
  );
}
.fold.on { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .fold { transition: none; } }
.gcard {
  position: relative; width: 100%; overflow: hidden; isolation: isolate;
  display: grid; grid-template-columns: 92px 1fr auto; align-items: start; gap: 14px;
  padding: 14px 16px;
  background: var(--plate); border: 1px solid var(--plate-brd);
  transition: background 1400ms ease, border-color 1400ms ease, transform 160ms ease,
    box-shadow 160ms ease, opacity 380ms ease;
}
.gcard:hover { transform: translateY(-2px); box-shadow: 0 12px 30px rgba(15, 20, 38, 0.14); }

/* the fit gutter — position/length, on a common baseline across every row */
.fitcol { grid-column: 1; padding-top: 2px; }
.fit-num { font-family: var(--mono); font-size: var(--t-lead); font-weight: 600; line-height: 1; letter-spacing: -0.03em; font-variant-numeric: tabular-nums; }
.fit-track { height: 5px; border-radius: 3px; margin: 6px 0 5px; background: color-mix(in srgb, var(--ink) 12%, transparent); }
.fit-bar { height: 100%; width: 0%; border-radius: 3px; background: var(--anchor); transition: width 900ms cubic-bezier(0.22, 1, 0.36, 1); }
.fit-bar.good { background: color-mix(in srgb, var(--anchor) 48%, transparent); }
.fit-bar.fair { background: color-mix(in srgb, var(--ink) 26%, transparent); }
.fit-word { font-family: var(--mono); font-size: var(--t-micro); color: var(--sub); }

.gcard-head { grid-column: 2; min-width: 0; }
.gcard-id .role { font-weight: 620; font-size: var(--t-lead); line-height: 1.2; letter-spacing: -0.015em; }
.gcard-id .org { font-size: var(--t-small); color: var(--sub); margin-top: 3px; }

/* the expand affordance — visible, not guessed at */
.chev {
  grid-column: 3; display: grid; place-items: center; width: 28px; height: 28px; margin-top: 4px;
  border: 1px solid var(--card-brd); border-radius: 50%; background: var(--field-bg);
  font-size: var(--t-micro); color: var(--sub); transition: transform 180ms ease, background 180ms ease;
}
.gcard.expanded .chev { transform: rotate(180deg); background: var(--anchor); color: var(--on-anchor); border-color: transparent; }

/* WHY the job ranked where it did — visible on the COLLAPSED row, because that is
   the moment the user is judging the ranking. These are the real matched signals
   from the scorer; when it matched nothing the list is simply empty. */
.why { list-style: none; display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0 0; padding: 0; }
.why li { font-family: var(--mono); font-size: var(--t-micro); color: var(--ink); background: var(--field-bg); border: 1px solid var(--card-brd); border-radius: 5px; padding: 2px 7px; }
.why:empty { display: none; }
/* bulleted job description — expand-only */
.desc-bullets { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 5px; }
.desc-bullets li { position: relative; font-size: var(--t-small); color: var(--ink); opacity: 0.86; line-height: 1.45; padding-left: 15px; }
.desc-bullets li::before { content: "›"; position: absolute; left: 2px; color: var(--accent); font-weight: 700; }
.detail-meta { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 9px; font-size: var(--t-small); }
.detail-meta .salary { color: var(--good); font-family: var(--mono); font-weight: 600; }
.detail-meta .posted, .detail-meta .source { color: var(--sub); }
.detail-meta span:empty { display: none; }
.acts { display: flex; align-items: center; gap: 8px; margin-top: 13px; flex-wrap: wrap; }

/* compact by default; click anywhere on the row to expand the full detail. The
   expand-only block spans the body + affordance columns so it isn't squeezed
   into the body column's width. */
.gcard { cursor: pointer; }
.gcard-detail { grid-column: 2 / -1; display: none; padding-top: 12px; }
.gcard.expanded .gcard-detail { display: block; }
.gcard.expanded { box-shadow: 0 1px 0 rgba(255, 255, 255, 0.22) inset, 0 18px 54px rgba(15, 20, 38, 0.22); border-color: var(--anchor); }
.gcard .role { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gcard.expanded .role { white-space: normal; }

/* ── board mode: ONE continuous scroll surface. The chat sits centered at the top;
   scroll down and the board flows up while the chat bar STICKS to the top; scroll back
   to the top and the chat re-centers. ─────────────────────────────────────────────── */
/* height, not min-height: the board owns the viewport below the masthead and scrolls
   INSIDE itself, so the page never scrolls and the cards are clipped by their own
   container rather than masked by a strip painted over them. */
body.board #stage {
  max-width: 900px; height: calc(100dvh - var(--masthead)); margin: 0 auto;
  padding: 0 24px 0; overflow: hidden;
  display: flex; flex-direction: column; min-height: 0;
}
body.board #chat-view { display: contents; } /* dissolve so the bar can dock independently */
/* THE HANDOFF. The transcript has done its job — four answered questions are not
   worth 40% of the viewport while you are reading results. It collapses into the
   criteria bar (#criteria), and the input DOCKS to the bottom instead of floating
   over the results header, which is what it used to collide with. */
/* #chat-view is display:contents on the board, so every front-door block has to be
   hidden explicitly — otherwise it reflows loose into the page (a share link that
   lands straight on the board never runs the chat's own hide). */
body.board .doorhead { display: none; }
body.board .doorextra { display: none; }
body.board .steps { display: none; } /* the interview is over */

/* Refine lives at the TOP, inside the header group, with the criteria it edits — not
   in a bar at the bottom. app.js moves the whole conversation block in here when the
   board opens. It stays closed until you ask for it (or the assistant answers), so the
   board keeps its full height while you are just reading. */
body.board .convo {
  width: 100%; margin: 0; display: none;
  padding: 2px 6px 10px;
}
body.board .boardbar.refining .convo { display: block; }
body.board .sayrow { margin-bottom: 8px; }
body.board .chat-say { font-size: var(--t-small); font-weight: 500; line-height: 1.4; }
body.board .chat-field { border-radius: 100px; padding: 9px 8px 9px 16px; }
body.board .chat-field input { font-size: var(--t-small); }
body.board .chipwrap { margin-top: 9px; }
/* chips behave identically here and on the front door — tap to add, ↵ to send — so
   the interaction never changes meaning underneath the user */
body.board .chips { justify-content: flex-start; }
body.board .chips-hint { text-align: left; }
/* the halo breathes on the front door; on the board it would pulse over the results */
body.board .halo, body.board .pulse { display: none; }
.crit-refine[aria-expanded="true"] { background: var(--anchor); }
/* flex:1 rather than height:100% — the board FILLS whatever the stage has left after
   anything above it, instead of assuming it starts at the stage's top edge. It does
   not: chat-view is display:contents on the board, so its leftovers still take part in
   the flow, and a fixed 100% pushed the list 34px past the fold. */
/* position: relative so the fold can pin itself to the board's bottom edge — which,
   because the stage owns the viewport, IS the bottom of the screen. */
body.board #results-view { position: relative; margin-top: 0; flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
/* The criteria bar + toolbar stick as ONE group and carry ONE glass plate between
   them. Two reasons: a criteria bar that wraps to a second line can never collide
   with a toolbar pinned at a constant offset, and a single surface means rows
   scrolling underneath are actually occluded — a translucent toolbar with nothing
   behind it let the notice text and card edges read straight through. It also stops
   the blur stacking (glass over glass is where legibility goes to die). */
body.board .boardbar {
  /* Sticks BELOW the masthead, not 20px under it. This was 52px, which was right when
     the header was shorter; the applied rail's 44px toggle now sets the header height,
     so a hardcoded offset let the bar slide beneath it and the applied pill overlapped
     the plate's top-right corner. Tied to the same variable the header height comes
     from, so the two cannot drift apart again. */
  /* Static, not sticky. It is the first row of a flex column that owns the viewport,
     so it stays put by construction — no `top` offset to drift out of sync with the
     masthead height, which is exactly what put the first card underneath it. */
  position: relative; z-index: 15; flex: none;
  /* Deliberately more opaque than --plate. Every other glass surface only has the sky
     behind it, but this one has moving CARDS behind it, and at --plate's ~50% alpha
     their text read straight through the toolbar. This is the one surface that has to
     actually occlude, so it gets its own value rather than the shared token. */
  background: var(--chrome);
  border: 1px solid var(--plate-brd); border-radius: 14px;
  padding: 4px 6px 0; margin-bottom: 10px;
  -webkit-backdrop-filter: blur(20px) saturate(1.3); backdrop-filter: blur(20px) saturate(1.3);
  box-shadow: 0 8px 26px rgba(15, 20, 38, 0.1);
  transition: background 1400ms ease, border-color 1400ms ease;
}
/* inside the group the criteria row is just a row — the plate is the group's job now */
body.board .criteria {
  background: none; border: 0; box-shadow: none; border-radius: 0;
  -webkit-backdrop-filter: none; backdrop-filter: none;
  padding: 6px 6px 2px; margin-bottom: 0;
}
body.board .toolbar { margin-bottom: 0; padding: 6px 6px 8px; }
/* No lid over the cards: they are clipped by their own scroll container now, so the
   masthead is plain transparent glass over the sky, the way the front door is. */

/* ── the criteria bar: the four answers, as editable pills ───────────────────── */
.criteria {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  background: var(--plate); border: 1px solid var(--plate-brd);
  border-radius: 14px; padding: 10px 12px; margin-bottom: 10px;
  -webkit-backdrop-filter: blur(20px); backdrop-filter: blur(20px);
  box-shadow: 0 8px 26px rgba(15, 20, 38, 0.1);
  transition: background 1400ms ease, border-color 1400ms ease;
}
.crit-lab { font-family: var(--mono); font-size: var(--t-micro); color: var(--sub); }
.crit {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--t-small); font-weight: 550;
  background: var(--field-bg); border: 1px solid var(--card-brd);
  border-radius: 100px; padding: 4px 6px 4px 11px; max-width: 34ch;
}
.crit .txt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* what you asked to keep OUT, shown struck through — the filter is visible, not hidden */
.crit.neg .txt { color: var(--sub); text-decoration: line-through; text-decoration-thickness: 1px; }
.crit .x { display: grid; place-items: center; width: 18px; height: 18px; flex: none; border: 0; border-radius: 50%; background: transparent; color: var(--sub); font-size: 11px; line-height: 1; padding: 0; }
.crit .x:hover { background: color-mix(in srgb, var(--ink) 10%, transparent); color: var(--ink); }
.crit-refine {
  margin-left: auto; display: inline-flex; align-items: center; gap: 7px;
  font-size: var(--t-small); font-weight: 600; color: var(--on-anchor);
  background: var(--anchor); border: 0; border-radius: 100px; padding: 7px 14px;
  transition: background 1400ms ease, color 1400ms ease;
}
/* fresh search: the results flow up beneath the chat bar as it settles into place */
@keyframes flowUp { from { opacity: 0; transform: translateY(34px); } to { opacity: 1; transform: none; } }
body.board #results-view.flow-up { animation: flowUp 620ms cubic-bezier(0.22, 1, 0.36, 1); }

/* buttons */
.btn { font-size: var(--t-small); font-weight: 600; border-radius: 9px; padding: 9px 16px; border: 1px solid var(--card-brd); background: var(--field-bg); color: var(--ink); text-decoration: none; display: inline-block; }
/* plane 3 — the primary action is one of the four objects allowed the full ink jump */
.btn-primary { background: var(--anchor); color: var(--on-anchor); border-color: transparent; transition: background 1400ms ease, color 1400ms ease; }
.btn-ghost { color: var(--sub); }

/* states */
.state { padding: 18px; text-align: center; }
.state .muted { color: var(--sub); font-size: 13.5px; margin-top: 4px; }
.spinner { display: inline-block; width: 14px; height: 14px; border: 2px solid var(--card-brd); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.8s linear infinite; vertical-align: -2px; }
@keyframes spin { to { transform: rotate(360deg); } }
.notice { font-size: 13px; color: var(--sub); padding: 12px 16px; margin-bottom: 12px; }

/* ── the filter drawer (left edge) ──────────────────────────────────────────── */
.drawer { position: fixed; top: 20px; left: 0; z-index: 8; max-width: 300px; width: 82%; }
.drawer-body { transform: translateX(-92%); transition: transform 360ms cubic-bezier(0.22, 1, 0.36, 1); padding: 16px 18px; border-top-left-radius: 0; border-bottom-left-radius: 0; }
.drawer:hover .drawer-body, .drawer.open .drawer-body { transform: translateX(0); }
.drawer-head { font-size: 14px; font-weight: 640; margin-bottom: 10px; }
.fgroup { padding: 11px 0; border-bottom: 1px solid var(--card-brd); }
.fgroup:last-of-type { border-bottom: 0; }
.flab { display: flex; justify-content: space-between; font-size: 12px; font-weight: 600; color: var(--ink); margin-bottom: 8px; }
.flab .val { font-family: var(--mono); color: var(--good); font-weight: 500; }
.toggle3 { display: flex; border: 1px solid var(--card-brd); border-radius: 8px; overflow: hidden; }
.toggle3 button { flex: 1; border: 0; border-right: 1px solid var(--card-brd); background: transparent; color: var(--sub); font-size: 11.5px; padding: 6px 4px; }
.toggle3 button:last-child { border-right: 0; }
.toggle3 button.sel { background: color-mix(in srgb, var(--accent) 78%, var(--ink)); color: #fff; font-weight: 600; }
.tagpick { display: flex; flex-wrap: wrap; gap: 6px; }
.tagpick button { font-family: var(--mono); font-size: 10.5px; color: var(--sub); background: var(--field-bg); border: 1px solid var(--card-brd); border-radius: 100px; padding: 3px 9px; }
.tagpick button.sel { color: #fff; background: color-mix(in srgb, var(--accent) 78%, var(--ink)); border-color: transparent; }
/* tag-driven facet chips: a value with its live count badge */
.facet-chip { display: inline-flex; align-items: center; gap: 5px; }
.facet-chip .fc { font-size: 9.5px; opacity: 0.7; }
.facet-chip.sel .fc { opacity: 0.85; }
/* the drawer can hold up to five facet groups — let it scroll rather than overflow */
.drawer-body { max-height: calc(100vh - 40px); overflow-y: auto; }
.fcheck { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--ink); padding: 4px 0; }
.fcount { font-family: var(--mono); font-size: 12px; color: var(--sub); margin-top: 10px; }
.fcount b { color: var(--good); font-size: 15px; }
/* The unlabelled-exemption note. Deliberately quiet: it explains why a row the user did not
   ask for is on the board (its posting never stated that facet), so it must be readable but
   must not compete with the count itself. See passesFacets() in app.js. */
.unlab { opacity: 0.72; font-style: italic; }

/* board mode: the filters DROP DOWN from the sticky toolbar (click-only), not a
   left-edge slide-out — a centered panel between the chat and the cards. */
body.board .drawer { position: fixed; top: 158px; left: 50%; transform: translateX(-50%); width: min(760px, calc(100% - 40px)); max-width: none; z-index: 7; }
/* The closed panel is invisible but still LAID OUT, so the container would sit as a
   transparent 760px-wide sheet over the chat bar, the toolbar, and the top cards and
   eat every click. Only the open drawer takes pointer events; closed, clicks pass through. */
body.board .drawer { pointer-events: none; }
body.board .drawer.open { pointer-events: auto; }
body.board .drawer-body {
  transition: transform 240ms cubic-bezier(0.22, 1, 0.36, 1), opacity 240ms ease;
  border-radius: var(--radius); max-height: min(60vh, 520px); overflow-y: auto; padding: 16px 20px;
  display: flex; flex-wrap: wrap; gap: 12px 26px; align-items: flex-start;
  /* Same surface as the toolbar it drops out of, for the same reason .rail-body gives:
     this panel has moving CARDS behind it, not just the sky, and at --card-bg's alpha their
     titles read straight through the chip rows. --chrome is the one token that actually
     occludes, so the drawer and the bar it belongs to now match by construction rather than
     by coincidence — they are one control surface and looked like two. Overrides .glass on
     the element (see .rail-body, which made the same move for the applied rail). */
  background: var(--chrome);
  border-color: var(--plate-brd);
  box-shadow: 0 8px 26px rgba(15, 20, 38, 0.1);
}
body.board .drawer:not(.open) .drawer-body { transform: translateY(-12px) !important; opacity: 0; pointer-events: none; }
body.board .drawer.open .drawer-body { transform: translateY(0); opacity: 1; pointer-events: auto; }
body.board .drawer-head { width: 100%; margin-bottom: 2px; }
body.board .fgroup { padding: 0; border-bottom: 0; min-width: 150px; }
body.board .fcount { width: 100%; margin-top: 4px; }

/* The TRACK stays 5px; the INPUT is 24px so there is something to actually hit. A 5px
   control is a miss on a mouse and hopeless on a thumb (WCAG 2.5.8 wants 24 minimum).
   The height goes on the element with a transparent background and the visible bar moves
   to the track pseudo-element, so the hit area grows without the bar getting fatter — and
   24px still fits inside the existing ~36px toolbar row, so the sticky mobile header does
   not grow (the reason it is not the 44px Apple would prefer). */
input[type="range"] { -webkit-appearance: none; appearance: none; width: 100%; height: 24px; background: transparent; outline: none; }
input[type="range"]::-webkit-slider-runnable-track { height: 5px; border-radius: 4px; background: color-mix(in srgb, var(--ink) 20%, transparent); }
input[type="range"]::-moz-range-track { height: 5px; border-radius: 4px; background: color-mix(in srgb, var(--ink) 20%, transparent); }
/* margin-top centres the thumb on the track: (track 5 - thumb 17) / 2 = -6 */
input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 17px; height: 17px; margin-top: -6px; border-radius: 50%; background: #fff; border: 2px solid var(--accent); cursor: pointer; }
input[type="range"]::-moz-range-thumb { width: 17px; height: 17px; border-radius: 50%; background: #fff; border: 2px solid var(--accent); cursor: pointer; }

/* ── the applied rail (right edge) ──────────────────────────────────────────── */
/* In the MASTHEAD, beside the nav — this is the user's own state, not search state.
   Every other home broke something. Floating, it sat as an invisible sheet (sized to its
   260px body while that body was off-screen) swallowing clicks meant for sort and the
   salary slider. Below the header it covered the first card at every width under
   ~1300px. Docked in the toolbar it was crowded between Refine and the match count AND
   invisible, because a --chrome pill on the --chrome boardbar is paper on paper. Here it
   sits over the sky, which is what that surface was built to read against, and it is
   back on the front door instead of appearing only once a board exists.
   The header has no overflow, so the panel is a plain absolute dropdown. */
.rail { position: relative; flex: none; margin-left: 4px; }
.rail-body { position: absolute; top: calc(100% + 8px); right: 0; z-index: 30; }
.rail:not(.open) .rail-body { opacity: 0; pointer-events: none; }
/* Chrome, not content. On --card-bg (the same 0.34-alpha glass the job cards use) the
   pill read as another card floating among the listings, so it blended into the role
   titles behind it. --chrome is the opaque surface the sticky header already uses, which
   is where the rail now hangs — so it reads as part of the furniture instead of
   competing with the results. */
.rail-toggle { display: inline-flex; align-items: center; gap: 8px; background: var(--chrome); border: 1px solid var(--plate-brd); border-right: 0; border-radius: 100px 0 0 100px; padding: 9px 16px; color: var(--ink); -webkit-backdrop-filter: blur(18px) saturate(1.3); backdrop-filter: blur(18px) saturate(1.3); box-shadow: 0 6px 18px rgba(15, 20, 38, 0.08); }
.rail-count { font-weight: 700; font-family: var(--mono); }
.rail-word { font-size: 12px; color: var(--sub); }
/* The open panel matches the toggle — it carries a list of real links over moving cards,
   so it has to occlude for the same reason the boardbar does (glass over glass is where
   legibility goes to die). Overrides the .glass class on the element. */
.rail-body { transform: translateY(-6px); transition: transform 200ms cubic-bezier(0.22, 1, 0.36, 1), opacity 200ms ease; width: 260px; padding: 14px 16px; text-align: left; background: var(--chrome); border-color: var(--plate-brd); box-shadow: 0 8px 26px rgba(15, 20, 38, 0.1); }
.rail.open .rail-body { transform: translateY(0); opacity: 1; }
.rail-head { font-family: var(--mono); font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--sub); margin-bottom: 8px; }
.rail-list { list-style: none; margin: 0; padding: 0; }
.rail-item { display: flex; align-items: center; gap: 8px; padding: 8px 0; border-bottom: 1px solid var(--card-brd); font-size: 12.5px; }
.rail-item:last-child { border-bottom: 0; }
.rail-item a { color: var(--ink); text-decoration: none; flex: 1; }
.rail-item .ro { display: block; color: var(--sub); font-size: 11px; }
.rail-remove { background: none; border: 0; color: var(--sub); font-size: 15px; line-height: 1; }
.rail-share { margin-top: 10px; width: 100%; font-size: 12px; font-weight: 600; color: #fff; background: color-mix(in srgb, var(--accent) 78%, var(--ink)); border: 0; border-radius: 8px; padding: 8px; }

/* the card-flies-to-rail animation (FLIP endpoint set in JS via --fly-x/--fly-y) */
@keyframes fly { to { transform: translate(var(--fly-x, 0), var(--fly-y, 0)) scale(0.2); opacity: 0; } }
.gcard.flying { animation: fly 460ms cubic-bezier(0.5, 0, 0.75, 0) forwards; z-index: 30; }
.gcard.dismissing { transition: opacity 260ms ease, transform 260ms ease; opacity: 0; transform: scale(0.94); }

/* ── the fallback form (glassed) ────────────────────────────────────────────── */
.form-view { min-height: 62vh; display: flex; align-items: center; }
.search-form { padding: 26px 26px 24px; display: flex; flex-direction: column; gap: 20px; width: 100%; }
.form-head { display: flex; flex-direction: column; gap: 4px; padding-bottom: 4px; border-bottom: 1px solid var(--card-brd); }
.form-head h2 { font-size: 20px; font-weight: 660; letter-spacing: -0.02em; }
.form-sub { font-size: 13px; color: var(--sub); }
.field legend { padding: 0; }
.field { display: flex; flex-direction: column; gap: 6px; border: 0; padding: 0; margin: 0; }
.field label, .field legend, .field .label { font-weight: 600; font-size: 14px; }
.field .sub { font-weight: 500; font-size: 12.5px; color: var(--sub); }
.hint { color: var(--sub); font-size: 12px; }
.field input { border: 1px solid var(--card-brd); background: var(--field-bg); color: var(--ink); border-radius: 9px; padding: 9px 11px; font-size: 14px; outline: none; }
.field input::placeholder { color: var(--sub); }
.split { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.with-suffix { display: flex; align-items: center; gap: 6px; }
.with-suffix input { width: 80px; }
.suffix { color: var(--sub); font-size: 13px; }
.segmented { display: flex; flex-wrap: wrap; gap: 6px; }
.segmented input { position: absolute; opacity: 0; pointer-events: none; }
.segmented label { font-size: 12.5px; color: var(--sub); background: var(--field-bg); border: 1px solid var(--card-brd); border-radius: 100px; padding: 6px 12px; }
.segmented input:checked + label { color: #fff; background: color-mix(in srgb, var(--accent) 78%, var(--ink)); border-color: transparent; }
.form-actions { display: flex; gap: 10px; flex-wrap: wrap; }

/* ── narrow screens: the fit gutter shrinks, the chevron goes (the whole row is
   already tappable), and the detail block spans the full width. ───────────────── */
@media (max-width: 620px) {
  :root { --t-hero: 26px; --t-head: 21px; --t-lead: 18px; }
  .gcard { grid-template-columns: 62px 1fr; gap: 12px; padding: 13px 14px; }
  .chev { display: none; } /* the whole row is tappable; the chevron is desktop affordance */
  .gcard-detail { grid-column: 1 / -1; }
  .fit-num { font-size: var(--t-body); }
  .site-header { padding: 12px 14px; }
  body.board #stage { padding: 0 14px 56px; }
  /* No `top` here any more. It was the sticky bar's offset; the bar is position:
     relative now, where a `top` does not pin anything — it just SHIFTS the element
     down 72px out of its own flow slot, so on phones the bar sat on top of the first
     card and hid its title while leaving a gap where the bar should have been. */
  body.board .drawer { top: auto !important; bottom: 0; left: 0; transform: none; width: 100%; max-width: 100%; }
  body.board .drawer-body { border-radius: var(--radius) var(--radius) 0 0; max-height: 62vh; }

  /* Both header rows become horizontal scrollers instead of wrapping. Wrapping put
     every criteria pill and every toolbar control on its own line — a 262px-tall
     sticky header on an 844px phone, which pushed the board off screen entirely and
     ran the header into the docked input. Nothing is removed; it scrolls. */
  body.board .criteria, body.board .toolbar {
    flex-wrap: nowrap; overflow-x: auto; overscroll-behavior-x: contain;
    scrollbar-width: none; -ms-overflow-style: none;
  }
  body.board .criteria::-webkit-scrollbar, body.board .toolbar::-webkit-scrollbar { display: none; }
  body.board .criteria > *, body.board .toolbar > * { flex: none; }
  .crit { max-width: 20ch; }
  /* Refine must stay reachable without scrolling to the end of the pills */
  body.board .crit-refine { position: sticky; right: 0; margin-left: 4px; box-shadow: -10px 0 12px 4px var(--chrome); }
  .toolbar .spacer { display: none; }
  .tb-salary input[type="range"] { width: 92px; }
  /* the column is narrow — two wrapped lines beat a hard ellipsis on every title */
  .gcard .role { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
}

/* Below ~480px the masthead links do not fit beside the wordmark and wrap into the
   board header. Both are front-door sections anyway, reachable by scrolling. */
@media (max-width: 480px) {
}

@media (max-width: 560px) {
  :root { --masthead: 68px; }
  .split { grid-template-columns: 1fr; }
  .drawer { top: auto; bottom: 0; width: 100%; max-width: 100%; }
  .drawer-body { transform: translateY(100%); border-radius: var(--radius) var(--radius) 0 0; }
  .drawer:hover .drawer-body, .drawer.open .drawer-body { transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  #glow, #wash, #wash::before, #wash::after, .halo, .tw.on, .chat-say .cur, .spinner, .rip.run { animation: none !important; }
  .pulse { display: none; }
  #sky, #glow, #wash, .glass, .gcard, .fit-bar, .chev, .drawer-body, .rail-body, body { transition: none !important; }
  .gcard:hover { transform: none; }
  .gcard.flying { animation: none; opacity: 0; }
}

/* Suggested criteria — the model's related titles, not the user's answers. Muted on
   purpose: the pill has to read as "we added this for you" at a glance, or the board
   looks like it is searching for things the user never asked for. Still removable. */
.crit.suggested {
  opacity: 0.68;
  border-style: dashed;
  font-style: italic;
}
.crit.suggested:hover,
.crit.suggested:focus-within {
  opacity: 1;
}

/* A penalty has to READ as a penalty. The chips add up to the number beside them, so a
   subtraction rendered like an addition would make the arithmetic look wrong to anyone
   who checks it — and the whole reason the receipt is on the collapsed row is to be
   checked. */
.why .part.neg {
  color: var(--warn, #b4462e);
  border-color: color-mix(in srgb, var(--warn, #b4462e) 40%, transparent);
}

/* ── the answer, rendered in the conversation ──────────────────────────────
   The agent returns five jobs with reasons, not 200 scored rows, so these are
   cards in the transcript rather than a board you switch to. */
.echo-act { margin: .6rem 0 .2rem; font-size: .82rem; opacity: .62; }
.echo-act ul { margin: 0; padding-left: 1.05rem; }
.echo-act li { margin: .15rem 0; }
.echo-act b { font-weight: 600; opacity: .85; }
.act-n { opacity: .55; }
.echo-picks { display: grid; gap: .85rem; margin: .9rem 0 1.1rem; }
.pick { padding: 1rem 1.15rem; border-radius: 14px; }
.pick-title { font-size: 1.02rem; font-weight: 650; text-decoration: none; line-height: 1.3; }
.pick-title:hover { text-decoration: underline; }
.pick-meta { display: flex; flex-wrap: wrap; gap: .55rem; margin: .35rem 0 .55rem;
             font-size: .84rem; opacity: .72; }
.pick-co { font-weight: 600; opacity: .9; }
.pick-pay { font-variant-numeric: tabular-nums; }
.pick-tag { text-transform: capitalize; opacity: .8; }
.pick-why { margin: 0 0 .5rem; font-size: .92rem; line-height: 1.5; }
.pick-caveat { margin: 0 0 .55rem; font-size: .85rem; opacity: .7; font-style: italic; }
.pick-go { font-size: .85rem; font-weight: 600; text-decoration: none; }
.pick-go:hover { text-decoration: underline; }

/* ── the live activity list ────────────────────────────────────────────────
   Steps arrive one at a time now, mid-turn, instead of all at once when the
   turn ends. The search TERMS are shown alongside the model's own `why`,
   because watching it pivot to "1st Shift Supervisor" after someone says they
   want off nights is the moment the reasoning becomes visible; `why` alone
   tells you that it searched, not what it understood. */
.act-terms { display: inline-block; margin: .18rem 0 .1rem; opacity: .75; }
.act-terms code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .74rem; padding: .06rem .3rem; margin-right: .22rem;
  border-radius: .28rem; background: rgba(127, 127, 127, .14);
}
.echo-act li { animation: act-in .28s ease-out both; }
@keyframes act-in {
  from { opacity: 0; transform: translateY(.22rem); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) { .echo-act li { animation: none; } }

/* ── long text must never widen the page ──────────────────────────────────
   A model reply that named its picks in prose carried hundred-character
   tracking urls, and an unbroken url is one long "word": it set the width of
   the whole document and the body scrolled sideways. Seen 2026-08-20 in a
   browser, invisible to every test. The prompt no longer asks for that prose,
   but the layout must not DEPEND on the model behaving — this is the backstop. */
.chat-say, .echo-q, .echo-a, .pick-why, .pick-caveat {
  overflow-wrap: anywhere;
  word-break: break-word;
}
/* The question/answer echo had no rules at all — unstyled since 373afcc, which is
   why the two read as one undifferentiated block of text on screen. */
.echo-q { opacity: .55; margin: .5rem 0 .1rem; }
.echo-a { opacity: .85; margin: 0 0 .3rem; }

/* Cap the reply's line length once the board is showing, where the container is wider
   than the card column and a long answer visibly stepped outside the layout.
   NOT auto margins: `.sayrow` is a flex row, so auto margins CENTRE the text and the
   opening question stops being left-aligned with the input under it. Caught in a browser
   after introducing it — the kind of thing only looking at the page shows. */
body.board .chat-say { max-width: 62ch; }
