/* ─────────────────────────────────────────────────────────────
   Jobot design system — Wealthsimple-inspired.
   Linen off-white base, hunter-green accent, Inter body + Space Grotesk
   display typography, quiet cards, generous whitespace, tabular numerics.
   ───────────────────────────────────────────────────────────── */

:root {
  /* Type SYSTEM (Eduardo 2026-09-15): body = IBM Plex Sans (humanist, a bit of
     character, NOT the generic Inter/AI-SaaS look); headings = Schibsted
     Grotesk (clean grotesque with editorial lineage — flip to Archivo in the
     /jobs Font Lab). Hierarchy is not "two fonts + bold" — it's the scale below,
     where each level earns its rank through size + weight + line-height +
     TRACKING together. Both vars are live-swappable via the Font Lab. */
  --font-sans: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-display: 'Schibsted Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

/* Every heading level wears the display face. The per-level SIZE/WEIGHT/TRACKING
   that actually build the hierarchy live in the type-scale block further down
   (.text-display / .text-title / h3 / h4 / .text-eyebrow). */
h1, h2, h3, h4, h5, .text-display, .text-title, .font-display {
  font-family: var(--font-display);
}

/* Fix: sticky header (h-16 = 4rem) was overlapping content on scroll.
   Push all anchor / focus scrolls down by that height + a breath. */
html {
  scroll-padding-top: 5rem;
}

/* Custom DaisyUI theme.
   DaisyUI v4 expects OKLCH values as `L% C H` (no oklch() wrapper).
   Earlier HSL values were being reinterpreted as oklch → garbage colors →
   primary buttons rendered transparent-with-border. Now correct. */
[data-theme="jobot"] {
  /* Primary — hunter green (#004E3D) */
  --p:  35.5% 0.075 170;
  --pc: 100% 0 0;
  --pf: 30% 0.070 170;

  /* Secondary — near-black */
  --s:  20% 0.010 260;
  --sc: 100% 0 0;

  /* Accent — salmon coral (#FF7F6B territory). Used to highlight top-scoring
     matches, celebrations, "recommended" states — everywhere the eye should
     land first. Green stays as structural primary; salmon is where energy is. */
  --a:  72% 0.17 30;
  --ac: 100% 0 0;
  --af: 65% 0.18 28;

  /* Neutral */
  --n:  25% 0.010 260;
  --nc: 95% 0.003 260;

  /* Base — two defined levels, cooler & less yellow than the old cream
     (#F5F1E8) which read too warm/"YC". Cards are the lightest surface so
     they float forward; the page carries the subtle linen tint:
       cards = white #FFFFFF                      → --b1
       page  = light linen #F5F4F0 (cool tint)  → --b2
     Lighter + half the yellow chroma vs the earlier #F1EEE6, which read
     too "beige/dark" as a full-bleed bg while still keeping cards afloat. */
  --b1: 100% 0 0;              /* base-100 — cards, navbar, PAGE (pure white, app-like) */
  --b2: 98% 0 0;               /* base-200 — SUBTLE FILLS only (chips, coach bubble, meter track). Neutral, no warm hue (Eduardo: cream "editorial" read eewww). Page is --b1 now, not this. */
  --b3: 91.5% 0 0;             /* base-300 — quiet hairline border, neutral grey (was warm beige #E5E1D7 → read cream) */
  --bc: 20% 0.015 265;         /* base-content — near-black text */

  --in: 55% 0.17 240;          /* info blue */
  --inc: 100% 0 0;
  --su: 45% 0.11 165;          /* success — in the green family */
  --suc: 100% 0 0;
  --wa: 74% 0.16 65;           /* warning amber */
  --wac: 20% 0.010 260;
  --er: 58% 0.20 25;           /* error red */
  --erc: 100% 0 0;

  --rounded-box: 0.75rem;
  --rounded-btn: 0.5rem;
  --rounded-badge: 0.375rem;
  --animation-btn: 0.15s;
  --btn-text-case: none;
  --btn-focus-scale: 0.98;
  --border-btn: 1px;
  --tab-radius: 0.375rem;
}

/* ─────────────────────────────────────────────────────────────
   design.md token layer (2026-09-18) — ADDITIVE. Tool-defined green
   scale (Radix custom, accent #004E3D) + warm neutral ramp (Radix gray,
   hue 89) + radius / elevation / motion scales.
   Source of truth: /design.md §4, §6, §7, §8. Applied incrementally.
   ───────────────────────────────────────────────────────────── */
:root {
  /* Green scale — 12 steps, hue 171.8 (step 9 = --p primary #004E3D) */
  --green-1:  oklch(95.7% 0.008 171.8);
  --green-2:  oklch(94.3% 0.015 171.8);
  --green-3:  oklch(91.8% 0.043 171.8);
  --green-4:  oklch(88.9% 0.068 171.8);
  --green-5:  oklch(85.3% 0.088 171.8);
  --green-6:  oklch(80.7% 0.104 171.8);
  --green-7:  oklch(74.6% 0.110 171.8);
  --green-8:  oklch(66.9% 0.110 171.8);
  --green-9:  oklch(37.8% 0.073 171.8);
  --green-10: oklch(44%   0.110 171.8);
  --green-11: oklch(46%   0.110 171.8);
  --green-12: oklch(35%   0.073 171.8);

  /* Warm neutral ramp — hue 89 (fixes the old cool-hue ink) */
  --n-0: 100%  0     0;
  --n-1: 96.7% 0.005 89;
  --n-2: 94.3% 0.005 89;
  --n-3: 91.4% 0.005 89;
  --n-4: 80.1% 0.013 89;
  --n-5: 55.1% 0.020 89;
  --n-6: 44.6% 0.016 89;
  --n-7: 24.3% 0.017 89;

  /* Radius set — everything maps to these five */
  --r-sm: 0.375rem;
  --r-md: 0.5rem;
  --r-lg: 0.75rem;
  --r-xl: 1rem;
  --r-pill: 9999px;

  /* Surfaces (design.md §7A, revised 2026-09-29).
     BOXES = white + a light hairline (--hair) on the white page; hover adds
     depth (--lift). GREY FILLS (--surf-*) are only for SMALL parts inside or
     beside a box — chips, insets, tab track, secondary buttons — always with
     dark text. A whole section in grey-on-grey read washed (Eduardo).
     Selected = green border + faint green ring. */
  --hair: oklch(var(--n-3));
  --hair-strong: oklch(var(--n-4));
  --lift: 0 1px 2px oklch(var(--n-7) / 0.05), 0 8px 24px -12px oklch(var(--n-7) / 0.18);
  --surf-1: oklch(var(--n-7) / 0.035);
  --surf-2: oklch(var(--n-7) / 0.06);
  --surf-3: oklch(var(--n-7) / 0.085);
  --lead-bar: inset 3px 0 0 oklch(var(--p));

  /* Elevation — earned, warm/green-tinted. Only for things that truly float
     (modal, drawer, popover, the active seg-tab thumb) — never resting cards. */
  --elev-1: 0 1px 2px oklch(var(--p) / .05);
  --elev-2: 0 10px 25px -18px oklch(var(--p) / .28);
  --elev-3: 0 24px 60px -24px oklch(var(--n-7) / .28);

  /* Motion tokens */
  --dur-1: 120ms;
  --dur-2: 200ms;
  --dur-3: 320ms;
  --ease-out: cubic-bezier(.22,.61,.36,1);
  --ease-spring: cubic-bezier(.34,1.56,.64,1);
  --ease-in-out: cubic-bezier(.65,.05,.36,1);

  /* Verdict / semantic scale (design.md §4.5) — OKLCH, retires raw HSL.
     tint = soft bg · ink = text/icon · solid = ring stroke · bd = border. */
  --v-strong-tint: oklch(94% 0.04 165);  --v-strong-ink: oklch(38% 0.09 165);  --v-strong-solid: oklch(46% 0.11 165);  --v-strong-bd: oklch(38% 0.09 165 / 0.25);
  --v-stretch-tint: oklch(93% 0.05 75);  --v-stretch-ink: oklch(44% 0.10 60);  --v-stretch-solid: oklch(58% 0.13 65);  --v-stretch-bd: oklch(44% 0.10 60 / 0.28);
  --v-poor-tint: oklch(94% 0.04 25);     --v-poor-ink: oklch(50% 0.15 25);     --v-poor-solid: oklch(55% 0.17 25);     --v-poor-bd: oklch(50% 0.15 25 / 0.3);
}

/* Button system (design.md §9.1) — coexists with DaisyUI `.btn`.
   Primary stays DaisyUI `.btn-primary` (already green). These add the
   quiet (secondary) + danger variants, retiring the verbose
   `bg-base-100 border-base-300 …` combo and the inline-styled delete
   buttons (one of which still used raw HSL). */
.btn-quiet {
  background: var(--surf-1);
  border-color: transparent;
  color: oklch(var(--bc));
  font-weight: 500;
}
.btn-quiet:hover {
  background: var(--surf-3);
  border-color: transparent;
}
/* Soft danger — tint bg + danger ink (for inline "Yes, delete" confirms) */
.btn-danger {
  background: oklch(var(--er) / 0.12);
  border-color: oklch(var(--er) / 0.35);
  color: oklch(var(--er));
  font-weight: 600;
}
.btn-danger:hover {
  background: oklch(var(--er) / 0.18);
  border-color: oklch(var(--er) / 0.5);
}
/* Solid danger — the final destructive action (delete-all confirm) */
.btn-danger-solid {
  background: oklch(var(--er));
  border-color: oklch(var(--er));
  color: oklch(var(--erc));
  font-weight: 600;
}
.btn-danger-solid:hover {
  background: oklch(var(--er) / 0.9);
  border-color: oklch(var(--er) / 0.9);
}
/* Run-search CTA stays visibly GREEN even when disabled (no query yet). It
   should read as THE primary action — just dimmed + not-pressable until there's
   a search value — instead of graying out like a dead control (DaisyUI's
   default disabled look). */
.run-search-btn:disabled,
.run-search-btn[disabled] {
  background-color: oklch(var(--p)) !important;
  border-color: oklch(var(--p)) !important;
  color: oklch(var(--pc)) !important;
  opacity: 0.5;
  cursor: not-allowed;
}

/* ─────────────────────────────────────────────────────────────
   Typography — a SYSTEM. Body = IBM Plex Sans, headings = Schibsted Grotesk
   (via --font-display). Hierarchy = a deliberate scale (size · weight ·
   line-height · tracking), NOT bold-vs-regular. See the .text-* ladder below.
   ───────────────────────────────────────────────────────────── */

body {
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* IBM Plex Sans is a touch wider than Inter; ease the negative tracking so it
     breathes and reads warm rather than cramped. */
  letter-spacing: -0.003em;
  line-height: 1.55;
  /* Pure white, app-like (Eduardo 2026-09-25: the off-white page read as
     "cream / editorial AI app" — esp. on mobile where it fills the whole
     viewport). Page = --b1 (white); cards separate by hairline border + soft
     shadow, not by a page tint. Subtle component fills still use --b2. */
  background: oklch(var(--b1));
  min-height: 100vh;
}

/* ── The heading ladder ──
   Rule of thumb baked in: the BIGGER the type, the TIGHTER the tracking
   (large grotesque wants negative letter-spacing to feel considered); the
   SMALLER the type, the LOOSER (small caps/labels want positive tracking to
   stay legible + gain character). Weight steps too — not everything is 600. */

/* L1 · Display — page titles ("Jobs", "Profile"). */
.text-display, h1 {
  font-family: var(--font-display);
  font-size: 2.375rem;            /* 38px */
  line-height: 1.08;
  font-weight: 600;
  letter-spacing: -0.028em;       /* tightest — big type closes up */
}

/* L2 · Title — major section headers ("Top matches"). */
.text-title, h2 {
  font-family: var(--font-display);
  font-size: 1.5rem;              /* 24px */
  line-height: 1.22;
  font-weight: 600;
  letter-spacing: -0.018em;
}

/* L3 · Heading — card titles, sub-sections (job-card <h3>). */
h3 {
  font-family: var(--font-display);
  font-size: 1.0625rem;           /* 17px */
  line-height: 1.32;
  font-weight: 600;
  letter-spacing: -0.011em;
}

/* L4 · Subhead — small headings inside cards/panels. */
h4 {
  font-family: var(--font-display);
  font-size: 0.9375rem;           /* 15px */
  line-height: 1.4;
  font-weight: 600;
  letter-spacing: -0.004em;
}

/* L5 · Eyebrow / kicker — the character move. Small, UPPERCASE, WIDE tracking,
   muted. Turns bland "Recent searches"-style labels into a designed kicker that
   reads with intent instead of just being bold. Apply `.text-eyebrow` to
   section labels; bare <h5> gets it too. */
.text-eyebrow, .text-label, h5 {
  font-family: var(--font-display);
  font-size: 0.6875rem;           /* 11px */
  line-height: 1;
  font-weight: 600;
  letter-spacing: 0.13em;         /* loosest — small caps open up */
  text-transform: uppercase;
  color: oklch(var(--bc) / 0.5);
}

.text-metric {
  font-family: var(--font-display);   /* display grotesque numerals — see .hero-num */
  font-variant-numeric: tabular-nums;
  font-size: 2.25rem;             /* 36px */
  line-height: 1;
  font-weight: 600;
  letter-spacing: -0.02em;
}

.text-metric-sm {
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
  font-size: 1.5rem;              /* 24px */
  line-height: 1.1;
  font-weight: 600;
  letter-spacing: -0.018em;
}

/* `.text-label` is now an alias of the eyebrow (see above) — every section
   label across the app becomes a consistent uppercase kicker in one place,
   rather than editing 60+ template sites (Eduardo 2026-09-15: eyebrows on all
   sections). Update the eyebrow rule to restyle them all. */

.text-body-muted {
  /* Secondary lines ONLY (meta, sub-lines, hints) — reading text uses the
     full ink (§2A.8). Darkened 2026-09-29: .72 (≈42% L) read washed/floating
     on white → .84 (≈33% L). */
  color: oklch(var(--bc) / 0.84);
}
/* Text ramp below body-muted (2026-09-28) — replaces the ad-hoc
   text-base-content/NN opacities (10 different values in the wild).
   subtle = secondary meta, hints · faint = separators, decorative icons. */
.text-subtle { color: oklch(var(--n-5)); }
.text-faint  { color: oklch(var(--n-4)); }

/* ─────────────────────────────────────────────────────────────
   Layout primitives — quiet card, nav tab, pill
   ───────────────────────────────────────────────────────────── */

.card-quiet {
  /* A box = white + light hairline on the white page (§7A rev. 2026-09-29).
     No resting shadow; depth appears on hover for things you can act on. */
  background: oklch(var(--b1));
  border: 1px solid var(--hair);
  border-radius: var(--rounded-box);
  padding: 1.5rem;
  box-shadow: none;
  transition: border-color var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out),
              transform var(--dur-2) var(--ease-out);
}

/* Hover = depth, only on cards you can act on — a static card never reacts. */
.card-quiet:is(a, button, [role="button"], [role="option"], [hx-get], [hx-post], [\@click], .cursor-pointer):hover {
  border-color: var(--hair-strong);
  box-shadow: var(--lift);
  transform: translateY(-1px);
}

/* Selected wins over hover: green border + faint green ring, flat. */
.card-quiet--selected,
.card-quiet--selected:hover {
  border-color: oklch(var(--p));
  box-shadow: 0 0 0 1px oklch(var(--p) / 0.2);
  transform: none;
}

/* ─────────────────────────────────────────────────────────────
   UI kit helpers (macros/ui.html, 2026-09-28) — the small pieces every
   page kept re-inventing. Tinta, no contorno.
   ───────────────────────────────────────────────────────────── */
/* Back link above a page title. */
.ui-back {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-size: 0.875rem;
  color: oklch(var(--n-5));
  transition: color var(--dur-1) ease;
}
.ui-back:hover { color: oklch(var(--bc)); }
/* Quiet text link (section actions, "see all"). */
.ui-link-quiet {
  font-size: 0.875rem;
  font-weight: 500;
  color: oklch(var(--n-6));
  text-underline-offset: 3px;
}
.ui-link-quiet:hover { color: oklch(var(--bc)); text-decoration: underline; }
/* Level 2 — importance: green tint + 3px rule. One per view. */
.ui-callout {
  background: oklch(var(--p) / 0.07);
  box-shadow: var(--lead-bar);
  border-radius: 0 var(--r-md) var(--r-md) 0;
  padding: 1rem 1.25rem;
}
.ui-callout__eyebrow { color: oklch(var(--p)); margin-bottom: 0.5rem; }
/* Level 1 — context: grey tint, no rule, no border. */
.ui-notice {
  display: block;
  background: var(--surf-1);
  border-radius: var(--r-lg);
  padding: 0.85rem 1rem;
  font-size: 0.875rem;
  color: oklch(var(--bc));
}
/* A small grey part INSIDE a box (evidence, quote, code) — the one place a
   grey fill carries text; the text stays dark. */
.ui-inset {
  background: var(--surf-1);
  border-radius: var(--r-md);
  padding: 0.6rem 0.75rem;
  color: oklch(var(--n-7));
  line-height: 1.5;
}
.ui-notice--icon { display: flex; gap: 0.6rem; }
.ui-notice a { color: oklch(var(--p)); font-weight: 500; }
.ui-notice a:hover { text-decoration: underline; }
/* Empty state — a written sentence, left-aligned, no illustration box. */
.ui-empty {
  display: grid;
  gap: 0.5rem;
  justify-items: start;
  padding: 2rem 0.25rem;
  max-width: 32rem;
}
.ui-empty__icon { font-size: 1.75rem; color: oklch(var(--n-4)); }
.ui-empty__sentence { font-size: 1.0625rem; font-weight: 500; }
.ui-datum-unit {
  font-size: 0.45em;
  font-weight: 500;
  letter-spacing: 0;
  margin-left: 0.25rem;
  color: oklch(var(--n-5));
}

/* Job result cards (master-detail rail: Top matches / Saved / results page).
   Inverted selection model: UNSELECTED cards recede as a calm grey surface;
   the SELECTED card turns pure white and lifts, so it reads as one continuous
   white plane with the (white) detail pane on the right — visual congruency.
   A green accent bar on the leading edge marks the selection without a boxy
   ring. */
/* The whole Top matches / Saved workspace (list + detail) sits on ONE soft panel
   so the section reads as a single grouped surface. It's a DEEPER shade of the
   linen page in the SAME warm hue (not a cold grey — that read as ugly) so the
   white cards + detail lift cleanly off it and everything feels congruent. */
/* Panel REMOVED (Eduardo 2026-09-20): the grey container that encapsulated the
   cards read heavy. The workspace is now open — cards sit directly on the page.
   Class kept (it also carries the grid layout) but visually transparent. */
.results-panel {
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 0;
}

/* Job cards — compact, borderless tiles on the warm results panel. Clear
   hierarchy: the panel is the bordered container (larger radius); cards are
   borderless (smaller radius), so no box-in-box. UNSELECTED rest MUTED (a
   deeper shade of the warm hue) so they recede into the panel; the SELECTED
   one turns pure white, lifts, and carries a green leading accent bar. */
.job-card {
  /* Box language (§7A rev.): white + hairline; hover = depth; selected =
     green border + faint ring + the green leading bar growing in. */
  background: oklch(var(--b1));
  border: 1px solid var(--hair);
  border-radius: 0.6rem;
  padding: 0.85rem 1rem;
  box-shadow: none;
  overflow: hidden;                           /* clips the leading bar to the radius */
  transition: border-color var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out),
              transform var(--dur-2) var(--ease-out);
}
.job-card:hover {
  border-color: var(--hair-strong);
  box-shadow: var(--lift);
  transform: translateY(-1px);
}
.job-card--selected,
.job-card--selected:hover {
  border-color: oklch(var(--p));
  box-shadow: 0 0 0 1px oklch(var(--p) / 0.2);
  transform: none;
}
/* Green accent bar is always present but collapsed; it GROWS in when the card
   becomes selected, so the selection has a bit of motion. */
.job-card::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: oklch(var(--p));
  transform: scaleY(0);
  transform-origin: center;
  transition: transform .22s ease;
}
.job-card--selected::before { transform: scaleY(1); }
/* Already-opened cards recede (server-rendered; was an inline style). */
.job-card[data-viewed] { opacity: 0.72; }
.job-card__title {
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: -0.011em;
  line-height: 1.375;
}
/* Middle-dot separator in meta lines (company · location · site). */
.ui-sep { color: oklch(var(--n-4)); margin: 0 0.25rem; }
/* The small live dot inside the "New" chip. */
.chip-dot { display: inline-block; width: 6px; height: 6px; border-radius: 9999px; background: currentColor; }

/* Detail pane entrance — soft fade + slide IN FROM THE LEFT, following the
   card→detail flow direction (the content feels like it comes from the card
   you clicked on the left), not a top-down drop. */
@keyframes detailIn {
  from { opacity: 0; transform: translateX(-14px); }
  to   { opacity: 1; transform: none; }
}
.detail-in { animation: detailIn .3s cubic-bezier(.22,.61,.36,1); }

/* Scroll cue between the search zone and the results — a gently bouncing
   chevron that invites the user to keep going. Understated on purpose. */
@keyframes cueBounce {
  0%, 100% { transform: translateY(0);   opacity: .55; }
  50%      { transform: translateY(4px); opacity: 1;   }
}
.scroll-cue__chevron {
  font-size: 1.15rem;
  animation: cueBounce 1.7s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .scroll-cue__chevron { animation: none; }
}

/* Transition invite at the bottom of viewport 1 (search → matches). The whole
   region is hoverable; hovering it PULSES the CTA button to invite the click
   (natural scroll stays the fallback). The chevron reuses cueBounce, a touch
   larger than the plain scroll cue. Click behaviour is a JS smooth-scroll. */
@keyframes ctaPulse {
  0%, 100% { transform: none; }
  50%      { transform: scale(1.045); }
}
.scroll-invite__cue {
  font-size: 1.4rem;
  animation: cueBounce 1.7s ease-in-out infinite;
}
.scroll-invite:hover .scroll-invite__btn {
  animation: ctaPulse 1.1s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .scroll-invite__cue { animation: none; }
  .scroll-invite:hover .scroll-invite__btn { animation: none; }
}

/* Belt for the detail pane on both pages that use this id (jobs.html viewport 2
   and jobs_results.html): a long JD reaching its scroll edge must not chain up to
   move the page. `.workspace-pane` already sets this in its media block; this
   covers the short-viewport fallback (≥1024px wide, <560px tall) where that block
   is inert. On /jobs viewport 2 this also keeps a short list from yanking the page
   as you wheel over it (the bug that started this rework). */
@media (min-width: 1024px) {
  #job-detail-scroll { overscroll-behavior: contain; }
}

/* "Doom-scroll" reveal on the results list: each card fades + rises as it
   scrolls into its container's viewport. Scroll-driven, no JS, via
   animation-timeline: view() (the nearest scrollport is the rail column).
   Cards already in view on load render at the end state (no flash); only the
   ones below the fold animate as you scroll down. @supports-gated so browsers
   without scroll-driven animations just show the cards. */
@media (min-width: 1024px) {
  @supports (animation-timeline: view()) {
    .reveal-scroll > * {
      animation: cardReveal linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 35%;
    }
  }
}
@keyframes cardReveal {
  from { opacity: 0; transform: translateY(20px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .reveal-scroll > * { animation: none; opacity: 1; transform: none; }
}

/* Closed statuses (rejected/withdrawn) — denser, muted, less prominent. */
.card-compact-closed {
  padding: 1rem 1.25rem;
  opacity: 0.75;
}
.card-compact-closed:hover {
  opacity: 1;
}

/* Level-tile (Tailor drawer): 3-way segmented control.
   Uses [data-selected] attribute so we don't depend on Tailwind CDN's JIT
   picking up dynamic Alpine `:class` values (which it doesn't reliably). */
.level-tile {
  border-radius: 0.5rem;
  border: 1px solid oklch(var(--b3));
  background: oklch(var(--b1));
  color: oklch(var(--bc));
  padding: 0.75rem;
  text-align: left;
  transition: all 0.15s ease;
  cursor: pointer;
}
.level-tile:hover {
  border-color: oklch(var(--bc) / 0.3);
}
.level-tile[data-selected="true"] {
  background: oklch(var(--p));
  color: oklch(var(--pc));
  border-color: oklch(var(--p));
  box-shadow: 0 2px 6px oklch(var(--p) / 0.2);
}
.level-tile-title {
  font-size: 0.875rem;
  font-weight: 600;
}
.level-tile-desc {
  font-size: 0.75rem;
  margin-top: 0.25rem;
  color: oklch(var(--bc) / 0.6);
}
.level-tile[data-selected="true"] .level-tile-desc {
  color: oklch(var(--pc) / 0.85);
}

/* "Beta" tag next to a nav label (ADR-085: Prep ships as a beta). Quiet: small
   caps in the accent, no pill, so it reads as a note, not a button. */
.nav-beta {
  margin-left: 0.3rem;
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: oklch(var(--p));
  vertical-align: super;
}

/* Text-only nav tab with animated underline (Wealthsimple pattern) */
.nav-tab {
  position: relative;
  padding: 0.375rem 0;
  color: oklch(var(--bc) / 0.55);
  font-weight: 500;
  font-size: 0.9375rem;         /* 15px */
  transition: color 0.15s ease;
  letter-spacing: -0.005em;
}
.nav-tab:hover {
  color: oklch(var(--bc));
}
.nav-tab.active {
  color: oklch(var(--bc));
  font-weight: 600;
}
.nav-tab.active::after {
  content: '';
  position: absolute;
  bottom: -1.125rem;
  left: 0;
  right: 0;
  height: 2px;
  background: oklch(var(--p));
  border-radius: 1px;
}

/* Status pill — subtle bg + optional dot indicator */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.25rem 0.625rem;
  border-radius: 9999px;
  background: var(--surf-2);
  color: oklch(var(--n-7));
  font-size: 0.75rem;
  font-weight: 500;
  border: 1px solid transparent;
}
.pill-dot {
  width: 0.375rem;
  height: 0.375rem;
  border-radius: 9999px;
  background: oklch(var(--bc) / 0.35);
}
.pill-dot.live {
  background: oklch(var(--su));
  box-shadow: 0 0 0 2px oklch(var(--su) / 0.15);
}
.pill-dot.warn {
  background: oklch(var(--wa));
}

/* Divider — quieter than DaisyUI's default */
.hr-quiet {
  height: 1px;
  background: oklch(var(--b3));
  border: 0;
  margin: 1.5rem 0;
}

/* ─────────────────────────────────────────────────────────────
   HTMX polish — subtle fade on partial swap
   ───────────────────────────────────────────────────────────── */

.htmx-swapping { opacity: 0; transition: opacity 100ms ease-out; }
.htmx-settling { opacity: 1; transition: opacity 200ms ease-in; }
.htmx-indicator { opacity: 0; transition: opacity 200ms ease; }
.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator { opacity: 1; }

/* ─────────────────────────────────────────────────────────────
   Score badge — colored square with verdict-based tint
   ───────────────────────────────────────────────────────────── */

.score-badge {
  width: 56px;
  height: 56px;
  border-radius: 12px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  flex-shrink: 0;
}
/* Mobile: shrink the score badge so the card body has more horizontal room.
   48px still meets Apple's 44pt touch target when the whole card is the
   tap surface, which it is here. */
@media (max-width: 1023px) {
  .score-badge {
    width: 48px;
    height: 48px;
    border-radius: 10px;
  }
  .score-badge .score-num {
    font-size: 1.1rem;
  }
}

/* Toast wrapper — bottom-right fixed. On mobile the bar sits above the
   fixed bottom nav (~3.5rem tall) so the Undo button of a swipe toast
   isn't obscured by the Profile tab. On desktop there's no bottom nav
   so we use the base offset. */
.toast-wrapper {
  bottom: calc(4.5rem + env(safe-area-inset-bottom));
  right: calc(1rem + env(safe-area-inset-right));
}
@media (min-width: 1024px) {
  .toast-wrapper {
    bottom: calc(1rem + env(safe-area-inset-bottom));
  }
}

/* Kill horizontal scroll globally. Uses `clip` (not `hidden`): `overflow-x:
   hidden` forces the computed `overflow-y` to `auto`, which turns <body> into a
   scroll container and breaks `position: sticky` (it then sticks to the body's
   scrollport, not the viewport — the /jobs detail pane wouldn't stay put).
   `clip` prevents horizontal panning the same way WITHOUT creating a scroll
   container, so the document scrolls on <html> and sticky works. */
html, body {
  overflow-x: clip;
  max-width: 100%;
}

/* Attention pulse for the first-visit geography banner leading dot.
   Small enough to notice, subtle enough to not annoy. Respects
   prefers-reduced-motion for accessibility. */
.geo-banner-pulse {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: oklch(var(--p));
  box-shadow: 0 0 0 0 oklch(var(--p) / 0.5);
  animation: geo-pulse 2s ease-out infinite;
}
@keyframes geo-pulse {
  0%   { box-shadow: 0 0 0 0 oklch(var(--p) / 0.55); }
  70%  { box-shadow: 0 0 0 10px oklch(var(--p) / 0); }
  100% { box-shadow: 0 0 0 0 oklch(var(--p) / 0); }
}
@media (prefers-reduced-motion: reduce) {
  .geo-banner-pulse { animation: none; }
}

/* Floating feedback button — top-left. Hairline aesthetic matching the
   card-quiet system: white bg, thin border, soft shadow, no filled
   color. Reveals a subtle pulse (once per session, after 10 min) to
   draw the eye when the user hasn't clicked it. */
.feedback-fab {
  /* BOTTOM-LEFT icon-only FAB. History: right → bottom-left (2026-08-21) →
     top-left (2026-09-02, covered gap-map pills) → bottom-left icon-only
     (2026-09-03, top-left grazed the "Profile" title; a small corner icon
     clears both the pills and the H1). z-index: 60 sits above sticky headers
     but below dialogs. */
  position: fixed;
  bottom: calc(1.25rem + env(safe-area-inset-bottom));
  left: 1.25rem;
  z-index: 60;
  padding: 0.7rem;
  border-radius: 999px;
  background: oklch(var(--p));
  color: oklch(var(--pc));
  border: none;
  box-shadow: 0 8px 20px oklch(var(--p) / 0.28), 0 2px 6px rgba(0,0,0,0.08);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  cursor: pointer;
  font-size: 0.9rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  transition: transform 0.15s ease-out, box-shadow 0.15s ease-out;
}
.feedback-fab:hover {
  transform: translateY(-2px);
  box-shadow: 0 12px 26px oklch(var(--p) / 0.35), 0 4px 8px rgba(0,0,0,0.10);
}
.feedback-fab:active {
  transform: translateY(0);
}
.feedback-fab:focus-visible {
  outline: 3px solid oklch(var(--p) / 0.4);
  outline-offset: 2px;
}
.feedback-fab__icon {
  font-size: 1.15rem;
  line-height: 1;
}
.feedback-fab__label {
  /* Icon-only FAB (2026-09-03) — the text label is always hidden now; the
     chat-dots glyph + aria-label/title carry the affordance. */
  display: none;
}
.feedback-intro {
  /* REQ-058: the one-time hello next to the feedback button. */
  position: fixed;
  bottom: calc(1.4rem + env(safe-area-inset-bottom));
  left: 4.25rem;
  z-index: 60;
  max-width: min(17rem, calc(100vw - 6rem));
  padding: 0.6rem 0.85rem;
  border-radius: var(--r-md);
  background: oklch(var(--b1));
  box-shadow: 0 8px 24px oklch(var(--bc) / 0.12);
  font-size: 0.875rem;
  line-height: 1.4;
}
.feedback-fab--pulse {
  animation: feedback-pulse 1.4s ease-out 3;
}
@keyframes feedback-pulse {
  0%   { box-shadow: 0 8px 20px oklch(var(--p) / 0.28), 0 0 0 0 oklch(var(--p) / 0.4); }
  70%  { box-shadow: 0 8px 20px oklch(var(--p) / 0.28), 0 0 0 14px oklch(var(--p) / 0); }
  100% { box-shadow: 0 8px 20px oklch(var(--p) / 0.28), 0 0 0 0 oklch(var(--p) / 0); }
}
@media (prefers-reduced-motion: reduce) {
  .feedback-fab--pulse { animation: none; }
}
.score-badge .score-num {
  font-size: 1.25rem;
  line-height: 1;
}
.score-badge .score-max {
  font-size: 0.6rem;
  font-weight: 500;
  opacity: 0.55;
  margin-top: 2px;
}

.score-badge-strong_fit,
.score-badge-workable {
  background: var(--v-strong-tint);
  color: var(--v-strong-ink);
}
.score-badge-stretch {
  background: var(--v-stretch-tint);
  color: var(--v-stretch-ink);
}
.score-badge-poor_fit {
  background: var(--v-poor-tint);
  color: var(--v-poor-ink);
}
.score-badge-none {
  background: oklch(var(--b3));
  color: oklch(var(--bc) / 0.5);
}

/* ─────────────────────────────────────────────────────────────
   Score ring — circular progress indicator replacing the square badge
   ───────────────────────────────────────────────────────────── */
.score-ring { position: relative; width: 46px; height: 46px; flex-shrink: 0; }
.score-ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.score-ring .ring-track { fill: none; stroke: oklch(var(--b3)); stroke-width: 3.5; }
.score-ring .ring-fill  { fill: none; stroke-width: 3.5; stroke-linecap: round; transition: stroke-dasharray .5s ease-out; }
.score-ring .score-num  { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: .95rem; font-weight: 600; font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.score-ring-strong_fit .ring-fill, .score-ring-workable .ring-fill { stroke: var(--v-strong-solid); }
.score-ring-strong_fit .score-num, .score-ring-workable .score-num { color: var(--v-strong-ink); }
.score-ring-stretch  .ring-fill { stroke: var(--v-stretch-solid); }
.score-ring-stretch  .score-num { color: var(--v-stretch-ink); }
.score-ring-poor_fit .ring-fill { stroke: var(--v-poor-solid); }
.score-ring-poor_fit .score-num { color: var(--v-poor-ink); }
.score-ring-none .score-num { color: oklch(var(--bc) / .5); }

/* Skill chips — small pill for matched/gaps lists. Padding bumped on
   mobile so a thumb can actually tap chips that are also interactive
   (like the "Fresh" and "Viewed" indicators). */
.chip {
  display: inline-flex;
  align-items: center;
  padding: 0.125rem 0.5rem;
  border-radius: 0.375rem;
  font-size: 0.75rem;
  font-weight: 500;
  /* Tinta, no contorno: chips are a tint field, no drawn outline. The border
     stays (transparent) so box size is unchanged for every existing chip. */
  border: 1px solid transparent;
}
@media (max-width: 1023px) {
  .chip {
    padding: 0.25rem 0.6rem;
    font-size: 0.8rem;
  }
}
.chip-matched {
  background: var(--v-strong-tint);
  color: var(--v-strong-ink);
}
.chip-gap {
  background: var(--v-stretch-tint);
  color: var(--v-stretch-ink);
}
.chip-neutral {
  background: var(--surf-2);
  color: oklch(var(--n-7));
}
/* Job-state chips (2026-09-28) — replace the inline hsl() in job_card.html.
   applied = you acted (success) · fresh = came from your last Expand (green)
   · viewed = quiet, text-only · dismissed = soft poor tint. */
.chip-applied {
  background: var(--v-strong-tint);
  color: var(--v-strong-ink);
}
.chip-fresh {
  background: oklch(var(--p) / 0.08);
  color: oklch(var(--p));
}
.chip-viewed {
  background: transparent;
  color: oklch(var(--n-5));
  padding-left: 0;
  padding-right: 0;
}
.chip-dismissed {
  background: var(--v-poor-tint);
  color: var(--v-poor-ink);
}
/* AI role suggestions (For-you) — GREEN, not salmon. Salmon is reserved for
   top-match/celebration (≤1 per view); suggestions read as "Jobot's picks"
   in the product's own primary green. */
.chip-suggest {
  background: oklch(var(--p) / 0.08);
  color: oklch(var(--p));
}

/* Salmon accent variants — for top matches, recommended badges, celebrations. */
.chip-accent, .pill-accent {
  background: oklch(var(--a) / 0.15);
  color: oklch(var(--af));
  border-color: transparent;
}
.badge-accent-solid {
  background: oklch(var(--a));
  color: oklch(var(--ac));
  border-color: oklch(var(--a));
}

/* ─────────────────────────────────────────────────────────────
   Status-pill palette — semantic classes replacing the ~15 ad-hoc
   `style="background: hsl(...);"` inline blocks that had grown into
   6 different color families across statuses. Now: 5 semantic tints,
   all sourced from design tokens. The "celebration" tint uses the
   salmon accent so the win-state (offer) wears the brand's energy
   color — accent finally shows up at a value moment, not just decor.
   ───────────────────────────────────────────────────────────── */
.pill-success {
  background: oklch(var(--su) / 0.14);
  color: oklch(var(--su) / 0.9);
  border-color: transparent;
}
.pill-warn {
  background: oklch(var(--wa) / 0.16);
  color: oklch(30% 0.09 65);
  border-color: transparent;
}
.pill-danger {
  background: oklch(var(--er) / 0.12);
  color: oklch(var(--er));
  border-color: transparent;
}
.pill-info {
  background: oklch(var(--in) / 0.12);
  color: oklch(var(--in) / 0.9);
  border-color: transparent;
}
.pill-neutral {
  background: var(--surf-2);
  color: oklch(var(--bc) / 0.7);
  border-color: transparent;
}
.pill-celebration {
  background: oklch(var(--a) / 0.15);
  color: oklch(var(--af));
  border-color: transparent;
}

/* Quoted-italic — pseudo-element quotes so LLM content that already
   contains " chars doesn't produce nested "" in the reasoning line.
   Uses proper curly typography (U+201C / U+201D). */
.quoted::before { content: '\201C'; margin-right: 0.05em; }
.quoted::after  { content: '\201D'; margin-left: 0.05em; }

/* pill-accent used to breathe infinitely (pillBreathe) — REMOVED per the
   motion rule (no infinite loops on resting UI). Top-match pills are now
   static salmon; the reserved color alone carries the "land here" signal. */

/* Section-header dot — small salmon dot before a label. Purely decorative.
   Add via class="text-label label-dot" wherever a section deserves a beat. */
.label-dot::before {
  content: '';
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: oklch(var(--p));
  vertical-align: middle;
  margin-right: 0.6rem;
  margin-bottom: 3px;
  box-shadow: 0 0 10px oklch(var(--p) / 0.5);
}

/* Phosphor icons render as font glyphs — size = font-size, color = color.
   The helpers below are drop-in replacements for the old Lucide .i-* helpers
   so we don't have to rewrite every icon site while switching families. */
[class^="ph-"], [class*=" ph-"] {
  display: inline-block;
  line-height: 1;
  vertical-align: -0.15em;
}
.i-3 { font-size: 0.75rem; }
.i-4 { font-size: 1rem;    }
.i-5 { font-size: 1.25rem; }
.i-6 { font-size: 1.5rem;  }

/* Tab group — Chrome × Wealthsimple. Active tab is visibly elevated
   (white bg, primary text, colored top-border accent, drop shadow). Inactive
   tabs are muted ghost. Generous padding + gap = tabs "breathe". */
.seg-tabs {
  /* Segmented control (2026-09-28, tinta): one grey track, tabs sit inside
     it with no borders. Active = the white "thumb" — the one resting element
     allowed a hairline of elevation, because it physically sits ON the track. */
  display: inline-flex;
  gap: 0.25rem;
  padding: 0.25rem;
  background: var(--surf-2);
  border: none;
  border-radius: 0.85rem;
}
.seg-tab {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.6rem 1.3rem;
  border-radius: 0.65rem;
  font-size: 0.9375rem;
  font-weight: 500;
  color: oklch(var(--n-6));
  cursor: pointer;
  transition: color var(--dur-1) ease, background var(--dur-2) var(--ease-out),
              box-shadow var(--dur-2) var(--ease-out);
  border: none;
  background: transparent;
  position: relative;
  letter-spacing: -0.005em;
}
.seg-tab i {
  transition: color 0.18s ease;
}
.seg-tab:hover:not(.seg-tab--active) {
  color: oklch(var(--bc));
  background: var(--surf-1);
}
.seg-tab:hover:not(.seg-tab--active) i {
  color: oklch(var(--p) / 0.7);
}
.seg-tab--active {
  background: oklch(var(--b1));
  color: oklch(var(--p));
  font-weight: 600;
  box-shadow: 0 1px 2px oklch(var(--n-7) / 0.08), 0 1px 1px oklch(var(--n-7) / 0.04);
}
.seg-tab--active i {
  color: oklch(var(--p));
}

/* Cascade fade-in — apply .cascade-in to a container; its direct children
   fade up in sequence. Cheap way to make a list of cards "arrive" instead
   of just popping in. Caps at 20 elements — enough for our lists. */
.cascade-in > * {
  animation: cascadeUp 0.4s ease both;
  opacity: 0;
}
.cascade-in > *:nth-child(1) { animation-delay: 0.00s; }
.cascade-in > *:nth-child(2) { animation-delay: 0.04s; }
.cascade-in > *:nth-child(3) { animation-delay: 0.08s; }
.cascade-in > *:nth-child(4) { animation-delay: 0.12s; }
.cascade-in > *:nth-child(5) { animation-delay: 0.16s; }
.cascade-in > *:nth-child(6) { animation-delay: 0.20s; }
.cascade-in > *:nth-child(7) { animation-delay: 0.24s; }
.cascade-in > *:nth-child(8) { animation-delay: 0.28s; }
.cascade-in > *:nth-child(9) { animation-delay: 0.32s; }
.cascade-in > *:nth-child(n+10) { animation-delay: 0.36s; }

@keyframes cascadeUp {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Save-action bounce — plays once on HTMX swap of the save button, giving
   the "heart-filled" click a satisfying micro-response. */
.save-bounce {
  animation: saveBounce 0.42s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes saveBounce {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.25); }
  100% { transform: scale(1); }
}

/* ─────────────────────────────────────────────────────────────
   Drawer (Tailor side-panel)
   ───────────────────────────────────────────────────────────── */

/* Tailor is opened FROM the mobile job sheet (z 60/61), so it must stack
   above it — at 50/51 it slid in underneath and only showed once the sheet
   was closed (2026-10-17). Still below modals (100+) and toasts. */
.drawer-backdrop {
  position: fixed;
  inset: 0;
  background: hsl(220 20% 10% / 0.35);
  z-index: 62;
}
/* Mobile job-detail bottom sheet. Slides up from the bottom on card tap
   below lg breakpoint, replaces the hidden lg:block sidebar that made
   the detail-viewing UX vanish on mobile. Full-height with drag-handle,
   sticky bottom action bar, and body scroll lock while open. */
.mobile-detail-sheet-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.35);
  z-index: 60;
}
.mobile-detail-sheet-panel {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  height: 100vh;
  height: -webkit-fill-available;
  height: 100dvh;
  background: oklch(var(--b1));
  border-radius: 16px 16px 0 0;
  box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.15);
  z-index: 61;
  display: flex;
  flex-direction: column;
}
.mobile-detail-sheet-handle {
  align-self: center;
  width: 40px;
  height: 4px;
  border-radius: 2px;
  background: hsl(0 0% 80%);
  margin: 8px 0 4px;
  flex-shrink: 0;
}
.mobile-detail-sheet-header {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 16px;
  border-bottom: 1px solid oklch(var(--b3));
  flex-shrink: 0;
}
.mobile-detail-sheet-scroll {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
  padding-bottom: calc(72px + env(safe-area-inset-bottom));
  -webkit-overflow-scrolling: touch;
}
.mobile-detail-sheet-actions {
  position: sticky;
  bottom: 0;
  background: oklch(var(--b1));
  border-top: 1px solid oklch(var(--b3));
  padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
  display: flex;
  gap: 8px;
  flex-shrink: 0;
}

.drawer-panel {
  position: fixed;
  top: 0;
  right: 0;
  /* 100vh counts the Safari address bar at load but not when scrolled —
     causing a jitter as the bar collapses. 100dvh (dynamic viewport
     height) tracks the actual visible area on modern iOS/Android. Fallback
     via -webkit-fill-available for older iOS Safari. */
  height: 100vh;
  height: -webkit-fill-available;
  height: 100dvh;
  width: 100%;
  max-width: 42rem;
  background: oklch(var(--b1));
  border-left: 1px solid oklch(var(--b3));
  overflow-y: auto;
  z-index: 63;
  display: flex;
  flex-direction: column;
}
.drawer-header {
  position: sticky;
  top: 0;
  background: oklch(var(--b1));
  border-bottom: 1px solid oklch(var(--b3));
  padding: 1rem 1.5rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  z-index: 1;
}
.drawer-body {
  padding: 1.5rem;
  flex: 1;
}

/* Hide anything with x-cloak until Alpine is ready */
[x-cloak] { display: none !important; }

/* Hide the "idle" element (icon) while HTMX is in-flight — its sibling
   .htmx-indicator becomes visible per HTMX defaults. Used on the heart
   save/unsave button so users see a spinner instead of a stale icon. */
.htmx-request .htmx-only-when-idle { display: none; }

/* ─────────────────────────────────────────────────────────────
   Utilities
   ───────────────────────────────────────────────────────────── */

.mono-peek {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.8125rem;
  line-height: 1.55;
  color: oklch(var(--bc) / 0.85);
  white-space: pre-wrap;
  word-break: break-word;
}

/* Rendered job description — structured HTML from jd_html filter.
   Small, readable, clear hierarchy. Same font as body (IBM Plex Sans), not mono. */
.jd-body {
  font-size: 0.875rem;
  line-height: 1.6;
  color: oklch(var(--bc) / 0.85);
}
.jd-body .jd-h {
  font-weight: 600;
  font-size: 0.9rem;
  color: oklch(var(--bc));
  margin: 1rem 0 0.4rem 0;
  letter-spacing: -0.005em;
}
.jd-body .jd-h:first-child { margin-top: 0; }
.jd-body .jd-p {
  margin-bottom: 0.75rem;
}
.jd-body .jd-list {
  list-style: disc;
  padding-left: 1.25rem;
  margin-bottom: 0.75rem;
}
.jd-body .jd-list li {
  margin-bottom: 0.25rem;
}
.jd-body .jd-list li:last-child { margin-bottom: 0; }
.jd-body strong {
  font-weight: 600;
  color: oklch(var(--bc));
}

.section-list {
  font-size: 0.875rem;
  line-height: 1.55;
}
.section-list li {
  padding: 0.125rem 0;
}

/* ─────────────────────────────────────────────────────────────
   Nav progress bar — thin top ribbon that pulses across the viewport
   during full-page navigations. Reserved for regular <a href> clicks
   (HTMX ops get the full-screen overlay). Subtle salmon-to-primary
   gradient so it reads as a brand accent, not a system spinner.
   ───────────────────────────────────────────────────────────── */
.nav-progress {
  position: fixed;
  top: 0;
  left: 0;
  height: 3px;
  width: 0;
  z-index: 1000;
  background: linear-gradient(90deg,
    oklch(var(--p) / 0.55) 0%,
    oklch(var(--p)) 60%,
    oklch(var(--p) / 0.55) 100%);
  box-shadow: 0 0 12px oklch(var(--p) / 0.45);
  opacity: 0;
  transition: opacity 0.15s ease;
  pointer-events: none;
}
.nav-progress--active {
  opacity: 1;
  animation: navFill 12s cubic-bezier(0.15, 0.85, 0.45, 1) forwards;
}
@keyframes navFill {
  0%   { width: 0; }
  30%  { width: 45%; }
  65%  { width: 78%; }
  95%  { width: 92%; }
  100% { width: 95%; }
}

/* ─────────────────────────────────────────────────────────────
   Journey hero — 3 big numbers with warm gradient bg. Numbers use
   the display face so the tab has an anchor when you land on it.
   ───────────────────────────────────────────────────────────── */
.hero-num {
  /* -10% per feedback: card was taking too much vertical estate */
  /* Big metrics wear the DISPLAY grotesque at a medium weight — IBM Plex at 700
     read heavy/"sad" here (Eduardo 2026-09-15); Schibsted's open numerals at 600
     look designed, not mechanical, and the looser tracking lets "161" breathe. */
  font-family: var(--font-display);
  font-size: 2.5rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1;
  color: oklch(var(--bc));
  font-variant-numeric: tabular-nums;
}
.hero-label {
  font-size: 0.8125rem;
  color: oklch(var(--bc) / 0.55);
  font-weight: 500;
  margin-top: 0.35rem;
  letter-spacing: 0.005em;
}

/* ─────────────────────────────────────────────────────────────
   Typeahead (custom dropdown) — replaces the native <datalist> so
   we control the styling and kill the browser dropdown-arrow that
   used to appear next to the input. LinkedIn-clean: floating panel
   below the input, max 5 items, keyboard/mouse hover states.
   ───────────────────────────────────────────────────────────── */
.typeahead-wrap {
  position: relative;
}

/* Underline mini-tabs — sub-section switch (Top matches / Saved) on the Jobs
   landing. Lighter + smaller than the main nav underline so the hierarchy
   reads: big nav underline up top, small section switch down here. */
.utabs { display: inline-flex; gap: 1.5rem; }
.utab {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding-bottom: 8px;
  font-family: var(--font-sans);
  font-size: 0.9rem;
  font-weight: 600;
  /* Inactive tab was 0.5 — read as washed-out grey (user: "too hidden, make it
     darker with more contrast"). Bumped so an unselected tab is clearly legible;
     the green underline + full-strength colour still mark the active one. */
  color: oklch(var(--bc) / 0.72);
  border-bottom: 2px solid transparent;
  cursor: pointer;
  transition: color .15s ease, border-color .15s ease;
}
.utab:hover { color: oklch(var(--bc) / 0.92); }
.utab--active {
  color: oklch(var(--bc));
  border-bottom-color: oklch(var(--p));
}
.utab__count {
  font-size: 0.7rem;
  font-weight: 600;
  color: oklch(var(--bc) / 0.6);
}
.utab--active .utab__count { color: oklch(var(--p)); }

/* Airline-style connected search bar (Jobs landing). Soft focus-within ring so
   the whole bar lights up when any segment (roles / location) is focused. */
.jobot-searchbar {
  /* A box (§7A rev.): white + hairline; focus = soft green ring. */
  background: oklch(var(--b1));
  border: 1px solid var(--hair);
  transition: background var(--dur-2) var(--ease-out), box-shadow .15s ease;
}
.jobot-searchbar:focus-within {
  border-color: oklch(var(--p) / 0.45);
  box-shadow: 0 0 0 1px oklch(var(--p) / 0.35), 0 0 0 4px oklch(var(--p) / 0.08);
}
.typeahead-dropdown {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 40;
  background: oklch(var(--b1));
  border: 1px solid oklch(var(--b3));
  border-radius: 0.5rem;
  box-shadow: 0 8px 24px -8px oklch(var(--bc) / 0.18),
              0 2px 4px -2px oklch(var(--bc) / 0.08);
  overflow: hidden;
  max-height: 260px;
  overflow-y: auto;
}
.typeahead-dropdown:empty {
  display: none;
}
.typeahead-list {
  list-style: none;
  margin: 0;
  padding: 0.25rem 0;
}
.typeahead-item {
  width: 100%;
  text-align: left;
  padding: 0.5rem 0.85rem;
  font-size: 0.875rem;
  color: oklch(var(--bc) / 0.85);
  cursor: pointer;
  border: none;
  background: transparent;
  transition: background 0.1s ease;
}
.typeahead-item:hover,
.typeahead-item:focus {
  background: oklch(var(--p) / 0.06);
  color: oklch(var(--bc));
  outline: none;
}
/* Kill Safari's caps/native list arrow on inputs that used to have list= */
.typeahead-input::-webkit-calendar-picker-indicator { display: none !important; }

/* Journey layout — sticky left rail + main content column. The rail is
   fixed-width (~150px) so section titles always fit without wrapping;
   content takes the rest of max-w-7xl. Uses scroll-margin so anchor
   clicks land below the sticky global nav. */
html { scroll-behavior: smooth; }
.journey-section { scroll-margin-top: 90px; }

.journey-layout {
  display: grid;
  grid-template-columns: 150px 1fr;
  gap: 2.5rem;
  align-items: start;
}
@media (max-width: 900px) {
  .journey-layout { grid-template-columns: 1fr; gap: 1rem; }
  .journey-rail { position: static !important; padding-top: 0 !important; }
  .journey-rail__inner { flex-direction: row !important; gap: 1rem !important; }
  .journey-rail__inner::before { display: none !important; }
}
.journey-rail {
  position: sticky;
  top: 5rem;
  padding-top: 0.5rem;
}
.journey-rail__inner {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 2.75rem;
}
/* Connecting vertical line — sits behind the dots, subtle */
.journey-rail__inner::before {
  content: '';
  position: absolute;
  left: 7px;
  top: 8px;
  bottom: 8px;
  width: 1px;
  background: oklch(var(--b3));
}
.journey-rail__step {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.65rem;
  color: oklch(var(--bc) / 0.45);
  font-size: 0.875rem;
  font-weight: 500;
  text-decoration: none;
  transition: color 0.18s ease;
  cursor: pointer;
}
.journey-rail__step:hover {
  color: oklch(var(--bc) / 0.85);
}
.journey-rail__dot {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: oklch(var(--b1));
  border: 2px solid oklch(var(--b3));
  flex-shrink: 0;
  transition: all 0.22s ease;
  z-index: 1;
}
.journey-rail__step--active {
  color: oklch(var(--bc));
  font-weight: 600;
}
.journey-rail__step--active .journey-rail__dot {
  background: oklch(var(--p));
  border-color: oklch(var(--p));
  box-shadow: 0 0 0 4px oklch(var(--p) / 0.15);
}

/* Kanban columns — light bg with header stripe, cards stack vertically. */
.kanban-column {
  background: oklch(var(--b2) / 0.6);
  border: 1px solid oklch(var(--b3));
  border-radius: 0.625rem;
  display: flex;
  flex-direction: column;
  min-height: 200px;
}
.kanban-column__header {
  padding: 0.65rem 0.75rem;
  border-bottom: 1px solid oklch(var(--b3));
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}
.kanban-column__count {
  font-size: 0.7rem;
  font-weight: 600;
  color: oklch(var(--bc) / 0.55);
  background: oklch(var(--b1));
  padding: 1px 8px;
  border-radius: 999px;
  border: 1px solid oklch(var(--b3));
  font-variant-numeric: tabular-nums;
}
.kanban-column__body {
  padding: 0.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  flex: 1;
}
.kanban-column--closed {
  opacity: 0.75;
}
.kanban-column--closed .kanban-column__header {
  background: oklch(var(--b3) / 0.35);
}

/* Funnel v3 — same-height flat rectangles per stage. All green, intensity
   grows left→right (from muted to saturated) so visual weight tracks the
   value of each stage. Numbers win the eye; shape is boring on purpose. */
.funnel-cell {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.55rem;
}
.funnel-bar-flat {
  height: 96px;
  border-radius: 0.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.15s ease, filter 0.15s ease;
}
.funnel-bar-flat:hover {
  transform: translateY(-1px);
  filter: brightness(1.05);
}
.funnel-count {
  font-family: var(--font-display);
  color: white;
  font-size: 1.625rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.015em;
  text-shadow: 0 1px 2px oklch(0% 0 0 / 0.18);
}
.funnel-label {
  font-size: 0.875rem;
  color: oklch(var(--bc) / 0.8);
  font-weight: 500;
  text-align: center;
}
.funnel-carry {
  font-size: 0.72rem;
  color: oklch(var(--bc) / 0.45);
  font-variant-numeric: tabular-nums;
  text-align: center;
}

/* Monthly calendar — GitHub-contributions-style grid, one cell per day.
   Fixed width columns so the grid stays uniform. Day number in the corner,
   background intensity carries the activity signal. */
.calendar-grid {
  width: 100%;
  border-spacing: 4px;
  border-collapse: separate;
}
.calendar-dow {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 500;
  color: oklch(var(--bc) / 0.45);
  padding-bottom: 0.35rem;
  text-align: left;
}
.calendar-cell {
  width: calc(100% / 7);
  height: 42px;
  border-radius: 6px;
  vertical-align: top;
  padding: 4px 6px;
  transition: transform 0.12s ease;
}
.calendar-cell:hover {
  transform: scale(1.03);
}
.calendar-cell--blank {
  background: transparent !important;
}
.calendar-daynum {
  font-size: 0.72rem;
  font-weight: 500;
  color: oklch(var(--bc) / 0.55);
  font-variant-numeric: tabular-nums;
}
.calendar-daynum--active {
  color: oklch(100% 0 0 / 0.95);
  font-weight: 600;
}
.calendar-legend-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
}

/* ─────────────────────────────────────────────────────────────
   Score hero — mirrors the results-page card visual language:
   left = big score badge (same colors as .score-badge); right =
   verdict pill + meta line + italic insight text. Reads at a glance
   the same way a job card reads.
   ───────────────────────────────────────────────────────────── */
.score-hero {
  position: relative;
  padding: 1.25rem 1.25rem;
  /* Consolidated to var(--rounded-box) = 12px so score-hero, card-quiet,
     and the drawer panel all share one radius — kills the 4-different-radii
     drift on the same screen the designer flagged. */
  border-radius: var(--rounded-box);
  border: 1px solid oklch(var(--b3));
  background: oklch(var(--b1));
  display: flex;
  gap: 1.25rem;
  align-items: flex-start;
  overflow: hidden;
}
.score-hero--jumped {
  border-color: oklch(var(--a) / 0.35);
  background: linear-gradient(120deg,
    oklch(var(--b1)) 60%,
    oklch(var(--a) / 0.06) 100%);
}
.score-hero__ribbon {
  position: absolute;
  top: 0;
  right: 1rem;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: oklch(var(--a));
  background: oklch(var(--b1));
  border: 1px solid oklch(var(--a) / 0.3);
  border-top: none;
  padding: 0.3rem 0.65rem;
  border-radius: 0 0 0.5rem 0.5rem;
}

/* Big score badge — same visual as .score-badge on cards, just larger. */
.score-hero__badge {
  width: 84px;
  height: 84px;
  border-radius: 14px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  flex-shrink: 0;
}
.score-hero__badge--strong_fit,
.score-hero__badge--workable {
  background: var(--v-strong-tint);
  color: var(--v-strong-ink);
}
.score-hero__badge--stretch {
  background: var(--v-stretch-tint);
  color: var(--v-stretch-ink);
}
.score-hero__badge--poor_fit {
  background: var(--v-poor-tint);
  color: var(--v-poor-ink);
}
.score-hero__num {
  font-size: 2rem;
  line-height: 1;
}
.score-hero__max {
  font-size: 0.75rem;
  font-weight: 500;
  opacity: 0.55;
  margin-top: 4px;
}

.score-hero__body {
  flex: 1;
  min-width: 0;
  padding-top: 0.15rem;
}
.score-hero__row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}
.score-hero__meta {
  margin-top: 0.5rem;
  font-size: 0.8125rem;
  color: oklch(var(--bc) / 0.65);
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.score-hero__meta strong {
  font-weight: 600;
  color: oklch(var(--bc) / 0.85);
}
.score-hero__insight {
  margin-top: 0.7rem;
  font-size: 0.8125rem;
  line-height: 1.55;
  color: oklch(var(--bc) / 0.75);
  font-style: italic;
  border-left: 2px solid oklch(var(--p) / 0.5);
  padding-left: 0.75rem;
}

/* Verdict pill inside the hero — matches .pill visual but with per-verdict tints. */
.verdict-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.25rem 0.7rem;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  border: 1px solid;
}
.verdict-pill--strong_fit,
.verdict-pill--workable {
  background: var(--v-strong-tint);
  color: var(--v-strong-ink);
  border-color: var(--v-strong-bd);
}
.verdict-pill--stretch {
  background: var(--v-stretch-tint);
  color: var(--v-stretch-ink);
  border-color: var(--v-stretch-bd);
}
.verdict-pill--poor_fit {
  background: var(--v-poor-tint);
  color: var(--v-poor-ink);
  border-color: var(--v-poor-bd);
}

/* ─────────────────────────────────────────────────────────────
   Modal (light, non-blocking) — used for the ATS report and any
   other secondary detail view triggered from a page. Different from
   the full-screen overlay (which blocks all interaction) because a
   modal expects the user to read + close, not wait for work to finish.
   ───────────────────────────────────────────────────────────── */
.modal-scrim {
  position: fixed;
  inset: 0;
  z-index: 900;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem 1rem;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  background: oklch(var(--b2) / 0.72);
}
.modal-panel {
  background: oklch(var(--b1));
  border: 1px solid oklch(var(--b3));
  border-radius: var(--rounded-box);
  max-width: 720px;
  width: 100%;
  max-height: 82vh;
  display: flex;
  flex-direction: column;
  box-shadow: 0 24px 60px -24px oklch(var(--bc) / 0.28);
  overflow: hidden;
}
.modal-panel__header {
  padding: 1.15rem 1.5rem;
  border-bottom: 1px solid oklch(var(--b3));
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  flex-shrink: 0;
}
.modal-panel__body {
  padding: 1.5rem;
  overflow-y: auto;
  flex: 1;
}

/* ─────────────────────────────────────────────────────────────
   Full-screen overlay loader
   Blocks interaction during long ops. Backdrop-blurred so context is
   preserved but nothing is clickable. Center: a 3×3 grid of squares
   pulsing in a diagonal wave — the "cube deconstruction" effect.
   Center square uses the accent (salmon) to break monotony.
   ───────────────────────────────────────────────────────────── */
.overlay-scrim {
  position: fixed;
  inset: 0;
  z-index: 999;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2rem;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  background: oklch(var(--b2) / 0.72);
}

.geo-loader {
  position: relative;
  width: 120px;
  height: 120px;
  display: grid;
  grid-template-columns: repeat(3, 32px);
  grid-template-rows: repeat(3, 32px);
  gap: 6px;
}
.geo-loader__cell {
  background: oklch(var(--p));
  border-radius: 5px;
  animation: geoWave 1.6s cubic-bezier(0.4, 0.15, 0.25, 1) infinite;
  transform-origin: center;
  opacity: 0.35;
  will-change: transform, opacity;
}
/* Diagonal wave — top-left starts, bottom-right ends. */
.geo-loader__cell:nth-child(1) { animation-delay: 0s; }
.geo-loader__cell:nth-child(2) { animation-delay: 0.10s; }
.geo-loader__cell:nth-child(3) { animation-delay: 0.20s; }
.geo-loader__cell:nth-child(4) { animation-delay: 0.10s; }
.geo-loader__cell:nth-child(5) {
  animation-delay: 0.20s;
  background: oklch(var(--a));
}
.geo-loader__cell:nth-child(6) { animation-delay: 0.30s; }
.geo-loader__cell:nth-child(7) { animation-delay: 0.20s; }
.geo-loader__cell:nth-child(8) { animation-delay: 0.30s; }
.geo-loader__cell:nth-child(9) { animation-delay: 0.40s; }

@keyframes geoWave {
  0%, 100% {
    opacity: 0.30;
    transform: scale(0.80) rotate(0deg);
    border-radius: 5px;
  }
  40% {
    opacity: 1;
    transform: scale(1.12) rotate(45deg);
    border-radius: 2px;
  }
  60% {
    opacity: 1;
    transform: scale(1.0) rotate(45deg);
    border-radius: 5px;
  }
}

.overlay-caption {
  text-align: center;
  max-width: 32rem;
  padding: 0 1rem;
}
.overlay-caption__label {
  font-size: 1.375rem;
  font-weight: 600;
  letter-spacing: -0.012em;
  color: oklch(var(--bc));
}
.overlay-caption__msg {
  font-size: 0.9375rem;
  color: oklch(var(--bc) / 0.65);
  margin-top: 0.5rem;
  min-height: 1.4em;
}
.overlay-caption__meta {
  font-size: 0.75rem;
  color: oklch(var(--bc) / 0.45);
  margin-top: 1.25rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

/* ────────────────────────────────────────────────────────────────
   .lang-pill — accessible radio-pill for language / preference
   toggles. Replaces the peer-checked bold-only affordance the user
   flagged in the 2026-08-20 retro pass. Now: filled bg + solid
   ring + checkmark when active, muted stroke when not.
   Usage:
     <label class="cursor-pointer flex-1">
       <input type="radio" name="x" value="y" class="lang-pill__input peer sr-only">
       <span class="lang-pill">
         <i class="ph-thin ph-check lang-pill__check"></i>Label
       </span>
     </label>
   Works without Tailwind's peer-checked variant chaining — relies
   only on the base sibling combinator, which is stable everywhere.
   ──────────────────────────────────────────────────────────────── */
.lang-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  width: 100%;
  padding: 0.55rem 0.75rem;
  border: 1.5px solid oklch(var(--b3));
  background: oklch(var(--b1));
  color: oklch(var(--bc) / 0.62);
  border-radius: 0.5rem;
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.15s ease-out;
}
.lang-pill:hover {
  border-color: oklch(var(--bc) / 0.35);
  color: oklch(var(--bc) / 0.85);
}
.lang-pill__check {
  font-size: 1rem;
  opacity: 0;
  width: 0;
  overflow: hidden;
  transition: opacity 0.12s ease-out, width 0.12s ease-out;
}
/* Active state — sibling combinator on the hidden radio input. */
.lang-pill__input:checked + .lang-pill {
  border-color: oklch(var(--p));
  background: oklch(var(--p));
  color: oklch(var(--pc));
  font-weight: 600;
  box-shadow: 0 2px 6px oklch(var(--p) / 0.25);
}
.lang-pill__input:checked + .lang-pill .lang-pill__check {
  opacity: 1;
  width: 1rem;
}
.lang-pill__input:focus-visible + .lang-pill {
  outline: 2px solid oklch(var(--p));
  outline-offset: 2px;
}

/* ────────────────────────────────────────────────────────────────
   Floating settings panel — slide-in from the right.
   Triggered by the gear icon in the top nav. See partials/settings_panel.html
   Added 2026-08-20 per retro item 2.1 (asked twice by user).
   ──────────────────────────────────────────────────────────────── */
.settings-panel-backdrop {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(0, 0, 0, 0.32);
}
.settings-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 101;
  width: 100%;
  max-width: 26rem;
  background: oklch(var(--b1));
  box-shadow: -12px 0 32px rgba(0, 0, 0, 0.14);
  overflow-y: auto;
  will-change: transform;
}
.settings-panel__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 1.25rem 1.5rem 1rem;
  border-bottom: 1px solid oklch(var(--b3));
  position: sticky;
  top: 0;
  background: oklch(var(--b1));
  z-index: 1;
}
.settings-panel__body {
  padding: 0.25rem 1.5rem 2rem;
}

/* What's-new drawer — same as settings-panel, anchored to the right. */
.whats-new-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 101;
  width: 100%;
  max-width: 26rem;
  background: oklch(var(--b1));
  box-shadow: -12px 0 32px rgba(0, 0, 0, 0.14);
  overflow-y: auto;
  will-change: transform;
}

/* ─────────────────────────────────────────────────────────────
   Results workspace shell — ADR-012
   ───────────────────────────────────────────────────────────── */

/* Opt-in per page via `{% block body_class %}app-shell{% endblock %}`.
   Turns the page into a fixed-height workspace: the document stops
   scrolling and each pane owns its own scroll.

   Gated on BOTH a wide viewport (two panes need the width) and a tall
   one. Below ~560px of viewport height each pane degrades to an
   unusable sliver, so we hand scrolling back to the document. Outside
   the query every rule in this block is inert and the page scrolls
   normally — which is exactly the behaviour we want below `lg`, where
   the detail pane is a bottom sheet rather than a column.

   `min-height: 0` on every link in the chain is load-bearing, not
   decorative. A flex or grid child defaults to `min-height: auto`,
   which refuses to shrink below its content — so the inner
   `overflow-y: auto` never receives a bounded height and the PAGE
   scrolls instead of the pane. That is the exact bug class this shell
   exists to remove (see ADR-012 context: four failed attempts at
   pinning a pane inside a scrolling document). If the workspace ever
   starts scrolling the document again, a missing `min-height: 0` is
   the first thing to look for. */
@media (min-width: 1024px) and (min-height: 560px) {
  body.app-shell {
    height: 100dvh;
    overflow: hidden;
  }
  body.app-shell > main {
    display: flex;
    flex-direction: column;
    min-height: 0;
  }
  /* The page's Alpine root sits between `main` and the chrome/grid,
     because x-data has to wrap everything that reads the store. It is
     a link in the height chain like any other: without this it keeps
     `min-height: auto`, refuses to shrink, and the grid below overflows
     the viewport instead of scrolling inside it — verified in Chromium,
     where `main` bounded correctly at 706px while the grid stayed at
     its 996px content height. */
  .workspace-root {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
  }
  /* The two-pane row. Everything above it in `main` is `shrink-0`
     chrome; this takes whatever height is left. */
  .workspace-grid {
    flex: 1 1 auto;
    min-height: 0;
  }
  /* A column of the grid AND the scroll container — the grid item
     itself, with no wrapper. A grid child already stretches to the row
     height, so there is nothing for a wrapper to flex inside; skipping
     it keeps one fewer invisible link in the min-height:0 chain.
     `overscroll-behavior: contain` stops a pane reaching its end from
     chaining the scroll to the body, which on a fixed shell reads as
     the whole app twitching. */
  .workspace-pane {
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    /* Room so focus rings and card shadows aren't clipped by the
       scroll container's edge. */
    padding: 2px 0.5rem 1.5rem 2px;
  }
  /* Viewport 2 on /jobs — the matches/Saved workspace, bounded to ONE screen
     (100dvh − the sticky 4rem nav). Its Top/Saved tabs are an internal `shrink-0`
     header and the split grid (.workspace-grid) is the body, so a long list or a
     long JD scrolls inside its own .workspace-pane column instead of growing the
     page. NOT app-shell: viewport 1 (search) sits above it and the document
     scrolls between the two screens. Same gating as the rest of the shell — below
     lg / a short viewport the class is inert and everything stacks + scrolls. */
  .jobs-results-shell {
    height: calc(100dvh - 4rem);
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }
}

/* Short-viewport fallback (wide enough for two panes, not tall enough
   for the shell). Keeps the pre-ADR-012 behaviour for the detail pane
   only — capped height with its own scroll, document scrolls normally.
   The list is left to flow so it doesn't become a second sliver. */
@media (min-width: 1024px) and (max-height: 559.98px) {
  /* Scoped to `body.app-shell` on purpose: only jobs_results.html opts into the
     shell. pages/jobs.html uses this same id for viewport 2's detail pane but is
     NOT app-shell (two-viewport scrolling document), so it must not inherit this
     fallback — its short-viewport behaviour is plain document flow. Every other
     rule in this file's workspace block is class-scoped for the same reason. */
  body.app-shell #job-detail-scroll {
    max-height: calc(100dvh - 6rem);
    overflow-y: auto;
  }
}

/* Keyboard affordance for the selectable job cards and the scrollable
   panes. Cards are click-selected by mouse; without a visible focus
   ring the keyboard path added in ADR-012 slice 4 is invisible. */
.workspace-pane:focus-visible,
article.job-swipeable:focus-visible {
  outline: 2px solid oklch(var(--p));
  outline-offset: 2px;
}

/* The cascade-in arrival animation is decorative. Honour a user's
   reduced-motion preference — it was the only animation in this file
   without a guard. */
@media (prefers-reduced-motion: reduce) {
  .cascade-in > * {
    animation: none;
    opacity: 1;
  }
}

/* ── Prep generating-screen "thinking" indicator (interview_generating.html).
   A calm set of concentric pulsing rings in the hunter-green primary — a
   restrained stand-in for the full animated orb, which belongs to Practice
   (ADR-049). Reduced-motion: the rings hold static at mid-opacity. ── */
.gen-orb {
  position: relative;
  width: 88px;
  height: 88px;
}
.gen-orb span {
  position: absolute;
  inset: 0;
  border-radius: 9999px;
  border: 2px solid oklch(var(--p));
  opacity: 0;
  animation: gen-orb-pulse 2.4s ease-out infinite;
}
.gen-orb span:nth-child(2) { animation-delay: 0.8s; }
.gen-orb span:nth-child(3) { animation-delay: 1.6s; }
.gen-orb::after {
  content: "";
  position: absolute;
  inset: 32px;
  border-radius: 9999px;
  background: oklch(var(--p) / 0.9);
}
@keyframes gen-orb-pulse {
  0%   { transform: scale(0.35); opacity: 0.7; }
  100% { transform: scale(1);    opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .gen-orb span { animation: none; opacity: 0.25; transform: scale(1); }
  .gen-orb span:nth-child(1) { transform: scale(0.7); }
  .gen-orb span:nth-child(2) { transform: scale(0.85); }
}

/* ── Practice coach visual (canvas, static/voice_visuals.js "wave", ADR-073).
   Green while the coach speaks, salmon while the candidate does — both driven
   by real AnalyserNode energy. Calm when silent; slowed under reduced-motion. ── */
.voice-orb-wrap {
  width: min(240px, 62vw);
  aspect-ratio: 1 / 1;
  display: grid;
  place-items: center;
  margin-inline: auto;
}
.voice-orb { width: 100%; height: 100%; display: block; }

/* Hide the scrollbar on horizontally-scrollable tab bars (mobile) while keeping
   scroll. Used by the Get Ready seg-tabs. */
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
.no-scrollbar::-webkit-scrollbar { display: none; }

/* ─────────────────────────────────────────────────────────────
   Prep stepper (partials/interview_stepper.html) — also the page's top nav.
   ───────────────────────────────────────────────────────────── */
.stepper-step { display: inline-flex; align-items: center; gap: 0.4rem; border-radius: var(--r-md); }
.stepper-step--current { font-weight: 600; color: oklch(var(--p)); }
.stepper-step--done { color: oklch(var(--n-6)); }
.stepper-step--upcoming { color: oklch(var(--n-5)); }
a.stepper-step:hover { color: oklch(var(--bc)); }
a.stepper-step:hover .stepper-dot { border-color: oklch(var(--n-5)); }
.stepper-dot {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.25rem; height: 1.25rem; border-radius: 9999px;
  font-size: 0.75rem; border: 1px solid oklch(var(--n-4));
}
.stepper-dot i { font-size: 0.7rem; }
.stepper-step--current .stepper-dot { background: oklch(var(--p)); border-color: oklch(var(--p)); color: oklch(var(--pc)); }
.stepper-step--done .stepper-dot { background: oklch(var(--n-3)); border-color: oklch(var(--n-3)); }
.stepper-line { width: 1.5rem; height: 1px; background: oklch(var(--n-3)); }
.stepper-arrow {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.75rem; height: 1.75rem; border-radius: 9999px;
  background: var(--surf-1); color: oklch(var(--bc));
  transition: background var(--dur-1) ease;
}
a.stepper-arrow:hover { background: var(--surf-3); }
.stepper-arrow--off { color: oklch(var(--n-4)); background: transparent; }
.stepper-line--done { background: oklch(var(--n-4)); }

/* ─────────────────────────────────────────────────────────────
   Editorial two-column rows (Brief). Left = the section label, one style for
   every section, STICKY so the rail never reads empty/floating while the
   right column scrolls; right = content at a readable measure.
   ───────────────────────────────────────────────────────────── */
.brief-row { display: grid; gap: 1rem 2rem; }
@media (min-width: 768px) {
  .brief-row { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .brief-rail { grid-column: span 1 / span 1; position: sticky; top: 6rem; align-self: start; }
}
.brief-label {
  font-family: var(--font-display);
  font-size: 1rem;                /* body size — weight carries it (§5A) */
  line-height: 1.4;
  font-weight: 600;
  letter-spacing: -0.011em;
  color: oklch(var(--bc));
}

/* ─────────────────────────────────────────────────────────────
   Type roles used by the Brief-style pages (design.md §5A). One screen uses
   at most 4 sizes, each a clear step apart (≥1.2×): answer 30 · masthead 20
   · body 16 · small 14. Near-misses (15/17 beside 16) read as mistakes.
   ───────────────────────────────────────────────────────────── */
.text-answer {
  font-family: var(--font-display);
  font-size: 1.875rem;            /* 30px */
  line-height: 1.15;
  font-weight: 600;
  letter-spacing: -0.022em;
  text-wrap: balance;
}
@media (max-width: 639px) { .text-answer { font-size: 1.5rem; } }
.text-masthead {
  font-family: var(--font-display);
  font-size: 1.25rem;             /* 20px */
  line-height: 1.3;
  font-weight: 600;
  letter-spacing: -0.014em;
}
/* Body-size heading: same size as body, weight does the work. */
.text-body-strong {
  font-family: var(--font-display);
  font-size: 1rem;
  line-height: 1.4;
  font-weight: 600;
  letter-spacing: -0.006em;
}

/* ─────────────────────────────────────────────────────────────
   Lists — ONE bullet system (design.md §5B). A hanging en dash in faint ink;
   text in full ink; optional muted sub-line. No arrows, no warning icons, no
   dots-in-spans per list. Icons are for state, used once — not bullets.
   ───────────────────────────────────────────────────────────── */
.ui-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.75rem; }
.ui-list > li { position: relative; padding-left: 1.25rem; }
.ui-list > li::before {
  content: "–";
  position: absolute;
  left: 0;
  color: oklch(var(--n-4));
  font-weight: 500;
}
.ui-list__sub { font-size: 0.875rem; color: oklch(var(--bc) / 0.84); margin-top: 0.2rem; }
.ui-source {
  display: inline-flex; align-items: center; gap: 0.15rem; margin-left: 0.25rem;
  font-size: 0.875rem; color: oklch(var(--p));
}
.ui-source:hover { text-decoration: underline; }

/* Numbered list — only when order is real (question flow, ranked actions). */
.ui-list--num { counter-reset: ui-num; }
.ui-list--num > li { counter-increment: ui-num; padding-left: 1.75rem; }
.ui-list--num > li::before {
  content: counter(ui-num);
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: oklch(var(--n-5));
}

/* Form field in the box language: white + hairline, green focus ring. */
.ui-field {
  width: 100%;
  background: oklch(var(--b1));
  border: 1px solid var(--hair);
  border-radius: var(--r-md);
  padding: 0.6rem 0.75rem;
  font-size: 1rem;
  line-height: 1.5;
  transition: border-color var(--dur-1) ease, box-shadow var(--dur-1) ease;
}
.ui-field:focus { outline: none; border-color: oklch(var(--p) / 0.45); box-shadow: 0 0 0 3px oklch(var(--p) / 0.08); }

/* Selectable option tile (mode / channel pickers): a box; picked = green border. */
.ui-option {
  text-align: left;
  background: oklch(var(--b1));
  border: 1px solid var(--hair);
  border-radius: var(--r-lg);
  padding: 0.85rem 1rem;
  transition: border-color var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out);
}
.ui-option:hover { border-color: var(--hair-strong); box-shadow: var(--lift); }
.ui-option--on, .ui-option--on:hover { border-color: oklch(var(--p)); box-shadow: 0 0 0 1px oklch(var(--p) / 0.2); }

/* Conversation bubbles (practice live + feedback transcript). Coach = small
   grey part with dark text; you = green. */
.ui-bubble { max-width: 85%; border-radius: 1rem; padding: 0.5rem 0.875rem; font-size: 0.875rem; line-height: 1.5; }
.ui-bubble--coach { background: var(--surf-2); color: oklch(var(--bc)); }
.ui-bubble--me { background: oklch(var(--p)); color: oklch(var(--pc)); }

/* Small labelled line inside a card ("From your résumé — …"). */
.ui-kv { font-size: 0.875rem; }
.ui-kv > b { font-weight: 600; margin-right: 0.35rem; }
/* Rubric meter — n of total dots (ui.meter). */
.ui-meter { display: inline-flex; gap: 0.25rem; align-items: center; }
.ui-meter__dot { width: 0.5rem; height: 0.5rem; border-radius: 999px; background: oklch(var(--n-7) / 0.15); }
.ui-meter__dot--on { background: oklch(var(--p)); }
/* Voice lab takes list (REQ-046) — scrolls inside its card. */
.lab-takes { max-height: 70vh; overflow-y: auto; margin: 0 -0.25rem; padding: 0 0.25rem; }
.lab-take { display: flex; gap: 0.75rem; align-items: flex-start; padding: 0.75rem 0.5rem; border-radius: var(--r-md); }
.lab-take--on { background: var(--surf-1); }
.lab-star { padding: 0 0.1rem; color: oklch(var(--n-7) / 0.22); font-size: 1rem; line-height: 1; }
.lab-star--on { color: var(--v-stretch-solid); }
.lab-heart { color: oklch(var(--n-7) / 0.45); font-size: 1.05rem; line-height: 1; }
.lab-heart--on { color: var(--v-poor-solid); }
/* Delivery gauge (ui.gauge, REQ-043) — zoned track + marker. */
.ui-gauge { display: flex; flex-direction: column; gap: 0.5rem; }
.ui-gauge__head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; }
.ui-gauge__track { position: relative; height: 0.5rem; border-radius: 999px; background: oklch(var(--n-7) / 0.08); }
.ui-gauge__zone { position: absolute; top: 0; bottom: 0; }
.ui-gauge__zone:first-child { border-radius: 999px 0 0 999px; }
.ui-gauge__zone:nth-last-child(2) { border-radius: 0 999px 999px 0; }
.ui-gauge__zone--good { background: var(--v-strong-tint); }
.ui-gauge__zone--ok { background: oklch(var(--n-7) / 0.10); }
.ui-gauge__zone--warn { background: var(--v-poor-tint); }
.ui-gauge__marker { position: absolute; top: -0.25rem; width: 0.25rem; height: 1rem; margin-left: -0.125rem;
  border-radius: 999px; background: oklch(var(--bc)); }
.ui-gauge__pill--good { background: var(--v-strong-tint); color: var(--v-strong-ink); margin-left: 0.5rem; }
.ui-gauge__pill--ok { background: oklch(var(--n-7) / 0.08); color: oklch(var(--n-7)); margin-left: 0.5rem; }
.ui-gauge__pill--warn { background: var(--v-poor-tint); color: var(--v-poor-ink); margin-left: 0.5rem; }
/* A numbered list whose items are cards: the number sits inside the box. */
.ui-list--num > li.card-quiet { padding-left: 2.75rem; }
.ui-list--num > li.card-quiet::before { left: 1rem; top: 1rem; }
/* Mic level meter track (practice live) + bounded captions pane. */
.ui-meter { width: 6rem; height: 0.375rem; border-radius: 9999px; background: var(--surf-2); overflow: hidden; }
.ui-captions { height: 46vh; }
@media (min-width: 1024px) { .ui-captions { height: 60vh; } }
/* Live practice shell — like .gr-shell: on a desktop-size viewport header ·
   controls · stage fit above the footer; the stage's regions (conversation,
   prepared answer) have FIXED boxes and scroll inside themselves, so a new
   caption or the prepared answer appearing never moves the layout. */
.live-shell { display: flex; flex-direction: column; gap: 1.25rem; }
.live-captions { height: 46vh; }
.live-cue { max-height: 50vh; }
.live-cue__q { animation: live-cue-in var(--dur-2) var(--ease-out); }
@keyframes live-cue-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.voice-orb-wrap--sm { width: 9rem; }
/* Call bar (under the stepper): mic-with-level · End, one quiet pill — like a
   video-call control strip. Ending is an exit, not the page's primary action. */
.live-bar { display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.25rem; border: 1px solid var(--hair); border-radius: 9999px; background: oklch(var(--b1)); }
.live-bar__btn { display: inline-flex; align-items: center; gap: 0.5rem; height: 2rem; padding: 0 0.85rem; border-radius: 9999px; font-size: 0.875rem; color: oklch(var(--bc)); transition: background var(--dur-1) ease; }
.live-bar__btn:hover { background: var(--surf-2); }
.live-bar__btn--off { color: oklch(var(--n-5)); }
.live-bar__sep { width: 1px; height: 1.25rem; background: var(--hair); }
.live-bar__meter { width: 3rem; height: 0.25rem; border-radius: 9999px; background: var(--surf-2); overflow: hidden; }
.live-bar__meter > span { display: block; height: 100%; background: oklch(var(--p)); transition: width 75ms linear; }
@media (min-width: 1024px) and (min-height: 640px) {
  .live-shell { height: calc(100dvh - 4rem - 3rem - 4rem); }
  .live-stage { flex: 1 1 auto; min-height: 0; grid-template-rows: minmax(0, 1fr); }
  .live-stage > * { min-height: 0; }
  .live-captions { flex: 1 1 auto; height: auto; min-height: 0; }
  .live-stage > .live-captions, .live-cue { height: 100%; max-height: none; }
}

.deck-rate { gap: 0.4rem; }
.deck-rate--got:hover { background: var(--v-strong-tint); color: var(--v-strong-ink); }
.deck-rate--missed:hover { background: var(--v-stretch-tint); color: var(--v-stretch-ink); }

/* ─────────────────────────────────────────────────────────────
   Get Ready shell (ADR-057) — on a desktop-size viewport the whole screen fits:
   header · tabs · the active panel (flex:1), which scrolls inside itself if it
   must. Below lg / short viewports it's a normal page.
   ───────────────────────────────────────────────────────────── */
.gr-shell { display: flex; flex-direction: column; gap: 1.25rem; }
.gr-panel { min-height: 0; }
@media (min-width: 1024px) and (min-height: 640px) {
  /* nav 4rem + main py-6 (3rem) + footer (py-6 + one line ≈ 4rem) — the
     footer counts, or the page still scrolls by its height (Eduardo 2026-09-29). */
  .gr-shell { height: calc(100dvh - 4rem - 3rem - 4rem); }
  .gr-panel { flex: 1 1 auto; }
  .gr-panel > * { height: 100%; }
}
.gr-scroll { overflow-y: auto; min-height: 0; padding-right: 2px; }

/* Answer deck: the card grows to fill the panel; controls hug each other. */
.deck, .deck-body { display: flex; flex-direction: column; gap: 0.9rem; height: 100%; min-height: 0; }
.deck-controls { display: flex; align-items: center; justify-content: center; gap: 0.5rem; }
.deck-arrow { padding-inline: 0.75rem; }

/* Real card flip (3D). The two faces are boxes in the §7A language; the back
   scrolls inside itself when an answer is long. */
.flip { perspective: 1600px; height: 24rem; cursor: pointer; }
@media (min-width: 1024px) and (min-height: 640px) {
  .flip { height: auto; flex: 1 1 auto; min-height: 15rem; }
}
.flip-inner { position: relative; width: 100%; height: 100%; transform-style: preserve-3d;
  transition: transform 0.55s var(--ease-out); }
.flip--on .flip-inner { transform: rotateY(180deg); }
.flip-face {
  position: absolute; inset: 0; backface-visibility: hidden; -webkit-backface-visibility: hidden;
  background: oklch(var(--b1)); border: 1px solid var(--hair); border-radius: var(--r-xl);
  padding: 1.75rem; overflow-y: auto;
}
.flip-front { display: flex; flex-direction: column; justify-content: space-between; gap: 1rem; }
.flip-question { margin: auto 0; }
.flip-back { transform: rotateY(180deg); cursor: default; }
.flip:hover .flip-front { border-color: var(--hair-strong); box-shadow: var(--lift); }
.flip:focus-visible { outline: 2px solid oklch(var(--p) / 0.45); outline-offset: 3px; border-radius: var(--r-xl); }
@media (prefers-reduced-motion: reduce) { .flip-inner { transition: none; } }

/* Stories split (list | STAR), each side scrolls on its own. */
.gr-split { display: grid; gap: 1rem; min-height: 0; }
@media (min-width: 768px) { .gr-split { grid-template-columns: minmax(0, 17rem) minmax(0, 1fr); } }
.gr-split__list, .gr-split__detail { min-height: 0; overflow-y: auto; padding: 2px; }
/* One column that never grows wider than its track: a long, truncated name
   used to set the min-content width and push a horizontal scrollbar. */
.gr-split__list { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.5rem; align-content: start; overflow-x: hidden; }
.gr-split__list > .ui-option { min-width: 0; }
.star { display: grid; gap: 0.9rem; margin: 0; }
.star__row { display: grid; gap: 0.2rem; }
@media (min-width: 640px) { .star__row { grid-template-columns: 7rem minmax(0, 1fr); gap: 1rem; } }
.star__key { font-family: var(--font-display); font-weight: 600; color: oklch(var(--p)); }
.star dd { margin: 0; line-height: 1.55; }

/* Answer skeleton (ADR-058) — sections with dash bullets; a hint slot is a
   dashed chip: "fill this in with your own words". */
.frame { display: grid; gap: 1rem; }
.frame__sec { display: grid; gap: 0.4rem; }
.frame__label { font-family: var(--font-display); font-weight: 600; color: oklch(var(--p)); }
.frame .ui-list { gap: 0.4rem; }
.ui-hint {
  display: inline; padding: 0.05rem 0.45rem; margin: 0 0.1rem;
  border: 1px dashed oklch(var(--wa) / 0.7); border-radius: var(--r-sm);
  background: oklch(var(--wa) / 0.08); color: oklch(var(--bc) / 0.84);
  font-size: 0.875em; box-decoration-break: clone; -webkit-box-decoration-break: clone;
}
.ui-hint::before { content: "✎ "; color: oklch(var(--wa)); }

/* Check-in (ADR-058 rev. 2026-10-01) — one question per screen. */
.checkin { display: flex; flex-direction: column; gap: 1.5rem; }   /* full width (2026-10-01) */
.checkin__step { display: grid; gap: 1rem; }
.checkin__field { field-sizing: content; min-height: 6rem; max-height: 18rem; resize: none; font-size: 1.0625rem; }
.checkin__dots { display: flex; gap: 0.4rem; }
.checkin__dots span { width: 1.75rem; height: 0.25rem; border-radius: 9999px; background: var(--surf-3); transition: background var(--dur-2) ease; }
.checkin__dots span.is-done { background: oklch(var(--p) / 0.45); }
.checkin__dots span.is-on { background: oklch(var(--p)); }

/* Answer card back as a script (2026-10-01): paragraphs, not headed sections.
   The structure letter (S·T·A·R / A·E·R …) sits faintly in the margin. */
.script { display: grid; gap: 0.9rem; }
.script__para { display: grid; gap: 0.15rem; }
@media (min-width: 768px) { .script__para { grid-template-columns: 7.5rem minmax(0, 1fr); gap: 1rem; align-items: baseline; } }
.script__para p { margin: 0; line-height: 1.65; }
.script__cue { font-size: 0.8125rem; font-weight: 500; color: oklch(var(--n-5)); line-height: 1.65; }
.script__takeaway {
  margin: 0.4rem 0 0; padding-top: 0.9rem; border-top: 1px solid var(--hair);
  font-style: italic; color: oklch(var(--bc) / 0.84);
}
.ui-hint--btn { cursor: pointer; font: inherit; font-size: 0.875em; }
.ui-hint--btn:hover { background: oklch(var(--wa) / 0.16); }

/* Fill a ✎ gap on the card back */
.gap-editor { display: grid; gap: 0.5rem; padding: 0.85rem 1rem; background: var(--surf-1); border-radius: var(--r-md); }
.gap-editor .ui-field { field-sizing: content; min-height: 3.25rem; resize: none; }

/* Get Ready's sub-steps: 1 · A few quick questions — 2 · Answer cards */
.substeps { display: flex; align-items: center; gap: 0.6rem; font-size: 0.875rem; }
.substeps__item { display: inline-flex; align-items: center; gap: 0.4rem; color: oklch(var(--n-5)); }
.substeps__item:hover { color: oklch(var(--bc)); }
.substeps__item.is-on { color: oklch(var(--bc)); font-weight: 600; }
.substeps__n {
  display: inline-flex; align-items: center; justify-content: center; width: 1.15rem; height: 1.15rem;
  border-radius: 9999px; font-size: 0.7rem; border: 1px solid oklch(var(--n-4));
}
.substeps__item.is-on .substeps__n { background: oklch(var(--bc)); border-color: oklch(var(--bc)); color: oklch(var(--b1)); }
.substeps__line { width: 1.25rem; height: 1px; background: oklch(var(--n-3)); }

/* Competency row on Feedback: name | meter | band, the band in its own fixed
   column so every meter lines up whatever the band label's length. */
.score-row { display: grid; grid-template-columns: minmax(0, 1fr) auto 8.5rem; align-items: center; column-gap: 0.75rem; }
.score-row__band { justify-self: end; }

/* Multiselect (macro kit): a select-looking trigger + a popover of checkboxes. */
.ui-multiselect { position: relative; }
.ui-multiselect__trigger { text-align: left; font-weight: 400; }
.ui-multiselect__panel { position: absolute; z-index: 30; left: 0; right: 0; margin-top: 0.25rem; max-height: 18rem; overflow-y: auto;
  padding: 0.25rem; background: oklch(var(--b1)); border-radius: var(--r-lg); box-shadow: var(--elev-3); }
.ui-multiselect__item { display: flex; align-items: flex-start; gap: 0.625rem; padding: 0.5rem 0.625rem; border-radius: var(--r-md); cursor: pointer; font-size: 0.875rem; }
.ui-multiselect__item:hover { background: var(--surf-1); }
.ui-multiselect__item .checkbox { margin-top: 0.125rem; }
.ui-multiselect__sub { display: block; color: oklch(var(--n-5)); font-size: 0.8125rem; }

/* Live interview aid (REQ-050): the prepared answer read like a teleprompter
   script — flowing prose at body size, no bullets, no labels. */
.live-script { font-size: 1rem; line-height: 1.75; }
.live-script p + p { margin-top: 0.75rem; }

/* A short scrolling list inside a section (e.g. Feedback's past sessions). */
.ui-scroll-list { max-height: 14rem; overflow-y: auto; }

/* Dictate (macro kit): "Speak your answer" → "Listening · tap to stop". */
.ui-dictate { display: inline-flex; align-items: center; gap: 0.375rem; }
.ui-dictate--on { color: oklch(var(--p)); background: oklch(var(--p) / 0.08); }
.ui-dictate__dot { width: 0.5rem; height: 0.5rem; border-radius: 999px; background: oklch(var(--er)); animation: ui-dictate-pulse 1.2s var(--ease-in-out) infinite; }
@keyframes ui-dictate-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
@media (prefers-reduced-motion: reduce) { .ui-dictate__dot { animation: none; } }

/* ── Text input (design.md §9.0) — grey fill, no outline; ring on focus ── */
.ui-input {
  width: 100%;
  background: var(--surf-1);
  border: 0;
  border-radius: var(--r-md);
  padding: 0.7rem 0.9rem;
  font-size: 1rem;
  color: oklch(var(--bc));
  outline: none;
}
.ui-input:focus { box-shadow: 0 0 0 2px oklch(var(--p) / 0.45); }
.ui-input--code { font-size: 1.5rem; letter-spacing: 0.35em; text-align: center; font-variant-numeric: tabular-nums; }

/* ── Sign-in page (ADR-082) — one narrow column, centred ── */
.login-page { font-family: var(--font-sans); background: oklch(var(--b1)); min-height: 100vh;
              display: flex; align-items: flex-start; justify-content: center; padding: 12vh 16px 32px; }
.login-box { width: 100%; max-width: 24rem; display: flex; flex-direction: column; gap: 1.5rem; }
.login-brand { display: flex; align-items: center; gap: 0.5rem; font-family: var(--font-display);
               font-weight: 700; font-size: 1.125rem; }
.login-brand__mark { width: 1.75rem; height: 1.75rem; }
.login-form { display: flex; flex-direction: column; gap: 0.75rem; }
.login-label { font-size: 0.875rem; font-weight: 500; }
.login-error { font-size: 0.875rem; color: oklch(var(--er)); }
.login-fine { font-size: 0.8125rem; color: oklch(var(--bc) / 0.6); }
.login-alt { display: flex; gap: 1.25rem; align-items: center; font-size: 0.875rem; color: oklch(var(--bc) / 0.6); }
.login-link { color: oklch(var(--p)); text-decoration: underline; text-underline-offset: 2px; }

/* ─────────────────────────────────────────────────────────────
   Profile kit (REQ-061 / ADR-094, 2026-10-18) — design.md §9.0.
   rail · grade tile · bar · rows · document · popover.
   ───────────────────────────────────────────────────────────── */

/* Rail: a page's own section nav (left column ≥lg). Active = grey step +
   green leading bar (§7: selection never uses a border or ring). Labels are
   body size (§5A): they differ from content by weight, not size. */
.ui-rail { display: flex; flex-direction: column; gap: 2px; }
.ui-rail__item { display: flex; align-items: center; gap: 0.65rem; width: 100%; padding: 0.55rem 0.7rem;
  border-radius: var(--r-md); font-size: 1rem; font-weight: 500; color: oklch(var(--n-6)); text-align: left;
  transition: background var(--dur-1) ease; }
.ui-rail__item:hover { background: var(--surf-1); }
.ui-rail__item--active { background: var(--surf-2); color: oklch(var(--n-7)); font-weight: 600;
  box-shadow: inset 3px 0 0 oklch(var(--p)); }
.ui-rail__badge { margin-left: auto; font-size: 0.75rem; font-weight: 600; font-variant-numeric: tabular-nums; color: oklch(var(--n-6)); }
.ui-rail__item--active .ui-rail__badge { color: oklch(var(--p)); }
/* Below lg the rail becomes utabs on one line that scrolls sideways. */
.ui-tabs-scroll { overflow-x: auto; scrollbar-width: none; margin-inline: -1rem; padding-inline: 1rem; }
.ui-tabs-scroll::-webkit-scrollbar { display: none; }
.ui-tabs-scroll .utab { white-space: nowrap; }

.ui-avatar { width: 48px; height: 48px; flex: none; border-radius: var(--r-pill); display: flex; align-items: center;
  justify-content: center; background: oklch(91.8% 0.043 171.8); color: oklch(35% 0.073 171.8);
  font-family: var(--font-display); font-weight: 600; font-size: 1.05rem; }

/* Grade tile: the ATS letter (ADR-096). Letter at the answer size (30px, §5A).
   Tints from the §4.5 verdict scale. */
.ui-grade { width: 56px; height: 56px; flex: none; border-radius: var(--r-lg); display: flex; align-items: center;
  justify-content: center; font-family: var(--font-display); font-size: 1.875rem; font-weight: 600; }
.ui-grade--a, .ui-grade--b { background: oklch(94% 0.04 165); color: oklch(38% 0.09 165); }
.ui-grade--c { background: oklch(93% 0.05 75); color: oklch(44% 0.10 60); }
.ui-grade--d { background: oklch(94% 0.04 25); color: oklch(50% 0.15 25); }

/* Bar: a share of something (how often jobs ask). Never a score (scores use
   the ring) and never a rubric count (that's ui.meter). */
.ui-bar { height: 6px; border-radius: var(--r-pill); background: var(--surf-2); overflow: hidden; }
.ui-bar > span { display: block; height: 100%; border-radius: var(--r-pill); background: oklch(var(--n-5)); }
/* --gap: something you lack (warning hue, §4.5). Neutral by default so a plain
   share (e.g. today's AI use at 16%) never reads as a warning. */
.ui-bar--gap > span { background: oklch(66% 0.11 60); }

/* Rows: a hairline list. --edit makes the whole row the button (touch-safe). */
.ui-row { display: flex; align-items: center; gap: 0.75rem; padding: 0.7rem 0; border-bottom: 1px solid var(--hair); }
.ui-row:last-child { border-bottom: 0; }
.ui-row--edit { width: calc(100% + 1rem); margin-inline: -0.5rem; padding-inline: 0.5rem; text-align: left; cursor: pointer;
  border-radius: var(--r-md); transition: background var(--dur-1) ease; }
.ui-row--edit:hover { background: var(--surf-1); }
.chip-edit { cursor: pointer; }
.chip-edit:hover { background: var(--surf-3); }

/* Document: one box that reads like the resume itself (ADR-094 update).
   Sections inside are separated by a hairline, not boxed. */
.ui-doc { background: oklch(var(--b1)); border: 1px solid var(--hair); border-radius: var(--r-lg); }
.ui-doc__section { padding: 1.1rem 1.25rem; border-bottom: 1px solid var(--hair); }
.ui-doc__section:last-child { border-bottom: 0; }
.ui-doc__head { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; margin-bottom: 0.35rem; }

/* Popover: floats from its trigger, never pushes the layout. */
.ui-popover-anchor { position: relative; display: inline-block; }
.ui-popover { position: absolute; z-index: 30; top: calc(100% + 0.4rem); left: 0; min-width: 18rem; max-width: min(24rem, 90vw);
  background: oklch(var(--b1)); border: 1px solid var(--hair); border-radius: var(--r-lg); box-shadow: var(--elev-2);
  padding: 0.35rem 0.9rem; }
.ui-popover--end { left: auto; right: 0; }
/* Menu in a popover: a short list of actions (✦ Coach options, REQ-068). */
.ui-menu { min-width: 11rem; padding: 0.3rem; }
.ui-menu__item { display: block; width: 100%; text-align: left; padding: 0.5rem 0.65rem; border-radius: var(--r-md);
  font-size: 0.875rem; transition: background var(--dur-1) ease; }
.ui-menu__item:hover, .ui-menu__item:focus-visible { background: var(--surf-1); outline: none; }

/* Profile wiring (REQ-061, 2026-10-19). */
.ui-doc__section, .ui-row-group { scroll-margin-top: 5rem; }
.ui-row-group:last-child > .ui-row { border-bottom: 0; }
.ui-check { color: oklch(var(--p)); }
.ui-divider-top { border-top: 1px solid var(--hair); }
.ui-doc__bullet { position: relative; padding-left: 1rem; }
.ui-doc__bullet::before { content: "•"; position: absolute; left: 0.1rem; color: oklch(var(--n-5)); }
.ui-summary { list-style: none; cursor: pointer; }
.ui-summary::-webkit-details-marker { display: none; }

/* Dialog: a centred panel over a dimmed page (typed confirmations). */
.ui-dialog { position: fixed; inset: 0; z-index: 120; display: flex; align-items: center; justify-content: center; padding: 1rem; }
.ui-dialog__backdrop { position: absolute; inset: 0; background: oklch(0% 0 0 / 0.4); }
.ui-dialog__panel { position: relative; width: 100%; max-width: 28rem; padding: 1.25rem; background: oklch(var(--b1));
  border-radius: var(--r-lg); box-shadow: var(--elev-3); }

/* Term: a jargon word with a plain tip (ADR-099). Dotted underline = "hover or
   tap me"; the tip opens above, left-aligned so it never runs off a phone edge. */
.ui-term { position: relative; cursor: help; text-decoration: underline dotted; text-underline-offset: 3px;
  text-decoration-thickness: 1px; outline: none; }
.ui-term:focus-visible { border-radius: var(--r-sm); box-shadow: 0 0 0 2px oklch(var(--p) / 0.35); }
.ui-term__tip { position: absolute; z-index: 40; left: 0; bottom: calc(100% + 0.4rem); width: max-content; max-width: min(18rem, 80vw);
  padding: 0.5rem 0.65rem; border-radius: var(--r-md); background: oklch(var(--n-7)); color: oklch(var(--b1));
  font-size: 0.875rem; font-weight: 400; line-height: 1.4; letter-spacing: normal; text-transform: none; white-space: normal;
  box-shadow: var(--elev-2); opacity: 0; visibility: hidden; transition: opacity var(--dur-1) ease; }
.ui-term:hover .ui-term__tip, .ui-term:focus .ui-term__tip { opacity: 1; visibility: visible; }

/* Path: where someone is in a journey (ADR-099). Labels, not a score. */
.ui-path { display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; margin: 0; padding: 0; list-style: none; font-size: 0.875rem; }
.ui-path__step { display: inline-flex; align-items: center; gap: 0.4rem; color: oklch(var(--n-6)); }
.ui-path__step--done { color: oklch(var(--n-7)); }
.ui-path__step--done i { color: oklch(var(--p)); }
.ui-path__step--now { color: oklch(var(--p)); font-weight: 600; }
