/* ==========================================================================
   Profitable Prop Trader — design system
   Dark restyle: near-black ground (#0E1011), off-white ink, brand-green
   accent ramp held at ~101°. All type in Roboto — headings, numerics
   and body copy. Green = profit / up-candles. Accent TEXT on the dark
   ground uses the 400 step (#6CB54A, 7.6:1 on bg); 500 is the CTA fill;
   900 is the dark fill behind light accent text. No glassmorphism.
   ========================================================================== */
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;600;700&display=swap');

:root {
  /* Ground & ink — near-black ground, off-white ink */
  --color-bg: #0E1011;          /* near-black ground */
  --color-surface: #1A1D1C;     /* raised surface / dialogs / toasts / inputs */
  --color-text: #E8EAE6;        /* off-white ink (15.8:1 on bg) */
  --color-divider: color-mix(in srgb, #E8EAE6 14%, transparent);
  --color-muted: #8B918D;       /* secondary text (5.9:1 on bg) */

  /* Brand green — hue ~101° (logo green). 400 is the accent TEXT step on
     dark (7.6:1 on bg); 500 is base / CTA fill; 900 is the dark fill behind
     light accent text. */
  --color-accent: #578F3D;      /* base / CTA fill */
  --color-accent-100: #E8F3D6;
  --color-accent-200: #C8E2A6;
  --color-accent-300: #A6D478;
  --color-accent-400: #6CB54A;  /* accent text on dark (7.6:1 on bg) */
  --color-accent-500: #578F3D;  /* base / CTA fill */
  --color-accent-600: #4B643F;
  --color-accent-700: #3B5E2C;  /* ramp completeness only — not the text step */
  --color-accent-800: #2F4A23;
  --color-accent-900: #143C02;  /* dark green fill behind light accent text */

  /* Neutral ramp for chips, table rules, subtle fills */
  --color-neutral-100: #E6E9E5;
  --color-neutral-200: #C8CDC9;
  --color-neutral-300: #A2A8A4;
  --color-neutral-400: #6E7470;
  --color-neutral-500: #525854;
  --color-neutral-600: #3A403C;
  --color-neutral-700: #272C29;
  --color-neutral-800: #16191A; /* dark fill replacing old light neutral fills */
  --color-neutral-900: #0C0E0F;

  --font-heading: "Roboto", system-ui, sans-serif;
  --font-heading-weight: 700;
  --font-body: "Roboto", system-ui, sans-serif;
  /* Tabular figures for numeric columns — same monospace family so
     allocations / percentages / prices line up down the page. */
  --font-numeric: "Roboto", system-ui, sans-serif;

  --space-0-5: 2px;
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-6: 24px;
  --space-8: 32px;

  --radius-sm: 2px;
  --radius-md: 4px;
  --radius-lg: 6px;

  /* ── Density scale (Story 28: Yahoo Finance density) ──
     The quote table needs steps between the ones the blueprint system had:
     a 10px gutter, a 6px micro-gap, and a numeric/label type ramp small
     enough that a whole firm fits one 56px line. Declared here so the
     tokens-only rule still holds for the density pass. */
  --space-1-5: 6px;
  --space-2-5: 10px;

  /* Page measure. A market table wants width; 1080px could not seat the
     eight columns without crushing the firm name. */
  --container-max: 1200px;

  /* Dense type ramp — mono for anything that must scan vertically. */
  --text-micro: 10px;   /* column labels, LW #N, +N overflow counts */
  --text-xs: 11px;      /* tags, country, review count */
  --text-sm: 13px;      /* secondary copy, table cells */
  --text-num: 15px;     /* the quote figures */
  --text-name: 15px;    /* firm name in the row */
  --text-rank: 20px;    /* the rank figure */
  --text-title-dense: 30px; /* profile H1 at density */
  --text-title-mobile: 26px; /* every page title at <=640, homepage and profile alike */

  /* Heading ramp (Story 33). Four steps and a label step, and the base
     h1-h6 rules take the same ones, so a bare heading and a classed one
     cannot disagree: hero 46 (the homepage only) / page 30 / section 20 /
     sub 16 / label --text-sm 13 uppercase. --text-title-section duplicates
     --text-rank's value on purpose: binding every section heading to the
     rank figure would make a change to the quote table resize the page. */
  --text-title-hero: 46px;
  --text-title-section: 20px;
  --text-title-sub: 16px;

  /* The quote row itself. */
  --row-height: 56px;
  --row-pad-y: var(--space-2);
  --row-pad-x: var(--space-3);
  --col-gap: var(--space-2-5);

  /* One grid, declared once, used by the header row and every firm row.
     No `auto` or `max-content` anywhere in it: those size to content, so
     ten separate grids would land their columns at ten different x
     positions and the alignment criterion would fail by a few invisible
     px. Every track resolves from the container width alone.

     Retuned for parity, and the budget is measured rather than guessed. At
     1.3fr / 1.25fr the identity track resolved to ~232px and three firm names
     ("Alpha Capital Group", "Funded Trading Plus", "Goat Funded Trader" —
     18-19 glyphs, 168px at --text-name) ellipsised. The numeric tracks were
     sized for prose: Split now carries the derived SplitShort figure (63px at
     its widest), Max Alloc the widest "$400,000" (72px), Plans one digit.

     **The binding width is 1141px, not 1200.** The eight-column band runs
     1141-1200 and both fr tracks are narrowest at its foot, where the elastic
     pool is 463px. Measured content maxima across the ten rows: identity cell
     265px (plate 88 + gap 8 + name 168), tags cell 155px, and the tags cell
     masks its last 20px, so its counted overflow chip needs to land 20px clear
     of the right edge to render opaque — a 175px budget. 278 + 185 at 1141
     clears both. Two platform chips would have needed 248px of tags track and
     do not fit at any split, which is why the cap is one platform + one asset,
     each with its own count. */
  --firm-grid: 84px minmax(0, 1.5fr) 80px 72px 40px minmax(0, 1fr) 124px 152px;

  /* Elevation — black-tinted shadows on the dark ground (offsets/blur
     unchanged from the light theme; only the tint source swapped to #000). */
  --shadow-sm: 0 1px 2px color-mix(in srgb, #000 40%, transparent);
  --shadow-md: 0 3px 10px color-mix(in srgb, #000 50%, transparent);
  --shadow-lg: 0 12px 32px color-mix(in srgb, #000 60%, transparent);
}

*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.55;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  line-height: 1.12;
  letter-spacing: -0.01em;
  margin: 0 0 var(--space-2);
}
h1 { font-size: var(--text-title-dense); }
h2, h3 { font-size: var(--text-title-section); }
h4, h5 { font-size: var(--text-title-sub); }
h6 { font-size: var(--text-sm); letter-spacing: 0.08em; text-transform: uppercase; }
p { margin: 0 0 var(--space-3); }
a { color: var(--color-accent-400); text-underline-offset: 3px; }
img { display: block; max-width: 100%; }

:focus { outline: none; }
:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
::selection { background: color-mix(in srgb, var(--color-accent) 28%, transparent); }

.text-muted { color: var(--color-muted); }
.text-accent { color: var(--color-accent-400); }
.text-center { text-align: center; }
.text-start { text-align: left; }
.text-end { text-align: right; }

/* ── Layout ── */
.container { width: 100%; max-width: var(--container-max); margin: 0 auto; padding: 0 var(--space-4); }
.max-w-2xl { max-width: 720px; }
.grid { display: grid; }
.grid-cols-2 { grid-template-columns: 1fr 1fr; }
.d-flex { display: flex; }
.ms-auto { margin-left: auto; }
.w-full { width: 100%; }
.hidden { display: none !important; }
.border-top { border-top: 1px solid var(--color-divider); }

/* Spacing utilities. The margin ones are NOT here — see the end of the file. */
.gap-2 { gap: var(--space-2); }
.gap-6 { gap: var(--space-6); }

/* ── Header / nav ── */
.ppt-header {
  border-bottom: 1px solid var(--color-divider);
  background: var(--color-bg);
}
.header-content {
  display: flex; align-items: center; gap: var(--space-4);
  /* Vertical only. `padding: X 0` is a shorthand at equal specificity to
     `.container`, so it zeroed the page gutter: the logo sat flush against
     the viewport edge while main sat --space-4 in. Longhands keep both. */
  padding-top: var(--space-3); padding-bottom: var(--space-3);
}
/* Story 31: the logo is the same first child on every page and absorbs the
   whole leftover width, so nothing else in the header may carry an auto
   margin. `.brand` plus a sibling `.ms-auto` gave two auto margins, which
   centres the logo in the gap instead of pinning it left. */
.brand { margin-right: auto; display: flex; align-items: center; text-decoration: none; }
.brand-logo { height: 48px; width: auto; }
.visitor-counter-badge {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-family: var(--font-heading);
  font-size: var(--text-sm); font-weight: 500; color: var(--color-accent-400);
  padding: var(--space-1-5) var(--space-3); border: 1px solid var(--color-divider);
}
.live-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--color-accent-500);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-accent) 70%, transparent);
  animation: ppt-pulse 2s infinite;
}
.counter-unavailable { color: var(--color-muted); }
@keyframes ppt-pulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-accent) 60%, transparent); }
  70%  { box-shadow: 0 0 0 8px color-mix(in srgb, var(--color-accent) 0%, transparent); }
  100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-accent) 0%, transparent); }
}

/* ── Error page (Story 31) ──
   Sits with the header because it is chrome, not a page type: the same
   ground, the same header, and nothing that reads the store. */
.error-page { padding: var(--space-8) 0; }
.error-status {
  font-family: var(--font-numeric); font-size: var(--text-rank);
  letter-spacing: 0.08em; color: var(--color-accent-400);
  margin: 0 0 var(--space-2);
}
.error-title { font-size: var(--text-title-dense); margin-bottom: var(--space-3); }
.error-message { color: var(--color-muted); margin-bottom: var(--space-6); }

/* ── Hero ── */
.hero-section { padding: var(--space-8) 0 var(--space-6); }
.badge-pill {
  display: inline-block; font-size: var(--text-xs); font-weight: 600;
  font-family: var(--font-heading);
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--color-accent-200);
  background: var(--color-accent-900);
  border: 1px solid var(--color-accent-700);
  padding: 5px var(--space-3);
}
.hero-title { font-size: var(--text-title-hero); text-wrap: balance; }
.hero-subtitle { font-size: 17px; color: var(--color-muted); max-width: 640px; margin: 0 auto; }

/* ── Buttons ── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-1-5);
  cursor: pointer; text-decoration: none;
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 14px; line-height: 1.2; color: var(--color-text); white-space: nowrap;
  background: transparent; border: 1px solid var(--color-divider);
  border-radius: 0; padding: var(--space-2) var(--space-4);
}
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-sm { font-size: var(--text-sm); padding: var(--space-1) var(--space-2); }
.btn-primary {
  background: var(--color-accent-500); color: var(--color-bg);
  border-color: var(--color-accent-500);
}
.btn-primary:hover { background: var(--color-accent-400); border-color: var(--color-accent-400); }
.btn-primary:active { background: var(--color-accent-500); border-color: var(--color-accent-500); }
.btn-secondary { border-color: var(--color-divider); }
.btn-secondary:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.btn-secondary:active { background: color-mix(in srgb, var(--color-text) 14%, transparent); }

/* ── Cards ── */
.card {
  display: block; padding: var(--space-4);
  background: transparent;
}
.card-title { font-family: var(--font-heading); font-weight: var(--font-heading-weight); font-size: var(--text-title-section); }

/* Padding utilities sit AFTER .card deliberately. `.card { padding }` is a
   shorthand at equal specificity, so while these lived up with the other
   utilities every `class="card p-6"` panel silently rendered at 16px rather
   than the 24px the markup asked for — the trap recorded on 2026-08-24.
   Source order is the fix; a specificity hack would only hide it. */
.p-3 { padding: var(--space-3); }
.p-4 { padding: var(--space-4); }
.p-6 { padding: var(--space-6); }
.pt-3 { padding-top: var(--space-3); }
.pt-4 { padding-top: var(--space-4); }
.py-6 { padding-top: var(--space-6); padding-bottom: var(--space-6); }
.py-8 { padding-top: var(--space-8); padding-bottom: var(--space-8); }

/* ── Tags ──
   One rule across both views: a GREEN OUTLINE means "platform" — the ranking
   row's platform tags and the profile's .logo-chip. Everything else is
   neutral: assets, instrument types, max leverage, resource categories,
   payout methods. Before this the outline meant "platform" on the ranking row
   and "leverage" / "category" on the profile, so the same green border said
   two different things about the same firm. */
.tag {
  display: inline-flex; align-items: center; font-size: var(--text-micro);
  font-family: var(--font-heading); line-height: 1.5;
  letter-spacing: 0.02em; padding: 0 var(--space-1); border-radius: 0;
  white-space: nowrap; flex: none;
}
.tag-outline, .tag-platform { border: 1px solid var(--color-accent-500); color: var(--color-accent-400); }
.tag-neutral, .tag-asset { background: var(--color-neutral-800); color: var(--color-neutral-200); border: 1px solid var(--color-neutral-700); }
.tag-accent { background: var(--color-accent-900); color: var(--color-accent-200); border: 1px solid var(--color-accent-700); }

/* ==========================================================================
   Ranking list — the quote table (Story 28: Yahoo Finance density)
   One firm per row, one row per line. Every row is the same grid
   (--firm-grid) as the header strip above it, so rank / firm / the three
   numeric columns / tags / coupon / actions land on identical x positions
   all the way down. Rows are delimited by a single hairline — no box,
   no frame (settled 2026-08-24, shipped in PR #24).
   ========================================================================== */
.ranking-list { display: flex; flex-direction: column; padding-bottom: var(--space-8); }

/* Repeated-at-top column header. Sticky so the columns stay named while the
   ten rows scroll under it. */
.firm-table-head {
  position: sticky; top: 0; z-index: 5;
  display: grid; grid-template-columns: var(--firm-grid);
  gap: var(--col-gap); align-items: center;
  padding: var(--space-1-5) var(--row-pad-x);
  background: var(--color-bg);
  border-bottom: 1px solid var(--color-divider);
  font-family: var(--font-heading); font-weight: 600;
  font-size: var(--text-micro); letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--color-muted);
}
.firm-table-head .num { text-align: right; }

.firm-card { padding: 0; }
/* One hairline between adjacent rows — none above the first or below the last. */
.firm-card + .firm-card { border-top: 1px solid var(--color-divider); }

.firm-row {
  display: grid; grid-template-columns: var(--firm-grid);
  gap: var(--col-gap); align-items: center;
  min-height: var(--row-height);
  padding: var(--row-pad-y) var(--row-pad-x);
}
.row-clickable { cursor: pointer; transition: background .12s ease; }
.row-clickable:hover { background: color-mix(in srgb, var(--color-accent-500) 12%, transparent); }
.row-clickable:focus-visible { outline: 2px solid var(--color-accent); outline-offset: -2px; }

/* ─ Rank cell: the figure, then the movement glyph over last week's slot ─ */
.rank-cell { display: flex; align-items: center; gap: var(--space-1); min-width: 0; }
.rank-number {
  font-family: var(--font-numeric); font-variant-numeric: tabular-nums;
  font-size: var(--text-rank); font-weight: 700; line-height: 1.1;
  color: var(--color-accent-400); white-space: nowrap;
}
/* Movement and LW stack in a micro-column so the row stays one line tall. */
.rank-meta { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; }

/* ─ Firm cell: plate, then name over country + rating ─ */
.identity-col { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
/* Landscape plate. Most firm marks are wide wordmarks — up to 10:1 — and a
   square box makes width the binding constraint, crushing them to a few
   pixels tall. The plate is wider than tall so wordmarks get real height;
   square marks still cap on height and are unaffected. At quote-row density
   it is the largest plate a 56px line can seat. */
.logo-box {
  width: 88px; height: 40px; flex: none; display: grid; place-items: center;
  border: 1px solid var(--color-divider); background: transparent;
}
.logo-box.lg { width: 140px; height: 76px; }
.logo-box.sm { width: 56px; height: 40px; }
/* Pixel maxima, not percentages: a percentage max-height on a grid item does
   not resolve against the plate here, so square marks overflowed it. */
.firm-logo-img { max-width: 78px; max-height: 30px; object-fit: contain; }
.logo-box.lg .firm-logo-img { max-width: 126px; max-height: 62px; }
.logo-box.sm .firm-logo-img { max-width: 48px; max-height: 32px; }
.firm-logo-img.sm { max-width: 48px; max-height: 32px; }
/* Monogram fallback when a firm logo asset isn't wired up yet */
.logo-mono { font-family: var(--font-heading); font-weight: 700; font-size: var(--text-rank); color: var(--color-accent-400); line-height: 1; }
.logo-box.lg .logo-mono { font-size: 36px; }
.logo-box.sm .logo-mono { font-size: 18px; }
.toast-logo .logo-mono { font-size: 18px; }

.identity-meta { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.firm-header { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.firm-name-link { text-decoration: none; color: inherit; min-width: 0; }
.firm-name-link:hover .firm-name { color: var(--color-accent-400); }
.firm-name {
  font-size: var(--text-name); line-height: 1.2; margin: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Country and rating share the second line so the cell is two tight lines. */
.identity-sub { display: flex; align-items: center; gap: var(--space-1-5); min-width: 0; white-space: nowrap; }
.country-pill {
  font-size: var(--text-micro); line-height: 1.4; color: var(--color-muted);
  border: 1px solid var(--color-divider); padding: 0 var(--space-1);
}
.star-badge { color: var(--color-accent-400); font-weight: 600; font-size: var(--text-xs); }
.review-count { color: var(--color-muted); font-size: var(--text-micro); }

/* ─ Numeric cells: right-aligned tabular figures, so the columns scan ─ */
.num-cell { display: flex; flex-direction: column; align-items: flex-end; justify-content: center; min-width: 0; overflow: hidden; }
.stat-label { font-family: var(--font-heading); font-size: var(--text-micro); letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-muted); }
.stat-value {
  display: block; width: 100%; text-align: right;
  font-family: var(--font-numeric); font-variant-numeric: tabular-nums;
  font-weight: 600; font-size: var(--text-num); line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ─ Tags cell: one line, honest overflow ─
   Platforms and assets keep their meaning (outline vs neutral) but are
   capped and counted rather than wrapped to three lines. */
.tags-cell {
  display: flex; align-items: center; gap: var(--space-1);
  flex-wrap: nowrap; overflow: hidden; min-width: 0;
  /* Only the alpha channel of a mask is read, so the colour stop is a token
     purely to keep the no-raw-hex rule literal. */
  -webkit-mask-image: linear-gradient(to right, var(--color-text) calc(100% - 20px), transparent);
          mask-image: linear-gradient(to right, var(--color-text) calc(100% - 20px), transparent);
}
.tag-more {
  font-family: var(--font-heading); font-size: var(--text-micro); font-weight: 600;
  color: var(--color-muted); flex: none;
}

/* ─ Coupon cell ─ */
.coupon-cell { display: flex; align-items: center; min-width: 0; }

/* ─ Actions cell ─ */
.actions-cell { display: flex; align-items: center; justify-content: flex-end; gap: var(--space-1); min-width: 0; }

/* Story 8: hidden discount code — reveal affordance separate from modal trigger */
.discount-box {
  position: relative; display: flex; flex-direction: column; align-items: center; gap: 0;
  border: 1px dashed var(--color-accent-500); padding: var(--space-0-5) var(--space-1-5);
  text-align: center; width: 100%; min-width: 0;
}
.discount-badge {
  font-family: var(--font-numeric); font-variant-numeric: tabular-nums;
  font-weight: 700; font-size: var(--text-xs); line-height: 1.3;
  color: var(--color-accent-400); white-space: nowrap;
}
.discount-reveal-line { display: flex; align-items: baseline; gap: var(--space-1); max-width: 100%; }
.discount-code-blur {
  font-family: var(--font-numeric); font-weight: 600; font-size: var(--text-micro);
  line-height: 1.3; letter-spacing: 0.04em; color: var(--color-text);
  filter: blur(4px); transition: filter .18s ease; user-select: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.discount-box.revealed .discount-code-blur { filter: blur(0); user-select: text; }
.discount-box.revealed .reveal-prompt { visibility: hidden; }
.reveal-prompt { font-size: var(--text-micro); color: var(--color-muted); }
.reveal-btn {
  appearance: none; background: transparent; border: 0; cursor: pointer;
  font: inherit; font-family: var(--font-heading); font-size: var(--text-micro);
  color: var(--color-accent-400); flex: none;
  text-decoration: underline; text-underline-offset: 2px; padding: 0;
}
.reveal-btn:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.discount-box.revealed .reveal-btn { display: none; }

/* ── Firm profile ── */
.profile-hero-row { display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; }
.profile-title-box { display: flex; flex-direction: column; gap: var(--space-1); }
/* The profile H1 drops off the 46px hero size: at density the page is a
   record, not a landing page. */
.profile-title-box .hero-title { font-size: var(--text-title-dense); }
.meta-pills { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
/* Key/value rows read as a two-column ledger: label muted left, tabular
   figure right, one hairline per row. */
.param-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.param-list li {
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-3);
  padding: var(--space-1) 0; border-bottom: 1px solid var(--color-divider);
  font-family: var(--font-numeric); font-variant-numeric: tabular-nums;
  font-size: var(--text-sm); line-height: 1.4;
}
.param-list li span { font-family: var(--font-body); color: var(--color-muted); }
.tag-row { display: flex; flex-wrap: wrap; gap: var(--space-1); }
.spread-grid { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2); }
.spread-grid li {
  display: flex; flex-direction: column; gap: 0;
  font-family: var(--font-numeric); font-variant-numeric: tabular-nums;
  font-size: var(--text-sm); line-height: 1.4;
}
.spread-grid li span { font-family: var(--font-heading); color: var(--color-muted); font-size: var(--text-micro); letter-spacing: 0.06em; text-transform: uppercase; }
.logo-strip { display: flex; flex-wrap: wrap; gap: var(--space-1-5); align-items: center; }
/* A platform chip on the profile is the same statement as a .tag-outline on
   the ranking row, so it takes the same green outline and accent-400 ink. The
   icon is the only thing that stays — the surface fill goes, because a filled
   chip beside an outlined tag reads as a different category. */
.logo-chip {
  display: inline-flex; align-items: center; gap: var(--space-1);
  padding: var(--space-0-5) var(--space-1-5);
  border: 1px solid var(--color-accent-500); background: transparent;
  color: var(--color-accent-400);
  font-size: var(--text-xs); font-weight: 500; line-height: 1.6;
}
.logo-chip img { width: 18px; height: 14px; object-fit: contain; }

/* ── Articles per firm (story 26) — same ledger rhythm as the lists above. */
.article-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.article-list li {
  display: flex; flex-direction: column; gap: var(--space-0-5);
  padding: var(--space-1-5) 0; border-bottom: 1px solid var(--color-divider);
  font-size: var(--text-sm); line-height: 1.45;
}
.article-list li:last-child { border-bottom: 0; }
.article-source { color: var(--color-muted); font-size: var(--text-xs); }

/* ── Strategy resource list ──
   Carried browser defaults (disc bullets, 16px indent, 1.55 body leading)
   while every panel around it moved to the dense rhythm, so it read as a
   different system on the same page. Hairline-delimited rows, like the
   ledger lists above it. */
.resource-list { list-style: none; margin: 0; padding: 0; }
.resource-list li { padding: var(--space-1-5) 0; border-bottom: 1px solid var(--color-divider); }
.resource-list li:last-child { border-bottom: 0; }
.resource-list p { margin: var(--space-0-5) 0 0; font-size: var(--text-sm); line-height: 1.45; }
.resource-link {
  display: flex; align-items: baseline; gap: var(--space-1-5);
  color: var(--color-text); text-decoration: none; font-size: var(--text-sm);
}
.resource-link:hover strong { color: var(--color-accent-400); }
.resource-link .text-muted { font-family: var(--font-numeric); font-size: var(--text-micro); }

/* ── Data table ──
   A profile can render 40+ challenge rows (all plans x all account sizes),
   so cell padding is the whole readability budget. Header row gets its own
   fill and sticks; numeric columns are right-aligned tabular figures. */
.table-scroll { overflow-x: auto; }
.data-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.data-table th {
  text-align: left; font-size: var(--text-micro); letter-spacing: 0.1em; text-transform: uppercase;
  font-family: var(--font-heading); font-weight: 600; white-space: nowrap;
  color: var(--color-muted); background: var(--color-neutral-800);
  padding: var(--space-1) var(--space-2);
  border-bottom: 1px solid var(--color-divider);
}
.data-table td {
  padding: var(--space-1) var(--space-2); line-height: 1.4; white-space: nowrap;
  border-bottom: 1px solid color-mix(in srgb, var(--color-text) 8%, transparent);
}
.data-table tbody tr:hover { background: color-mix(in srgb, var(--color-text) 4%, transparent); }
.data-table th.num, .data-table td.num { text-align: right; }
.data-table td.num { font-family: var(--font-numeric); font-variant-numeric: tabular-nums; }

/* ── Forms ── */
.form-stack { display: flex; flex-direction: column; }
.form-group { display: flex; flex-direction: column; gap: var(--space-1-5); }
.form-label { font-size: 12px; color: var(--color-muted); }
.form-control {
  width: 100%; min-height: 40px; padding: var(--space-2) var(--space-3); font: inherit; font-size: 14px;
  color: var(--color-text); background: var(--color-surface);
  border: 1px solid var(--color-divider); border-radius: 0;
  caret-color: var(--color-accent);
}
select.form-control { min-height: 40px; }
textarea.form-control { min-height: 96px; resize: vertical; }
.form-control:hover { border-color: color-mix(in srgb, var(--color-text) 45%, transparent); }
.form-control:focus-visible { border-color: var(--color-accent); outline-offset: 0; }
.form-checkbox-label { display: inline-flex; align-items: center; gap: var(--space-2); font-size: 14px; cursor: pointer; }
.form-checkbox-label input { accent-color: var(--color-accent); width: 16px; height: 16px; }
/* A read-only value inside a form group is a field value, not a table cell:
   .stat-value right-aligns for the quote columns, which put the label and
   its value at opposite ends of a 720px form. */
.form-group .stat-value { text-align: left; margin: 0; }

.subtitle-text, .small-text { color: var(--color-muted); }
.small-text { font-size: var(--text-sm); }
.sub-title { font-family: var(--font-heading); font-weight: var(--font-heading-weight); font-size: var(--text-title-sub); }

.alert { border: 1px solid var(--color-divider); }
.alert-success { background: var(--color-accent-900); border-color: var(--color-accent-700); color: var(--color-accent-200); }
/* No red in this palette, so failure reads on the neutral ramp and the ✕
   glyph in the partial carries the meaning — never colour alone. */
.alert-error { background: var(--color-neutral-800); border-color: var(--color-neutral-600); color: var(--color-neutral-200); }
.success-icon { font-size: 40px; }

/* ── Dialog (Story 6/8 modals): Industry dialog pattern ── */
.dialog-backdrop {
  position: fixed; inset: 0; z-index: 50; display: grid; place-items: center;
  padding: var(--space-4);
  background: color-mix(in srgb, var(--color-neutral-900) 50%, transparent);
}
.dialog {
  width: min(460px, 100%); max-height: 90vh; overflow-y: auto;
  display: flex; flex-direction: column; gap: var(--space-3);
  padding: var(--space-6); border: 1px solid var(--color-divider);
  background: var(--color-surface); box-shadow: var(--shadow-lg);
}
.dialog-header { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-3); }
.dialog-title { font-family: var(--font-heading); font-weight: var(--font-heading-weight); font-size: var(--text-title-section); margin: 0; }
.dialog-close {
  appearance: none; background: transparent; border: 0; cursor: pointer;
  /* A <button> does not inherit the page face: this rendered in Arial while
     every heading beside it was JetBrains Mono. font-family, not the `font`
     shorthand, which would silently reset the size and leading below. */
  font-family: var(--font-heading);
  font-size: 24px; line-height: 1; color: var(--color-muted); padding: 0 var(--space-1);
}
.dialog-close:hover { color: var(--color-text); }
.dialog-close:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.dialog-body { font-size: 14px; }

.firm-coupon-header { display: flex; flex-direction: column; align-items: center; gap: var(--space-2); }
.code-box-display {
  display: flex; align-items: center; justify-content: center; gap: var(--space-2);
  border: 1px dashed var(--color-accent-500); padding: var(--space-3);
}
.code-text { font-family: var(--font-heading); font-weight: 700; font-size: 18px; letter-spacing: 0.08em; color: var(--color-accent-400); }

.score-banner { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); padding: var(--space-3); border: 1px solid var(--color-divider); }
.score-label { font-family: var(--font-heading); font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--color-muted); }
.score-value { font-family: var(--font-heading); font-weight: 700; font-size: 24px; color: var(--color-accent-400); }
.rationale-text { font-size: 14px; }
.score-grid, .highlights-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-1-5); font-size: 14px; }
.score-grid li { display: flex; justify-content: space-between; gap: var(--space-4); }
.score-grid li span { color: var(--color-muted); }
.highlights-list li { color: var(--color-accent-400); }

/* ── Toast (Story 18) ── */
.toast-popup {
  position: fixed; bottom: 20px; left: 20px; z-index: 40; max-width: 340px;
  border: 1px solid var(--color-divider); background: var(--color-surface);
  box-shadow: var(--shadow-md);
  transform: translateY(12px); opacity: 0; transition: opacity .25s ease, transform .25s ease;
}
.toast-popup:not(.hidden) { transform: translateY(0); opacity: 1; }
.toast-content { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-4); }
.toast-logo {
  width: 40px; height: 40px; flex: none; display: grid; place-items: center;
  border: 1px solid var(--color-divider); background: var(--color-bg);
}
.toast-logo img { max-width: 78%; max-height: 78%; object-fit: contain; }
.toast-text { font-size: var(--text-sm); line-height: 1.4; }

/* Story 13: giveaway popup uses the dialog pattern; contest terms link */
.giveaway-terms { font-size: 12px; }
.giveaway-terms summary { cursor: pointer; color: var(--color-accent-400); }
.giveaway-fineprint { margin-top: var(--space-2); color: var(--color-muted); }

/* ==========================================================================
   Responsive: the quote table sheds columns, it does not reflow into cards.
   Each band redeclares --firm-grid once, and the header strip drops the same
   columns the rows do, so alignment holds at every width.
   ========================================================================== */

/* ── 861–1140px: drop Plans and the tag cell ── */
@media (max-width: 1140px) {
  .firm-table-head, .firm-row { --firm-grid: 84px minmax(0, 1fr) 100px 76px 124px 152px; }
  .num-cell--plans, .tags-cell, .head-plans, .head-tags { display: none; }
}

/* ── 641–860px: drop the coupon reveal too; the Coupon button still opens
      the dialog, which carries the code. ── */
@media (max-width: 860px) {
  .firm-table-head, .firm-row { --firm-grid: 84px minmax(0, 1fr) 92px 72px 152px; }
  .coupon-cell, .head-coupon { display: none; }
  .logo-box { width: 56px; height: 36px; }
  .firm-logo-img { max-width: 48px; max-height: 28px; }
  .grid-cols-2 { grid-template-columns: 1fr; }
  .brand-logo { height: 38px; }
  .hero-title { font-size: 34px; }
}

/* ── ≤640px: two-line row ──
   Line 1: rank · firm name · actions.  Line 2: rank · max allocation ·
   profit split · the coupon reveal.  Nothing is hidden except Plans and the
   tag cell, and every dialog trigger stays on the row. Cells are placed by
   explicit grid area rather than reordered in the template, because the row
   is one grid and the markup must stay identical between the two views. */
@media (max-width: 640px) {
  .firm-table-head { display: none; }
  .firm-row {
    /* Max Alloc is fixed and Split takes the free track: the allocation is
       always eight glyphs ("$400,000") while the derived split figure is
       six to eight, so the elastic track goes to the one that can give. */
    --firm-grid: 64px 76px minmax(0, 1fr) 104px;
    row-gap: var(--space-1); column-gap: var(--space-1);
    align-items: center; min-height: 0;
  }
  /* Line 1: rank · firm · actions.  Line 2: rank · allocation · split · coupon. */
  .rank-cell      { grid-area: 1 / 1 / 3 / 2; flex-direction: column; align-items: flex-start; gap: 0; }
  .identity-col   { grid-area: 1 / 2 / 2 / 4; }
  .actions-cell   { grid-area: 1 / 4 / 2 / 5; }
  .num-cell--alloc{ grid-area: 2 / 2 / 3 / 3; align-items: flex-start; }
  .num-cell--split{ grid-area: 2 / 3 / 3 / 4; align-items: flex-start; }
  .coupon-cell    { grid-area: 2 / 4 / 3 / 5; display: flex; }
  .logo-box { display: none; }
  /* The movement glyph and LW #N sit side by side rather than stacked, so the
     rank cell is the same two lines the row is — stacked, it made "#1 / — /
     LW #1" a third line the other cells had no content for. */
  .rank-meta { flex-direction: row; align-items: baseline; gap: var(--space-1); }
  .num-cell::before {
    content: attr(data-label);
    font-family: var(--font-heading); font-size: var(--text-micro);
    letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-muted);
  }
  /* The label is a ::before on the cell and the figure is a block that fills
     it, so a right-aligned figure put the two at opposite edges of the row —
     "MAX ALLOC" at x=137 and "$200,000" at x=680 at 640px. On two lines the
     pair has to read as a pair: label directly above its own value. */
  .num-cell .stat-value { text-align: left; }
  .actions-cell .btn-sm { font-size: var(--text-micro); padding: var(--space-0-5) var(--space-1); }
  /* Pre-existing on origin/main since PR #21: the monospace headline is far
     wider per character than the Barlow Condensed it replaced and ran off
     the right edge at 390px, taking the rows with it. Adopted here because
     this story owns "no horizontal scroll on mobile". */
  /* One page-title size at this width, whichever page it is. Three selectors
     because each outspecifies the last: the profile H1 is
     .profile-title-box .hero-title, the error page's is .error-title, and the
     rating form's is a bare h1 — left to the band's .hero-title rule alone,
     all three would have held 30px while the homepage read 26. */
  h1, .hero-title, .error-title { font-size: var(--text-title-mobile); overflow-wrap: anywhere; }
  .profile-title-box .hero-title { font-size: var(--text-title-mobile); }
  .hero-section { padding: var(--space-6) 0 var(--space-4); }
  .container { padding-left: var(--space-2); padding-right: var(--space-2); }
}

/* ── Rank movement + ticker tape (Story 29) ──
   Movement gets its own tokens rather than borrowing the brand accent
   wholesale: --color-up sits a step lighter than the accent-text step the
   rank number uses, and there is deliberately no red in this palette, so
   "down" reads on the neutral ramp. Direction never depends on colour alone —
   every indicator pairs a glyph with its figure (▲ 2 / ▼ 1 / — / NEW).
   Appended and additive; tokens only. ── */
:root {
  --color-up: var(--color-accent-300);
  --color-down: var(--color-neutral-300);
  --color-flat: var(--color-muted);
}

.rank-change {
  display: inline-flex; align-items: center; gap: 3px;
  font-family: var(--font-numeric); font-variant-numeric: tabular-nums;
  font-size: 12px; font-weight: 600; letter-spacing: 0.04em;
  white-space: nowrap;
}
.rank-change--up { color: var(--color-up); }
.rank-change--down { color: var(--color-down); }
.rank-change--flat { color: var(--color-flat); }
.rank-change--new {
  font-size: var(--text-micro); letter-spacing: 0.1em;
  color: var(--color-accent-200); background: var(--color-accent-900);
  border: 1px solid var(--color-accent-700); padding: 0 var(--space-1);
}

/* The indicator travels with the rank number inside the row's rank cell. */
.rank-stack { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-0-5); }
/* Last week's position, market-table shorthand: LW #4. */
.rank-last {
  font-family: var(--font-numeric); font-variant-numeric: tabular-nums;
  font-size: var(--text-micro); letter-spacing: 0.06em; color: var(--color-muted);
}

/* The tape is chrome: a hairline band, no fill, body-size type. */
.ticker {
  border-bottom: 1px solid var(--color-divider);
  background: var(--color-bg);
  overflow: hidden;
}
.ticker-track { display: flex; width: max-content; animation: ppt-ticker 64s linear infinite; }
.ticker:hover .ticker-track,
.ticker:focus-within .ticker-track { animation-play-state: paused; }
.ticker-item {
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border-right: 1px solid var(--color-divider);
  font-size: var(--text-sm); white-space: nowrap;
}
.ticker-rank {
  font-family: var(--font-numeric); font-variant-numeric: tabular-nums;
  font-weight: 600; color: var(--color-accent-400);
}
.ticker-name { color: var(--color-text); }

/* Two identical copies of the list sit in the track, so travelling exactly
   half its width lands back where it started. */
@keyframes ppt-ticker {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* Static tape when the visitor asks for less motion; it stays readable by
   scrolling instead. */
@media (prefers-reduced-motion: reduce) {
  .ticker { overflow-x: auto; }
  .ticker-track { animation: none; }
}

/* ==========================================================================
   Margin utilities — last in the file, deliberately (Story 33)
   Same trap as the padding utilities above .card, for margins: every
   component that sets `margin` (.score-grid, .param-list, .highlights-list,
   .article-list, .resource-list, .dialog-title, .firm-name) declares it at
   equal specificity and later in the source, so a `.mb-4` written up with
   the other utilities silently rendered 0. In the Why dialog that closed the
   gap between the metric list and "Top Highlights"; on the firm profile it
   did the same between the parameter list and "Instrument Types".
   Source order is the fix — a specificity hack would only hide it.
   They sit after the responsive bands too: no band sets a margin, so nothing
   below can be overridden by this block.
   ========================================================================== */
.mb-2 { margin-bottom: var(--space-2); }
.mb-3 { margin-bottom: var(--space-3); }
.mb-4 { margin-bottom: var(--space-4); }
.mb-6 { margin-bottom: var(--space-6); }
.mb-8 { margin-bottom: var(--space-8); }
.mt-2 { margin-top: var(--space-2); }
.mt-3 { margin-top: var(--space-3); }
.ms-2 { margin-left: var(--space-2); }
