/* ============================================================================
   RAVE Finance Labs — "Trading Desk" design system
   ----------------------------------------------------------------------------
   Light is the default surface (the brand mark is designed against white and
   loses contrast on the dark tokens below). Every colour is a token, so the
   dark theme is the same sheet with a different token block
   ([data-theme="light"] overrides the :root block, which remains the dark
   set for compatibility with existing selectors). Class names are unchanged
   from the previous stylesheet so no template had to be rewritten to adopt
   this.

   Palette rules (deliberate, please keep):
     - green  = gain, red = loss. Never used decoratively.
     - amber  = brand mark + exactly one attention state. Never decoration.
     - surface hierarchy is carried by hairline borders, not heavy shadows.
     - every numeral is tabular so live-updating figures don't jitter.
   ========================================================================== */

* { box-sizing: border-box; }

:root {
  color-scheme: dark;

  /* Surfaces — near-black page, raised panels, hairline separation */
  --bg: #080b12;
  --bg-2: #0d121c;
  --surface: #131a27;
  --surface-2: #18202f;
  --surface-3: #1e2839;
  --line: rgba(255, 255, 255, 0.08);
  --line-strong: rgba(255, 255, 255, 0.14);

  /* Motion — shared curve/duration so individual rules stop hand-typing
     near-duplicate values (0.12s/0.14s/0.15s/0.18s ease was scattered
     across the file with no token to converge on). Same across themes. */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --duration-fast: 140ms;

  /* Type */
  --ink: #eef2f8;
  --ink-2: #b3becd;
  --muted: #7d8899;
  --muted-2: #5d6878;

  /* Semantics */
  --accent: #22c07f;
  --accent-deep: #0f7a52;
  --accent-glow: rgba(34, 192, 127, 0.20);
  --positive: #22c07f;
  --positive-glow: rgba(34, 192, 127, 0.16);
  --negative: #ff5c5c;
  --negative-glow: rgba(255, 92, 92, 0.16);
  --warn: #e6b23c;
  --warn-glow: rgba(230, 178, 60, 0.16);

  --gold: #e6b23c;
  --gold-glow: rgba(230, 178, 60, 0.20);
  --silver: #a8b4c4;
  --silver-glow: rgba(168, 180, 196, 0.16);
  --bronze: #c07b4a;
  --bronze-glow: rgba(192, 123, 74, 0.16);

  /* Categorical series palette for multi-line charts (Compare). Identity
     encoding only — deliberately avoids the green/red/amber hues reserved
     above for gain/loss/brand, so a series colour can never be misread as a
     status. Chosen by search, not by eye: each theme's six colours clear
     WCAG 3:1 on that theme's panel and stay separable under protanopia,
     deuteranopia and tritanopia. Worst pair sits above the deltaE 8 floor,
     which is legal here because every series is also named in the legend
     and again in the hover tooltip. Keep the order fixed — never cycle. */
  --series-1: #27aeff;
  --series-2: #da6600;
  --series-3: #12909f;
  --series-4: #9455ef;
  --series-5: #fba0cc;
  --series-6: #a1ca08;

  --radius: 14px;
  --radius-sm: 9px;
  --shadow: 0 18px 40px -24px rgba(0, 0, 0, 0.9);

  --font-display: 'Libre Baskerville', Georgia, 'Times New Roman', serif;
  --font-body: 'IBM Plex Sans', -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
}

[data-theme="light"] {
  color-scheme: light;
  --bg: #f6f7f4;
  --bg-2: #eceee8;
  --surface: #ffffff;
  --surface-2: #f4f5f1;
  --surface-3: #e9ebe5;
  --line: rgba(12, 20, 30, 0.10);
  --line-strong: rgba(12, 20, 30, 0.18);
  --ink: #10161f;
  --ink-2: #3a4553;
  --muted: #66707e;
  --muted-2: #8b95a3;
  --accent: #0f8a5b;
  --accent-glow: rgba(15, 138, 91, 0.14);
  --positive: #0f8a5b;
  --positive-glow: rgba(15, 138, 91, 0.12);
  --negative: #c8362f;
  --negative-glow: rgba(200, 54, 47, 0.10);
  --warn: #a3730c;
  --warn-glow: rgba(163, 115, 12, 0.12);
  --gold: #a3730c;
  --silver: #6b7480;
  --bronze: #96562c;
  --shadow: 0 14px 32px -22px rgba(16, 22, 30, 0.45);

  /* Same six hue families as dark, re-solved against a white panel — a
     colour bright enough to read on near-black is far too pale on white,
     so this is a separate solution rather than a tint of the block above. */
  --series-1: #0736c2;
  --series-2: #cc6004;
  --series-3: #1d93a6;
  --series-4: #8b4be5;
  --series-5: #810b74;
  --series-6: #3d5700;
}

/* Fixed (non-theme-swapping) tool-identity palette for illustrative card
   fills — the belt/hub-preview cards on the landing page. Deliberately NOT
   the --accent/--gold/--series-N tokens above: those are tuned per-theme
   for thin strokes on a matching background (bright on black, deep on
   white), so the same token needs opposite text colour depending on theme.
   A solid card fill can't flip its overlaid text every time someone toggles
   the theme, so this is a fixed palette, same hue family as the semantic
   tokens, chosen once for consistent white-text contrast either way. */
:root {
  --tool-portfolio: #22c07f;  --tool-portfolio-deep: #0f6b47;
  --tool-networth: #e6b23c;   --tool-networth-deep: #8a6a1a;
  --tool-calc: #27aeff;       --tool-calc-deep: #0d5f96;
  --tool-goals: #9455ef;      --tool-goals-deep: #5c2fa8;
  --tool-loan: #da6600;       --tool-loan-deep: #8a4a00;
  --tool-practice: #0d9488;   --tool-practice-deep: #0a6b62;
  --tool-test: #c2185b;       --tool-test-deep: #8a1140;
}

/* ---------------------------------------------------------------- base ---- */

html { scroll-behavior: smooth; }

body {
  font-family: var(--font-body);
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  /* One very quiet radial lift behind the header area so the page doesn't
     read as a flat black rectangle. No blobs, no gradients over content. */
  background-image:
    radial-gradient(1100px 420px at 50% -180px, var(--accent-glow), transparent 70%);
  background-attachment: fixed;
  color: var(--ink);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* Numbers are the product. Tabular everywhere by default. */
body, table, input, select, button, .value, .metric-value {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

a { color: var(--accent); text-underline-offset: 3px; }
a:hover { color: var(--ink); }
p { color: var(--ink-2); }
hr { border: none; border-top: 1px solid var(--line); margin: 22px 0; }

/* ----------------------------------------------------------------- nav ---- */

.navbar {
  background: color-mix(in oklab, var(--bg-2) 88%, transparent);
  backdrop-filter: saturate(140%) blur(10px);
  color: var(--ink);
  padding: 12px 26px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 50;
}
.navbar .brand {
  color: var(--ink);
  font-family: var(--font-display);
  font-weight: 700;
  text-decoration: none;
  font-size: 0.98rem;
  letter-spacing: -0.01em;
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
.brand-mark { flex-shrink: 0; display: block; }
.nav-links { display: flex; align-items: center; flex-wrap: wrap; }
.nav-links a {
  color: var(--muted);
  text-decoration: none;
  margin-left: 4px;
  font-size: 0.855rem;
  font-weight: 500;
  padding: 6px 11px;
  border-radius: 999px;
  border: 1px solid transparent;
  transition: color 0.14s ease, background 0.14s ease;
}
.nav-links a:hover { color: var(--ink); background: var(--surface-2); }
.nav-links a.active {
  color: var(--ink);
  font-weight: 600;
  background: var(--surface-2);
  border-color: var(--line-strong);
}
.nav-user {
  margin-left: 10px;
  color: var(--ink-2);
  font-size: 0.855rem;
  text-decoration: none;
}
.nav-user:hover { color: var(--ink); }
.nav-toggle {
  display: none;
  background: transparent;
  border: 1px solid var(--line-strong);
  color: var(--ink);
  font-size: 1.1rem;
  line-height: 1;
  padding: 6px 12px;
  border-radius: 8px;
  cursor: pointer;
  box-shadow: none;
}
.nav-toggle:hover { background: var(--surface-2); transform: none; }

/* Theme switch — an icon button, deliberately the quietest control in the bar */
.theme-toggle {
  margin-left: 10px;
  background: transparent;
  border: 1px solid var(--line-strong);
  color: var(--muted);
  width: 32px;
  height: 32px;
  padding: 0;
  border-radius: 999px;
  font-size: 0.9rem;
  line-height: 1;
  cursor: pointer;
  box-shadow: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.theme-toggle:hover { color: var(--ink); background: var(--surface-2); transform: none; box-shadow: none; }
.theme-toggle .icon-sun { display: none; }
[data-theme="light"] .theme-toggle .icon-sun { display: inline; }
[data-theme="light"] .theme-toggle .icon-moon { display: none; }

/* ------------------------------------------------------------- layout ---- */

.container {
  max-width: 1400px;
  width: 94%;
  margin: 26px auto 56px;
  padding: 0 16px;
}

/* ------------------------------------------------------------ typography - */

h1 {
  font-family: var(--font-display);
  font-size: 1.55rem;
  color: var(--ink);
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.25;
}
h2 {
  font-family: var(--font-display);
  font-size: 1.08rem;
  color: var(--ink);
  font-weight: 700;
  letter-spacing: -0.01em;
  display: flex;
  align-items: center;
  gap: 9px;
}
h2::before {
  content: "";
  display: inline-block;
  width: 3px;
  height: 15px;
  border-radius: 2px;
  background: var(--accent);
  flex-shrink: 0;
}
h3 { font-family: var(--font-display); font-weight: 700; color: var(--ink); }

/* Secondary text. These exist so templates never need an inline colour —
   the whole reason the old stylesheet needed a legacy bridge. `.hint` is
   the standard explanatory line under a control or metric; `.hint-sm` is
   the same thing where space is tight (table captions, section subtitles). */
.hint { font-size: 0.8rem; color: var(--muted); }
.hint-sm { font-size: 0.72rem; color: var(--muted); }
.hint-inline { font-size: 0.85rem; color: var(--muted); font-weight: 400; }
.muted { color: var(--muted); }
.muted-2 { color: var(--muted-2); }
/* Sits directly under an h2, which carries its own bottom margin. */
.section-note { font-size: 0.9rem; color: var(--muted); margin-top: -8px; }

/* Per-row action cluster in the admin roster. The roster gained Status and
   Last Login columns, so the actions have to stop sprawling: buttons shrink,
   and the move-to-cohort picker is capped rather than sizing to the longest
   cohort name (which was making every row three lines tall). */
.row-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.row-actions form { margin: 0; }
/* Must cover .btn as well as <button>: the watchlist pairs a link-styled
   Trade with a real Remove button, and styling only one of them is what
   made them render at different heights side by side. */
.row-actions button,
.row-actions .btn { padding: 6px 11px; font-size: 0.78rem; }
.row-actions select {
  max-width: 190px;
  padding: 5px 8px;
  font-size: 0.78rem;
  text-overflow: ellipsis;
}
/* Timestamps read better on one line; the table scrolls if it must. */
.cell-nowrap { white-space: nowrap; }

/* Monospace value meant to be read aloud or copied (temp passwords, codes). */
.code-chip {
  display: inline-block;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 1.1rem;
  letter-spacing: 0.06em;
  padding: 5px 12px;
  background: var(--surface-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  color: var(--ink);
}

/* Card that needs to announce itself (a just-generated credential, a result
   the teacher must act on) without using an error/success colour. */
.card-highlight { border-left: 2px solid var(--accent); }

/* --------------------------------------------------------------- cards ---- */

.card {
  background: var(--surface);
  padding: 20px 22px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
  margin-bottom: 20px;
}

.empty-state {
  background: var(--surface);
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius);
  padding: 26px 24px;
  text-align: center;
  color: var(--ink-2);
}
.empty-state p { margin: 6px 0; }
.empty-state p:first-child { font-size: 1.02rem; color: var(--ink); }

/* -------------------------------------------------------------- tables ---- */

.table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
  background: var(--surface);
}
table {
  width: 100%;
  border-collapse: collapse;
  background: var(--surface);
  border-radius: var(--radius);
  overflow: hidden;
}
.table-scroll > table { border-radius: 0; box-shadow: none; }
th, td {
  text-align: left;
  padding: 11px 14px;
  border-bottom: 1px solid var(--line);
  font-size: 0.865rem;
  color: var(--ink-2);
}
td strong, td b { color: var(--ink); }
th {
  background: var(--surface-2);
  color: var(--muted);
  font-weight: 600;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  white-space: nowrap;
  border-bottom: 1px solid var(--line-strong);
}
tbody tr { transition: background 0.12s ease; }
tbody tr:hover { background: var(--surface-2); }
tbody tr:last-child td { border-bottom: none; }

/* ------------------------------------------------------------- numbers ---- */

.pl-positive { color: var(--positive); font-weight: 600; }
.pl-negative { color: var(--negative); font-weight: 600; }
.readonly-price {
  background: var(--surface-2);
  border: 1px solid var(--line);
  padding: 9px 12px;
  border-radius: var(--radius-sm);
  font-weight: 600;
  color: var(--ink);
}

/* Live tick flash — brief, low contrast; several values can move at once.
   A transition (not @keyframes) so a second tick before the first one
   finishes fading retargets smoothly from wherever it currently is,
   instead of hard-snapping back to full intensity. */
[data-live-key] { transition: background-color 220ms var(--ease-out); border-radius: 3px; }
.live-tick-up { background-color: color-mix(in oklab, var(--positive) 32%, transparent); }
.live-tick-down { background-color: color-mix(in oklab, var(--negative) 32%, transparent); }
@media (prefers-reduced-motion: reduce) {
  [data-live-key] { transition: none; }
  html { scroll-behavior: auto; }
}

/* ------------------------------------------------------ summary metrics --- */

.summary-row {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 20px;
}
.summary-box {
  position: relative;
  background: var(--surface);
  padding: 15px 18px 16px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
  flex: 1;
  min-width: 165px;
  overflow: hidden;
  transition: border-color 0.18s ease, transform 0.18s ease;
}
/* Status is carried by a hairline rail on the left edge, not a thick top
   border — it keeps the six cards reading as one instrument cluster. */
.summary-box::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 2px;
  background: var(--line-strong);
}
.summary-box:has(.pl-positive)::before { background: var(--positive); }
.summary-box:has(.pl-negative)::before { background: var(--negative); }
@media (hover: hover) and (pointer: fine) {
  .summary-box:hover { transform: translateY(-2px); border-color: var(--line-strong); }
}
.summary-box .label {
  font-size: 0.66rem;
  color: var(--muted);
  margin-bottom: 5px;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  font-weight: 600;
}
.summary-box .value {
  font-size: 1.42rem;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -0.02em;
  line-height: 1.2;
}
/* Hero figure: the headline portfolio value should dwarf a table cell.
   Add class="summary-box hero" in a template to promote one card. */
.summary-box.hero .value { font-size: 2.15rem; font-family: var(--font-display); }

/* --------------------------------------------------------------- forms ---- */

.form-group { margin-bottom: 15px; }
label {
  display: block;
  margin-bottom: 5px;
  font-weight: 600;
  font-size: 0.8rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
input[type=text], input[type=password], input[type=number], input[type=email],
input[type=date], select, textarea {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  font-size: 0.95rem;
  font-family: inherit;
  background: var(--surface-2);
  color: var(--ink);
  transition: border-color 0.14s ease, box-shadow 0.14s ease;
}
/* A label wrapping a radio/checkbox is naming an OPTION, not a field, so it
   drops the uppercase field-label treatment and reads as normal text. */
.label-option {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin: 0 18px 0 0;
  font-weight: 500;
  font-size: 0.9rem;
  color: var(--ink-2);
  text-transform: none;
  letter-spacing: normal;
  cursor: pointer;
}
.label-option input { width: auto; margin: 0; accent-color: var(--accent); }
.label-option:hover { color: var(--ink); }

input::placeholder, textarea::placeholder { color: var(--muted-2); }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-glow);
}
select option { background: var(--surface); color: var(--ink); }

/* ------------------------------------------------------------- buttons ---- */

button, .btn {
  background: var(--accent);
  color: #04120c;
  border: 1px solid transparent;
  padding: 9px 18px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: 0.875rem;
  font-family: inherit;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-decoration: none;
  /* inline-flex + a fixed line-height so a link-button (<a class="btn">) and
     a real <button> come out exactly the same height. As inline-block they
     differed: the <a> sized to its text box while the <button> added the UA's
     own line-height, which is what made Trade and Remove sit at odd sizes
     next to each other in the watchlist. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1.25;
  box-shadow: none;
  transition: filter var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out);
}
[data-theme="light"] button, [data-theme="light"] .btn { color: #ffffff; }
@media (hover: hover) and (pointer: fine) {
  button:hover, .btn:hover { filter: brightness(1.1); transform: translateY(-1px); color: #04120c; }
  [data-theme="light"] button:hover, [data-theme="light"] .btn:hover { color: #fff; }
}
button:active, .btn:active { filter: brightness(0.95); transform: scale(0.97); transition-duration: 80ms; }
button:focus-visible, .btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.btn-danger { background: var(--negative); color: #150404; }
[data-theme="light"] .btn-danger { color: #fff; }
.btn-danger:hover { color: #150404; }
[data-theme="light"] .btn-danger:hover { color: #fff; }

/* Secondary / inactive segment control (research 1D/5D/1M/... tabs).
   These need the theme-attribute prefix too. Without it the earlier
   `[data-theme="light"] .btn { color: #fff }` rule out-specifies a bare
   `.btn-range-inactive`, and the unselected ranges rendered white on a light
   background — invisible. Keep both selectors in sync if either changes. */
.btn-range-inactive,
[data-theme="light"] .btn-range-inactive {
  background: transparent;
  color: var(--ink-2);
  border: 1px solid var(--line-strong);
  box-shadow: none;
}
.btn-range-inactive:hover,
[data-theme="light"] .btn-range-inactive:hover {
  background: var(--surface-2);
  color: var(--ink);
  box-shadow: none;
  transform: none;
  filter: none;
}

/* ------------------------------------------------------------- notices ---- */

.error {
  background: var(--negative-glow);
  border: 1px solid color-mix(in oklab, var(--negative) 40%, transparent);
  color: var(--negative);
  padding: 11px 15px;
  border-radius: var(--radius-sm);
  margin-bottom: 16px;
  font-size: 0.88rem;
  white-space: pre-line;
}
.success {
  background: var(--positive-glow);
  border: 1px solid color-mix(in oklab, var(--positive) 40%, transparent);
  color: var(--positive);
  padding: 11px 15px;
  border-radius: var(--radius-sm);
  margin-bottom: 16px;
  font-size: 0.88rem;
}

/* -------------------------------------------------------------- badges ---- */

.badge {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  border: 1px solid transparent;
}
.badge-positive {
  background: var(--positive-glow);
  color: var(--positive);
  border-color: color-mix(in oklab, var(--positive) 34%, transparent);
}
.badge-negative {
  background: var(--negative-glow);
  color: var(--negative);
  border-color: color-mix(in oklab, var(--negative) 34%, transparent);
}
.badge-neutral {
  background: var(--surface-2);
  color: var(--muted);
  border-color: var(--line-strong);
}

/* Glossary hint marker — findable when wanted, invisible when not. */
.info-dot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  margin-left: 5px;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--muted);
  font-size: 0.62rem;
  font-weight: 700;
  line-height: 1;
  cursor: help;
  vertical-align: middle;
  user-select: none;
  border: 1px solid var(--line);
}
.info-dot:hover { background: var(--accent); color: #04120c; border-color: var(--accent); }
.info-dot:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Achievement strip — earned reads lit, unearned stays a dim outline. */
.achievement-strip { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 18px; }
.achievement-badge {
  display: inline-block;
  padding: 5px 13px;
  border-radius: 999px;
  font-size: 0.76rem;
  font-weight: 500;
  cursor: default;
}
.achievement-badge.earned {
  background: var(--positive-glow);
  color: var(--positive);
  border: 1px solid color-mix(in oklab, var(--positive) 36%, transparent);
}
.achievement-badge.unearned {
  background: transparent;
  color: var(--muted-2);
  border: 1px dashed var(--line-strong);
}

/* -------------------------------------------------------------- charts ---- */

.sparkline { display: block; overflow: visible; }
.chart-card { position: relative; }
.chart-legend {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
  font-size: 0.75rem;
  color: var(--muted);
  margin-bottom: 8px;
}
.legend-swatch {
  display: inline-block;
  width: 10px; height: 10px;
  border-radius: 2px;
  margin-right: 6px;
  vertical-align: middle;
}
.chart-tooltip {
  position: absolute;
  display: none;
  pointer-events: none;
  background: var(--surface-3);
  border: 1px solid var(--line-strong);
  color: var(--ink);
  padding: 7px 11px;
  border-radius: var(--radius-sm);
  font-size: 0.78rem;
  line-height: 1.5;
  white-space: nowrap;
  /* Centred above the pointer by default; public/chart-tooltip.js adds
     .is-below near the top of a card, where there's no room above. */
  transform: translate(-50%, -100%);
  box-shadow: 0 10px 24px -12px rgba(0,0,0,0.8);
  z-index: 20;
}
.chart-tooltip.is-below { transform: translate(-50%, 0); }

/* SVG chart furniture. Charts are server-rendered inline SVG, so their
   colours have to come from CSS rather than presentation attributes or they
   can't follow the theme. Every <line>/<text>/<polyline> in a chart carries
   one of these classes instead of a hardcoded stroke/fill. */
.chart-axis { stroke: var(--line-strong); }
.chart-gridline { stroke: var(--line-strong); }
/* Colour only — font-size stays on the element, since axis labels and bar
   labels are deliberately different sizes. */
.chart-label { fill: var(--muted); }
/* Revenue bars sit beside net-income bars that are green when positive and
   red when negative. Revenue is IDENTITY, not status, so it must not reuse
   the accent — which is the same value as --positive and would make revenue
   and profit indistinguishable. It takes the first categorical series slot. */
.chart-bar { fill: var(--series-1); }
/* Gain/loss fills and strokes for chart geometry — the same semantic pair
   as .pl-positive/.pl-negative, but for SVG rather than text. */
.chart-fill-positive { fill: var(--positive); }
.chart-fill-negative { fill: var(--negative); }
.chart-stroke-positive { stroke: var(--positive); }
.chart-stroke-negative { stroke: var(--negative); }
.chart-line { stroke: var(--accent); fill: none; }
.chart-line-benchmark { stroke: var(--muted); fill: none; }
.chart-dot { fill: var(--accent); }
.chart-dot.is-live { fill: var(--positive); }
.chart-crosshair { stroke: var(--muted); }
/* The crosshair dot needs a ring in the panel colour to stay visible where
   it crosses the series line itself. */
.chart-dot-ring { stroke: var(--surface); }

/* Series colours for multi-line charts, applied by index. */
.series-1 { stroke: var(--series-1); }
.series-2 { stroke: var(--series-2); }
.series-3 { stroke: var(--series-3); }
.series-4 { stroke: var(--series-4); }
.series-5 { stroke: var(--series-5); }
.series-6 { stroke: var(--series-6); }
.stroke-series-1 { stroke: var(--series-1); }
.stroke-series-2 { stroke: var(--series-2); }
.stroke-series-3 { stroke: var(--series-3); }
.stroke-series-4 { stroke: var(--series-4); }
.stroke-series-5 { stroke: var(--series-5); }
.stroke-series-6 { stroke: var(--series-6); }
/* "Other" — the fold-up bucket, deliberately outside the categorical set. */
.stroke-series-0 { stroke: var(--muted); }
.swatch-series-0 { background: var(--muted); }
.swatch-series-1 { background: var(--series-1); }
.swatch-series-2 { background: var(--series-2); }
.swatch-series-3 { background: var(--series-3); }
.swatch-series-4 { background: var(--series-4); }
.swatch-series-5 { background: var(--series-5); }
.swatch-series-6 { background: var(--series-6); }
.text-series-1 { color: var(--series-1); }
.text-series-2 { color: var(--series-2); }
.text-series-3 { color: var(--series-3); }
.text-series-4 { color: var(--series-4); }
.text-series-5 { color: var(--series-5); }
.text-series-6 { color: var(--series-6); }
.dot-series-1 { fill: var(--series-1); }
.dot-series-2 { fill: var(--series-2); }
.dot-series-3 { fill: var(--series-3); }
.dot-series-4 { fill: var(--series-4); }
.dot-series-5 { fill: var(--series-5); }
.dot-series-6 { fill: var(--series-6); }

/* Donut charts (flat, not 3D — see docs/CODE_GUIDE.md for why) */
.donut-wrap {
  display: flex;
  align-items: center;
  gap: 26px;
  flex-wrap: wrap;
  animation: donutIn 0.6s cubic-bezier(0.2, 0.7, 0.3, 1) both;
}
@keyframes donutIn {
  from { opacity: 0; transform: scale(0.9); }
  to { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) { .donut-wrap { animation: none; } }
/* A printed/PDF report must never capture the entrance mid-flight. */
@media print { .donut-wrap { animation: none; } }
.donut { flex-shrink: 0; }
.donut-slice { transition: opacity 0.15s ease; }
.donut-slice:hover { opacity: 0.75; }
.donut-total-label {
  font-size: 9px;
  fill: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
}
.donut-total-value { font-size: 15px; fill: var(--ink); font-weight: 700; }
.legend { list-style: none; padding: 0; margin: 0; font-size: 0.84rem; color: var(--ink-2); }
.legend li { margin-bottom: 8px; display: flex; align-items: center; gap: 9px; }
.legend .swatch { width: 11px; height: 11px; border-radius: 3px; flex-shrink: 0; }

/* --------------------------------------------------------- leaderboard ---- */

.podium { display: flex; gap: 14px; flex-wrap: wrap; margin-bottom: 20px; }
.podium-card {
  flex: 1;
  min-width: 180px;
  background: var(--surface);
  border-radius: var(--radius);
  padding: 20px 16px;
  text-align: center;
  border: 1px solid var(--line);
  border-top: 2px solid var(--line-strong);
  box-shadow: var(--shadow);
}
.podium-card.rank-1 { border-top-color: var(--gold); background: linear-gradient(180deg, var(--gold-glow), transparent 55%), var(--surface); order: 2; }
.podium-card.rank-2 { border-top-color: var(--silver); order: 1; }
.podium-card.rank-3 { border-top-color: var(--bronze); order: 3; }
.podium-card .rank-medal { font-size: 1.5rem; }
.podium-card .rank-name { font-weight: 600; color: var(--ink); margin: 5px 0; }
.podium-card .rank-value { font-size: 1.1rem; font-weight: 600; color: var(--ink); }

/* Challenge countdown. Loud enough to register at a glance, quiet enough not
   to compete with the portfolio value directly beneath it — until the last
   few days, when it earns the attention colour. */
.countdown-banner {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 11px 16px;
  margin-bottom: 18px;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--line);
}
.countdown-value {
  font-family: var(--font-display);
  font-size: 1.45rem;
  font-weight: 700;
  color: var(--ink);
  line-height: 1;
}
.countdown-label { font-size: 0.88rem; color: var(--ink-2); }
/* The coloured rail only appears once there's something to be urgent
   about — a permanent accent on every countdown would just be decoration. */
.countdown-banner.is-urgent {
  border-left: 2px solid var(--warn);
  background: var(--warn-glow);
}
.countdown-banner.is-urgent .countdown-value { color: var(--warn); }

/* Standout-player awards. Deliberately not gold/silver/bronze — these are
   categories, not places, and colouring them like ranks would imply one is
   better than another. */
.award-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 12px;
  margin-bottom: 22px;
}
.award-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 13px 15px;
}
.award-label {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  font-weight: 700;
  color: var(--accent);
}
.award-winner {
  font-family: var(--font-display);
  font-size: 1.02rem;
  font-weight: 700;
  color: var(--ink);
  margin: 3px 0 4px;
}
.award-desc { font-size: 0.78rem; color: var(--muted); line-height: 1.45; }

/* The same awards, repeated inline on the winner's own row. */
.row-awards { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.award-pill {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 0.68rem;
  font-weight: 600;
  background: var(--accent-glow);
  color: var(--accent);
  border: 1px solid color-mix(in oklab, var(--accent) 32%, transparent);
  cursor: default;
}

/* The reader's own row, so it stays findable in a long board. */
.leaderboard-table tr.is-you { background: var(--surface-2); }
.leaderboard-table tr.is-you td { font-weight: 600; }

/* Ranked movers list */
.mover-list { list-style: none; padding: 0; margin: 0; }
.mover-list li {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
  font-size: 0.84rem;
}
.mover-list li:last-child { border-bottom: none; }
.mover-ticker { flex: 1.1; font-weight: 600; color: var(--ink); }
.mover-price { flex: 1; text-align: right; color: var(--muted); }
.mover-change { flex: 1.5; text-align: right; font-weight: 600; }

/* --------------------------------------------------------- dashboard ---- */

/* Gainers and losers sit side by side under the watchlist rather than
   stacked in a sidebar — they read as a pair, and it frees the full page
   width for the watchlist table. */
.mover-columns {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 18px;
  align-items: start;
}
@media (max-width: 760px) {
  .mover-columns { grid-template-columns: 1fr; }
}

/* Watchlist column sizing. Everything numeric is nowrap (via .cell-nowrap in
   the template) so a Day Range or Change value is always one line; only the
   two genuinely free-text columns are allowed to wrap, and both are capped
   so one long company name can't push the action buttons out of view. */
.watchlist-table .col-name { max-width: 230px; }
.watchlist-table .col-sector { max-width: 165px; }
.watchlist-table td, .watchlist-table th { padding-left: 11px; padding-right: 11px; }

/* ------------------------------------------------------ auth / landing ---- */

.auth-layout {
  display: flex;
  gap: 48px;
  flex-wrap: wrap;
  align-items: center;
  /* No vh min-height: on a tall window it strands the hero in the middle of
     an empty screen. The content's own height sets the fold. */
  min-height: 0;
  padding: 26px 0 8px;
}
.auth-hero { flex: 1.1; min-width: 280px; }
.auth-hero h1 {
  font-size: 2.6rem;
  line-height: 1.15;
  color: var(--ink);
  margin-bottom: 14px;
}
.auth-hero p {
  font-size: 1.02rem;
  color: var(--ink-2);
  line-height: 1.65;
  max-width: 500px;
}
.auth-hero-points { list-style: none; padding: 0; margin-top: 22px; }
.auth-hero-points li {
  padding-left: 27px;
  position: relative;
  margin-bottom: 11px;
  color: var(--ink-2);
  font-size: 0.92rem;
}
.auth-hero-points li::before {
  content: "→";
  position: absolute;
  left: 0;
  color: var(--accent);
  font-weight: 700;
}
.auth-form-side { flex: 1; min-width: 280px; max-width: 420px; }
.auth-form-side .card { margin-bottom: 0; }

/* The dedicated /login page — just a mark and a form, no marketing pitch.
   That content now lives on the landing page (see .landing-hero below). */
.auth-solo {
  max-width: 400px;
  margin: 64px auto;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.auth-solo-brand { display: block; margin-bottom: 28px; }
.auth-solo .card { width: 100%; }

/* ----------------------------------------------------------- landing hero */

/* A flat page-background colour behind a centred headline is the single
   most common "AI-generated landing page" tell — see artifact-design's own
   guidance against it. A soft radial glow gives the hero some depth without
   adding a new colour to the palette; deliberately NOT a tiled grid/line
   texture (that's its own recognised generated-UI signature — reserved for
   an actual canvas/blueprint/measurement surface, which this isn't, "Money
   Lab" theming notwithstanding). Lives on a ::before behind the content,
   not the section background directly, so the fade-out mask only fades the
   glow, never the belt cards. */
/* Split layout: copy left, tool-stack carousel right — text no longer sits
   centred above the visual, both share the fold. */
.landing-hero {
  padding: 20px 0 6px;
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1.05fr;
  gap: 36px;
  align-items: center;
}
.landing-hero::before {
  content: "";
  position: absolute;
  inset: -12px 0 0 0;
  z-index: 0;
  background-image: radial-gradient(900px 460px at 18% -12%, color-mix(in oklab, var(--accent) 12%, transparent), transparent 65%);
  -webkit-mask-image: linear-gradient(to bottom, #000 75%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 75%, transparent 100%);
}
.landing-hero > * { position: relative; z-index: 1; }
.hero-copy h1 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.7rem, 3vw, 2.5rem);
  line-height: 1.16;
  letter-spacing: -0.015em;
  margin: 0 0 14px;
  text-wrap: balance;
  color: var(--ink);
}
.hero-copy .lede {
  max-width: 460px;
  margin: 0 0 24px;
  color: var(--ink-2);
  font-size: 0.98rem;
}
.landing-hero-cta { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 0; }
.landing-hero-cta .btn { padding: 11px 26px; font-size: 0.92rem; }
@media (max-width: 900px) {
  .landing-hero { grid-template-columns: 1fr; padding-top: 8px; }
  .hero-copy { text-align: center; }
  .hero-copy .lede { margin-left: auto; margin-right: auto; }
  .landing-hero-cta { justify-content: center; }
}

/* ----------------------------------------------- discrete tool-stack ---- */
/* A raised centre card with two dimmed, scaled-down flanking cards, arrows
   and dot pagination — a discrete/paginated carousel, not the infinite belt
   (partials/tool-belt.ejs, still available, currently unused). Card colours
   are the same fixed --tool-* palette as the hub grid — see "Per-tool
   identity colour" above. */
/* Every direct child here (.stack-cards' cards, the arrows, the dots) is
   position:absolute, so .stack has no in-flow content to size itself from
   and collapses to width:0 without an explicit width — which also made the
   prev/next arrows (anchored to that same zero-width box) land on top of
   each other in the centre instead of the real left/right edges. */
.stack { position: relative; width: 100%; height: 420px; margin: 24px auto 0; max-width: 420px; }
.stack-cards { position: relative; width: 100%; height: 100%; }
.stack-card {
  position: absolute;
  top: 50%; left: 50%;
  width: 230px;
  height: 370px;
  margin: -185px 0 0 -115px;
  border-radius: var(--radius);
  padding: 24px 22px;
  color: #fff;
  background: linear-gradient(165deg, var(--card-color), var(--card-deep) 82%);
  box-shadow: 0 28px 56px -24px rgba(0, 0, 0, 0.45);
  display: flex;
  flex-direction: column;
  transition: transform 0.55s cubic-bezier(.2,.8,.2,1), opacity 0.55s ease, filter 0.55s ease;
}
.stack-card .tag { font-size: 0.6rem; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; opacity: 0.85; margin-bottom: 14px; }
.stack-card .scene { height: 88px; margin-bottom: 16px; display: flex; align-items: flex-end; }
.stack-card h3 { font-family: var(--font-display); font-size: 1.18rem; margin: 0 0 8px; }
.stack-card p { font-size: 0.84rem; line-height: 1.5; margin: 0; color: rgba(255, 255, 255, 0.88); }
.stack-card.on-light { color: #241a03; }
.stack-card.on-light p { color: rgba(36, 26, 3, 0.78); }
.stack-card.on-light .tag { opacity: 0.7; }

.stack-card[data-pos="center"] { transform: translate(0, 0) scale(1); opacity: 1; z-index: 3; filter: none; }
.stack-card[data-pos="left"]   { transform: translate(-140px, 16px) scale(0.82) rotate(-4deg); opacity: 0.5; z-index: 2; filter: saturate(0.7); }
.stack-card[data-pos="right"]  { transform: translate(140px, 16px) scale(0.82) rotate(4deg); opacity: 0.5; z-index: 2; filter: saturate(0.7); }
.stack-card[data-pos="hidden"] { transform: translate(0, 0) scale(0.6); opacity: 0; z-index: 1; pointer-events: none; }
@media (prefers-reduced-motion: reduce) { .stack-card { transition: none; } }

.stack-arrow {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 40px; height: 40px; border-radius: 50%;
  background: var(--surface); border: 1px solid var(--line-strong);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; z-index: 4; color: var(--ink); font-size: 1rem;
  box-shadow: 0 10px 22px -14px rgba(0, 0, 0, 0.35);
}
.stack-arrow:hover { background: var(--surface-2); }
.stack-arrow.prev { left: -10px; }
.stack-arrow.next { right: -10px; }

.stack-dots { position: absolute; bottom: -4px; left: 50%; transform: translateX(-50%); display: flex; gap: 8px; z-index: 4; }
.stack-dots button { width: 7px; height: 7px; border-radius: 50%; border: none; background: var(--line-strong); padding: 0; cursor: pointer; }
.stack-dots button.active { background: var(--accent); width: 20px; border-radius: 4px; }


@media (max-width: 480px) {
  .stack { max-width: 280px; height: 380px; }
  .stack-card { width: 200px; height: 330px; margin: -165px 0 0 -100px; }
  .stack-card[data-pos="left"]  { transform: translate(-95px, 14px) scale(0.78) rotate(-4deg); }
  .stack-card[data-pos="right"] { transform: translate(95px, 14px) scale(0.78) rotate(4deg); }
}
/* The theme-attribute prefix on the light-theme override is required, not
   decorative — see the .btn-range-inactive comment above for why a bare
   .btn-ghost loses to `[data-theme="light"] .btn { color: #fff }` and
   renders invisible white-on-white. */
.btn-ghost,
[data-theme="light"] .btn-ghost {
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--line-strong);
}
.btn-ghost:hover,
[data-theme="light"] .btn-ghost:hover {
  background: var(--surface-2);
  filter: none;
  color: var(--ink);
}

/* Small uppercase eyebrow above a hero heading */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 14px;
}
.eyebrow::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 4px var(--accent-glow);
  animation: pulse-dot 2.4s ease-in-out infinite;
}
@keyframes pulse-dot {
  0%, 100% { box-shadow: 0 0 0 3px var(--accent-glow); }
  50% { box-shadow: 0 0 0 7px transparent; }
}
@media (prefers-reduced-motion: reduce) { .eyebrow::before { animation: none; } }

/* Brand lockup on the landing page. Sized as a logo slot — swapping in a
   real logo means replacing the mark + wordmark with one
   <img class="brand-lockup-logo">, no layout changes needed. */
.brand-lockup {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 22px;
}
.brand-lockup-mark { width: 44px; height: 44px; flex-shrink: 0; color: var(--ink); }
.brand-lockup-logo { max-height: 108px; width: auto; display: block; }
@media (max-width: 600px) { .brand-lockup-logo { max-height: 76px; } }
.brand-lockup-name {
  font-family: var(--font-display);
  font-size: 1.85rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--ink);
  line-height: 1.1;
}
.brand-lockup-sub { color: var(--muted); font-weight: 400; }

@media (max-width: 600px) {
  .brand-lockup { gap: 10px; margin-bottom: 16px; }
  .brand-lockup-mark { width: 34px; height: 34px; }
  .brand-lockup-name { font-size: 1.35rem; }
}

/* ------------------------------------------------------- landing band ---- */

.hero-accent { color: var(--accent); }

.landing-band {
  margin-top: 64px;
  padding-top: 44px;
  border-top: 1px solid var(--line);
}
.stat-strip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  margin-bottom: 52px;
}
.stat { background: var(--surface); padding: 20px 22px; }
.stat-value {
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--ink);
  letter-spacing: -0.02em;
}
.stat-label {
  margin-top: 4px;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--muted);
  font-weight: 600;
}
.landing-heading { font-size: 1.35rem; margin-bottom: 22px; }
.principle-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px; margin-bottom: 8px; }
.principle { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 22px 22px 24px; }
.principle-num { display: block; font-size: 0.7rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--accent); margin-bottom: 14px; }
.principle h4 { margin: 0 0 10px; font-size: 1rem; }
.principle p { color: var(--ink-2); font-size: 0.86rem; line-height: 1.6; margin: 0; }
@media (max-width: 900px) { .principle-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .principle-grid { grid-template-columns: 1fr; } }

/* Infinite one-directional belt of illustrated tool cards. The track holds
   the card set twice, back-to-back, and animates a single translateX(-50%)
   — the standard seamless-marquee technique, no JS timer or reset-snap. */
.belt-viewport {
  overflow: hidden;
  padding: 4px 0 6px;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}
.belt-track {
  display: flex;
  gap: 20px;
  width: max-content;
  animation: belt-scroll 34s linear infinite;
}
.belt-viewport:hover .belt-track { animation-play-state: paused; }
@keyframes belt-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) { .belt-track { animation-duration: 90s; } }

.belt-card {
  width: 230px;
  flex-shrink: 0;
  border-radius: var(--radius);
  background: linear-gradient(165deg, var(--card-color), var(--card-deep) 82%);
  padding: 18px 18px 16px;
  color: #fff;
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
}
.belt-card .tag { font-family: var(--font-body); font-size: 0.6rem; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; opacity: 0.85; margin-bottom: 10px; }
.belt-card .scene { height: 58px; margin-bottom: 10px; display: flex; align-items: flex-end; }
.belt-card h3 { font-size: 1.02rem; margin: 0 0 6px; color: #fff; }
.belt-card p { font-size: 0.8rem; line-height: 1.5; margin: 0; color: rgba(255, 255, 255, 0.86); }
.belt-card.on-light { color: #241a03; }
.belt-card.on-light p { color: rgba(36, 26, 3, 0.78); }
.belt-card.on-light .tag { opacity: 0.7; }

@media (max-width: 900px) {
  .stat-strip { grid-template-columns: repeat(2, 1fr); }
}

/* ---------------------------------------------------------- net worth ---- */

/* Personal view is a two-column layout: the entry form (main, ~64%) and a
   sticky sidebar (~36%) carrying stats/insights/charts, so "what your
   numbers mean" is visible at the top of the page the whole time you're
   scrolling a form that can run to 46 categories, not something you only
   reach after scrolling past all of it. Inverse ratio of .calc-layout
   (Financial Calculators' form is short, its sidebar is the main content;
   here the form IS the main content). */
.nw-layout { display: grid; grid-template-columns: 1.75fr 1fr; gap: 26px; align-items: start; }
.nw-sidebar { position: sticky; top: 18px; display: flex; flex-direction: column; gap: 16px; }
@media (max-width: 900px) {
  .nw-layout { grid-template-columns: 1fr; }
  .nw-sidebar { position: static; }
}

/* Assets and Liabilities read as two distinct zones while scrolling — a
   coloured rail plus a very faint wash, the same green/red vocabulary the
   rest of the app reserves for gain/loss, which fits here too: assets are
   literally the "what helps your net worth" side of the sheet, liabilities
   the "what hurts it" side. h2 already gets a rail via ::before (see the
   base rule) coloured --accent, which happens to equal --positive today —
   restated explicitly here so Assets stays correct even if that ever
   changes, and Liabilities is overridden to --negative. */
.nw-section { border-radius: var(--radius); padding: 16px 18px 4px; margin-bottom: 8px; border-left: 3px solid transparent; }
.nw-section-assets { border-left-color: var(--positive); background: color-mix(in oklab, var(--positive) 4%, transparent); }
.nw-section-assets h2::before { background: var(--positive); }
.nw-section-liabilities { border-left-color: var(--negative); background: color-mix(in oklab, var(--negative) 4%, transparent); }
.nw-section-liabilities h2::before { background: var(--negative); }

/* Each of the ~33 asset / 13 liability categories is a <details> — expand
   and collapse need no JS at all, only the "Expand/Collapse all" buttons
   and the running totals are scripted (see the inline script at the bottom
   of networth.ejs). Categories start open only when they already hold real
   data (routes/networth.js's `hasData`), so a first-time student sees a
   short list of closed rows, not a wall of empty forms — the whole point
   of this rewrite. Categories are nested under `.nw-group` section headers
   (Cash & Bank, Precious Metals, ...) so the sheer category count still
   scans as a handful of familiar groups, not a flat list of 33 things. */
.nw-controls { display: flex; gap: 8px; margin: -4px 0 14px; }
.nw-group { margin-bottom: 22px; }
.nw-group-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 10px;
}
.nw-group-head h3 { margin: 0; }
.nw-group-total {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: var(--ink);
  font-size: 0.98rem;
  flex-shrink: 0;
}
.nw-category {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  margin-bottom: 8px;
}
.nw-category[open] { background: var(--surface-2); }
.nw-category-summary {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.nw-category-summary::-webkit-details-marker { display: none; }
.nw-category-summary::before {
  content: '';
  flex-shrink: 0;
  width: 7px; height: 7px;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  transform: rotate(-45deg);
  transition: transform 0.15s ease;
}
.nw-category[open] > .nw-category-summary::before { transform: rotate(45deg); }
.nw-category-name { flex: 1; min-width: 0; font-weight: 600; font-size: 0.94rem; color: var(--ink); display: flex; align-items: center; }
.nw-category-total {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--ink-2);
  font-size: 0.88rem;
  flex-shrink: 0;
}
.nw-category-body { padding: 0 14px 14px; }
.lineitem-rows { margin: 12px 0 10px; }
.lineitem-row {
  display: flex;
  gap: 8px;
  margin-bottom: 8px;
  align-items: center;
}
/* Three fields per row (description, platform/institution, amount) — e.g.
   "Savings Account" / "HDFC Bank" / 200000, not one free-text line, so
   totals can still be trusted without parsing what someone typed.
   Description gets a bit more room than Platform since it's usually the
   longer of the two. */
.lineitem-row input[type=text] { flex: 1.3; min-width: 0; }
.lineitem-row input.lineitem-platform { flex: 1; min-width: 0; }
.lineitem-row input[type=number] { width: 140px; flex-shrink: 0; }
/* Same theme-attribute-prefix requirement as .btn-ghost/.calc-mode-tab —
   a real <button>, otherwise loses to `[data-theme="light"] button {
   color: #fff }` and renders invisible. */
.lineitem-remove,
[data-theme="light"] .lineitem-remove {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  padding: 0;
  background: transparent;
  border: 1px solid var(--line-strong);
  color: var(--muted);
  font-size: 1rem;
  line-height: 1;
  border-radius: var(--radius-sm);
}
.lineitem-remove:hover,
[data-theme="light"] .lineitem-remove:hover { background: var(--negative-glow); color: var(--negative); border-color: color-mix(in oklab, var(--negative) 40%, transparent); transform: none; }

/* Liquidity Mix insight — a stacked horizontal bar answering "how much of
   this could I actually get my hands on quickly," a different cut through
   the same asset totals than the by-group donut. Uses the categorical
   --series-* tokens, not --positive/--negative/--warn: illiquid doesn't
   mean "bad," it's a fact about EPF/real estate/retirement accounts that
   are *supposed* to be locked up, so it must not read as a status colour
   (see the "never used decoratively" rule at the top of this file). */
.nw-liquidity-bar { display: flex; height: 14px; border-radius: 999px; overflow: hidden; background: var(--surface-2); margin: 4px 0 12px; }
.nw-liquidity-seg { height: 100%; }
.nw-liquidity-seg.liquid { background: var(--series-1); }
.nw-liquidity-seg.semi { background: var(--series-4); }
.nw-liquidity-seg.illiquid { background: var(--series-3); }
.nw-liquidity-legend { display: flex; flex-wrap: wrap; gap: 14px; font-size: 0.84rem; color: var(--ink-2); padding: 0; margin: 0; list-style: none; }
.nw-liquidity-legend li { display: flex; align-items: center; gap: 7px; }
.nw-liquidity-legend .swatch { width: 11px; height: 11px; border-radius: 3px; flex-shrink: 0; }
.nw-liquidity-legend .swatch.liquid { background: var(--series-1); }
.nw-liquidity-legend .swatch.semi { background: var(--series-4); }
.nw-liquidity-legend .swatch.illiquid { background: var(--series-3); }

/* Net Worth Over Time upgraded from one line to three — net worth alone
   says THAT it moved, assets/liabilities alongside it says WHY (debt
   shrinking vs. assets growing look identical on a net-worth-only line).
   Net worth stays the fixed --tool-networth identity colour (bold, solid);
   the two supporting lines are thin and dashed so they read as context,
   not competing with the headline series. */
.chart-line-networth { stroke: var(--tool-networth); fill: none; stroke-width: 2.5; }
.chart-line-assets { stroke: var(--series-1); fill: none; stroke-width: 1.5; stroke-dasharray: 4,3; }
.chart-line-liabilities { stroke: var(--series-2); fill: none; stroke-width: 1.5; stroke-dasharray: 4,3; }

/* --------------------------------------------------- financial calculators */
/* Two-column layout so a calculator with only 1-3 short inputs never leaves
   the page mostly blank — the sidebar carries the "why this formula, what
   does it look like" content that used to have nowhere to live. */
/* Main column is deliberately narrow (~35%) — a calculator is 1-4 short
   inputs, it doesn't need half the screen. The sidebar carries the rest. */
.calc-layout { display: grid; grid-template-columns: 1fr 1.8fr; gap: 26px; align-items: start; }
.calc-sidebar { position: sticky; top: 18px; display: flex; flex-direction: column; gap: 16px; }
.calc-sidebar .card { margin-bottom: 0; }
@media (max-width: 860px) {
  .calc-layout { grid-template-columns: 1fr; }
  .calc-sidebar { position: static; }
}

.calc-blind-card { max-width: 620px; margin: 0 auto; }

/* Cash Flow Analyzer's NPV/IRR tabs — real navigation (different inputs
   per tab, not a client-side toggle), styled like the existing mode
   toggle above it but visually distinct so the two tab rows don't blur
   together. */
.calc-submode-tabs { display: flex; gap: 6px; margin-bottom: 18px; border-bottom: 1px solid var(--line); }
.calc-submode-tab {
  padding: 9px 4px;
  margin-bottom: -1px;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--muted);
  border-bottom: 2px solid transparent;
  text-decoration: none;
}
.calc-submode-tab:hover { color: var(--ink); }
.calc-submode-tab.active { color: var(--accent); border-bottom-color: var(--accent); }

/* Lump Sum's four sidebar tabs (FV/PV/Rate/Years) — client-side toggle
   between static explanation text, no navigation. */
.calc-mode-tabs { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 4px; }
/* Same theme-attribute-prefix requirement as .btn-ghost above — this is a
   real <button>, and a bare .calc-mode-tab loses to
   `[data-theme="light"] button { color: #fff }`, rendering invisible
   white-on-cream text. Don't drop this prefix on a future button class. */
.calc-mode-tab,
[data-theme="light"] .calc-mode-tab {
  padding: 6px 11px;
  font-size: 0.78rem;
  font-weight: 600;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--muted);
  cursor: pointer;
}
.calc-mode-tab:hover,
[data-theme="light"] .calc-mode-tab:hover { background: var(--surface-3); color: var(--ink); }
.calc-mode-tab.active,
[data-theme="light"] .calc-mode-tab.active { background: color-mix(in oklab, var(--accent) 16%, var(--surface-2)); border-color: color-mix(in oklab, var(--accent) 40%, transparent); color: var(--accent); }

/* Cash-flow year rows: "Year N" reads as a label, not part of the number
   field, so it needs its own fixed-width slot in the row. */
.cashflow-year-label { flex-shrink: 0; width: 52px; font-size: 0.82rem; color: var(--muted); font-weight: 600; }

/* ------------------------------------------------------------ timed test */
.test-size-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 28px; }
.test-size-card {
  /* Vibrant solid fill, same fixed --tool-* palette as the calculator and
     hub cards — per the user's general "2+ choice cards" preference. */
  display: block;
  text-align: center;
  border-radius: var(--radius);
  padding: 26px 16px;
  background: linear-gradient(165deg, var(--card-color), var(--card-deep) 82%);
  box-shadow: var(--shadow);
  text-decoration: none;
  color: #fff;
  transition: transform 0.18s ease;
}
@media (hover: hover) and (pointer: fine) {
  .test-size-card:hover { transform: translateY(-3px); }
}
.test-size-num { font-family: var(--font-display); font-size: 2.4rem; font-weight: 700; color: #fff; line-height: 1; }
.test-size-label { font-size: 0.92rem; font-weight: 600; margin-top: 6px; color: #fff; }
.test-size-card .hint-sm { color: rgba(255, 255, 255, 0.85); }
@media (max-width: 480px) { .test-size-grid { grid-template-columns: 1fr; } }

.test-progress-bar { height: 5px; border-radius: 999px; background: var(--surface-2); overflow: hidden; margin-bottom: 16px; }
.test-progress-fill { height: 100%; background: var(--accent); border-radius: 999px; }

.test-timer { font-variant-numeric: tabular-nums; font-weight: 700; color: var(--ink); }
.test-timer-low { color: var(--negative); }

/* -------------------------------------------- vibrant calculator cards -- */
/* Solid gradient fill, not a coloured border on a white card — same fixed
   --tool-* palette as the landing page's belt cards (see that block's
   comment for why these are fixed rather than theme-reactive tokens). Two
   explicit rows: actions (Practice / Timed Test) on top, the four
   categories below — a plain auto-fit grid wouldn't reliably keep that
   2-then-4 split at every viewport width. */
.calc-action-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 18px; }
.calc-category-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 28px; }
@media (max-width: 860px) {
  .calc-action-grid { grid-template-columns: 1fr; }
  .calc-category-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 520px) { .calc-category-grid { grid-template-columns: 1fr; } }

.calc-vibrant-card {
  display: block;
  border-radius: var(--radius);
  padding: 22px 22px 20px;
  background: linear-gradient(165deg, var(--card-color), var(--card-deep) 82%);
  color: #fff;
  text-decoration: none;
  box-shadow: var(--shadow);
  transition: transform 0.18s ease;
}
@media (hover: hover) and (pointer: fine) {
  .calc-vibrant-card:hover { transform: translateY(-3px); }
}
.calc-vibrant-card .calc-vibrant-icon {
  width: 38px; height: 38px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255, 255, 255, 0.18);
  border-radius: var(--radius-sm);
  margin-bottom: 14px;
  color: #fff;
}
.calc-vibrant-card h2 { color: #fff; margin: 0 0 6px; font-size: 1.05rem; }
.calc-vibrant-card p { color: rgba(255, 255, 255, 0.88); font-size: 0.86rem; line-height: 1.55; margin: 0 0 14px; }
.calc-vibrant-card .calc-vibrant-cta { font-size: 0.84rem; font-weight: 700; color: #fff; }

/* -------------------------------------------------------------- nav dropdown */

/* Groups a tool's several pages (e.g. every Portfolio Challenge page) under
   one nav trigger, rather than flattening them all into the top bar — see
   docs/CODE_GUIDE.md's note on why this exists. Click-toggled, not
   hover-only (hover-only menus fail on touch and violate the "reliance on
   hover" UX check), and closes on an outside click or Escape. */
.nav-dropdown { position: relative; }
.nav-dropdown-trigger {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  cursor: pointer;
}
.nav-dropdown-trigger::after {
  content: "";
  width: 6px; height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translateY(-2px);
  opacity: 0.7;
}
.nav-dropdown-panel {
  position: absolute;
  top: 100%;
  left: 0;
  margin-top: 6px;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  padding: 6px;
  min-width: 190px;
  display: none;
  flex-direction: column;
  z-index: 60;
}
.nav-dropdown.is-open .nav-dropdown-panel { display: flex; }
.nav-dropdown-panel a {
  padding: 8px 12px;
  border-radius: 8px;
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--ink-2);
  text-decoration: none;
  margin: 0;
}
.nav-dropdown-panel a:hover { background: var(--surface-2); color: var(--ink); }
.nav-dropdown-panel a.active { background: var(--surface-2); color: var(--ink); font-weight: 600; }

@media (max-width: 600px) {
  /* The whole nav is already collapsed behind the hamburger on mobile, so a
     second, nested collapse here would just be a menu inside a menu. The
     panel renders as a plain always-visible sub-list instead. */
  .nav-dropdown-panel {
    position: static;
    display: flex;
    box-shadow: none;
    border: none;
    background: transparent;
    padding: 0 0 4px 14px;
    margin-top: 0;
  }
  .nav-dropdown-trigger::after { display: none; }
}

/* ------------------------------------------------------------- tool hub ---- */

.tool-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 18px;
}
.tool-card {
  /* Solid vibrant gradient fill, not a colour-accented white card — same
     fixed --tool-* palette the calculator cards and landing-page belt use
     (see the "vibrant calculator cards" block below for why these are
     fixed rather than theme-reactive), per the user's general preference:
     any page with 2+ choice cards should read as bold solid colour, not
     white cards with a coloured border/icon. */
  --card-color: var(--accent);
  --card-deep: var(--accent);
  display: block;
  border-radius: var(--radius);
  background: linear-gradient(165deg, var(--card-color), var(--card-deep) 82%);
  box-shadow: var(--shadow);
  padding: 24px 24px 22px;
  text-decoration: none;
  color: #fff;
  transition: transform 0.18s ease;
}
.tool-card--portfolio   { --card-color: var(--tool-portfolio); --card-deep: var(--tool-portfolio-deep); }
.tool-card--networth    { --card-color: var(--tool-networth);  --card-deep: var(--tool-networth-deep); }
.tool-card--calculators { --card-color: var(--tool-calc);      --card-deep: var(--tool-calc-deep); }
.tool-card--goals       { --card-color: var(--tool-goals);     --card-deep: var(--tool-goals-deep); }
.tool-card--loan        { --card-color: var(--tool-loan);      --card-deep: var(--tool-loan-deep); }
@media (hover: hover) and (pointer: fine) {
  a.tool-card:hover { transform: translateY(-3px); }
}
.tool-card-icon {
  width: 42px; height: 42px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255, 255, 255, 0.18);
  border-radius: var(--radius-sm);
  margin-bottom: 14px;
  color: #fff; /* SVGs inside use stroke/fill="currentColor" */
}
.tool-card h2 { color: #fff; }
.tool-card-desc { color: rgba(255, 255, 255, 0.88); font-size: 0.9rem; line-height: 1.55; margin: 0 0 16px; }
.tool-card-stat { margin-bottom: 16px; display: flex; flex-direction: column; gap: 2px; }
.tool-card-stat .value,
.tool-card-stat .pl-positive,
.tool-card-stat .pl-negative { color: #fff; font-weight: 700; }
.tool-card-stat .hint-inline { color: rgba(255, 255, 255, 0.78); font-weight: 400; }
.tool-card-cta { font-size: 0.85rem; font-weight: 700; color: #fff; }
.tool-card-soon {
  background: var(--surface);
  border: 1px solid var(--line);
  color: inherit;
  opacity: 0.6;
  cursor: default;
}
.tool-card-soon:hover { transform: none; }
.tool-card-soon .tool-card-icon { background: var(--surface-2); border: 1px solid var(--line); }
.tool-card-soon .tool-card-desc { color: var(--ink-2); }

/* ==========================================================================
   Legacy inline-style bridge — REMOVED
   --------------------------------------------------------------------------
   The original bridge retargeted hardcoded light-theme colours (#666, #2a78d6,
   #c3c2b7 …) that were still sitting in inline `style` attributes and SVG
   presentation attributes across the view templates.
   Every one of those has since been replaced at source:
     - inline `style` colours   -> theme tokens, or a .hint / .muted utility
     - SVG stroke/fill colours  -> .chart-* / .series-* classes above
       (presentation attributes can't reliably carry var(), so those had to
        become classes rather than token values)
   Nothing depends on the bridge any more, so it is gone rather than left to
   ossify. If a hardcoded colour reappears in a template, fix it there — do
   not reinstate a bridge rule for it.
   ========================================================================== */

/* ========================================================== responsive === */

@media (max-width: 900px) {
  .watchlist-table .col-name { max-width: 160px; }
  .watchlist-table .col-sector { max-width: 130px; }
}

@media (max-width: 600px) {
  .container { padding: 0 12px; margin: 16px auto 40px; }
  h1 { font-size: 1.3rem; }
  h2 { font-size: 1rem; }
  .navbar { padding: 10px 14px; }
  .nav-toggle { display: block; order: 3; }
  .theme-toggle { order: 2; margin-left: auto; }
  .nav-links {
    display: none;
    width: 100%;
    flex-direction: column;
    align-items: stretch;
    margin-top: 10px;
    border-top: 1px solid var(--line);
    padding-top: 8px;
    order: 4;
  }
  .nav-links.open { display: flex; }
  .nav-links a {
    margin-left: 0;
    font-size: 0.92rem;
    padding: 10px 12px;
    border-radius: var(--radius-sm);
  }
  .nav-user { margin-left: 0; font-size: 0.92rem; padding: 10px 12px; }
  th, td { padding: 9px 11px; font-size: 0.8rem; }
  .summary-box { min-width: 140px; padding: 13px 15px; }
  .summary-box .value { font-size: 1.15rem; }
  .summary-box.hero .value { font-size: 1.7rem; }
  .summary-box:hover { transform: none; }
  .card { padding: 15px 16px; }
  button, .btn { padding: 9px 15px; font-size: 0.85rem; }
  /* .row-actions button/.btn (line ~299) ties this rule's specificity, so
     without a matching override here it wins on source order alone and the
     Trade/Remove buttons stayed pinned at their dense desktop size on phones
     — the one place accurate taps matter most. */
  .row-actions button,
  .row-actions .btn { padding: 8px 13px; font-size: 0.82rem; }
  .auth-hero h1 { font-size: 1.85rem; }
  .auth-layout { min-height: 0; gap: 28px; }
  .podium-card.rank-1, .podium-card.rank-2, .podium-card.rank-3 { order: initial; }
  .lineitem-row { flex-wrap: wrap; }
  .lineitem-row input[type=text] { flex-basis: 100%; }
  .lineitem-row input[type=number] { flex: 1; width: auto; }
}

/* --------------------------------------------------------------- print --- */
/* `.no-print` is a generic utility (any page can use it); the token reset
   below is currently only exercised by the Net Worth report
   (views/networth-report.ejs), the one page in the app meant to be printed
   or saved as a PDF. Printing while dark mode is on would otherwise waste
   a page of ink on a near-black background, so this forces the light
   theme's values regardless of which theme is active on screen — light
   itself needs no override, it already prints fine. */
.no-print { display: revert; }
@media print {
  .navbar, .no-print { display: none !important; }
  a { color: inherit; }
  .container { max-width: 100%; width: 100%; margin: 0; padding: 0; }
  .card, .nw-section, .summary-box { break-inside: avoid; box-shadow: none; }
  [data-theme="dark"] {
    --bg: #f6f7f4; --bg-2: #eceee8;
    --surface: #ffffff; --surface-2: #f4f5f1; --surface-3: #e9ebe5;
    --line: rgba(12, 20, 30, 0.10); --line-strong: rgba(12, 20, 30, 0.18);
    --ink: #10161f; --ink-2: #3a4553; --muted: #66707e; --muted-2: #8b95a3;
    --accent: #0f8a5b; --positive: #0f8a5b; --negative: #c8362f; --warn: #a3730c;
    --gold: #a3730c; --silver: #6b7480; --bronze: #96562c;
    --series-1: #0736c2; --series-2: #cc6004; --series-3: #1d93a6;
    --series-4: #8b4be5; --series-5: #810b74; --series-6: #3d5700;
  }
}
