/* ============================================================================
   oppam — design system
   ============================================================================

   Three rules this file exists to enforce:

   1. No raw colour in a component. Every colour is a semantic token, so dark
      mode is a token swap rather than a second stylesheet.
   2. No raw spacing, radius, or type size in a component. The previous version
      had --sp:8px with ZERO references and thirteen hardcoded radius values;
      the result was that nothing lined up and the page read as unstructured
      text rather than UI.
   3. Anything that is the ONLY visual boundary of a control uses
      --border-field, which is held at >= 3:1 against its surface (WCAG 1.4.11).
      Decorative dividers use --border and may be much lighter.

   Section map:
      1  colour ramps          6  buttons            11  tables and lists
      2  semantic tokens       7  forms              12  dialog and toast
      3  scales                8  badges and chips   13  landing page
      4  base and reset        9  cards and panels   14  dashboard views
      5  utilities            10  navigation         15  responsive + motion
   ============================================================================ */


/* ---------------------------------------------------------------------------
   1 + 2. COLOUR TOKENS
   Every colour in the app is one entry here, holding BOTH themes.

   light-dark() resolves against the element's used `color-scheme`, so setting
   `color-scheme: dark` on :root re-resolves every token at once. That gives us
   three behaviours from one declaration each:

     no override        color-scheme: light dark  -> the UA follows the OS, and
                                                     this is correct with
                                                     JavaScript disabled
     data-theme="dark"  color-scheme: dark        -> forces dark on a light OS
     data-theme="light" color-scheme: light       -> forces light on a dark OS

   The override therefore wins in BOTH directions, and there is no duplicated
   dark block to drift out of sync — the previous approach needed the same
   value written three times, which is exactly how a typo survives.

   Declaring color-scheme also fixes what CSS alone cannot: native form
   controls, scrollbars, and the spellcheck underline all follow the theme.
   --------------------------------------------------------------------------- */
:root {
  color-scheme: light dark;

  /* Surfaces */
  --bg:            light-dark(#F7F8FA, #0B0F17);
  --surface:       light-dark(#FFFFFF, #131926);
  --surface-2:     light-dark(#F1F3F7, #1A2230);

  /* Text. Light values measured on #FFFFFF, dark values on #131926. */
  --text:          light-dark(#161C27, #E8ECF4);   /* 16.9:1 / 15.1:1 */
  --text-muted:    light-dark(#4E5768, #A5B0C2);   /*  7.3:1 /  8.3:1 */
  --text-subtle:   light-dark(#6B7688, #7E8AA0);   /*  4.9:1 /  4.8:1 */

  /* Borders. --border is decorative (dividers, card edges) and may be faint.
     --border-field is the ONLY visual boundary of a control and is held at
     >= 3:1 on --surface in both themes, per WCAG 1.4.11. The old palette used
     one #DBEAFE for both, which measured 1.2:1 and made every input edge
     effectively invisible. */
  --border:        light-dark(#E4E7ED, #253044);
  --border-strong: light-dark(#D2D7E0, #2F3B52);
  --border-field:  light-dark(#8B94A6, #5A6880);   /*  3.05:1 / 3.12:1 */

  /* Brand — a confident blue with a slight indigo lean. 6.6:1 on white, so it
     carries text as well as filling a button; the dark arm is 6.7:1. */
  --brand:         light-dark(#2F4CDB, #7C9BFF);
  --brand-hover:   light-dark(#263CB4, #9DB5FF);
  --brand-soft:    light-dark(#EEF3FF, #18233B);
  --brand-line:    light-dark(#BDCEFF, #2C3A5E);
  --on-brand:      light-dark(#FFFFFF, #0B1220);

  /* Status. The light arms are the DARK end of each hue because they carry
     text: the old --accent:#D97706 was 3.1:1 on white and failed outright as a
     text colour, which is why "pending" was hard to read. */
  --ok:            light-dark(#047857, #34D399);
  --ok-soft:       light-dark(#ECFDF5, #0B2A22);
  --warn:          light-dark(#B45309, #FBBF24);
  --warn-soft:     light-dark(#FFFBEB, #2C2109);
  --danger:        light-dark(#C81E1E, #FB7185);
  --danger-soft:   light-dark(#FEF2F2, #331116);
  --on-danger:     light-dark(#FFFFFF, #3B0710);

  --ring:          light-dark(#4667EE, #7C9BFF);
  --scrim:         light-dark(rgb(11 15 23 / .5), rgb(3 6 12 / .72));

  /* Elevation. Because the shadow COLOUR is itself a token, the shadow
     definitions need no theme duplication. The inset hairline is what makes a
     surface read as raised in dark mode, where a drop shadow on a near-black
     background does almost nothing. */
  --sh:        light-dark(rgb(16 24 40 / .07), rgb(0 0 0 / .5));
  --sh-strong: light-dark(rgb(16 24 40 / .14), rgb(0 0 0 / .68));
  --hairline:  light-dark(transparent, rgb(255 255 255 / .05));
  --e-1: 0 1px 2px var(--sh), 0 1px 3px var(--sh), inset 0 1px 0 var(--hairline);
  --e-2: 0 2px 4px -1px var(--sh), 0 6px 14px -3px var(--sh), inset 0 1px 0 var(--hairline);
  --e-3: 0 12px 26px -8px var(--sh-strong), inset 0 1px 0 var(--hairline);
  --e-4: 0 32px 64px -24px var(--sh-strong), inset 0 1px 0 var(--hairline);
}

/* The manual override. Two declarations, and light-dark() does the rest. */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark;  }


/* ---------------------------------------------------------------------------
   3. SCALES — space, radius, type, motion, layout, z-index
   --------------------------------------------------------------------------- */
:root {
  /* 4pt grid. Every margin, padding and gap in this file uses one of these. */
  --sp-1:4px;   --sp-2:8px;   --sp-3:12px; --sp-4:16px;  --sp-5:20px;
  --sp-6:24px;  --sp-7:32px;  --sp-8:40px; --sp-9:48px;  --sp-10:64px;
  --sp-11:80px; --sp-12:96px;

  --r-xs:6px; --r-sm:8px; --r-md:12px; --r-lg:16px; --r-xl:24px; --r-full:999px;

  --fs-1:12px; --fs-2:13px; --fs-3:14px; --fs-4:16px; --fs-5:18px;
  --fs-6:20px; --fs-7:24px; --fs-8:30px; --fs-9:38px;
  --fs-display: clamp(36px, 6.2vw, 60px);

  --lh-tight:1.15; --lh-head:1.28; --lh-body:1.55;

  /* Inter needs negative tracking as it gets larger, or headings look loose. */
  --tr-display:-0.028em; --tr-head:-0.015em; --tr-wide:0.04em;

  --dur-fast:120ms; --dur:180ms; --dur-slow:280ms;
  --ease-out:cubic-bezier(.2,.8,.2,1);
  --ease-spring:cubic-bezier(.34,1.4,.64,1);

  /* Content widths, replacing ten hand-written max-width values. */
  --w-sm:620px; --w-md:780px; --w-lg:980px; --w-xl:1120px; --w-page:1200px;

  /* Ordered so a toast can never be buried. Note that z-index alone is not
     enough against a modal <dialog>: showModal() promotes it to the TOP LAYER,
     which sits above every stacking context regardless of z-index. The toast
     container therefore joins the top layer too, via the popover attribute —
     see .toasts. These values order everything that is NOT in the top layer. */
  --z-sticky:100; --z-popover:400; --z-toast:900; --z-skip:1000;
}


/* ---------------------------------------------------------------------------
   4. BASE
   --------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: Inter, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: var(--fs-4);
  line-height: var(--lh-body);
  background: var(--bg);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* Inter's optical sizing and contextual alternates; harmless where absent. */
  font-feature-settings: "cv02","cv03","cv04","calt";
}

h1, h2, h3, h4 {
  margin: 0;
  color: var(--text);
  line-height: var(--lh-head);
  letter-spacing: var(--tr-head);
  font-weight: 640;
}
h1 { font-size: var(--fs-7); }
h2 { font-size: var(--fs-6); }
h3 { font-size: var(--fs-4); }
h4 { font-size: var(--fs-3); }
p  { margin: 0; }

a { color: var(--brand); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--brand-hover); }

button, input, select, textarea { font: inherit; color: inherit; }
img, svg { max-width: 100%; }

/* Focus is restyled, never removed. */
:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}
:focus:not(:focus-visible) { outline: none; }

::selection { background: var(--brand-soft); color: var(--text); }

.skip {
  position: absolute; left: var(--sp-2); top: -64px; z-index: 100;
  padding: var(--sp-3) var(--sp-4); border-radius: var(--r-sm);
  background: var(--brand); color: var(--on-brand);
  font-weight: 600; text-decoration: none;
  transition: top var(--dur) var(--ease-out);
}
.skip:focus { top: var(--sp-2); }
#main:focus { outline: none; }


/* ---------------------------------------------------------------------------
   5. UTILITIES
   These exist so components never carry inline layout. The previous version
   had `display:flex;align-items:center;gap:12px` written out five times.
   --------------------------------------------------------------------------- */
.row      { display:flex; align-items:center; gap: var(--gap, var(--sp-3)); }
.row-top  { display:flex; align-items:flex-start; gap: var(--gap, var(--sp-3)); }
.row-wrap { display:flex; align-items:center; flex-wrap:wrap; gap: var(--gap, var(--sp-3)); }
.between  { justify-content: space-between; }
.stack    { display:flex; flex-direction:column; gap: var(--gap, var(--sp-4)); }
.spacer   { flex: 1; min-width: 0; }
.grow     { flex: 1; min-width: 0; }

.panel-xs { max-width: 420px; }
.panel-sm { max-width: var(--w-sm); }
.panel-md { max-width: var(--w-md); }
.panel-lg { max-width: var(--w-lg); }
.panel-xl { max-width: var(--w-xl); }

/* Two fields side by side, stacking early. The vertical rhythm rule
   (.field + .field) would otherwise push the right-hand field down. */
.split-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); }
.split-2 > .field + .field, .split-2 > .field { margin-top: 0; }
@media (max-width: 599px) { .split-2 { grid-template-columns: 1fr; gap: 0; }
  .split-2 > .field + .field { margin-top: var(--sp-4); } }

/* Margin utilities. Deliberately only two axes and only the steps actually
   used — an exhaustive utility set is how a "design system" turns back into
   inline styles with extra steps. */
.mt-2 { margin-top: var(--sp-2); }  .mt-3 { margin-top: var(--sp-3); }
.mt-4 { margin-top: var(--sp-4); }  .mt-5 { margin-top: var(--sp-5); }
.mb-3 { margin-bottom: var(--sp-3); } .mb-4 { margin-bottom: var(--sp-4); }
.mb-5 { margin-bottom: var(--sp-5); }

.mt-6 { margin-top: var(--sp-6); }
.w-num  { width: 110px; }
.w-num2 { width: 140px; }
.w-code { max-width: 280px; }
.input-search { min-width: 200px; }

.hint   { color: var(--text-muted); font-size: var(--fs-3); }
.subtle { color: var(--text-subtle); font-size: var(--fs-2); }
.strong { font-weight: 600; color: var(--text); }

.mono {
  font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-variant-numeric: tabular-nums;
  font-size: .94em;
  letter-spacing: -0.01em;
}
/* Numbers that change in place — counts, scores, prices — must not reflow. */
.tnum { font-variant-numeric: tabular-nums; }

.truncate { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.clamp-2 {
  display:-webkit-box; -webkit-line-clamp:2; line-clamp:2;
  -webkit-box-orient:vertical; overflow:hidden;
}
/* Two hiding mechanisms, with one job each — mixing them is how a tab ends up
   blank. `hidden` is for views and tab panels (it also removes them from the
   accessibility tree); `.hidden` is only for inline elements such as error
   spans. */
[hidden] { display: none !important; }
.hidden  { display: none !important; }

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

.wrap { max-width: var(--w-page); margin: 0 auto; padding: var(--sp-6) var(--sp-5) var(--sp-11); }


/* ---------------------------------------------------------------------------
   6. BUTTONS
   --------------------------------------------------------------------------- */
.btn {
  --btn-bg: var(--surface);
  --btn-fg: var(--text);
  --btn-bd: var(--border-field);
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--sp-2);
  min-height: 44px;                     /* touch target, not decoration */
  padding: 0 var(--sp-4);
  border: 1px solid var(--btn-bd);
  border-radius: var(--r-sm);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-size: var(--fs-3);
  font-weight: 560;
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.btn:hover  { background: var(--surface-2); }
.btn:active { transform: translateY(1px); }

.btn-primary {
  --btn-bg: var(--brand); --btn-fg: var(--on-brand); --btn-bd: var(--brand);
  box-shadow: var(--e-1);
}
.btn-primary:hover { --btn-bg: var(--brand-hover); --btn-bd: var(--brand-hover); }

.btn-ghost { --btn-bg: transparent; --btn-bd: var(--border-strong); }
.btn-ghost:hover { background: var(--surface-2); border-color: var(--border-field); }

.btn-danger { --btn-bg: var(--danger); --btn-fg: var(--on-danger); --btn-bd: var(--danger); }

.btn-lg   { min-height: 52px; padding: 0 var(--sp-6); font-size: var(--fs-4); }
.btn-sm   { min-height: 36px; padding: 0 var(--sp-3); font-size: var(--fs-2); }
.btn-full { width: 100%; }

.btn[disabled], .btn[aria-disabled="true"] {
  opacity: .5; cursor: not-allowed; transform: none; box-shadow: none;
}

/* Icon-only. Needs an aria-label — see the markup. */
.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; padding: 0;
  border: 1px solid transparent; border-radius: var(--r-sm);
  background: none; color: var(--text-muted); cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.icon-btn:hover { background: var(--surface-2); color: var(--text); }
.icon-btn svg { width: 20px; height: 20px; }

/* A button that reads as a link. Still a button, because it performs an
   action rather than navigating. */
.linkbtn {
  border: 0; background: none; padding: 0;
  color: var(--brand); font-size: inherit; font-weight: 560;
  cursor: pointer; text-decoration: underline;
  text-decoration-thickness: 1px; text-underline-offset: 2px;
}
.linkbtn:hover { color: var(--brand-hover); }

/* Async state: the label is swapped by JS, the spinner is CSS. */
.btn[data-busy="1"] { color: transparent; position: relative; pointer-events: none; }
.btn[data-busy="1"]::after {
  content: ""; position: absolute; width: 16px; height: 16px;
  border: 2px solid currentColor; border-top-color: transparent;
  border-radius: 50%; color: var(--btn-fg);
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }


/* ---------------------------------------------------------------------------
   7. FORMS
   --------------------------------------------------------------------------- */
.field { display: flex; flex-direction: column; gap: var(--sp-2); }
/* All four combinations, or the field after a two-column row loses its top
   margin and its label collides with the inputs above it. */
.field + .field,
.field + .split-2,
.split-2 + .field,
.split-2 + .split-2 { margin-top: var(--sp-4); }

.field > label, .lbl {
  font-size: var(--fs-2); font-weight: 560; color: var(--text);
}
.field .hint { font-size: var(--fs-2); margin-top: calc(var(--sp-1) * -1); }

.req { color: var(--danger); margin-left: 2px; }

input[type=text], input[type=email], input[type=password], input[type=url],
input[type=tel], input[type=number], input[type=search], select, textarea {
  width: 100%;
  min-height: 44px;
  padding: 10px var(--sp-3);
  border: 1px solid var(--border-field);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-4);              /* 16px — below this iOS zooms on focus */
  transition: border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
textarea { min-height: 96px; padding: var(--sp-3); resize: vertical; line-height: 1.5; }

input::placeholder, textarea::placeholder { color: var(--text-subtle); }

input:hover:not(:disabled), select:hover:not(:disabled), textarea:hover:not(:disabled) {
  border-color: var(--text-subtle);
}
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-soft);
}
input:disabled, textarea:disabled, select:disabled {
  background: var(--surface-2); color: var(--text-subtle); cursor: not-allowed;
}
/* Read-only is not disabled: the value matters and must stay selectable. */
input[readonly] { background: var(--surface-2); border-style: dashed; }

/* The chevron is a masked pseudo-element on a wrapper rather than a
   background-image on the select itself. A data-URI background has to bake its
   stroke colour in, so it would stay light-mode grey on a dark surface;
   currentColor through a mask follows the theme for free. */
.selectwrap { position: relative; display: block; }
/* min-width so the longest option ("Any status") is not clipped to "Any s…"
   when the wrapper sits in a flex row that can shrink it. */
.selectwrap select { appearance: none; padding-right: var(--sp-8); min-width: 150px; }
.selectwrap::after {
  content: ""; position: absolute; right: var(--sp-3); top: 50%;
  width: 16px; height: 16px; margin-top: -8px;
  background: var(--text-muted); pointer-events: none;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") center/contain no-repeat;
}

/* Password reveal sits inside the field box. */
.pw { position: relative; }
.pw input { padding-right: 76px; }
.pw-toggle {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  min-height: 32px; padding: 0 var(--sp-2);
  border: 0; border-radius: var(--r-xs); background: none;
  color: var(--text-muted); font-size: var(--fs-2); font-weight: 560; cursor: pointer;
}
.pw-toggle:hover { background: var(--surface-2); color: var(--text); }

/* Inline error, below its field. Announced via role="alert" in the markup. */
.err {
  display: flex; align-items: flex-start; gap: 6px;
  color: var(--danger); font-size: var(--fs-2); font-weight: 500;
}
.err::before {
  content: ""; flex: none; width: 14px; height: 14px; margin-top: 3px;
  background: currentColor;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 7v6M12 16.5v.01'/%3E%3C/svg%3E") center/contain no-repeat;
}
input[aria-invalid="true"], textarea[aria-invalid="true"] { border-color: var(--danger); }
input[aria-invalid="true"]:focus { box-shadow: 0 0 0 3px var(--danger-soft); }

.ok-msg { color: var(--ok); font-size: var(--fs-3); font-weight: 500; }

/* Checkbox / radio rows */
.check { display: flex; align-items: flex-start; gap: var(--sp-3); cursor: pointer; }
.check input[type=checkbox], .check input[type=radio] {
  flex: none; width: 20px; height: 20px; margin-top: 2px;
  accent-color: var(--brand); cursor: pointer;
}
.check span { font-size: var(--fs-3); }

fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }
legend { padding: 0; font-size: var(--fs-3); font-weight: 600; }

.form-foot {
  display: flex; align-items: center; gap: var(--sp-3);
  margin-top: var(--sp-6); padding-top: var(--sp-5);
  border-top: 1px solid var(--border);
}


/* ---------------------------------------------------------------------------
   8. BADGES, PILLS, CHIPS, SEGMENTS
   --------------------------------------------------------------------------- */
/* Never colour alone — every badge carries a glyph and a word. */
.badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px var(--sp-2);
  border: 1px solid transparent; border-radius: var(--r-full);
  font-size: var(--fs-1); font-weight: 600; line-height: 1.5;
  white-space: nowrap;
  /* No text-transform: the label is capitalised at the source, because
     `capitalize` would render "Below your score floor" as Title Case. */
}
.badge svg { width: 12px; height: 12px; flex: none; }
.b-ok   { background: var(--ok-soft);     color: var(--ok);     border-color: color-mix(in srgb, var(--ok) 26%, transparent); }
.b-warn { background: var(--warn-soft);   color: var(--warn);   border-color: color-mix(in srgb, var(--warn) 30%, transparent); }
.b-err  { background: var(--danger-soft); color: var(--danger); border-color: color-mix(in srgb, var(--danger) 26%, transparent); }
.b-mut  { background: var(--surface-2);   color: var(--text-muted); border-color: var(--border); }
.b-brand{ background: var(--brand-soft);  color: var(--brand);  border-color: var(--brand-line); }

.pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px var(--sp-3); border: 1px solid var(--border);
  border-radius: var(--r-full); background: var(--surface-2);
  font-size: var(--fs-2); color: var(--text-muted);
}

/* Selectable chips (preference pickers, inbox filters). The input is visually
   hidden but stays in the tab order and drives every visual state. */
.chipset { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.chipset > span { display: inline-flex; }
.chipset input {
  position: absolute; width: 1px; height: 1px; opacity: 0;
  clip: rect(0 0 0 0); clip-path: inset(50%);
}
.chipset label {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 38px; padding: 0 var(--sp-4);
  border: 1px solid var(--border-field); border-radius: var(--r-full);
  background: var(--surface); color: var(--text-muted);
  font-size: var(--fs-3); font-weight: 500; cursor: pointer;
  transition: all var(--dur-fast) var(--ease-out);
}
.chipset label:hover { background: var(--surface-2); color: var(--text); }
.chipset input:checked + label {
  background: var(--brand); border-color: var(--brand);
  color: var(--on-brand); font-weight: 600;
}
.chipset input:checked + label::before {
  content: ""; width: 12px; height: 12px; background: currentColor;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center/contain no-repeat;
}
.chipset input:focus-visible + label { outline: 2px solid var(--ring); outline-offset: 2px; }

/* Segmented control (billing period, applied/skipped). */
.segwrap {
  display: inline-flex; padding: var(--sp-1);
  border: 1px solid var(--border); border-radius: var(--r-full);
  background: var(--surface-2);
}
.seg {
  min-height: 38px; padding: 0 var(--sp-5);
  border: 0; border-radius: var(--r-full); background: none;
  color: var(--text-muted); font-size: var(--fs-3); font-weight: 560;
  cursor: pointer; white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.seg:hover { color: var(--text); }
.seg[aria-checked="true"], .seg[aria-selected="true"] {
  background: var(--surface); color: var(--text);
  box-shadow: var(--e-1); font-weight: 600;
}
.seg-save {
  margin-left: 6px; padding: 1px 7px; border-radius: var(--r-full);
  background: var(--ok-soft); color: var(--ok);
  font-size: var(--fs-1); font-weight: 700;
}


/* ---------------------------------------------------------------------------
   9. CARDS, SECTIONS, EMPTY AND LOADING STATES
   --------------------------------------------------------------------------- */
.card {
  padding: var(--sp-6);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--e-1);
}
.card + .card { margin-top: var(--sp-5); }
.card-tight { padding: var(--sp-5); }
.card-flush { padding: 0; overflow: hidden; }

.card-head {
  display: flex; align-items: flex-start; flex-wrap: wrap; gap: var(--sp-3);
  margin-bottom: var(--sp-5);
}
.card-head .hint { margin-top: var(--sp-1); }
.card-head + * { margin-top: 0; }

.section-label {
  font-size: var(--fs-1); font-weight: 700; letter-spacing: var(--tr-wide);
  text-transform: uppercase; color: var(--text-subtle);
}

.divide > * + * { border-top: 1px solid var(--border); }

/* Empty state — always a reason and, where one exists, an action. */
.empty {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: var(--sp-3); padding: var(--sp-9) var(--sp-5); color: var(--text-muted);
}
.empty svg { width: 40px; height: 40px; color: var(--text-subtle); opacity: .7; }
.empty h3 { color: var(--text); }
.empty p { max-width: 42ch; font-size: var(--fs-3); }

/* Skeleton. Paired with aria-busy on the container so the wait is announced,
   not just drawn. */
/* Height comes from --h so a skeleton never needs an inline height rule. */
.skel {
  height: var(--h, 14px);
  border-radius: var(--r-xs);
  background: linear-gradient(90deg,
    var(--surface-2) 25%,
    color-mix(in srgb, var(--surface-2) 55%, var(--surface)) 37%,
    var(--surface-2) 63%);
  background-size: 400% 100%;
  animation: shimmer 1.4s ease-in-out infinite;
}
@keyframes shimmer { from { background-position: 100% 50%; } to { background-position: 0 50%; } }
.skel-line { height: 14px; margin-bottom: var(--sp-2); }
.skel-line:last-child { width: 60%; margin-bottom: 0; }

/* Error state with a retry — every failure must be recoverable in place,
   not only via a toast that disappears after four seconds. */
.errbox {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: var(--sp-3); padding: var(--sp-8) var(--sp-5);
  border: 1px dashed color-mix(in srgb, var(--danger) 40%, var(--border));
  border-radius: var(--r-md); background: var(--danger-soft);
}
.errbox p { color: var(--text); font-size: var(--fs-3); max-width: 46ch; }

/* One progress bar for the whole app. Previously #bysource and #planQuota
   were the same component, written twice, in JS string literals. */
.meter {
  height: 8px; border-radius: var(--r-full);
  background: var(--surface-2); overflow: hidden;
}
.meter > i {
  display: block; height: 100%; width: var(--pct, 0%);
  border-radius: var(--r-full);
  background: var(--brand);
  transition: width var(--dur-slow) var(--ease-out);
}
.meter.is-full > i { background: var(--ok); }
.meter.is-warn > i { background: var(--warn); }
.meter-head {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: var(--sp-3); margin-bottom: 6px; font-size: var(--fs-3);
}


/* ---------------------------------------------------------------------------
   10. NAVIGATION — topbar, tabs, sub-nav, footer
   --------------------------------------------------------------------------- */
.topbar {
  position: sticky; top: 0; z-index: var(--z-sticky);
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-5);
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--border);
}
@supports not (backdrop-filter: blur(1px)) { .topbar { background: var(--surface); } }

.brand {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-5); font-weight: 700; letter-spacing: var(--tr-head);
  color: var(--text); text-decoration: none;
}
.brand .mark { width: 26px; height: 26px; color: var(--brand); flex: none; }
.brand:hover { color: var(--text); }

#whoami {
  min-width: 0; max-width: 34vw;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--fs-2); color: var(--text-muted);
}

/* Tabs. role="tab" + aria-selected + roving tabindex are set in the markup and
   JS; this only styles them. */
.tabs {
  display: flex; gap: var(--sp-1);
  margin-bottom: var(--sp-6);
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
  scrollbar-width: none;
  /* On a phone the strip scrolls; without an edge fade there is nothing to
     say so, and the tabs past the fold look like they do not exist. */
  mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
}
@media (min-width: 768px) { .tabs { mask-image: none; } }
.tabs::-webkit-scrollbar { display: none; }
.tabs button {
  position: relative;
  display: inline-flex; align-items: center; gap: var(--sp-2);
  min-height: 44px; padding: 0 var(--sp-4);
  border: 0; background: none; color: var(--text-muted);
  font-size: var(--fs-3); font-weight: 560; white-space: nowrap; cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out);
}
.tabs button svg { width: 17px; height: 17px; }
.tabs button:hover { color: var(--text); }
.tabs button::after {
  content: ""; position: absolute; left: var(--sp-3); right: var(--sp-3); bottom: -1px;
  height: 2px; border-radius: 2px 2px 0 0; background: var(--brand);
  transform: scaleX(0); transition: transform var(--dur) var(--ease-out);
}
.tabs button[aria-selected="true"] { color: var(--brand); font-weight: 640; }
.tabs button[aria-selected="true"]::after { transform: scaleX(1); }
.tabs .tab-count {
  padding: 1px 6px; border-radius: var(--r-full);
  background: var(--surface-2); color: var(--text-muted);
  font-size: var(--fs-1); font-weight: 600;
}

[role="tabpanel"]:focus { outline: none; }

/* Settings sub-navigation */
.subnav { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-6); }
.subnav button {
  min-height: 38px; padding: 0 var(--sp-4);
  border: 1px solid var(--border); border-radius: var(--r-full);
  background: var(--surface); color: var(--text-muted);
  font-size: var(--fs-3); font-weight: 560; cursor: pointer;
  transition: all var(--dur-fast) var(--ease-out);
}
.subnav button:hover { color: var(--text); border-color: var(--border-field); }
.subnav button[aria-selected="true"] {
  background: var(--text); border-color: var(--text);
  color: var(--surface); font-weight: 600;
}

.foot {
  border-top: 1px solid var(--border);
  padding: var(--sp-9) var(--sp-5) var(--sp-8);
  background: var(--surface);
}
.foot-inner {
  max-width: var(--w-xl); margin: 0 auto;
  display: flex; flex-wrap: wrap; gap: var(--sp-6); justify-content: space-between;
  /* Without this the nav stretches to the height of the brand block, and then
     the <button> centres its label vertically while the <a> does not — putting
     the two links ~32px apart on the same row and reading exactly like a wrap.
     Measuring the DOM shows one row, which is what makes it confusing. */
  align-items: flex-start;
}
/* flex:none + nowrap. With flex-shrink allowed, the nav gets squeezed once the
   webfont loads and its two links wrap onto separate lines — which measures
   fine in a fallback font and only shows up in a screenshot. There is ample
   room here, so it should never shrink in the first place. */
.foot-links { display: flex; flex: none; align-items: center; gap: var(--sp-5); }
.foot-links > * { white-space: nowrap; }
.foot-links button, .foot-links a {
  border: 0; background: none; padding: 0;
  color: var(--text-muted); font-size: var(--fs-3); cursor: pointer;
  text-decoration: none;
}
.foot-links button:hover, .foot-links a:hover { color: var(--text); text-decoration: underline; }
.foot-note { color: var(--text-subtle); font-size: var(--fs-2); max-width: 46ch; }


/* ---------------------------------------------------------------------------
   11. TABLES AND LISTS
   One list component, two layouts, one DOM. Above 768px each row is a grid
   that reads as a table; below it the same row becomes a stacked card, so the
   phone never gets a horizontally scrolling table with the status and date
   pushed off the right edge.
   --------------------------------------------------------------------------- */
.list { border-top: 1px solid var(--border); }

.lrow {
  display: grid;
  grid-template-columns: minmax(0,1.5fr) minmax(0,2fr) 92px 116px 120px;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-2);
  border-bottom: 1px solid var(--border);
  transition: background var(--dur-fast) var(--ease-out);
}
.lrow:hover { background: var(--surface-2); }
.lrow-head {
  padding-top: var(--sp-2); padding-bottom: var(--sp-2);
  font-size: var(--fs-1); font-weight: 700; letter-spacing: var(--tr-wide);
  text-transform: uppercase; color: var(--text-subtle);
  background: none;
}
.lrow-head:hover { background: none; }

.lrow .co    { font-weight: 600; }
.lrow .ttl a { color: var(--text); text-decoration: none; }
.lrow .ttl a:hover { color: var(--brand); text-decoration: underline; }
.lcell-label { display: none; }

/* The match score. A number alone does not answer "why was this skipped?", so
   the track also carries a notch at the user's own min_score floor: a bar that
   stops short of the notch is a self-explanatory refusal. --pct and --floor
   are set inline; they are the only inline styles this codebase permits,
   because both are clamped numbers and neither can escape the attribute. */
.score { display: inline-flex; flex-direction: column; gap: 3px; min-width: 62px; }
.score-n { display: flex; align-items: baseline; gap: 1px; font-weight: 700; line-height: 1; }
.score-n b { font-size: var(--fs-4); font-variant-numeric: tabular-nums; }
.score-n span { font-size: var(--fs-1); color: var(--text-subtle); font-weight: 500; }
.score-track {
  position: relative; height: 4px; border-radius: var(--r-full);
  background: var(--surface-2); overflow: hidden;
}
.score-track > i {
  display: block; height: 100%; width: var(--pct, 0%);
  border-radius: var(--r-full); background: currentColor;
}
.score-track::after {
  content: ""; position: absolute; top: -1px; bottom: -1px;
  left: var(--floor, 60%); width: 2px;
  background: var(--text-subtle); opacity: .55;
}
.score-hi  { color: var(--ok); }
.score-mid { color: var(--brand); }
.score-lo  { color: var(--text-subtle); }

/* Dismissible page-level notice, e.g. "couldn't load your profile". Distinct
   from a toast: it persists, because the condition it reports persists. */
.banner {
  display: flex; align-items: flex-start; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4); margin-bottom: var(--sp-5);
  border: 1px solid var(--border-strong); border-radius: var(--r-md);
  background: var(--surface-2); font-size: var(--fs-3);
}
.banner svg { flex: none; width: 18px; height: 18px; margin-top: 2px; }
.banner.warn { border-color: color-mix(in srgb, var(--warn) 45%, var(--border)); background: var(--warn-soft); }
.banner.warn svg { color: var(--warn); }

.why {
  grid-column: 1 / -1;
  margin-top: calc(var(--sp-1) * -1);
  color: var(--text-muted); font-size: var(--fs-2);
}

@media (max-width: 767px) {
  .lrow {
    grid-template-columns: 1fr auto;
    gap: var(--sp-2) var(--sp-3);
    padding: var(--sp-4) var(--sp-3);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    margin-bottom: var(--sp-3);
    background: var(--surface);
  }
  .list { border-top: 0; }
  .lrow-head { display: none; }
  .lrow .co  { grid-column: 1; font-size: var(--fs-2); color: var(--text-muted); }
  .lrow .st  { grid-column: 2; grid-row: 1; justify-self: end; }
  .lrow .ttl { grid-column: 1 / -1; font-size: var(--fs-4); font-weight: 600; }
  .lrow .sc, .lrow .dt {
    grid-row: 3; font-size: var(--fs-2); color: var(--text-muted);
  }
  .lrow .sc { grid-column: 1; }
  .lrow .dt { grid-column: 2; justify-self: end; }
}


/* ---------------------------------------------------------------------------
   12. DIALOG AND TOAST
   Built on <dialog>: the focus trap, Escape handling and background inertness
   are the browser's, not ours. The previous hand-rolled modal had none of the
   three — Tab walked straight out of it into the page behind.
   --------------------------------------------------------------------------- */
dialog {
  width: min(720px, calc(100vw - var(--sp-8)));
  max-height: min(84vh, 780px);
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--e-4);
  overflow: hidden;
}
dialog::backdrop { background: var(--scrim); backdrop-filter: blur(3px); }
dialog[open] { animation: dialogIn var(--dur-slow) var(--ease-spring); }
dialog::backdrop { animation: fadeIn var(--dur) var(--ease-out); }
@keyframes dialogIn { from { opacity:0; transform: translateY(12px) scale(.98); } }
@keyframes fadeIn   { from { opacity:0; } }

.dialog-in { display: flex; flex-direction: column; max-height: inherit; }
.dialog-head {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-5) var(--sp-5) var(--sp-4);
  border-bottom: 1px solid var(--border);
}
.dialog-body {
  padding: var(--sp-5);
  overflow-y: auto;
  overscroll-behavior: contain;
  font-size: var(--fs-3);
  line-height: 1.65;
}
.dialog-body h1 { font-size: var(--fs-6); margin: var(--sp-6) 0 var(--sp-3); }
.dialog-body h1:first-child { margin-top: 0; }
.dialog-body h2 { font-size: var(--fs-5); margin: var(--sp-6) 0 var(--sp-2); }
.dialog-body h3 { font-size: var(--fs-4); margin: var(--sp-5) 0 var(--sp-2); }
.dialog-body p, .dialog-body ul { margin: 0 0 var(--sp-3); color: var(--text-muted); }
.dialog-body li { margin-bottom: var(--sp-2); }
.dialog-body hr { border: 0; border-top: 1px solid var(--border); margin: var(--sp-6) 0; }
.dialog-body strong { color: var(--text); }
.dialog-foot {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-top: 1px solid var(--border);
  background: var(--surface-2);
}

/* Small confirm dialog, replacing native confirm() for destructive actions. */
dialog.confirm { width: min(420px, calc(100vw - var(--sp-8))); }
dialog.confirm .dialog-body { padding: var(--sp-6); }

/* The container carries popover="manual" so it enters the TOP LAYER alongside
   any open modal dialog. Without that, "Could not load the terms" would render
   behind the terms dialog that failed to load — the one moment the message
   matters most. Popover also needs its UA styles undone: it defaults to
   fixed-inset-0, auto-sized, bordered and display:none. */
.toasts {
  position: fixed; inset: auto auto var(--sp-5) 50%;
  z-index: var(--z-toast);
  transform: translateX(-50%);
  width: min(440px, calc(100vw - var(--sp-8)));
  margin: 0; padding: 0; border: 0; overflow: visible; background: none;
  pointer-events: none;
}
/* Author display beats the UA's `[popover]:not(:popover-open){display:none}`,
   which is deliberate: the container stays laid out even when the popover was
   never opened, so a browser without popover support (or a showPopover() that
   throws) still shows toasts — just not above a modal. It is empty and
   pointer-events:none when idle, so being present costs nothing. */
.toasts { display: flex; flex-direction: column-reverse; gap: var(--sp-2); }
.toasts::backdrop { background: none; }
.toast {
  display: flex; align-items: flex-start; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border-strong); border-radius: var(--r-md);
  background: var(--surface); color: var(--text);
  font-size: var(--fs-3); box-shadow: var(--e-3);
  animation: toastIn var(--dur-slow) var(--ease-spring);
  pointer-events: auto;
}
.toast svg { flex: none; width: 18px; height: 18px; margin-top: 2px; }
.toast.bad  { border-color: color-mix(in srgb, var(--danger) 45%, var(--border)); }
.toast.bad svg  { color: var(--danger); }
.toast.good { border-color: color-mix(in srgb, var(--ok) 45%, var(--border)); }
.toast.good svg { color: var(--ok); }
@keyframes toastIn { from { opacity:0; transform: translateY(10px); } }


/* ---------------------------------------------------------------------------
   13. LANDING PAGE
   --------------------------------------------------------------------------- */
/* Dark hero band, from the Stitch mock. The page is otherwise light, so the
   band does the work a big background image usually does: it separates the
   pitch from the product, and it makes the white auth card the brightest thing
   on screen, which is where the eye should go.

   The band is a FIXED dark surface — dark in light mode too — so anything
   inside it needs light-on-dark tokens. Those are scoped to .hero-copy, NOT to
   .hero: putting them on .hero re-tinted the auth card as well, and since the
   card keeps a white background that produced white text on white. The card
   sits in the same band but keeps the page's own theme. */
.hero {
  position: relative; overflow: hidden;
  padding: var(--sp-10) var(--sp-5) var(--sp-11);
  background:
    radial-gradient(ellipse 80% 60% at 15% 0%, #1B2F63 0%, transparent 62%),
    linear-gradient(160deg, #142449 0%, #0C182F 55%, #0A1428 100%);
}
.hero-copy {
  --text: #F4F7FF;
  --text-muted: #B3C4E2;
  --text-subtle: #93A7CB;
  --border: #26385E;
  --border-strong: #31456F;
  --brand: #9CBAFF;
  --brand-hover: #B6CCFF;
  --brand-soft: #1B2C55;
  --brand-line: #2E4478;
  color: var(--text);
}

/* Replaces three 46vw blurred blobs. A dot grid plus one soft wash costs a
   fraction of the compositing and does not tie layer size to viewport width. */
.hero-bg { position: absolute; inset: 0; pointer-events: none; }
.hero-bg::before {
  content: ""; position: absolute; inset: 0;
  background-image: radial-gradient(circle at center, rgb(255 255 255 / .16) 1px, transparent 1px);
  background-size: 26px 26px;
  mask-image: radial-gradient(ellipse 80% 62% at 50% 0%, #000 30%, transparent 76%);
}
.hero-bg::after { content: none; }

.hero-inner {
  position: relative;
  max-width: var(--w-xl); margin: 0 auto;
  display: grid; grid-template-columns: minmax(0,1fr) minmax(0,440px);
  gap: var(--sp-10); align-items: start;
}

.eyebrow {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  margin-bottom: var(--sp-4); padding: 5px var(--sp-3);
  border: 1px solid var(--brand-line); border-radius: var(--r-full);
  background: var(--brand-soft); color: var(--brand);
  font-size: var(--fs-2); font-weight: 600;
}
.dot-live {
  width: 7px; height: 7px; border-radius: 50%; background: var(--ok);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--ok) 70%, transparent);
  animation: ping 2.2s var(--ease-out) infinite;
}
@keyframes ping {
  70%  { box-shadow: 0 0 0 7px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

.display {
  font-size: var(--fs-display);
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-display);
  font-weight: 700;
  text-wrap: balance;
}
.lede {
  margin-top: var(--sp-5);
  max-width: 54ch;
  font-size: var(--fs-5);
  color: var(--text-muted);
  text-wrap: pretty;
}

.proof {
  display: flex; flex-wrap: wrap; gap: var(--sp-8);
  margin: var(--sp-8) 0 0;
}
.proof div { display: flex; flex-direction: column; gap: 2px; }
.proof dt {
  font-size: var(--fs-8); font-weight: 700; line-height: 1.1;
  letter-spacing: var(--tr-head); font-variant-numeric: tabular-nums;
}
.proof dd {
  margin: 0; font-size: var(--fs-2); color: var(--text-muted);
  letter-spacing: var(--tr-wide); text-transform: uppercase; font-weight: 560;
}
.hero-note { margin-top: var(--sp-6); font-size: var(--fs-3); color: var(--text-subtle); }

.authcard {
  padding: var(--sp-6);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
  /* Deeper than --e-3: it is floating on a dark band, and the theme shadows are
     tuned for a light page where a soft one reads. */
  box-shadow: 0 24px 60px -20px rgb(4 10 24 / .55), 0 2px 8px rgb(4 10 24 / .3);
}
.pane-h { font-size: var(--fs-6); }
.pane-sub { margin: var(--sp-2) 0 var(--sp-6); color: var(--text-muted); font-size: var(--fs-3); }
.auth-rule {
  display: flex; align-items: center; gap: var(--sp-3);
  margin: var(--sp-5) 0; color: var(--text-subtle); font-size: var(--fs-2);
}
.auth-rule::before, .auth-rule::after {
  content: ""; flex: 1; height: 1px; background: var(--border);
}
.auth-alt { margin-top: var(--sp-4); text-align: center; font-size: var(--fs-3); color: var(--text-muted); }

/* Content lanes */
.lane { padding: var(--sp-11) var(--sp-5); }
.lane-inner { max-width: var(--w-xl); margin: 0 auto; }
.lane-tint { background: var(--surface); border-block: 1px solid var(--border); }
.lane-cta  { text-align: center; }

/* Centred from the Stitch mock. The hero stays left-aligned — it carries the
   argument and reads as prose; the sections below are scannable blocks. */
.section-h {
  font-size: clamp(24px, 3.4vw, 34px);
  letter-spacing: var(--tr-display);
  text-wrap: balance;
  text-align: center;
}
.lane .section-sub { margin-inline: auto; text-align: center; }
.lane .section-foot { margin-inline: auto; text-align: center; }
.section-sub {
  margin-top: var(--sp-3); max-width: 60ch;
  font-size: var(--fs-5); color: var(--text-muted);
}
.lane-cta .section-sub { margin-inline: auto; }
.section-foot { margin-top: var(--sp-6); font-size: var(--fs-3); color: var(--text-muted); max-width: 66ch; }
.lane-cta .section-foot { margin-inline: auto; }

.steps {
  display: grid; grid-template-columns: repeat(4, minmax(0,1fr));
  gap: var(--sp-5); margin: var(--sp-8) 0 0; padding: 0; list-style: none;
  counter-reset: step;
}
.step {
  position: relative;
  padding: var(--sp-6);
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--surface);
  transition: transform var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out);
}
.step:hover { transform: translateY(-2px); box-shadow: var(--e-2); }
.step-ico {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; margin-bottom: var(--sp-4);
  border-radius: var(--r-sm); background: var(--brand-soft); color: var(--brand);
}
.step-ico svg { width: 20px; height: 20px; }
/* Filled chip, top-left, with the icon moved opposite it — the mock's
   arrangement, and it reads as a sequence rather than as decoration. */
.step-n {
  position: absolute; top: var(--sp-5); left: var(--sp-5);
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 30px; height: 24px; padding: 0 var(--sp-2);
  border-radius: var(--r-xs);
  background: var(--brand-soft); color: var(--brand);
  font-size: var(--fs-1); font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/* The icon is the first child in the flow and the number is absolute, so
   pushing the icon right puts the two on ONE row at the top of the card. An
   earlier padding-top pushed the icon below the number and left a gap where
   the title should have been. */
.step-ico { margin-left: auto; margin-bottom: var(--sp-5); }
.step { display: flex; flex-direction: column; padding-top: var(--sp-5); }
.step-n { top: calc(var(--sp-5) + 8px); }
.step h3 { margin-bottom: var(--sp-2); }
.step p  { font-size: var(--fs-3); color: var(--text-muted); }

.chips {
  display: flex; flex-wrap: wrap; gap: var(--sp-3);
  margin: var(--sp-8) 0 0; padding: 0; list-style: none;
  justify-content: center;          /* follows the centred section heading */
}
.chip {
  display: inline-flex; align-items: baseline; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-5);
  border: 1px solid var(--border); border-radius: var(--r-full);
  background: var(--bg);
}
.chip-k { font-weight: 640; }
.chip-v { font-size: var(--fs-2); color: var(--text-muted); font-variant-numeric: tabular-nums; }

.limits { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: var(--sp-5); margin-top: var(--sp-8); }
.limit {
  padding: var(--sp-5) var(--sp-5) var(--sp-5) var(--sp-6);
  border: 1px solid var(--border); border-left: 3px solid var(--warn);
  border-radius: var(--r-md); background: var(--surface);
}
.limit h3 { margin-bottom: var(--sp-2); }
.limit p  { font-size: var(--fs-3); color: var(--text-muted); }

/* Product preview — built in CSS, showing only what the product really does. */
.preview {
  margin: var(--sp-9) auto 0; max-width: var(--w-lg);
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--surface); box-shadow: var(--e-4); overflow: hidden;
}
.preview-bar {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--border);
  background: var(--surface-2);
}
.preview-bar i { width: 10px; height: 10px; border-radius: 50%; background: var(--border-strong); }
.preview-bar span { margin-left: var(--sp-3); font-size: var(--fs-2); color: var(--text-subtle); }
.preview-body { padding: var(--sp-6); }
.preview-kpis { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-4); margin-bottom: var(--sp-6); }


/* ---------------------------------------------------------------------------
   14. DASHBOARD VIEWS
   --------------------------------------------------------------------------- */
.view-head { margin-bottom: var(--sp-6); }
.view-head h1 { font-size: var(--fs-8); letter-spacing: var(--tr-display); }
.view-head p  { margin-top: var(--sp-2); color: var(--text-muted); }

.kpis { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: var(--sp-4); }
.kpi {
  padding: var(--sp-5);
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--surface); box-shadow: var(--e-1);
}
.kpi .label {
  font-size: var(--fs-1); font-weight: 700; letter-spacing: var(--tr-wide);
  text-transform: uppercase; color: var(--text-subtle);
}
.kpi .value {
  margin-top: var(--sp-2);
  font-size: var(--fs-8); font-weight: 700; line-height: 1.1;
  letter-spacing: var(--tr-head); font-variant-numeric: tabular-nums;
}
.kpi .sub { margin-top: var(--sp-2); font-size: var(--fs-2); color: var(--text-muted); }

/* Funnel — proportional bars, so the drop-off is visible rather than implied
   by six equal boxes. */
.funnel { display: flex; flex-direction: column; gap: var(--sp-3); }
/* 168px, because "Confirmed by employer" is the longest label and wrapping it
   makes every row a different height. */
.fstep { display: grid; grid-template-columns: 168px minmax(0,1fr) 72px; align-items: center; gap: var(--sp-4); }
.fstep .fname { font-size: var(--fs-3); font-weight: 560; }
.fstep .fbar  { height: 26px; border-radius: var(--r-xs); background: var(--surface-2); overflow: hidden; }
.fstep .fbar > i {
  display: block; height: 100%; width: var(--pct, 0%);
  min-width: 3px; border-radius: var(--r-xs);
  background: var(--brand);
  transition: width var(--dur-slow) var(--ease-out);
}
.fstep .fnum { text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; }
.fstep.is-ok   .fbar > i { background: var(--ok); }
.fstep.is-bad  .fbar > i { background: var(--danger); }
.fstep .fdrop { font-size: var(--fs-1); color: var(--text-subtle); }

/* Plans */
/* auto-fit, not a fixed count: the catalogue went from two plans to three and a
   hardcoded repeat(2,...) left the third card orphaned on its own row. */
.plangrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(258px, 1fr)); gap: var(--sp-5); }
.plan {
  display: flex; flex-direction: column;
  padding: var(--sp-6);
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--surface); box-shadow: var(--e-1);
}
.plan.is-featured { border-color: var(--brand); box-shadow: var(--e-2); }
.plan.is-current  { border-color: var(--ok); }
.plan-name { display: flex; align-items: center; gap: var(--sp-3); }
.plan-price {
  display: flex; align-items: baseline; gap: var(--sp-2); margin: var(--sp-4) 0 var(--sp-2);
}
.plan-price b {
  font-size: var(--fs-9); font-weight: 700; line-height: 1;
  letter-spacing: var(--tr-display); font-variant-numeric: tabular-nums;
}
.plan-price span { color: var(--text-muted); font-size: var(--fs-3); }
.plan-tag { color: var(--text-muted); font-size: var(--fs-3); min-height: 42px; }
.plan-feats { list-style: none; margin: var(--sp-5) 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-3); }
.plan-feats li { display: flex; align-items: flex-start; gap: var(--sp-3); font-size: var(--fs-3); }
.plan-feats svg { flex: none; width: 16px; height: 16px; margin-top: 3px; color: var(--ok); }
.plan .btn { margin-top: auto; }

/* Inbox */
.mrow {
  display: grid; grid-template-columns: 1fr auto; gap: var(--sp-2) var(--sp-4);
  padding: var(--sp-4) var(--sp-2);
  border-bottom: 1px solid var(--border);
  transition: background var(--dur-fast) var(--ease-out);
}
.mrow:hover { background: var(--surface-2); }
.mrow .msubj { font-weight: 600; }
.mrow .mfrom { font-size: var(--fs-2); color: var(--text-muted); }
.mrow .mprev { grid-column: 1 / -1; font-size: var(--fs-2); color: var(--text-muted); }
.mrow .mmeta { display: flex; align-items: center; gap: var(--sp-2); justify-self: end; }

.alias-box {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px dashed var(--border-field); border-radius: var(--r-md);
  background: var(--surface-2);
}
.alias-box code { font-size: var(--fs-4); font-weight: 600; }

/* Invite / promo code rows */
.coderow {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) 0; border-bottom: 1px solid var(--border);
}
.coderow code {
  padding: 4px var(--sp-3); border: 1px solid var(--border);
  border-radius: var(--r-xs); background: var(--surface-2);
  font-size: var(--fs-3); letter-spacing: .02em;
}

/* Résumé */
.filecard {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-4);
  padding: var(--sp-4);
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--surface-2);
}
.filecard .fico {
  display: flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; flex: none;
  border-radius: var(--r-sm); background: var(--brand-soft); color: var(--brand);
}
.dropzone {
  padding: var(--sp-7) var(--sp-5); text-align: center;
  border: 2px dashed var(--border-field); border-radius: var(--r-md);
  background: var(--surface-2);
  transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.dropzone.is-over { border-color: var(--brand); background: var(--brand-soft); }


/* ---------------------------------------------------------------------------
   15. RESPONSIVE
   Four breakpoints, used consistently: 480 / 768 / 1024 / 1280.
   --------------------------------------------------------------------------- */
@media (max-width: 1023px) {
  .hero-inner { grid-template-columns: 1fr; gap: var(--sp-8); }
  .hero-auth  { max-width: 480px; }
  .steps  { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .limits { grid-template-columns: 1fr; }
  .kpis   { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .preview-kpis { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 767px) {
  .wrap { padding: var(--sp-5) var(--sp-4) var(--sp-10); }
  .lane { padding: var(--sp-9) var(--sp-4); }
  .hero { padding: var(--sp-8) var(--sp-4) var(--sp-9); }
  .topbar { padding: var(--sp-3) var(--sp-4); }
  .plangrid { grid-template-columns: 1fr; }
  .proof { gap: var(--sp-6); }
  .proof dt { font-size: var(--fs-7); }
  .fstep { grid-template-columns: 100px minmax(0,1fr) 56px; gap: var(--sp-3); }
  .foot-inner { flex-direction: column; gap: var(--sp-5); }
  .view-head h1 { font-size: var(--fs-7); }
  /* The topbar cannot hold brand + address + a three-way theme control +
     sign-out on a 375px screen, and the account is the thing that must stay
     visible. The same control lives in Settings > Account, and a visitor who
     has never chosen simply follows their OS. */
  #themeSeg { display: none; }
}

@media (max-width: 479px) {
  .steps { grid-template-columns: 1fr; }
  /* Deliberately NOT 1fr. Four full-width cards for four numbers is most of a
     phone screen of scrolling before any content; a 2x2 grid shows the whole
     summary at once, which is the point of a summary. */
  .kpis  { grid-template-columns: repeat(2, minmax(0,1fr)); gap: var(--sp-3); }
  .kpi   { padding: var(--sp-4); }
  .kpi .value { font-size: var(--fs-7); }
  .card  { padding: var(--sp-5); border-radius: var(--r-md); }
  .authcard { padding: var(--sp-5); }
  .fstep { grid-template-columns: 96px minmax(0,1fr) 44px; }
  .fstep .fname { font-size: var(--fs-2); }
}

/* Entrance motion, gated so it never delays first contentful paint. */
.rise { animation: rise var(--dur-slow) var(--ease-out) backwards; }
.rise:nth-child(2) { animation-delay: 40ms; }
.rise:nth-child(3) { animation-delay: 80ms; }
.rise:nth-child(4) { animation-delay: 120ms; }
@keyframes rise { from { opacity: 0; transform: translateY(8px); } }

/* Every animation above is decoration over a fully usable page, so switching
   them all off is safe and complete. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

@media print {
  .topbar, .tabs, .toasts, .btn, .subnav { display: none !important; }
  .card { box-shadow: none; border-color: #999; }
}

/* The applications filter bar. Search + status side by side needs ~360px, so
   on the narrowest phones they stack to full width instead of pushing the
   select off the edge of its card. */
@media (max-width: 519px) {
  .filterbar { width: 100%; }
  .filterbar .input-search,
  .filterbar .selectwrap { flex: 1 1 100%; min-width: 0; }
  .filterbar .selectwrap select { min-width: 0; width: 100%; }
}

/* ---------------------------------------------------------------------------
   Onboarding readiness banner.
   Not dismissible on purpose: while it is showing, the bot is refusing to run,
   and a user who hides it sees a dashboard of zeroes with no explanation.
   --------------------------------------------------------------------------- */
.ready {
  display: flex; flex-direction: column; gap: var(--sp-4);
  padding: var(--sp-5);
  border: 1px solid color-mix(in srgb, var(--warn) 40%, var(--border));
  border-left: 3px solid var(--warn);
  border-radius: var(--r-md);
  background: var(--warn-soft);
  margin-bottom: var(--sp-6);
}
.ready.is-ready {
  border-color: color-mix(in srgb, var(--ok) 40%, var(--border));
  border-left-color: var(--ok);
  background: var(--ok-soft);
}
.ready-head { display: flex; align-items: flex-start; gap: var(--sp-3); }
.ready-head svg { flex: none; width: 20px; height: 20px; margin-top: 2px; color: var(--warn); }
.ready.is-ready .ready-head svg { color: var(--ok); }
.ready-title { font-weight: 640; }
.ready-steps { display: flex; flex-direction: column; gap: var(--sp-2); margin: 0; padding: 0; list-style: none; }
.ready-step {
  display: flex; align-items: flex-start; gap: var(--sp-3);
  padding: var(--sp-3);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--surface);
}
.ready-step.done { opacity: .62; }
.ready-tick {
  display: flex; align-items: center; justify-content: center;
  flex: none; width: 20px; height: 20px; margin-top: 1px;
  border: 1.5px solid var(--border-field); border-radius: 50%;
  font-size: var(--fs-1); color: var(--text-subtle);
}
.ready-step.done .ready-tick {
  border-color: var(--ok); background: var(--ok); color: var(--on-brand);
}
.ready-step.done .ready-tick svg { width: 11px; height: 11px; }
.ready-step .grow > div:first-child { font-weight: 560; font-size: var(--fs-3); }
.ready-step .grow > div + div { font-size: var(--fs-2); color: var(--text-muted); margin-top: 2px; }
.ready-progress { font-size: var(--fs-2); color: var(--text-muted); white-space: nowrap; }

/* FAQ inside a dialog */
.faq-q {
  border-top: 1px solid var(--border);
}
.faq-q:first-child { border-top: 0; }
.faq-q summary {
  cursor: pointer; padding: var(--sp-4) 0; font-weight: 560;
  list-style: none; display: flex; align-items: center; gap: var(--sp-3);
}
.faq-q summary::-webkit-details-marker { display: none; }
.faq-q summary::after {
  content: ""; margin-left: auto; flex: none; width: 16px; height: 16px;
  background: var(--text-subtle);
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") center/contain no-repeat;
  transition: transform var(--dur) var(--ease-out);
}
.faq-q[open] summary::after { transform: rotate(180deg); }
.faq-q p { padding: 0 0 var(--sp-4); color: var(--text-muted); font-size: var(--fs-3); }
.faq-cat {
  margin-top: var(--sp-5); font-size: var(--fs-1); font-weight: 700;
  letter-spacing: var(--tr-wide); text-transform: uppercase; color: var(--text-subtle);
}
.faq-cat:first-child { margin-top: 0; }

/* Inbox rows are <details>. The grid that used to be on .mrow moves to the
   summary, so the collapsed row looks exactly as before and the full message
   appears underneath when opened. */
details.mrow { display: block; padding: 0; }
details.mrow > summary {
  display: grid; grid-template-columns: 1fr auto; gap: var(--sp-2) var(--sp-4);
  padding: var(--sp-4) var(--sp-7) var(--sp-4) var(--sp-2);
  position: relative; cursor: pointer; list-style: none;
}
details.mrow > summary::-webkit-details-marker { display: none; }
details.mrow > summary::after {
  content: ""; position: absolute; right: var(--sp-2); top: var(--sp-4);
  width: 16px; height: 16px; background: var(--text-subtle);
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") center/contain no-repeat;
  transition: transform var(--dur) var(--ease-out);
}
details.mrow[open] > summary::after { transform: rotate(180deg); }
details.mrow[open] > summary .mprev { display: none; }  /* the full text is below */
details.mrow > summary:focus-visible { outline: 2px solid var(--ring); outline-offset: -2px; }
.mbody {
  padding: 0 var(--sp-7) var(--sp-5) var(--sp-2);
  font-size: var(--fs-3); line-height: 1.65; color: var(--text-muted);
  white-space: pre-wrap; overflow-wrap: anywhere;
  max-height: 460px; overflow-y: auto;
}

/* Waiting state: set up correctly, but nothing has actually run. Amber rather
   than green, because from the user's side it is indistinguishable from broken
   and should not be dressed up as success. */
.ready.is-waiting {
  border-color: color-mix(in srgb, var(--warn) 40%, var(--border));
  border-left-color: var(--warn);
  background: var(--warn-soft);
}
.ready.is-waiting .ready-head svg { color: var(--warn); }
/* Shown only while genuinely running, so it means something when it is there. */
.live-dot {
  flex: none; width: 10px; height: 10px; margin-top: 6px;
  border-radius: 50%; background: var(--ok);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--ok) 70%, transparent);
  animation: ping 2.2s var(--ease-out) infinite;
}

/* ── Trend chart + queue ────────────────────────────────────────────────── */
.trend { width: 100%; height: auto; display: block; overflow: visible; }
/* Both series use the brand hue, separated by weight rather than by colour, so
   the same information survives greyscale printing and colour-vision
   differences. Each sits in its own band with its own maximum — see the comment
   on trendChart for why a shared axis was wrong. */
.tr-applied  { fill: var(--brand); }
.tr-reviewed { fill: var(--brand); opacity: .32; }
.tr-axis     { stroke: var(--border); stroke-width: 1; }
.tr-tick     { fill: var(--text-muted); font-size: 11px; text-anchor: middle;
               font-variant-numeric: tabular-nums; }
.tr-cap      { fill: var(--text-muted); font-size: 11px; font-weight: 500;
               letter-spacing: .02em; }
.tr-end      { text-anchor: end; font-variant-numeric: tabular-nums; }

.queue { list-style: none; margin: 0; padding: 0; }
.queue li { display: flex; align-items: center; gap: var(--sp-4);
            padding: var(--sp-3) var(--sp-5);
            border-top: 1px solid var(--border); }
.queue li:first-child { border-top: 0; }
.q-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.q-main a { font-weight: 500; }
.q-co { font-size: var(--fs-2); color: var(--text-muted); }
@media (max-width: 480px) {
  /* The source badge is the first thing to go on a phone: which board a role
     came from is not why anyone opens this list. */
  .queue li .badge { display: none; }
}

/* ── Background (education / work / skills) ─────────────────────────────── */
.h-sm { font-size: var(--fs-5); margin: 0 0 var(--sp-2); }
.bg-row { border: 1px solid var(--border); border-radius: var(--r-md);
          padding: var(--sp-4); margin-bottom: var(--sp-3);
          display: flex; flex-direction: column; gap: var(--sp-3); }
.bg-f { display: flex; flex-direction: column; gap: var(--sp-1); }
.bg-f > span { font-size: var(--fs-2); color: var(--text-muted); }
/* Inputs inside a row inherit the global field styling; only the label
   arrangement differs, so nothing here restates border or focus rules. */
.btn-sm { padding: var(--sp-1) var(--sp-3); font-size: var(--fs-2);
          min-height: 36px; }
.bg-row .btn-sm { align-self: flex-start; }
