/* ==========================================================================
   remote-cyber-jobs portal — design system v3, "verified anywhere"
   (FEAT-12, 2026-08-20 — supersedes FEAT-04 v2's "paper & ink" system.)

   NOTE (portal review #40): this header used to say the admin surface was a
   permanent fork loading style-admin-legacy.css. FEAT-16 item 7 REVERSED
   that by explicit user direction — /admin and /admin/login load THIS
   stylesheet like every other page, and base.html no longer keys its
   <link> on request.endpoint. style-admin-legacy.css remains on disk,
   unreferenced, as a historical/rollback artifact per this project's
   convention of not deleting shipped work; nothing loads it.

   Built directly against the approved reference mockup,
   pipeline/reference-feat-12-landing-mockup.html — its :root token block
   (12 custom properties), section patterns (hero, "spot the fake" listing
   comparison, 4-stop dashed pipeline explainer, category chips, ticket-stub
   CTA) and copy are the authoritative spec for this pass, not just
   inspiration. One resolved, PRD-required deviation (§1.2): the mockup
   embeds both fonts as inline base64 @font-face data URIs; this stylesheet
   self-hosts them as separate static/fonts/*.woff2 files instead — the
   same self-hosting MECHANISM FEAT-04 v2 already established for
   Fraunces/Inter, for the reasons named in the PRD (no per-page base64
   payload bloat, one consistent font-hosting convention across both design
   generations, avoids a second "large embedded blob in markup" pattern).
   ========================================================================== */

/* --------------------------------------------------------------------------
   Fonts — self-hosted, NOT loaded from any CDN at runtime (PRD §3 non-goal
   6). Both are real Google Fonts, both confirmed SIL Open Font License 1.1
   by fetching each family's actual OFL.txt directly from
   github.com/google/fonts (not assumed), committed under
   static/fonts/LICENSES/. The two woff2 files here were extracted verbatim
   from the reference mockup's own embedded base64 payloads (byte-for-byte
   identical font data — decoded, written to disk, and confirmed as valid
   woff2 via their `wOF2` magic bytes — never re-derived or re-encoded).

   - Archivo (variable, weight axis 400-800) — body copy, nav, labels,
     buttons, kickers/eyebrows. Matches the mockup's own weight-range
     declaration exactly.
   - Archivo Black (static, weight 400 only) — display headings (h1/h2/h3),
     the brand wordmark, stat numbers. A single heavy cut used ONLY for
     display type, mirroring the mockup's own usage.
   ========================================================================== */
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("fonts/archivo-variable.woff2") format("woff2");
}
@font-face {
  font-family: "Archivo Black";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/archivo-black.woff2") format("woff2");
}

/* --------------------------------------------------------------------------
   Tokens
   -------------------------------------------------------------------------- */
:root {
  /* The 12 tokens from the reference mockup's own :root block, verbatim —
     names and values unchanged (PRD success metric item 2: all 12 must
     survive into the real stylesheet, not a renamed/reduced subset). */
  /* v4 "matte" (2026-08-25) — approved from a mockup by the account owner.
     Same token NAMES as v3 so no rule below needed renaming; only the values
     moved. Three deliberate shifts:
       1. the accent is desaturated (#00d9a3 neon mint -> #5fb89a) and the
          negative softened (#ff5c4d -> #d18374);
       2. --line becomes a SOLID colour rather than rgba(255,255,255,.14).
          Once every glow and coloured shadow is removed, borders are the only
          thing separating surfaces, and the old alpha line was nearly
          invisible against the ground — this is what made the first, darker
          pass read as murky rather than flat;
       3. the whole ground is lifted a step (#0a1120 -> #171d26). */
  --ink: #171d26;
  --ink-raised: #212936;
  --ink-raised-2: #2b3543;
  --paper: #eef2f4;
  --text: #f0f3f7;
  --text-dim: #aab6c6;
  --line: #3c4757;
  --line-soft: #333d4b;
  --accent: #5fb89a;
  --accent-deep: #71c7aa;
  --accent-ink: #0c1512;
  --stamp: #d18374;
  --stamp-dim: rgba(209, 131, 116, 0.18);
  --accent-dim: rgba(95, 184, 154, 0.18);

  /* Additional, non-mockup tokens needed to actually build a working app UI
     (forms, tables, a caution state distinct from both error and success) —
     additive, not a replacement of any of the 12 above. Coral (--stamp) is
     reserved ONLY for negative/gated/error states per the PRD (§3 item 1);
     --warn is a separate amber used for the one caution-not-error state
     this app already has (the sub-threshold generation warning), so it
     never borrows the negative color for something that isn't a failure. */
  --warn: #d4ac68;
  --warn-dim: rgba(212, 172, 104, 0.18);

  --font-sans: "Archivo", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    Arial, sans-serif;
  --font-display: "Archivo Black", "Archivo", sans-serif;

  /* Type scale — 6 steps (>=4 required by FEAT-04's own carried-forward
     criterion, re-verified here against the NEW system per PRD §5 item 3). */
  --text-xs: 0.8125rem;   /* 13px — meta, timestamps, kickers/eyebrows */
  --text-sm: 0.9375rem;   /* 15px — labels, table cells, nav */
  --text-base: 1.0625rem; /* 17px — body */
  --text-lg: 1.375rem;    /* 22px — card/section h2 */
  --text-xl: 2.375rem;    /* 38px — page h1 */
  --text-2xl: 3.25rem;    /* 52px — hero h1 (clamped, see .hero h1) */

  /* Spacing scale (4/8/12/16/24/32/48/64) */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;

  /* Radius — rounder than FEAT-04 v2's crisp-editorial system on purpose:
     the mockup's own pill buttons/chips (999px) and rounded cards (14-20px)
     read as a modern, verification-badge/passport-stamp motif, not the
     print-editorial feel v2 was going for. */
  /* v4: one size system for every small control — see .badge's own comment.
     --control-min was first set to 6.5rem, which made every badge as wide as
     the widest button. That looked right in isolation and broke /admin: the
     Users table carries badges in two columns AND a select+button pair in two
     more, so an 8-column row blew past the 1280px container and clipped
     "Deactivate". 4.5rem is the compromise that actually holds — wide enough
     that "yes" is not a tiny lozenge next to "Deactivate", narrow enough that
     the table still fits. Shared HEIGHT, radius, font-size and padding are
     what really make the controls read as one family; identical width was
     never achievable alongside a dense table. */
  --control-min: 4.5rem;
  --control-h: 30px;

  /* Widest a single-line form control is ever allowed to get, regardless of
     how wide its page shell is. 40rem ≈ 640px — comfortably longer than any
     value actually typed into these fields (a job title, a city, a salary)
     while staying inside one eye span, so the label and the caret are not at
     opposite ends of the screen. */
  --field-max: 40rem;

  --radius-sm: 6px;
  --radius-md: 8px;
  --radius-lg: 12px;
  /* v4: was 999px. Kept as a token (rather than replaced at each use site) so
     the pill/squared decision stays in ONE place — and the landing page's
     round stamp-badge, which genuinely wants a circle, uses 50% directly and
     is unaffected. */
  --radius-pill: 6px;

  /* v4: flat. Every shadow is now `none` rather than being deleted at each
     of its 7 use sites — one place to reverse this if it ever needs to come
     back, and no rule below had to change. --shadow-accent in particular was
     a 30px green halo under every button; that glow is the single biggest
     reason the old system read as "neon". */
  --shadow-sm: none;
  --shadow-md: none;
  --shadow-accent: none;

  --transition-fast: 140ms ease;
}

/* --------------------------------------------------------------------------
   Base
   -------------------------------------------------------------------------- */
* { box-sizing: border-box; }

/* Portal review #29: without this, the browser renders every NATIVE control
   in its light-mode skin against this dark ground — unchecked radios and
   checkboxes as stark white circles/squares (ten of them on /onboarding),
   the file input's "Choose file" as a light macOS button, and select
   dropdowns/scrollbars to match. `accent-color` above only colours a control
   once it is CHECKED; this is what fixes the unchecked and chrome states. */
:root { color-scheme: dark; }

html, body {
  margin: 0;
  padding: 0;
}

body {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: 1.6;
  background: var(--ink);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 400; /* Archivo Black has only one real weight; 400 is its
                        normal cut, matching the mockup's own h1/h2 rules */
  color: var(--text);
  margin: 0 0 var(--space-3);
  line-height: 1.1;
  letter-spacing: -0.01em;
}
h1 { font-size: var(--text-xl); }
h2 { font-size: var(--text-lg); }

/* FEAT-17 (pipeline/10-prd-feat-17.md §3 item 4): a bare, un-.card-wrapped
   section heading (e.g. /profile's "Core strengths"/"Target roles") gets no
   top margin from the shared h1,h2,h3 rule above (margin: 0 0 var(--space-3)
   is bottom-only) — .card's own margin-bottom already gives every OTHER
   section on the page consistent separation; this closes the same gap for
   a heading that isn't itself inside a .card. --space-5 matches .card's own
   margin-bottom so a bare section heading gets the identical visual rhythm
   as a card-wrapped one. */
.section-heading { margin-top: var(--space-5); }
p { margin: 0 0 var(--space-3); color: var(--text); }
a { color: var(--accent); text-decoration: none; transition: color var(--transition-fast); }
a:hover { color: var(--accent-deep); text-decoration: underline; text-underline-offset: 3px; }
a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }

.mono { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; }

/* Kicker / eyebrow — small-caps-style label above a page's display headline.
   Same role FEAT-04 v2's .kicker played, restyled onto the new tokens; kept
   the exact class name so every existing template needs zero markup change
   (PRD's own stated preference: reuse class names, minimize template diffs). */
.kicker {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: var(--text-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 var(--space-2);
}
.lede {
  color: var(--text-dim);
  font-size: var(--text-sm);
  /* Portal review round 2: was 46ch, which is tighter than any readability
     guidance calls for (the usual range is 55–75) and broke short one-sentence
     ledes into three ragged lines directly under a display-size h1 — the
     narrowest text on the page sitting under the widest. 64ch keeps a real
     measure without the stutter. */
  max-width: 64ch;
  /* Stops the last line falling to a one- or two-word orphan, which is what a
     hard max-width does to a two-line sentence. Unsupported browsers just
     wrap normally. */
  text-wrap: pretty;
}

/* --------------------------------------------------------------------------
   Header / nav — sticky masthead with a blurred glass background over the
   dark ground, matching the mockup's header.site exactly. This is the ONE
   piece of chrome shared by literally every page — the admin surface
   included, which since FEAT-16 item 7 loads this same stylesheet (portal
   review #40: the note here claiming admin loads style-admin-legacy.css
   was stale).
   -------------------------------------------------------------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-6);
  background: rgba(10, 17, 32, 0.86);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}
/* 2026-08-27 — the brand is the supplied lockup image, not type.
   `.brand` is now purely the link box; the former glowing-dot span and the
   "RealRole" text node are both gone from the markup (the lockup already
   contains the wordmark, so keeping the text would render it twice). The
   font/colour declarations that styled that text were removed with it —
   nothing inherits them any more. Hover moves to opacity because `color`
   has no effect on an image. */
.site-header .brand {
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  transition: opacity var(--transition-fast);
}
.site-header .brand img {
  display: block;
  height: 30px;
  width: auto;
}
.site-header .brand:hover { opacity: 0.82; text-decoration: none; }
.site-header .brand:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; }
.site-header nav {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  flex-wrap: wrap;
}
.site-header nav a {
  color: var(--text-dim);
  text-decoration: none;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: var(--space-1) 0;
  border-bottom: 1px solid transparent;
  transition: color var(--transition-fast), border-color var(--transition-fast);
}
.site-header nav a:hover {
  color: var(--text);
  border-bottom-color: var(--accent);
  text-decoration: none;
}
.site-header nav a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* Portal review #43: an "admin session is active" marker in the header.
   Coral (--stamp) is this system's elevated/gated state colour, and an
   active admin session is exactly that -- it is deliberately NOT the accent,
   so it never reads as an ordinary nav item. Non-interactive (a <span>, no
   href), so it can't be confused for a link to somewhere. */
.site-header nav a.nav-tag {
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  border: 1px solid var(--stamp);
  background: var(--stamp-dim);
  color: var(--stamp);
  font-weight: 800;
  border-bottom: 1px solid var(--stamp); /* beats the nav a border-bottom reset */
}
.site-header nav a.nav-tag:hover {
  background: var(--stamp);
  color: var(--ink);
  border-color: var(--stamp);
  text-decoration: none;
}

/* Divider between the admin-session group and the user-session group when a
   browser holds both at once -- without it the two independent sets of links
   (each with its own sign-out) read as one flat list. */
.site-header nav .nav-sep {
  width: 1px;
  align-self: stretch;
  min-height: 1.2em;
  background: var(--line);
}

/* "Get started free" nav CTA — the one pill-shaped button in the header,
   matching the mockup's .nav-cta exactly. */
.site-header nav a.nav-cta {
  color: var(--ink);
  background: var(--accent);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  text-transform: none;
  letter-spacing: normal;
  font-weight: 700;
  border-bottom: none;
  transition: background var(--transition-fast);
}
.site-header nav a.nav-cta:hover {
  background: var(--accent-deep);
  color: var(--ink);
  text-decoration: none;
}

/* --------------------------------------------------------------------------
   Layout
   -------------------------------------------------------------------------- */
.container {
  max-width: 820px;
  margin: 0 auto;
  padding: var(--space-5) var(--space-5) var(--space-6);
}

/* The shell for every SIGNED-IN application page — /dashboard, /admin, and
   (portal review round 2) the three nav tabs /profile, /onboarding and
   /settings/2fa. /admin drove the original widening (a data-dense ops page
   whose 9-column tables scrolled horizontally inside an 820px column while
   leaving large empty margins either side of it), but the reason it applies
   to all of them is navigational, not per-page: at 1440px the narrow
   .container puts content at x=334 while this one puts it at x=104, so
   clicking between Dashboard and Profile shifted the whole page sideways by
   230px. Every tab now shares ONE left edge. Auth/transient pages
   (login, register, reset, generation status, message) deliberately stay on
   the centred narrow .container — a single focused card is what they are. */
.container-wide {
  max-width: 1280px;
}

/* Widening the shell must not widen the form controls with it: a 1232px text
   input is unusable, and it is the reason /profile and /admin could be wide
   long before the form pages could follow. Cap the controls, not the page —
   cards, tile grids, chip groups and radio columns still use the full width.
   Scoped to the wide shell so the narrow auth pages keep 100%-width fields. */
.container-wide input[type="text"],
.container-wide input[type="email"],
.container-wide input[type="tel"],
.container-wide input[type="password"],
.container-wide input[type="file"],
.container-wide select,
.container-wide textarea {
  max-width: var(--field-max);
}

/* Capping the width above re-opened space to the right of every field, and an
   inline-block <input> that no longer fills its line lets the NEXT element sit
   beside it — on /settings/2fa the submit button silently jumped up alongside
   the password box. Blockifying the controls restores "one control per line"
   as a property of the control rather than an accident of its width. Flex
   parents (.inline-form on /admin) blockify their children anyway, so the
   deliberately side-by-side select+button pairs there are unaffected. */
.container-wide input[type="text"],
.container-wide input[type="email"],
.container-wide input[type="tel"],
.container-wide input[type="password"],
.container-wide select,
.container-wide textarea {
  display: block;
}
/* A submit button directly after a field needs the separation the wrap used
   to give it for free. Matches .form-panel > button's existing --space-5.
   The type list is NOT decoration: a bare `input + button` also matches the
   hidden CSRF input that precedes the button in every mutating form, which
   pushed /admin's "Deactivate" 12px out of line with its status badge. Only
   inputs that actually occupy a line can justify spacing the next one. */
.container-wide form > input[type="text"] + button,
.container-wide form > input[type="email"] + button,
.container-wide form > input[type="tel"] + button,
.container-wide form > input[type="password"] + button,
.container-wide form > input[type="file"] + button,
.container-wide form > input[type="text"] + .btn,
.container-wide form > input[type="email"] + .btn,
.container-wide form > input[type="tel"] + .btn,
.container-wide form > input[type="password"] + .btn,
.container-wide form > input[type="file"] + .btn {
  margin-top: var(--space-5);
}

/* Same argument for page-level prose: a paragraph sitting directly in the
   1232px shell (e.g. /profile's "Generated <date> — this is what the daily
   matching run scores every job against…") ran ~150 characters per line, well
   past where the eye reliably finds the next line. Direct children only —
   paragraphs INSIDE a card or a match row are already constrained by it. */
.container-wide > p {
  max-width: 80ch;
  text-wrap: pretty;
}

/* The landing page's own <main> override (base.html's {% block main_class %})
   — full-bleed, no centering/padding of its own, because each <section>
   below manages its own background (some full-width, some not) and inner
   .wrap (max-width: 72rem, centered) exactly like the reference mockup. */
.landing-main {
  max-width: none;
  margin: 0;
  padding: 0;
}

/* --------------------------------------------------------------------------
   Page header block — kicker + display h1 + optional lede
   -------------------------------------------------------------------------- */
.page-head { margin-bottom: var(--space-5); }
.page-head h1 { margin-bottom: var(--space-2); }

/* --------------------------------------------------------------------------
   Forms
   -------------------------------------------------------------------------- */
.form-panel {
  padding-top: var(--space-6);
  border-top: 1px solid var(--line);
}

label {
  display: block;
  margin-top: var(--space-5);
  margin-bottom: var(--space-1);
  font-weight: 700;
  font-size: var(--text-sm);
  color: var(--text);
}
label:first-of-type { margin-top: 0; }

input[type="text"],
input[type="email"],
input[type="tel"],
input[type="password"],
select,
textarea {
  width: 100%;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--ink-raised);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  color: var(--text);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
input:hover, select:hover, textarea:hover { border-color: #2a3a5c; }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(95, 184, 154, 0.22);
}
input::placeholder, textarea::placeholder { color: var(--text-dim); }

input[type="file"] {
  width: 100%;
  padding: var(--space-3);
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--text);
  border: 1px dashed var(--line);
  border-radius: var(--radius-sm);
  background: var(--ink-raised);
  transition: border-color var(--transition-fast);
}
input[type="file"]:hover { border-color: var(--accent); }

/* --------------------------------------------------------------------------
   Consent checkbox (FEAT-05) — kept out of the block-label/bordered-input
   pattern above, same reasoning FEAT-04 v2 recorded: a checkbox reading
   "Consent" above an empty box would be meaningless on its own.
   -------------------------------------------------------------------------- */
.consent-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  margin-top: var(--space-5);
}
.consent-row input[type="checkbox"] {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  margin-top: 3px;
  accent-color: var(--accent);
  cursor: pointer;
}
.consent-row label {
  display: inline;
  margin: 0;
  font-weight: 400;
  font-size: var(--text-sm);
  color: var(--text);
  cursor: pointer;
}

/* --------------------------------------------------------------------------
   Radio groups (FEAT-07) — seniority / work authorization
   -------------------------------------------------------------------------- */
fieldset.choice-fieldset {
  border: none;
  padding: 0;
  margin: var(--space-5) 0 0;
}
.choice-fieldset legend {
  padding: 0;
  margin-bottom: var(--space-2);
  font-weight: 700;
  font-size: var(--text-sm);
  color: var(--text);
}
.choice-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.choice-option {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  margin: 0;
  font-weight: 400;
  cursor: pointer;
}
.choice-option input[type="radio"] {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  margin-top: 2px;
  accent-color: var(--accent);
  cursor: pointer;
}
.choice-option span {
  font-size: var(--text-base);
  color: var(--text);
}

/* Portal review #3: this used to be `display: none` unless the "other" radio
   was checked (`#wa_fieldset:has(#wa_other:checked)`). Two problems, one
   cosmetic and one a real data bug: FEAT-07 specifies this field as ALWAYS
   allowed regardless of which option is picked (§3, "a hybrid, never a hard
   replacement" — it is the safety valve for a compound situation), and a
   `display: none` input is still SUBMITTED, so a detail saved alongside any
   non-"other" option became invisible and uneditable while continuing to be
   written back on every save and fed into profile_gen's prompt. Always
   visible now, which matches the documented design and removes the
   invisible-but-live-value problem entirely. */
.detail-field {
  margin-top: var(--space-4);
  padding-left: calc(18px + var(--space-3));
}

/* --------------------------------------------------------------------------
   Chip group — dual-purpose, deliberately: the SAME rule set is used both
   for the interactive job-category checkbox chips on /onboarding (an
   <input> inside each chip) and the static, read-only category chips on the
   new landing page (a bare <span class="chip">, no input at all) — one
   visual language, reused rather than building a second "chip" look for the
   landing page (PRD's own "reuse before building" instruction). The
   :has(input:checked)/:has(input:focus-visible) selectors simply never
   match on a chip with no input, which is harmless.
   -------------------------------------------------------------------------- */
.chip-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-2);
  /* FEAT-17 (pipeline/10-prd-feat-17.md §3 item 4) gave this a margin-bottom
     so whatever follows a .chip-group (e.g. /profile's "Scoring guidance"
     .card) has guaranteed separation. Portal review #34: it was --space-2
     (8px) mirroring the top, but the heading ABOVE a chip group is
     .section-heading at --space-5 (24px) — so on /profile the next card
     nearly touched the chips while every other gap on the page breathed.
     Matched to --space-5 for one consistent rhythm. */
  margin-bottom: var(--space-5);
}
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--ink-raised-2);
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: var(--text-sm);
  color: var(--text);
  cursor: pointer;
  transition: border-color var(--transition-fast), background var(--transition-fast);
}
.chip:hover { border-color: var(--accent); }
.chip:has(input:checked) {
  background: var(--accent-dim);
  border-color: var(--accent);
  color: var(--text);
}
.chip:has(input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.chip input[type="checkbox"] {
  width: 14px;
  height: 14px;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}
.chip span { cursor: pointer; }

/* --------------------------------------------------------------------------
   Buttons / links styled as buttons — the mockup's pill-shaped, high-
   contrast primary CTA, applied uniformly (PRD §3 non-goal 4: one design
   system, no theme switcher) to every submit action in the app, not just
   the marketing page's own CTAs.
   -------------------------------------------------------------------------- */
/* Portal review #27: `margin-top: var(--space-6)` used to live HERE, on every
   button in the app, and was overridden in exactly two places (.hero .cta-row,
   .stub). That put 32px of dead space above every button that isn't a form's
   closing submit: a ~32px empty band inside every dashboard match card, two
   stacked generate buttons 32px apart on mobile, and admin table buttons
   hanging visibly below the baseline of every other cell in their row. The
   separation is only ever wanted where a submit button closes a form panel,
   so it is scoped to exactly that below. */
button, .btn {
  display: inline-block;
  border: 1px solid var(--accent);
  padding: var(--space-3) var(--space-5);
  background: var(--accent);
  color: var(--ink);
  border: none;
  border-radius: var(--radius-pill);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: 800;
  letter-spacing: 0.01em;
  text-decoration: none;
  cursor: pointer;
  box-shadow: var(--shadow-accent);
  transition: background var(--transition-fast), transform var(--transition-fast);
}
/* A destructive or secondary action in a table row. Every button in this
   codebase currently renders as the solid accent primary, so "Remove" and
   "Deactivate" carry exactly the visual weight of "Save" — the two controls a
   user least wants to hit by accident look like the one they came to press.
   Outlined and neutral until hovered, where it turns the negative colour so
   the consequence is stated before the click rather than after it. */
button.btn-quiet, .btn.btn-quiet {
  background: transparent;
  color: var(--text-dim);
  border: 1px solid var(--line);
  box-shadow: none;
}
button.btn-quiet:hover, .btn.btn-quiet:hover {
  background: transparent;
  border-color: var(--stamp);
  color: var(--stamp);
}

button:hover, .btn:hover {
  background: var(--accent-deep);
  border-color: var(--accent-deep);
  color: var(--ink);
  text-decoration: none;
}
button:active, .btn:active { transform: translateY(1px); }
button:focus-visible, .btn:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 2px;
}

/* Portal review #27: the one place the old global top margin was actually
   right — a submit button closing a form panel needs real separation from
   the last field above it. Direct children only, so a button nested in a
   table cell / action row inside a panel is unaffected. */
.form-panel > button,
.form-panel > .btn {
  margin-top: var(--space-5);
}

/* Portal review #37: a label that must exist for assistive tech but would be
   visually redundant (a per-row control in a table whose column header
   already names it). Clip rather than display:none — a display:none label is
   not announced at all, which defeats the point. */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Portal review #38: marks a required field in a form that mixes required and
   optional inputs with no visual difference between them. Paired with a
   legend at the top of the form — an asterisk alone explains nothing. */
.req { color: var(--stamp); font-weight: 800; margin-left: 2px; }
.form-legend { color: var(--text-dim); font-size: var(--text-xs); margin: 0 0 var(--space-2); }

/* Portal review #35: the two numbered steps of TOTP enrollment, each as its
   own block, so the manual-entry key clearly closes step 1 instead of
   floating between the two. */
.enroll-step { margin-bottom: var(--space-5); }
.enroll-step:last-child { margin-bottom: 0; }
.enroll-step > p:first-child { font-weight: 700; }
.enroll-secret {
  margin-top: var(--space-3);
  color: var(--text-dim);
  font-size: var(--text-sm);
}
.enroll-secret code {
  display: inline-block;
  margin-top: var(--space-1);
  font-size: var(--text-sm);
  letter-spacing: 0.04em;
  color: var(--text);
}
/* A 6-character code does not want a 100%-width field. */
.code-input { max-width: 11rem; letter-spacing: 0.28em; font-variant-numeric: tabular-nums; }

/* Portal review #39: the two halves of one decision (confirm / cancel) on one
   row, at two clearly-ranked weights, rather than a pill button with a 13px
   hint link stranded on the line below it. */
.confirm-actions {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-top: var(--space-4);
}
.confirm-actions form { margin: 0; }

/* Portal review #28: a form living inside a table cell (admin.html's per-row
   tier control, the deactivate/reactivate control). These used to carry
   `.form-panel`, whose `padding-top: 32px` + `border-top` drew a stray
   full-width rule and 32px of padding INSIDE every cell, stretching rows to
   ~115px and making the select span the whole column. An inline control row
   is what those cells actually want. */
.table-inline-form {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  /* Stage-30 D2 (pipeline/30-qa-portal-review.md): the .visually-hidden
     labels inside these cells are `position: absolute`, and with no
     positioned ancestor they resolve against the initial containing block —
     escaping .table-wrap's `overflow-x: auto` and pushing /admin's own
     horizontal scroll to 284px at 390px wide. Establishing a containing
     block here keeps them inside the scroll container. (Both obvious probes
     miss this: body.scrollWidth stays correct and an unclipped-offender scan
     comes back empty — see QA's note.) */
  position: relative;
}
.table-inline-form select {
  width: auto;
  min-width: 6.5rem;
  padding: var(--space-1) var(--space-2);
  font-size: var(--text-sm);
}
.table-inline-form button {
  margin: 0;
  min-width: var(--control-min);
  height: var(--control-h);
  padding: 0 var(--space-3);
  font-size: var(--text-xs);
  box-shadow: none;
}
.table-inline-form select {
  min-width: var(--control-min);
  height: var(--control-h);
  padding: 0 var(--space-2);
  font-size: var(--text-xs);
}

/* --------------------------------------------------------------------------
   States — coral (--stamp) is reserved ONLY for negative/gated/error states
   (PRD §3 item 1), which is exactly .error below. --warn (amber, NOT coral)
   covers the one caution-but-not-failure state this app has (FEAT-09's
   sub-threshold generation warning) so a caution is never visually
   conflated with an actual error/rejection.
   -------------------------------------------------------------------------- */
.error {
  color: var(--stamp);
  background: var(--stamp-dim);
  border: 1px solid var(--stamp);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
  margin: 0 0 var(--space-4);
  font-size: var(--text-sm);
}

.warning {
  color: var(--warn);
  background: var(--warn-dim);
  border: 1px solid var(--warn);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
  margin: 0 0 var(--space-4);
  font-size: var(--text-sm);
}

.hint {
  color: var(--text-dim);
  font-size: var(--text-xs);
  margin: var(--space-1) 0 0;
}

.meta { color: var(--text-dim); font-size: var(--text-sm); }
.meta a { font-weight: 600; }

/* --------------------------------------------------------------------------
   Data cards — profile/dashboard content surfaces
   -------------------------------------------------------------------------- */
.card {
  background: var(--ink-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  padding: var(--space-4) var(--space-5);
  margin-bottom: var(--space-4);
}
.card h1 { margin-bottom: var(--space-4); }
.card > p:last-child { margin-bottom: 0; }

/* --------------------------------------------------------------------------
   Tile grid (FEAT-10) — same surface/border/radius/shadow recipe as .card,
   at grid-cell scale; no new token introduced.
   -------------------------------------------------------------------------- */
.tile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--space-4);
  margin-top: var(--space-3);
  margin-bottom: var(--space-5);
}
.tile {
  background: var(--ink-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  padding: var(--space-4) var(--space-5);
  font-size: var(--text-sm);
  color: var(--text);
}

/* --------------------------------------------------------------------------
   Tables
   -------------------------------------------------------------------------- */
.table-wrap {
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  overflow-x: auto;
  margin-top: var(--space-4);
}
table { width: 100%; border-collapse: collapse; }
th, td {
  text-align: left;
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
  border-bottom: 1px solid var(--line);
  color: var(--text);
}
thead th {
  background: var(--ink-raised-2);
  color: var(--text-dim);
  font-weight: 700;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
tbody tr { transition: background var(--transition-fast); }
tbody tr:hover { background: rgba(95, 184, 154, 0.09); }
tbody tr:last-child td { border-bottom: none; }

.job-id-fallback {
  color: var(--text-dim);
  font-size: var(--text-xs);
  /* FEAT-14 (pipeline/10-prd-feat-14.md §2 Part A): same overflow fix shape
     FEAT-13 already applied to .match-link -- a real job_id fallback string
     is a long composite "source:source_id" value (e.g. a base64-blob-bearing
     jsearch id) that otherwise overflows the card. */
  overflow-wrap: anywhere;
  word-break: break-word;
}

/* --------------------------------------------------------------------------
   Match list (dashboard) -- replaces the original FEAT-12 table layout,
   which cut off the rightmost column(s) on anything narrower than a wide
   desktop viewport (`.table-wrap`'s old `overflow: hidden` clipped instead
   of scrolling) and had no room for a description snippet. A stacked list
   of cards handles variable-length description text far better than a
   rigid grid of columns ever could, and never needs horizontal scrolling.
   -------------------------------------------------------------------------- */
.match-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-top: var(--space-4);
}
.match-card {
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--ink-raised-2);
  padding: var(--space-4) var(--space-5);
}
.match-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.match-score {
  /* FEAT-13 (pipeline/10-prd-feat-13.md §2/§6): a compact corner element,
     not the lead -- markup order in dashboard.html now puts the title
     first; this pushes the score to the right via the head's own
     justify-content: space-between rather than an explicit margin/order,
     so it still wraps under the title gracefully on a narrow card. */
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 3.4em;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--accent-dim);
  border: 1px solid var(--accent);
  color: var(--accent);
  font-weight: 800;
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}
.match-title {
  margin: 0;
  font-weight: 700;
  font-size: var(--text-base);
  color: var(--text);
  /* FEAT-14 (pipeline/10-prd-feat-14.md §2 Part A): same overflow fix shape
     FEAT-13 already applied to .match-link -- .match-title is also a flex
     child of .match-card-head (display: flex), so min-width: 0 is needed
     alongside overflow-wrap for the same reason (a flex item's default
     min-width: auto otherwise refuses to shrink below the unbroken
     job_id-fallback content width). Live-verified with a real long job_id
     string -- see pipeline/20-build-feat-14.md. */
  overflow-wrap: anywhere;
  word-break: break-word;
  min-width: 0;
}
.match-company { color: var(--text-dim); font-weight: 500; }
.match-desc {
  margin: var(--space-3) 0 0;
  color: var(--text-dim);
  font-size: var(--text-sm);
  line-height: 1.6;
}
.match-reason {
  /* FEAT-13: the sanitized "why it matches" line -- sits between the
     description and the eligibility/delivery meta row per the PRD's card
     ordering. Italicized to visually read as the model's own reasoning,
     distinct from the factual job-description snippet above it. */
  margin: var(--space-3) 0 0;
  color: var(--text);
  font-size: var(--text-sm);
  font-style: italic;
  line-height: 1.6;
}
.match-card-meta {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-top: var(--space-4);
}
.match-link {
  font-weight: 700;
  /* FEAT-13 (pipeline/10-prd-feat-13.md §4 non-goal 5 / §7 risk 5): a long
     unbroken job-posting URL (several real ones in jobs.db exceed 80 chars)
     overflowed the card -- this was the one missing CSS rule. min-width: 0
     is the companion flex-overflow fix named in the PRD as likely needed:
     without it, a flex item's default min-width: auto refuses to shrink
     below the URL's own unbroken content width, so overflow-wrap alone is
     not sufficient inside a flex row. */
  overflow-wrap: anywhere;
  word-break: break-word;
  min-width: 0;
}
.match-card-actions {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--line);
}
/* FEAT-14 (pipeline/10-prd-feat-14.md §2 Part B): two independent generate
   forms now sit side by side where there's room, wrapping on a narrow card,
   rather than each <form>'s default block-level stacking -- additive, PRD §4
   non-goal 8 (no dashboard redesign beyond what two buttons require). */
.match-card-action-buttons {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}
.match-card-action-form { margin: 0; }

/* --------------------------------------------------------------------------
   Recent / Earlier match sections (FEAT-13) -- a Python-side partition of
   the same match list, not a new query. "Recent" renders open; "Earlier"
   is a native <details> element (zero JS, this codebase's own established
   convention -- see FEAT-07's CSS-only work-authorization reveal).
   -------------------------------------------------------------------------- */
.match-section { margin-top: var(--space-5); }
.match-section:first-child { margin-top: var(--space-2); }
.match-section-title {
  margin: 0 0 var(--space-3);
  font-size: var(--text-base);
  font-weight: 700;
  color: var(--text);
}
.match-section-count { color: var(--text-dim); font-weight: 500; }
details.match-section > summary.match-section-title {
  cursor: pointer;
  list-style: none;
}
details.match-section > summary.match-section-title::-webkit-details-marker {
  display: none;
}
details.match-section > summary.match-section-title::before {
  content: "▸ ";
  color: var(--text-dim);
}

/* Portal review #32: dashboard.html applies .match-section-earlier to the
   <details>, but nothing ever styled it — the only class in the app with no
   rule at all. The collapsed "Earlier matches (N)" summary ended up as an
   unstyled line flush against the bottom of the card, with nothing marking
   it as a separate section from the open list above. */
.match-section-earlier {
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--line);
}
.match-section-earlier > summary.match-section-title { color: var(--text-dim); }
.match-section-earlier > summary.match-section-title:hover { color: var(--text); }
details.match-section[open] > summary.match-section-title::before {
  content: "▾ ";
}

/* --------------------------------------------------------------------------
   Badges — yes/no/not_stated. Dark ink text on top of accent/stamp
   (matching the mockup's own .btn-primary contrast choice: var(--ink) text
   on the bright var(--accent) background, since a light/near-white text
   fails contrast on both accent and stamp at this saturation/lightness —
   confirmed by computing WCAG relative luminance for both combinations
   before choosing this, not assumed).
   -------------------------------------------------------------------------- */
/* v4 (account owner, 2026-08-25): "the button width for no, yes and active is
   different from set and deactivate, make this consistent."
   Badges and in-table buttons previously shared padding and font-size but had
   no min-width, so each shrank to its own label — "yes" rendered about a third
   the width of "Deactivate" in the same column, which read as ragged rather
   than as a grid. One min-width + one height now governs both, so a status
   badge and the control beside it line up exactly. --control-min/--control-h
   are new tokens so the two rules can never drift apart again. */
.badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--control-min);
  height: var(--control-h);
  padding: 0 var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.02em;
  white-space: nowrap;
  border: 1px solid transparent;
}
/* v4: tinted + bordered rather than a solid saturated fill. A solid accent
   block behind 13px text was the loudest element on the dashboard; the border
   carries the same semantic weight without shouting. */
.badge.yes { background: var(--accent-dim); color: var(--accent); border-color: var(--accent); }
.badge.no { background: var(--stamp-dim); color: var(--stamp); border-color: var(--stamp); }
/* Portal review #31: this neutral pill was being reached for as
   `.badge.not_stated` for six unrelated things — "eligibility unclear", "no
   CV on file", "account disabled", "run incomplete", "N skipped", and
   /profile's role tags — leaking an eligibility-specific domain word into
   what is really just "neutral badge". `.badge.neutral` is the same visual,
   correctly named; `.not_stated` stays as an alias for the ONE case that
   genuinely IS the not_stated eligibility state (dashboard.html), so no
   existing eligibility markup changes meaning. */
.badge.not_stated,
.badge.neutral { background: transparent; color: var(--text-dim); border-color: var(--line); }

/* --------------------------------------------------------------------------
   Misc content
   -------------------------------------------------------------------------- */
/* Portal review #36: inline <code> is used ~10 times across admin.html and
   admin_two_factor_verify.html (table/column names, CLI commands, env vars)
   and had no rule at all — only `pre` was styled — so it fell back to an
   unstyled browser-default monospace at the wrong size against the panel. */
code {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.9em;
  padding: 0.1em 0.35em;
  border-radius: var(--radius-sm);
  background: var(--ink);
  border: 1px solid var(--line);
  color: var(--text);
  overflow-wrap: anywhere;
}
pre code { padding: 0; background: none; border: none; font-size: inherit; }

pre {
  white-space: pre-wrap;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  background: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: var(--space-4);
  line-height: 1.65;
  color: var(--text);
}

/* ==========================================================================
   Landing page ("/", anonymous visitors only — webapp.py::index()).
   Section structure/copy/tokens follow pipeline/reference-feat-12-landing-
   mockup.html directly. Reuses .chip/.chip-group (above) for the category
   row rather than a second chip implementation; everything else here is
   landing-specific markup with no equivalent elsewhere in the app.
   ========================================================================== */
.wrap {
  max-width: 72rem;
  margin: 0 auto;
  padding: 0 1.75rem;
}

.hero {
  position: relative;
  padding: 5.5rem 0 4.5rem;
  overflow: hidden;
}
.hero::before {
  content: "";
  position: absolute;
  inset: -20% -10% auto -10%;
  height: 32rem;
  background: radial-gradient(60% 60% at 30% 20%, rgba(95, 184, 154, 0.16), transparent 70%),
              radial-gradient(45% 45% at 85% 10%, rgba(209, 131, 116, 0.12), transparent 70%);
  pointer-events: none;
}
.hero .kicker {
  position: relative;
  background: var(--accent-dim);
  border: 1px solid var(--accent);
  padding: 0.4rem 0.8rem;
  border-radius: var(--radius-pill);
  margin-bottom: 1.6rem;
}
.hero h1 {
  position: relative;
  font-size: clamp(2.5rem, 6vw, 4.3rem);
  line-height: 0.98;
  text-wrap: balance;
  margin: 0 0 1.6rem;
  max-width: 46rem;
}
.hero h1 .strike {
  color: var(--text-dim);
  text-decoration: line-through;
  text-decoration-color: var(--stamp);
  text-decoration-thickness: 0.06em;
}
.hero h1 .go { display: block; color: var(--accent); }
.hero .lede {
  position: relative;
  font-size: 1.15rem;
  color: var(--text-dim);
  max-width: 34rem;
  margin: 0 0 2.2rem;
}
.hero .cta-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 1.2rem;
  flex-wrap: wrap;
  margin-bottom: 2.6rem;
}
.btn-ghost {
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--text);
  text-decoration: none;
  border-bottom: 1px solid var(--line);
  padding-bottom: 2px;
  transition: color var(--transition-fast), border-color var(--transition-fast);
}
.btn-ghost:hover { color: var(--accent); border-bottom-color: var(--accent); text-decoration: none; }
.hero .stats { position: relative; display: flex; gap: 2.2rem; flex-wrap: wrap; }
.hero .stats div { display: flex; flex-direction: column; }
.hero .stats b { font-variant-numeric: tabular-nums; font-size: 1.4rem; font-weight: 800; }
.hero .stats span { font-size: 0.78rem; color: var(--text-dim); letter-spacing: 0.03em; }

.stamp-badge {
  position: absolute;
  top: 3.2rem;
  right: 0;
  width: 8.5rem;
  height: 8.5rem;
  border: 3px solid var(--accent);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  transform: rotate(-14deg);
  color: var(--accent);
  text-align: center;
  font-family: var(--font-sans);
  font-weight: 800;
  font-size: 0.85rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  line-height: 1.3;
  opacity: 0.9;
}
.stamp-badge::before {
  content: "";
  position: absolute;
  inset: 6px;
  border: 1px dashed var(--accent);
  border-radius: 50%;
  opacity: 0.6;
}
@media (max-width: 860px) { .stamp-badge { display: none; } }

.landing-main section { padding: 4.5rem 0; }
.landing-main section.alt {
  background: var(--ink-raised);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.section-head { max-width: 38rem; margin: 0 0 2.6rem; }
.eyebrow {
  font-family: var(--font-sans);
  font-weight: 800;
  font-size: var(--text-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 0.8rem;
}
.landing-main h2 { font-size: clamp(1.7rem, 3.4vw, 2.35rem); line-height: 1.06; margin: 0 0 0.8rem; }
.section-head p { color: var(--text-dim); margin: 0; font-size: 1.02rem; }

.compare { display: grid; grid-template-columns: 1fr 1fr; gap: 1.4rem; }
@media (max-width: 700px) { .compare { grid-template-columns: 1fr; } }
.listing {
  position: relative;
  background: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 1.6rem 1.6rem 1.8rem;
  overflow: hidden;
}
.listing .role {
  font-weight: 700;
  font-size: 1.08rem;
  margin: 0 0 0.35rem;
  /* Reserves room for the absolutely-positioned .stamp-tag badge so a long
     role title wrapping to 2 lines on a narrow viewport (found live at
     375px) doesn't run underneath it. */
  padding-right: 6.5rem;
}
.listing .meta { font-size: 0.85rem; color: var(--text-dim); margin: 0 0 1rem; }
.listing p.desc { font-size: 0.92rem; color: var(--text-dim); margin: 0; }
.listing.bad .role { text-decoration: line-through; text-decoration-color: var(--stamp); opacity: 0.7; }
.stamp-tag {
  position: absolute;
  top: 1.3rem;
  right: 1.3rem;
  font-family: var(--font-sans);
  font-weight: 800;
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.35rem 0.65rem;
  border-radius: var(--radius-sm);
  transform: rotate(6deg);
  border: 2px solid;
}
.listing.bad .stamp-tag { color: var(--stamp); border-color: var(--stamp); background: var(--stamp-dim); }
.listing.good .stamp-tag { color: var(--accent); border-color: var(--accent); background: var(--accent-dim); transform: rotate(-6deg); }
.listing.good { border-color: var(--accent); }

.path {
  position: relative;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.5rem;
  margin-top: 1rem;
}
.path::before {
  content: "";
  position: absolute;
  top: 1.4rem;
  left: 6%;
  right: 6%;
  height: 0;
  border-top: 2px dashed var(--line);
}
@media (max-width: 860px) {
  .path { grid-template-columns: 1fr; gap: 2.2rem; }
  .path::before { display: none; }
}
.stop { position: relative; }
.stop .node {
  width: 2.9rem;
  height: 2.9rem;
  border-radius: 50%;
  background: var(--ink-raised-2);
  border: 2px solid var(--accent);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-sans);
  font-weight: 800;
  font-size: 1rem;
  margin-bottom: 1rem;
  position: relative;
  z-index: 1;
}
.stop h3 { font-family: var(--font-sans); font-weight: 800; font-size: 1.05rem; margin: 0 0 0.5rem; color: var(--text); }
.stop p { font-size: 0.92rem; color: var(--text-dim); margin: 0; }

.sources {
  margin-top: 2.4rem;
  padding-top: 1.8rem;
  border-top: 1px dashed var(--line);
}
.sources .label {
  font-size: 0.78rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin: 0 0 0.9rem;
}
.source-list { display: flex; flex-wrap: wrap; gap: 0.6rem 1.4rem; }
.source-list span { font-size: 0.86rem; color: var(--text-dim); }
.source-list span b { color: var(--text); font-weight: 700; }

.stub {
  position: relative;
  background: linear-gradient(135deg, rgba(95, 184, 154, 0.10), rgba(209, 131, 116, 0.07));
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 3rem 2.4rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  flex-wrap: wrap;
}
.stub h2 { margin: 0 0 0.5rem; max-width: 26rem; }
.stub p { margin: 0; color: var(--text-dim); max-width: 24rem; }
.stub .side { flex: none; }

footer.site {
  border-top: 1px solid var(--line);
  padding: 2.4rem 0;
}
footer.site .wrap {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 1rem;
}
/* Same swap as the header brand, one step smaller. */
footer.site .brand {
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  transition: opacity var(--transition-fast);
}
footer.site .brand img {
  display: block;
  /* 28px, not the 22px a "one step down from the header" rule would suggest:
     inside the lockup the wordmark's cap-height is only ~40% of total image
     height, so 22px rendered "realrole.app" at ~9px and it stopped being
     readable. Checked against a real render of the landing footer. */
  height: 28px;
  width: auto;
}
footer.site .brand:hover { opacity: 0.82; text-decoration: none; }
footer.site .brand:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; }
footer.site .tag { font-size: 0.85rem; color: var(--text-dim); }

/* --------------------------------------------------------------------------
   Responsive breakpoint — app chrome (header/container/cards/tables).
   The landing page has its own additional breakpoints above (700px, 860px)
   for the comparison grid and pipeline path.
   -------------------------------------------------------------------------- */
@media (max-width: 640px) {
  .site-header {
    flex-direction: column;
    align-items: flex-start;
    padding: var(--space-4) var(--space-4);
  }
  .site-header .brand { margin-bottom: var(--space-2); }
  .site-header nav { gap: var(--space-3); }

  .container { padding: var(--space-5) var(--space-4) var(--space-6); }
  .card { padding: var(--space-4); }
  h1 { font-size: var(--text-lg); }
  .lede { max-width: none; }
  th, td { padding: var(--space-2) var(--space-3); }
}

/* --------------------------------------------------------------------------
   /profile — readiness console (2026-08-26)

   The page shows two kinds of number on two different clocks: one recomputed
   from the cached CV structure on every read, one a dated snapshot of the last
   CV review. `.prov` is what keeps them apart visually — every panel is
   preceded by a strip naming its source and its age. That is not decoration:
   averaging a live reading with a stale one produces a figure that moves when
   neither input did, which is precisely how a dashboard number stops being
   believed. See portal/cv_metrics.py's module docstring.
   -------------------------------------------------------------------------- */
.prov {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  font-size: 0.7rem; letter-spacing: 0.09em; text-transform: uppercase;
  font-weight: 800; color: var(--text-dim); margin-bottom: var(--space-3);
}
.prov .dot { width: 7px; height: 7px; border-radius: 50%; display: block; }
.prov .dot.live { background: var(--accent); }
.prov .dot.snap { background: var(--warn); }
.prov-age { text-transform: none; letter-spacing: 0; font-weight: 400; }
.snapshot-prov { margin-top: var(--space-6); }

.readiness-grid {
  display: grid; grid-template-columns: 1.35fr 1fr; gap: var(--space-4);
  margin-bottom: var(--space-5);
}
@media (max-width: 900px) { .readiness-grid { grid-template-columns: 1fr; } }

.score-row { display: flex; align-items: center; gap: var(--space-5); flex-wrap: wrap; }
.score-block { flex: none; }
.score-num {
  font-family: var(--font-display); font-size: 3rem; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.score-pct { font-size: 1.1rem; color: var(--text-dim); }
.bars { flex: 1; min-width: 240px; display: flex; flex-direction: column; gap: 7px; }
.bar {
  display: grid; grid-template-columns: 140px 1fr 48px;
  align-items: center; gap: var(--space-3); font-size: 0.78rem;
}
.track {
  height: 7px; border-radius: 99px; background: var(--ink-raised-2);
  overflow: hidden;
}
.fill { height: 100%; border-radius: 99px; background: var(--accent); display: block; }
.fill.warn { background: var(--warn); }
.fill.bad { background: var(--stamp); }
.tnum {
  text-align: right; color: var(--text-dim); font-variant-numeric: tabular-nums;
  font-size: 0.78rem;
}
.ok-tick { color: var(--accent); }

.readiness-fixes {
  margin-top: var(--space-4); padding-top: var(--space-3);
  border-top: 1px solid var(--line-soft);
}
.todo { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.todo li {
  display: flex; gap: var(--space-3); align-items: flex-start;
  padding: var(--space-3) 0; border-top: 1px solid var(--line-soft);
}
.todo li:first-child { border-top: none; padding-top: 0; }
.todo .txt { flex: 1; font-size: 0.86rem; }
.todo .txt b { display: block; font-weight: 700; margin-bottom: 2px; }
.todo .txt span { color: var(--text-dim); }

.card-title { font-size: 1rem; margin-bottom: var(--space-3); }
.gap-intro { margin-top: calc(-1 * var(--space-2)); margin-bottom: var(--space-4); }
.recur { display: flex; flex-direction: column; gap: var(--space-3); }
/* The trailing column holds either an action or a tick, so it is sized for the
   action — a grid that collapsed when every term was covered would make the
   bars jump width between renders. */
.recur .row {
  display: grid; grid-template-columns: 1fr 90px 28px 84px;
  align-items: center; gap: var(--space-2); font-size: 0.82rem;
}
.recur .term { overflow-wrap: anywhere; min-width: 0; }
.recur .track { height: 9px; }
.gap-add {
  font-size: 0.7rem; padding: 3px 8px; text-align: center;
  min-width: 0; box-shadow: none;
}

.metrics {
  display: grid; grid-template-columns: repeat(6, 1fr); gap: var(--space-3);
  margin: var(--space-3) 0 var(--space-5);
}
@media (max-width: 900px) { .metrics { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 520px) { .metrics { grid-template-columns: repeat(2, 1fr); } }
.metric {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: var(--space-3); background: var(--ink-raised);
}
.metric b {
  display: block; font-family: var(--font-display); font-size: 1.1rem;
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}
.metric span {
  font-size: 0.68rem; color: var(--text-dim); line-height: 1.35;
  display: block; margin-top: 3px;
}

.stale-note {
  display: flex; gap: var(--space-3); align-items: baseline; flex-wrap: wrap;
  border: 1px solid var(--warn); background: var(--warn-dim);
  border-radius: var(--radius-sm); padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4); font-size: 0.85rem;
}
.badge.warn-badge {
  color: var(--warn); border-color: var(--warn); background: transparent;
}
.stale-form { display: inline-flex; vertical-align: baseline; }
.stale-form button { font-size: 0.78rem; padding: 2px 9px; }

.prefill-note {
  border-left: 2px solid var(--accent); padding: var(--space-2) var(--space-3);
  margin: 0 0 var(--space-3); font-size: 0.85rem; background: var(--accent-dim);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

/* The generated-prose audit on the generation result page. `facts_used` has
   existed since FEAT-01 and was written only into an application.md the portal
   discarded — so the audit trail the design leaned on was never once readable
   by a user. Collapsed by default: it is a list to consult before sending, not
   something to read every time. */
.facts-used { margin-top: var(--space-4); font-size: 0.86rem; }
.facts-used summary { cursor: pointer; font-weight: 700; color: var(--text-dim); }
.facts-used summary:hover { color: var(--text); }
.facts-used ul { margin: var(--space-3) 0 0; padding-left: 1.2rem; color: var(--text-dim); }
.facts-used li { margin-bottom: var(--space-2); }

/* ==========================================================================
   FEAT-20 — /pricing (pipeline/10-prd-pricing-page.md)

   Ported from the approved, visually-verified `pricing-mockup.html` at the
   repo root. Every declaration below uses an EXISTING :root token — no new
   colour, radius, shadow, font or transition is introduced here, which is why
   this block needed no additions to the token table above.

   Three things in that mockup are deliberately NOT ported:

     .mockup-bar   the "MOCKUP · not wired to any route" banner
     .internal     the private drift-tracking/economics annotation panel
     .billing-toggle
                   a monthly/annual switch that was static in the mockup and
                   would need JavaScript to mean anything. This codebase has
                   never shipped any JS (FEAT-07 precedent) and v1 is monthly-
                   only (PRD §3 non-goal 5), so it is cut rather than shipped
                   as dead UI that looks clickable and is not.

   v4 "matte" rules observed throughout: flat surfaces, no glow, no coloured
   shadow, and --radius-pill is 6px (not 999px) so the CTAs here read as the
   same squared controls as every other button in the app.
   ========================================================================== */

.pricing-head { text-align: center; max-width: 44rem; margin: 0 auto 3rem; }
.pricing-head .kicker {
  background: var(--accent-dim); border: 1px solid var(--accent);
  padding: 0.4rem 0.8rem; border-radius: var(--radius-pill); margin-bottom: 1.4rem;
}
.pricing-head h1 { font-size: clamp(2.2rem, 5vw, 3.4rem); line-height: 1.0; margin: 0 0 1rem; }
.pricing-head h1 .go { color: var(--accent); }
.pricing-head p { color: var(--text-dim); font-size: 1.1rem; margin: 0 auto; max-width: 34rem; }

/* Confirmation shown after POST /billing/request-upgrade redirects back here.
   Reuses the accent-dim/accent pairing the kicker above already uses rather
   than inventing a "success" style — this app has no flash-message system and
   deliberately gains none for one banner. */
.pricing-confirm {
  max-width: 44rem; margin: 0 auto 2.5rem; text-align: center;
  border: 1px solid var(--accent); background: var(--accent-dim);
  border-radius: var(--radius-md); padding: var(--space-4) var(--space-5);
  font-size: 0.95rem;
}
.pricing-confirm b { color: var(--accent); }

.plans {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 1.25rem; align-items: start;
}
/* Single column well before the cards would crush: three ~20rem cards plus
   gaps stop being readable around here, and the featured card's offset "Most
   popular" tab needs horizontal room it does not have when squeezed. */
@media (max-width: 980px) { .plans { grid-template-columns: 1fr; max-width: 30rem; margin: 0 auto; } }

.plan {
  position: relative;
  background: var(--ink-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 2rem 1.7rem;
  display: flex; flex-direction: column;
}
/* The one gradient in this stylesheet, and it is a ~10%-alpha wash of the
   accent that fades out by 42% — not a glow and not a coloured shadow, so it
   is inside the v4 matte rules. It is what makes the middle card read as
   recommended without giving it a second border colour or a drop shadow. */
.plan.featured {
  border-color: var(--accent);
  background: linear-gradient(180deg, rgba(95, 184, 154, 0.10), rgba(95, 184, 154, 0.02) 42%), var(--ink-raised);
  padding-top: 2.6rem;
}
.plan .pop {
  position: absolute; top: -0.85rem; left: 1.7rem;
  background: var(--accent); color: var(--accent-ink);
  font-size: var(--text-xs); font-weight: 800;
  letter-spacing: 0.12em; text-transform: uppercase;
  padding: 0.34rem 0.7rem; border-radius: var(--radius-sm);
}
.plan .tier-no {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--text-dim); margin: 0 0 0.5rem;
}
.plan h2 { font-size: 1.7rem; margin: 0 0 0.35rem; }
.plan.featured h2 { color: var(--accent); }
/* min-height holds the three cards' price rows on one line even though the
   pitches wrap to different depths — without it the $0/$5/$8 sit at three
   different heights and the row reads as broken rather than deliberate. */
.plan .pitch { color: var(--text-dim); font-size: 0.95rem; margin: 0 0 1.5rem; min-height: 2.9rem; }

.price { display: flex; align-items: baseline; gap: 0.4rem; margin-bottom: 0.35rem; }
.price .amt {
  font-family: var(--font-display); font-size: 3rem; line-height: 1;
  letter-spacing: -0.02em; font-variant-numeric: tabular-nums;
}
.price .per { color: var(--text-dim); font-size: 0.9rem; font-weight: 700; }
.price-note { color: var(--text-dim); font-size: 0.82rem; margin: 0 0 1.6rem; }

/* Every card's action occupies the same box whether it is an <a>, a <button>
   inside a form, or the inert "current plan" marker — so the three cards'
   feature lists start at the same y regardless of who is logged in. */
.plan .btn, .plan .btn-outline, .plan .plan-current { width: 100%; text-align: center; }
.plan form { margin: 0; }
.plan form .btn, .plan form button { width: 100%; }

/* Ported from the mockup's `.btn-quiet-cta`. NOT the same as the existing
   `.btn-quiet` above, and the difference is semantic, not cosmetic:
   `.btn-quiet` is the destructive/secondary control that turns --stamp
   (coral, this system's negative colour) on hover, which is the wrong signal
   entirely for "Create a free account". This one is a neutral outline that
   warms to --accent, i.e. a secondary CTA rather than a dangerous one. */
.btn-outline {
  display: inline-block; padding: var(--space-3) var(--space-5);
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  background: transparent; color: var(--text);
  font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 800;
  text-decoration: none;
  transition: border-color var(--transition-fast), color var(--transition-fast);
}
.btn-outline:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }

/* The "you are here" / "already requested" states. Deliberately not a button:
   there is nothing to press, and rendering a disabled button instead would
   put a dead control in the tab order of the one card the user cares most
   about. */
.plan .plan-current {
  display: inline-block; padding: var(--space-3) var(--space-5);
  border: 1px dashed var(--line); border-radius: var(--radius-pill);
  color: var(--text-dim); font-size: var(--text-sm); font-weight: 800;
}
.plan .plan-current.requested { border-color: var(--accent); color: var(--accent); }

.plan .incl {
  font-size: 0.74rem; letter-spacing: 0.12em; text-transform: uppercase;
  font-weight: 800; color: var(--text-dim);
  margin: 1.8rem 0 0.9rem; padding-top: 1.4rem; border-top: 1px dashed var(--line);
}
.feats { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.72rem; }
.feats li {
  display: grid; grid-template-columns: 1.05rem 1fr; gap: 0.6rem;
  font-size: 0.92rem; line-height: 1.45; color: var(--text);
}
.feats li .m { font-weight: 800; line-height: 1.35; }
.feats li.yes .m { color: var(--accent); }
.feats li.no { color: var(--text-dim); }
.feats li.no .m { color: var(--stamp); }
.feats li.no span:last-child { text-decoration: line-through; text-decoration-color: var(--stamp); opacity: 0.75; }
.feats b { font-weight: 800; }

/* Comparison table. Inherits the app's existing <table> styling wholesale;
   these rules only set column widths, centre the three plan columns and tint
   the recommended one. */
.cmp th:first-child, .cmp td:first-child { width: 38%; }
.cmp th:not(:first-child), .cmp td:not(:first-child) { text-align: center; width: 20.6%; }
.cmp thead th { font-size: var(--text-sm); }
.cmp thead th b { display: block; color: var(--text); font-size: 1rem; font-weight: 800; letter-spacing: 0; text-transform: none; }
.cmp thead th.hi { background: var(--accent-dim); }
.cmp thead th.hi b { color: var(--accent); }
.cmp td.hi { background: rgba(95, 184, 154, 0.06); }
.cmp .grp td {
  background: var(--ink-raised-2); color: var(--text-dim);
  font-size: var(--text-xs); font-weight: 800;
  letter-spacing: 0.12em; text-transform: uppercase;
}
.cmp .tick { color: var(--accent); font-weight: 800; }
.cmp .cross { color: var(--stamp); font-weight: 800; }
.cmp td .num { font-variant-numeric: tabular-nums; font-weight: 700; }

.faq { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem 2.4rem; }
@media (max-width: 780px) { .faq { grid-template-columns: 1fr; } }
.faq h3 { font-family: var(--font-sans); font-weight: 800; font-size: 1rem; margin: 0 0 0.4rem; }
.faq p { color: var(--text-dim); font-size: 0.93rem; margin: 0; }

/* 2026-08-31 (stage-30 sev2). The comparison table's budget cells now carry
   their own period, because the three tiers no longer share one — tier1
   budgets by the week, the paid tiers by the day, and a single "per day" in
   the row header was false for a third of the table. Muted and smaller than
   the number: the figure is what a reader compares across columns, the unit
   is what stops them misreading it. */
.cmp .per-unit {
  display: block;
  font-size: 0.72rem;
  color: var(--text-dim);
  letter-spacing: 0.02em;
}
