/* ════════════════════════════════════════════════════════════════════
   Calm Minimal — UI refresh layer for PuenTrade
   Loads LAST, after styles.css → ui-readable.css → luminous.css.

   Principles
   ──────────
   1. Flat surfaces. No gradients, no backdrop-filter, no inset edge
      lights. A card is a plane with a 1px border. Depth comes from
      spacing and hierarchy, not from glass.
   2. One spacing scale (4 · 8 · 12 · 16 · 20 · 24 · 32 · 40). Every
      padding/gap/margin below resolves to a step on it.
   3. Colour is signal. Neutral ink for structure; --accent / --win /
      --loss reserved for things that mean something.
   4. Numbers are data. Tabular figures everywhere so columns align
      and digits don't jitter as values update.
   5. Token-only. Nothing here hardcodes a colour, so all 15 themes
      (Luminous → Lavender) keep working.

   Scope: the shared primitives (.card, .kpi, .seg, .chip, .btn,
   .view__header, tables, .chart-wrap) — which is what Dashboard,
   Trades and Analytics/Reports/Calendar are all built from.
   ════════════════════════════════════════════════════════════════════ */

/* ───────── 1. SCALE ───────── */

:root {
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 20px;
  --s-6: 24px;
  --s-7: 32px;
  --s-8: 40px;

  /* One radius family. Cards sit a step above controls. */
  --r-control: 8px;
  --r-card: 12px;

  /* Hairline used for every structural division. */
  --rule: 1px solid var(--border);

  /* Label treatment shared by KPI labels, table heads, eyebrows. */
  --label-size: 11px;
  --label-track: 0.08em;

  /* Two dimension tokens Luminous raises and the base themes do not.
     The 8px control radius is what makes buttons, inputs and segments
     read as one family; 10px left them slightly softer and looser. */
  --radius: 8px;
  --radius-sm: 8px;
  --radius-xs: 4px;
  --sidebar-w: 256px;
}

/* The type scale itself is Luminous's, applied at each primitive below.
   The first pass of this file used a tighter one (30/13/14/28), which is
   exactly why Luminous read as generous and every other theme read as
   cramped — same fonts, tighter leading. Luminous's is the roomier
   scale, so it became the single scale. */

/* Tabular figures for anything numeric. Declared once, inherited. */
.kpi__value,
.kpi__hint,
.stat__value,
.day-kpi__value,
.drawer-metric-val,
.pnl-stat-badge,
.pnl-stat-pct,
table td,
table th,
.mono,
[data-num] {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "cv01" 1;
}

/* ───────── 2. CARD ─────────
   Was: 3 stacked layers — a 180° gradient, blur(16px) saturate(135%),
   an inset edge light and an ambient shadow. Now: a plane and a rule. */

.card {
  background: var(--surface);
  border: var(--rule);
  /* !important only here: ui-readable.css pins .card/.kpi to
     `border-radius: var(--radius) !important`, which is the control
     radius. Cards sit one step above controls in this system. */
  border-radius: var(--r-card) !important;
  padding: var(--s-5);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  box-shadow: none;
  transition: border-color .18s var(--ease);
}

.card:hover {
  /* No lift, no glow — the border simply firms up. */
  transform: none;
  border-color: var(--border-strong);
  box-shadow: none;
}

.card__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  flex-wrap: wrap;
  margin-bottom: var(--s-4);
}

.card__title {
  font-size: 20px;
  line-height: 28px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text-1);
  margin: 0;
}

.card__sub {
  font-size: 14px;
  color: var(--text-3);
  margin-top: var(--s-1);
}

.card__foot {
  margin-top: var(--s-4);
  padding-top: var(--s-3);
  border-top: var(--rule);
}

/* A card whose body is a chart gets its padding from the wrap instead,
   so the plot can run closer to the edge without looking cramped. */
.card > .chart-wrap {
  margin-top: var(--s-2);
}

/* ───────── 3. KPI ─────────
   The headline numbers. Label small and quiet, value large and
   confident, hint neutral unless it carries a sign. */

.kpi-grid {
  display: grid;
  /* Was a hard repeat(4) that broke below ~1100px. Now it reflows. */
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--s-3);
  margin-bottom: var(--s-6);
}

.kpi {
  background: var(--surface);
  border: var(--rule);
  border-radius: var(--r-card) !important; /* see note on .card above */
  padding: var(--s-4) var(--s-5);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  box-shadow: none;
  overflow: hidden;
  transition: border-color .18s var(--ease);
}

.kpi:hover {
  transform: none;
  border-color: var(--border-strong);
}

.kpi__label {
  font-size: var(--label-size);
  font-weight: 600;
  letter-spacing: var(--label-track);
  text-transform: uppercase;
  color: var(--text-3);
  line-height: 1.2;
}

.kpi__value {
  font-size: 32px;
  line-height: 40px;
  font-weight: 600;
  letter-spacing: -0.03em;
  margin-top: var(--s-2);
  color: var(--text-1);
}

/* Sign carries the colour — the card stays neutral. */
.kpi__value.is-pos { color: var(--win); }
.kpi__value.is-neg { color: var(--loss); }

.kpi__hint {
  font-size: 12px;
  color: var(--text-3);
  margin-top: var(--s-1);
  line-height: 1.35;
}

/* Sparklines drop to a supporting role: thin, low-contrast, no fill. */
.kpi__spark {
  margin-top: var(--s-3);
  opacity: 0.55;
}

.kpi__spark svg { display: block; width: 100%; }

/* ───────── 4. VIEW HEADER ───────── */

.view__header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-6);
  flex-wrap: wrap;
  margin-bottom: var(--s-7);
  padding-bottom: var(--s-5);
  border-bottom: var(--rule);
}

.view__eyebrow {
  font-size: var(--label-size);
  font-weight: 600;
  letter-spacing: var(--label-track);
  text-transform: uppercase;
  /* Was --accent-3: an accent on every page title spends the colour
     budget on decoration. Signal is reserved for data. */
  color: var(--text-3);
  margin-bottom: var(--s-2);
}

.view__title {
  font-size: 32px;
  line-height: 40px;
  font-weight: 600;
  letter-spacing: -0.03em;
  margin: 0;
  /* Undo the hardcoded white→grey gradient text from styles.css, which
     was invisible on the light themes and muddy on the dark ones. */
  background: none;
  -webkit-background-clip: border-box;
  background-clip: border-box;
  -webkit-text-fill-color: currentColor;
  color: var(--text-1);
}

.view__subtitle {
  font-size: 16px;
  line-height: 24px;
  color: var(--text-3);
  margin: var(--s-2) 0 0;
}

.view__actions {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex-wrap: wrap;
}

/* ───────── 5. CONTROLS ─────────
   Segmented control, chips and buttons all flatten to the same
   language: 1px border, no gradient, no glow. Active = filled. */

.seg {
  display: inline-flex;
  gap: var(--s-1);
  padding: var(--s-1);
  background: var(--surface-2);
  border: var(--rule);
  border-radius: var(--r-control);
  box-shadow: none;
}

.seg__btn {
  padding: 6px var(--s-3);
  border-radius: 5px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-3);
  background: none;
  border: 1px solid transparent;
  transition: color .15s var(--ease), background .15s var(--ease);
}

.seg__btn:hover { color: var(--text-1); }

.seg__btn.is-active {
  /* Was a glowing accent gradient. Now the active tab simply lifts to
     the card plane, the way a real tab does. */
  background: var(--surface);
  border-color: var(--border);
  color: var(--text-1);
  box-shadow: none;
}

.chip {
  padding: 6px var(--s-3);
  border-radius: 999px;
  font-size: 12px;
  font-weight: 500;
  background: none;
  border: var(--rule);
  color: var(--text-2);
  transition: border-color .15s var(--ease), color .15s var(--ease);
}

.chip:hover { border-color: var(--border-strong); color: var(--text-1); }

.chip--toggle.is-active,
.chip.is-active {
  background: var(--surface-2);
  border-color: var(--border-strong);
  color: var(--text-1);
}

.btn {
  gap: 6px;
  padding: 8px var(--s-4);
  border-radius: var(--r-control);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0;
  transition: background .15s var(--ease), border-color .15s var(--ease), color .15s var(--ease);
}

.btn:hover { transform: none; }

.btn--primary {
  color: #fff;
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: none;
}

.btn--primary:hover {
  background: var(--accent-2);
  border-color: var(--accent-2);
  box-shadow: none;
}

.btn--ghost {
  color: var(--text-2);
  background: none;
  border: var(--rule);
  box-shadow: none;
}

.btn--ghost:hover {
  color: var(--text-1);
  background: var(--surface-2);
  border-color: var(--border-strong);
}

.btn--sm { padding: 6px var(--s-3); font-size: 12px; }

.icon-btn {
  border: var(--rule);
  border-radius: var(--r-control);
  background: none;
  color: var(--text-3);
  box-shadow: none;
  transition: color .15s var(--ease), border-color .15s var(--ease);
}

.icon-btn:hover {
  color: var(--text-1);
  border-color: var(--border-strong);
  background: var(--surface-2);
  transform: none;
}

/* Inputs and selects join the same family. */
.input,
.select {
  border-radius: var(--r-control);
  border: var(--rule);
  background: var(--surface);
  box-shadow: none;
}

.input:focus,
.select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(var(--accent-glow), 0.15);
  outline: none;
}

.input--sm { padding: 6px var(--s-3); font-size: 12px; }

/* ───────── 6. TABLES ─────────
   Trades list and the analytics breakdowns. Rules, not fills: a zebra
   pattern fights the sign colours that actually carry meaning. */

.admin-table,
.trades-table,
table.data {
  width: 100%;
  border-collapse: collapse;
}

.admin-table th,
.trades-table th,
table.data th,
.th-sortable {
  font-size: var(--label-size);
  font-weight: 600;
  letter-spacing: var(--label-track);
  text-transform: uppercase;
  color: var(--text-3);
  text-align: left;
  padding: var(--s-2) var(--s-3);
  border-bottom: var(--rule);
  background: none;
  white-space: nowrap;
}

.admin-table td,
.trades-table td,
table.data td {
  padding: var(--s-3);
  border-bottom: var(--rule);
  font-size: 13px;
  color: var(--text-2);
  background: none;
}

.admin-table tr:last-child td,
.trades-table tr:last-child td,
table.data tr:last-child td { border-bottom: none; }

.admin-table tbody tr,
.trades-table tbody tr,
table.data tbody tr {
  transition: background .12s var(--ease);
}

.admin-table tbody tr:hover,
.trades-table tbody tr:hover,
table.data tbody tr:hover {
  background: var(--surface-2);
}

/* Numeric columns right-align so magnitudes compare at a glance. */
.admin-table td.num,
.trades-table td.num,
table.data td.num,
.admin-table th.num,
.trades-table th.num,
table.data th.num { text-align: right; }

.sort-arrow { opacity: 0.4; }

/* ───────── 7. CHARTS ───────── */

.chart-wrap {
  position: relative;
  height: 300px;
}

/* Give the chart room to breathe inside a card without a double frame. */
.card .chart-wrap { margin-inline: calc(var(--s-1) * -1); }

/* ───────── 8. CALENDAR ─────────
   Performance calendar on the dashboard. Cells become a quiet grid;
   only the P&L figure carries colour. */

.cal-cell,
.calendar-day {
  border: var(--rule);
  border-radius: var(--r-control);
  background: var(--surface);
  box-shadow: none;
  transition: border-color .15s var(--ease);
}

.cal-cell:hover,
.calendar-day:hover { border-color: var(--border-strong); }

.cal-cell--empty,
.calendar-day.is-empty {
  background: var(--surface-2);
  border-color: transparent;
}

/* ───────── 9. HERO / EMPTY STATE ───────── */

.hero-card {
  background: var(--surface);
  border: var(--rule);
  border-radius: var(--r-card);
  padding: var(--s-8) var(--s-7);
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.hero-card__eyebrow {
  font-size: var(--label-size);
  font-weight: 600;
  letter-spacing: var(--label-track);
  text-transform: uppercase;
  color: var(--text-3);
}

.hero-card__title {
  font-size: 28px;
  font-weight: 650;
  letter-spacing: -0.03em;
  margin: var(--s-3) 0 0;
  color: var(--text-1);
}

.hero-card__title span { color: var(--accent); }

.hero-card__sub {
  color: var(--text-3);
  font-size: 14px;
  margin: var(--s-3) 0 0;
  max-width: 52ch;
  line-height: 1.6;
}

.hero-card__actions {
  display: flex;
  gap: var(--s-2);
  margin-top: var(--s-5);
  flex-wrap: wrap;
}

/* ───────── 10. RESPONSIVE ───────── */

@media (max-width: 720px) {
  .view__header {
    align-items: flex-start;
    gap: var(--s-4);
    margin-bottom: var(--s-5);
    padding-bottom: var(--s-4);
  }

  .view__title { font-size: 24px; }
  .kpi__value { font-size: 24px; }
  .card { padding: var(--s-4); }
  .hero-card { padding: var(--s-6) var(--s-5); }
  .chart-wrap { height: 240px; }
}

/* ───────── 11. CONTENT WIDTH ─────────
   The inline <style> in app.html pins every view to

     @media (min-width: 1200px) { .view { max-width: 1200px !important } }

   On a 1920px screen that leaves ~1620px of usable space (viewport minus
   the 256px sidebar and the 22px .main gutters) and throws away ~420px of
   it as empty margin — while the 8-column calendar inside gets squeezed.
   A trading dashboard is grids and tables, not prose: it wants the width.

   Cap raised, and the view's own 20px inset dropped since .main already
   supplies the gutter. Still centred, so ultra-wide screens don't stretch
   a table to 2500px. */

@media (min-width: 1200px) {
  .view {
    max-width: 1600px !important;
    margin: 0 auto !important;
    padding-left: 0 !important;
    padding-right: 0 !important;
  }
}

/* Journal and Settings are read-and-write text, not data grids — a long
   line length hurts them, so they keep a comfortable measure. */
@media (min-width: 1200px) {
  #view-journal,
  #view-settings {
    max-width: 1080px !important;
  }
}

/* ───────── 12. CALENDAR DENSITY ─────────
   Every empty day printed the words "No trades" — 35 repetitions of the
   same non-information on a typical month, out-shouting the handful of
   cells that actually carry a number. app.html already had the fix (hide
   the label, show a faint dash) but it was scoped inside a mobile media
   query, so desktop never got it. */

.cal-day:not(.cal-day--weekly) .txt-empty {
  display: none !important;
}

.cal-day.is-empty .cal-day__body::after {
  content: "—";
  display: block;
  font-size: 12px;
  color: var(--text-4);
  line-height: 1;
}

/* Empty days recede to the page ground so the traded days read as tiles. */
.cal-day.is-empty {
  background: var(--surface-2) !important;
}

/* The weekly summary keeps its $0.00 — a zero total is real information
   in a P&L column — but drops to the quiet ink. */
.cal-day--weekly .txt-empty,
.weekly-grid-pnl .txt-empty {
  color: var(--text-4);
}

.weekly-sub,
.weekly-grid-sub {
  color: var(--text-4);
  font-size: 11px;
}

.weekly-label,
.weekly-grid-label {
  font-size: var(--label-size);
  font-weight: 600;
  letter-spacing: var(--label-track);
  color: var(--text-3);
}

/* Day-of-week header row joins the label system used everywhere else. */
.cal-dow {
  font-size: var(--label-size) !important;
  font-weight: 600 !important;
  letter-spacing: var(--label-track) !important;
  color: var(--text-3) !important;
  background: none !important;
}


/* ───────── 14. LUMINOUS COLOUR PARITY ─────────
   The base stylesheet was authored dark-first, so ~91 selectors paint
   text in 300/400-weight pastels (teal-300, green-400, red-400,
   amber-300, violet-300). Those read fine on a near-black canvas and
   all but vanish on Luminous's white one — teal-300 on white is 1.5:1,
   where the readable floor is 4.5:1.

   luminous.css swapped the *tokens* but could not reach these, because
   the colours are hardcoded literals, not var() references. So the ink
   is redefined here per semantic role, and the pastel literals are
   redirected onto it. Every value below clears 4.5:1 on white.

   Non-Luminous themes inherit --*-ink = the original vivid token via
   the :root block, so the other 14 themes are byte-for-byte unchanged.
   ──────────────────────────────────────────────────────────────── */

:root {
  /* Dark themes: ink stays the vivid signal colour they were built on. */
  --win-ink: var(--win);
  --loss-ink: var(--loss);
  --warn-ink: var(--warn);
  --info-ink: var(--accent-3);
  --teal-ink: var(--accent-3);
  --cyan-ink: var(--accent-3);
}

html[data-theme="luminous"] {
  /* Light canvas: step down to the 700 weights. Contrast on #ffffff —
     win 5.5:1 · loss 6.5:1 · warn 5.0:1 · info 7.1:1 · teal 5.5:1 ·
     cyan 5.4:1.

     #047857 and #b91c1c are not new values: luminous.css already picked
     exactly these for .cal-day__pnl. Reusing them keeps one green and
     one red across the whole light theme. */
  --win-ink: #047857;
  --loss-ink: #b91c1c;
  --warn-ink: #b45309;
  --info-ink: #6d28d9;
  --teal-ink: #0f766e;
  --cyan-ink: #0e7490;
}

/* ── Text sitting on a soft tinted chip ──
   .badge--win painted --win (#10b981) on --win-soft (#d1fae5): 2.2:1.
   .badge--loss painted --loss (#ef4444) on --loss-soft (#fee2e2): 2.9:1.
   Both are small text, so both needed 4.5. The tint stays; the ink
   darkens to sit on it. Applies in every theme via the fallbacks above. */

.badge--win,
.trade-row__dir,
.txt-win {
  color: var(--win-ink);
}

.badge--loss,
.trade-row__dir--short,
.txt-loss {
  color: var(--loss-ink);
}

.badge--warn {
  color: var(--warn-ink);
}

/* luminous.css sets `color: var(--win)` on these at (0,2,1) specificity,
   which outranks the plain class rules above — so the light theme needs
   its own pass at matching weight. Same ink, higher specificity. */

html[data-theme="luminous"] .badge--win,
html[data-theme="luminous"] .chip.is-win,
html[data-theme="luminous"] .delta.is-pos,
html[data-theme="luminous"] .trade-row__dir,
html[data-theme="luminous"] .txt-win {
  color: var(--win-ink);
}

html[data-theme="luminous"] .badge--loss,
html[data-theme="luminous"] .chip.is-loss,
html[data-theme="luminous"] .delta.is-neg,
html[data-theme="luminous"] .trade-row__dir--short,
html[data-theme="luminous"] .txt-loss {
  color: var(--loss-ink);
}

/* ── Hardcoded pastels redirected onto the ink, Luminous only ── */

/* win: green/emerald-300/400 positives, ~1.7:1 on white */
html[data-theme="luminous"] .ab--paid .access-banner__x,
html[data-theme="luminous"] .ab--paid > svg,
html[data-theme="luminous"] .ab--paid>svg,
html[data-theme="luminous"] .auth-feature__icon.icn-net,
html[data-theme="luminous"] .auth-perk__icon,
html[data-theme="luminous"] .bg-mock__delta--pos,
html[data-theme="luminous"] .bg-mock__dir--long,
html[data-theme="luminous"] .bg-mock__pill--pos,
html[data-theme="luminous"] .bg-mock__pnl--pos,
html[data-theme="luminous"] .bg-mock__value--pos,
html[data-theme="luminous"] .billing__plan--paid,
html[data-theme="luminous"] .compound-val.is-pos,
html[data-theme="luminous"] .cv-clock__live,
html[data-theme="luminous"] .direction-glow-pill.buy,
html[data-theme="luminous"] .extract-row.is-filled .extract-row__icon,
html[data-theme="luminous"] .gallery-card-pnl.is-pos,
html[data-theme="luminous"] .gallery-result-badge.win,
html[data-theme="luminous"] .graph-stat--conn .graph-stat__val,
html[data-theme="luminous"] .graph-stat--conn::before,
html[data-theme="luminous"] .mc-val.is-pos,
html[data-theme="luminous"] .pf-preview__stat-val.is-pos,
html[data-theme="luminous"] .plan-badge--paid,
html[data-theme="luminous"] .stress-val.is-pos,
html[data-theme="luminous"] .vsn-regime--bull .vsn-regime__title {
  color: var(--win-ink);
}

/* loss: red-400 negatives, ~3.0:1 on white */
html[data-theme="luminous"] .ab--expired .access-banner__x,
html[data-theme="luminous"] .ab--expired > svg,
html[data-theme="luminous"] .ab--expired>svg,
html[data-theme="luminous"] .admin-del,
html[data-theme="luminous"] .admin-grant button.admin-expire[disabled],
html[data-theme="luminous"] .auth-error,
html[data-theme="luminous"] .avatar-menu__item--danger:hover,
html[data-theme="luminous"] .bg-mock__delta--neg,
html[data-theme="luminous"] .bg-mock__dir--short,
html[data-theme="luminous"] .bg-mock__pill--neg,
html[data-theme="luminous"] .bg-mock__pnl--neg,
html[data-theme="luminous"] .bg-mock__value--neg,
html[data-theme="luminous"] .cv-profile__delta.is-neg,
html[data-theme="luminous"] .cv-recent__result--loss,
html[data-theme="luminous"] .direction-glow-pill.sell,
html[data-theme="luminous"] .pf-card__big.is-neg,
html[data-theme="luminous"] .pf-card__pnl-val.is-neg,
html[data-theme="luminous"] .pf-hero__val.is-neg,
html[data-theme="luminous"] .pf-status--failed,
html[data-theme="luminous"] .plan-badge--expired,
html[data-theme="luminous"] .req,
html[data-theme="luminous"] .vsn-regime--bear .vsn-regime__title {
  color: var(--loss-ink);
}

/* warn: amber-300 warnings, ~1.6:1 on white */
html[data-theme="luminous"] .admin-grant button.admin-expire,
html[data-theme="luminous"] .auth-feature__icon.icn-clock,
html[data-theme="luminous"] .billing__owner,
html[data-theme="luminous"] .billing__plan--owner,
html[data-theme="luminous"] .chip--gold,
html[data-theme="luminous"] .compound-val.is-warn,
html[data-theme="luminous"] .nav__item--admin .nav__icon,
html[data-theme="luminous"] .pf-flag,
html[data-theme="luminous"] .pf-preview__stat-val.is-warn,
html[data-theme="luminous"] .stress-val.is-warn,
html[data-theme="luminous"] .vsn-regime--neutral .vsn-regime__title {
  color: var(--warn-ink);
}

/* info: violet-300 accents, ~2.4:1 on white */
html[data-theme="luminous"] .auth-feature__icon,
html[data-theme="luminous"] .auth-feature__icon.icn-tv,
html[data-theme="luminous"] .bg-mock__ai-icon,
html[data-theme="luminous"] .graph-stat--strat::before,
html[data-theme="luminous"] .pf-edge-inputs__head,
html[data-theme="luminous"] .pf-edge-inputs__reset {
  color: var(--info-ink);
}

/* teal: teal-300 — the dark theme accent, 1.5:1 on white */
html[data-theme="luminous"] .acc-switch-menu__action--primary,
html[data-theme="luminous"] .acc-switch-menu__action--primary svg,
html[data-theme="luminous"] .acc-switch[aria-expanded="true"] .acc-switch__caret,
html[data-theme="luminous"] .ap-recommend,
html[data-theme="luminous"] .checkout__assurance svg,
html[data-theme="luminous"] .checkout__method.is-active svg,
html[data-theme="luminous"] .checkout__qr-box,
html[data-theme="luminous"] .cs-line--saving .cs-pos,
html[data-theme="luminous"] .cv-card__chip,
html[data-theme="luminous"] .cv-card__title-accent,
html[data-theme="luminous"] .cv-chips .chip.is-active,
html[data-theme="luminous"] .cv-chk__edit:hover,
html[data-theme="luminous"] .cv-pb-item__rr,
html[data-theme="luminous"] .cv-profile__delta.is-pos,
html[data-theme="luminous"] .cv-recent__result--win,
html[data-theme="luminous"] .firm-logo--all,
html[data-theme="luminous"] .jrnl-entry__type--reflection,
html[data-theme="luminous"] .pc-cell--val.is-check,
html[data-theme="luminous"] .pc-cell--val.is-inf,
html[data-theme="luminous"] .pc-recommended,
html[data-theme="luminous"] .pf-card__big.is-pos,
html[data-theme="luminous"] .pf-card__pnl-val.is-pos,
html[data-theme="luminous"] .pf-hero__val.is-pos,
html[data-theme="luminous"] .pf-status--active,
html[data-theme="luminous"] .pf-status--passed,
html[data-theme="luminous"] .price-compare__num,
html[data-theme="luminous"] .seg__btn-badge {
  color: var(--teal-ink);
}

/* cyan: cyan-300 tag stat, ~1.6:1 on white */
html[data-theme="luminous"] .graph-stat--tag .graph-stat__val {
  color: var(--cyan-ink);
}

/* ───────── 15. LUMINOUS FORM, EVERY THEME ─────────
   luminous.css carries two separable things: a palette, and a *form* —
   flat planes, hairline rules, pill controls, a tighter type scale. The
   palette belongs to Luminous alone. The form does not, and it was
   locked behind html[data-theme="luminous"] along with it.

   This section re-emits that form unscoped, with every colour expressed
   as a token, so the other 14 themes get Luminous's shape while keeping
   their own colours.

   Specificity does the arbitration for free: luminous.css matches at
   (0,2,1) and these rules at (0,1,0), so under Luminous the original
   rules still win and that theme is untouched. Everywhere else, only
   these apply.
   ──────────────────────────────────────────────────────────────── */

/* The --lp-* container ramp exists only inside luminous.css. Mapping it
   onto each theme's own surface ladder is what lets the form travel
   without dragging the palette along. Luminous redefines these at
   (0,1,1), so its own ramp still wins there. */
:root {
  --lp-surface: var(--bg-1);
  --lp-container-lowest: var(--surface);
  --lp-container-low: var(--surface-2);
  --lp-container: var(--surface-2);
  --lp-container-high: var(--surface-3);
}

/* ── Gradient-filled text flattens to solid ink ──
   Dark-first headings painted themselves with a white→grey gradient and
   -webkit-text-fill-color: transparent. Luminous flattens them; so does
   every other theme now. */
.brand__name,
.view__title,
.spark-num,
.ai-hero__title,
.vsn-hero__title {
  background: none;
  -webkit-text-fill-color: currentColor;
  color: var(--text-1);
}

/* ── Sidebar ── */

.sidebar {
  padding: var(--s-4) var(--s-2);
}

.sidebar__brand {
  border-bottom: var(--rule);
  padding: var(--s-2) var(--s-4) var(--s-6);
  margin-bottom: var(--s-4);
}

.sidebar__footer {
  border-top: var(--rule);
}

.brand__logo {
  /* Luminous's controls are pills, not rounded squares. */
  border-radius: 999px;
}

.brand__name {
  font-size: 20px;
  line-height: 28px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.brand__sub {
  color: var(--text-3);
  font-size: 12px;
  letter-spacing: 0.05em;
}

.sidebar__toggle {
  background: var(--lp-container-lowest);
  border: var(--rule);
  color: var(--text-3);
  box-shadow: none;
}

.nav__item {
  color: var(--text-2);
  border-radius: var(--r-control);
  padding: var(--s-2) var(--s-4);
  font-size: 14px;
  font-weight: 500;
}

.nav__item.is-active {
  font-weight: 700; /* Luminous's active weight; base themes sat at 600. */
  box-shadow: none;
}

/* The active item was marked by a glowing left bar; Luminous carries the
   state on the fill alone. */
.nav__item.is-active::before {
  display: none;
}

.view__eyebrow,
.nav__section {
  color: var(--text-3);
  font-size: 12px;
  line-height: 16px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.account-card {
  box-shadow: none;
}

.account-card::before {
  display: none;
}

/* ── Topbar ── */

.topbar__search {
  border-radius: 999px;
  padding: 6px var(--s-4) 6px 14px;
  box-shadow: none;
}

.topbar__search input {
  font-size: 14px;
}

.icon-btn {
  border-radius: 999px;
}

/* The avatar's purple→pink gradient is written as an inline style by JS,
   so no stylesheet can reach it — luminous.css tries the same override
   and loses too, which means Luminous already shows that gradient. It is
   the user's identity colour, so it is left alone rather than forced
   with !important. Only the frame is normalised here. */
.topbar__avatar {
  border: var(--rule);
  box-shadow: none;
  color: #fff;
}

/* ── Remaining decoration, flattened ──
   Everything the sweep still found painting a gradient or a drop glow
   in the dark themes. */

.notif-badge {
  background: var(--loss);
  box-shadow: none;
}

.trade-row {
  background: none;
}

.firm-logo {
  background: var(--lp-container);
  box-shadow: none;
}

.access-banner,
.pdpa-banner {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  box-shadow: var(--shadow-lg);
}

/* ── Controls at Luminous's proportions ── */

.btn {
  font-size: 14px;
}

.chip {
  background: var(--lp-container);
  border: 1px solid transparent;
  border-radius: 999px;
  color: var(--text-3);
  font-size: 11px;
  font-weight: 500;
  padding: var(--s-1) 10px;
}

.cal-day {
  background: var(--lp-container-low);
  border: var(--rule);
}

/* Deliberately NOT ported: luminous.css sets
     .main { padding: calc(var(--topbar-h) + 40px) 48px 80px }
   Those 48px side gutters are the same wasted width that section 11
   above reclaims, and ui-readable.css pins .main to 22px !important
   regardless. Left alone on purpose. */

/* ───────── 16. LOG TRADE MODAL ─────────
   The form was authored as a 3-step wizard (.step-panel), but the steps
   were never wired: trades.js line 97 does

     $$(".step-panel").forEach(p => p.classList.add("is-active"))

   and hides #stepPrev/#stepNext, while styles.css:19801 forces
   `display: block !important` on every panel and lays all three out as
   equal columns. The result was 25 fields, two emoji grids and eleven
   tag chips presented at once with no hierarchy.

   Restructured to: the six fields that define a trade, always visible;
   everything else behind one <details>; live metrics, tags and the
   screenshot dropzone in a side rail. No input was removed or renamed —
   all 49 ids and 22 fields are still in the DOM, so trades.js reads and
   writes exactly what it did before.
   ──────────────────────────────────────────────────────────────── */

#tradeModal .modal {
  /* styles.css pins this to 1240px !important — a width chosen to fit
     three columns of fields. Two columns need less, and the narrower
     card is the point. */
  max-width: 1040px !important;
  width: calc(100vw - 48px);
}

#tradeModal .modal__body {
  padding: 0;
}

/* Two columns: the form, and a rail that stays put while the form grows.
   The !important overrides styles.css:19801's 3-column grid. */
#tradeModal #tradeForm {
  display: grid !important;
  grid-template-columns: minmax(0, 1fr) 300px !important;
  gap: 0 !important;
  align-items: stretch !important;
}

.tf-main {
  min-width: 0;
  padding: var(--s-6);
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}

.tf-rail {
  min-width: 0;
  padding: var(--s-6);
  border-left: var(--rule);
  background: var(--surface-2);
  display: flex;
  flex-direction: column;
  gap: var(--s-6);
}

.tf-rail__block { min-width: 0; }

.tf-rail__title,
.tf-section__label {
  font-size: var(--label-size);
  font-weight: 600;
  letter-spacing: var(--label-track);
  text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: var(--s-3);
}

.tf-section__label { margin-top: var(--s-2); }
.tf-main > .tf-section__label:first-of-type { margin-top: 0; }

/* ── Field grids ── */

.tf-grid {
  display: grid;
  gap: var(--s-3);
  min-width: 0;
}

.tf-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.tf-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.tf-grid > .form-group { min-width: 0; margin: 0; }

#tradeModal .form-group label {
  display: block;
  font-size: var(--label-size);
  font-weight: 600;
  letter-spacing: var(--label-track);
  text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: 6px;
}

#tradeModal .input,
#tradeModal .select,
#tradeModal textarea {
  width: 100%;
  border-radius: var(--r-control);
  border: var(--rule);
  background: var(--surface);
  box-shadow: none;
}

/* The per-label "?" buttons — 22 of them — were louder than the labels
   they annotate. Kept for keyboard and hover, dropped from the resting
   composition. */
#tradeModal .form-group label .help-dot,
#tradeModal .form-group label [class*="help"],
#tradeModal .form-group label button {
  opacity: 0;
  transition: opacity .15s var(--ease);
}

#tradeModal .form-group:hover label .help-dot,
#tradeModal .form-group:hover label [class*="help"],
#tradeModal .form-group:hover label button,
#tradeModal .form-group label button:focus-visible {
  opacity: 0.55;
}

/* ── Progressive disclosure ── */

.tf-more {
  border: var(--rule);
  border-radius: var(--r-card);
  background: var(--surface-2);
}

.tf-more__summary {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-4);
  cursor: pointer;
  list-style: none;
  user-select: none;
  border-radius: var(--r-card);
}

.tf-more__summary::-webkit-details-marker { display: none; }
.tf-more__summary:hover { background: var(--surface-3); }

.tf-more__caret {
  display: flex;
  color: var(--text-3);
  transition: transform .18s var(--ease);
}

.tf-more[open] .tf-more__caret { transform: rotate(90deg); }

.tf-more__text {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-2);
}

.tf-more__hint {
  font-size: 12px;
  color: var(--text-4);
  margin-left: auto;
}

.tf-more__body {
  padding: 0 var(--s-4) var(--s-4);
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  border-top: var(--rule);
  padding-top: var(--s-4);
}

/* ── AI Auto-Read ──
   The Thai copy overflowed its box because the card was sized for a
   one-line English string. It becomes a single quiet row: icon, one
   line of text that wraps, and the button. */

#tradeModal .ai-hero {
  display: block;
  background: var(--surface-2);
  border: var(--rule);
  border-radius: var(--r-card);
  padding: var(--s-3) var(--s-4);
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  overflow: visible;
}

#tradeModal .ai-hero__sparkles { display: none; }

#tradeModal .ai-hero__body {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex-wrap: wrap;
}

#tradeModal .ai-hero__icon {
  font-size: 18px;
  line-height: 1;
  flex: none;
}

#tradeModal .ai-hero__text {
  flex: 1 1 220px;
  min-width: 0;
}

#tradeModal .ai-hero__title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-1);
  background: none;
  -webkit-text-fill-color: currentColor;
}

#tradeModal .ai-hero__sub {
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-3);
  /* Was clipped mid-word; let it wrap instead. */
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
}

#tradeModal .ai-hero .btn { flex: none; }

/* ── Rail contents ── */

.tf-rail .pnl-preview {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  background: none;
  border: 0;
  padding: 0;
  font-size: 13px;
}

/* Eleven preset chips with remove buttons read as a wall. Quieted to
   outlines so the tags the user actually adds stand out. */
.tf-rail .tag-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.tf-rail .tag-row .chip {
  font-size: 11px;
  padding: var(--s-1) 10px;
}

.tf-rail .tag-input-row { margin-top: var(--s-2); }

/* Dropzone matches the reference: a dashed well, no fill. */
#tradeModal #screenshotZone {
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-card);
  background: none;
  padding: var(--s-6) var(--s-3);
  text-align: center;
  color: var(--text-3);
  font-size: 12px;
  transition: border-color .15s var(--ease);
}

#tradeModal #screenshotZone:hover { border-color: var(--accent); }

/* ── Emotion pickers ──
   Two 6-emoji grids stacked side by side; inside the disclosure they get
   room to sit on one row each. */
#tradeModal .emotion-split {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-3);
}

#tradeModal .emotion-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1);
}

/* ── Footer ── */

#tradeModal .modal__foot {
  border-top: var(--rule);
  padding: var(--s-3) var(--s-6);
}

@media (max-width: 900px) {
  #tradeModal #tradeForm { grid-template-columns: minmax(0, 1fr) !important; }
  .tf-rail {
    border-left: 0;
    border-top: var(--rule);
  }
  .tf-grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 560px) {
  .tf-grid--2,
  .tf-grid--3,
  #tradeModal .emotion-split { grid-template-columns: minmax(0, 1fr); }
  .tf-main, .tf-rail { padding: var(--s-4); }
  .tf-more__hint { display: none; }
}

/* ───────── 17. POSITION SIZE CALCULATOR ─────────
   The output grid leads with the one number a trader acts on — the lot
   size — and keeps the working (pips, value per pip, units) beside it
   so the figure can be checked rather than trusted. */

.calc-output {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--s-4);
  margin-top: var(--s-5);
  padding: var(--s-5);
  background: var(--surface-2);
  border: var(--rule);
  border-radius: var(--r-card);
}

.calc-out-item { min-width: 0; }

.calc-out-label {
  font-size: var(--label-size);
  font-weight: 600;
  letter-spacing: var(--label-track);
  text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: 6px;
}

.calc-out-value {
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text-1);
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}

.calc-out-value.is-pos { color: var(--win-ink); }

.calc-out-unit {
  font-size: 12px;
  font-weight: 500;
  color: var(--text-3);
  margin-left: 5px;
  letter-spacing: 0;
}

/* The headline spans the full first row on wide grids. */
.calc-out-item--hero {
  grid-column: 1 / -1;
  padding-bottom: var(--s-4);
  border-bottom: var(--rule);
}

.calc-out-item--hero .calc-out-value {
  font-size: 40px;
  line-height: 1.05;
  letter-spacing: -0.03em;
}

.calc-out-item--hero .calc-out-unit { font-size: 15px; }

.calc-out-warn {
  margin-top: var(--s-2);
  font-size: 12px;
  color: var(--warn-ink);
}

.calc-empty {
  grid-column: 1 / -1;
  font-size: 13px;
  color: var(--text-3);
  text-align: center;
  padding: var(--s-5) 0;
}

/* Contract spec line: which numbers the sizing is standing on. */
.calc-spec {
  font-size: 11px;
  line-height: 1.5;
  color: var(--text-3);
  margin-top: 6px;
}

.calc-spec strong { color: var(--text-2); font-weight: 600; }
.calc-spec__warn { color: var(--warn-ink); }

.calc-rate__fetch {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
  background: none;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 1px 8px;
  margin-left: 6px;
  cursor: pointer;
}

.calc-rate__fetch:hover { border-color: var(--accent); }
.calc-rate__fetch:disabled { opacity: 0.5; cursor: default; }

/* ───────── 18. MOBILE & TABLET ─────────
   Three concrete faults, each verified against the running app:

   1. A navigation gap on tablets. The sidebar undocks at max-width:1024
      but the bottom bar only appears at max-width:820 — so every width
      from 821 to 1024 (iPad portrait is 820, iPad Air landscape is 1180,
      iPad Mini portrait 744, most Android tablets 800-1000) had neither
      a visible sidebar nor a tab bar. Only the hamburger.

   2. The dashboard calendar is an 8-column grid. At 375px its WEEKLY
      column lands at x=388 — past the right edge — and the ancestor
      clips it, so that column simply cannot be read or reached.

   3. The tab bar hardcodes `background: rgba(13,13,18,0.88)` with a
      20px backdrop blur: a dark slab that stays dark on the light
      themes, and the one piece of chrome still using glass.
   ──────────────────────────────────────────────────────────────── */

/* ── 1. Tab bar — removed per explicit request. The hamburger → sidebar
   drawer is the only mobile nav now; this used to force it back on at
   <=1024px (overriding app.html's own display:none) and reserve 72px of
   bottom padding on .main for it. Neither applies anymore — .main gets its
   space back, nothing here re-enables the bar. ── */

/* ── The tab bar, rebuilt Minimal ──
   Flat surface, one hairline, no blur, no glow. The active tab is marked
   by ink and a short rule above it rather than a shadowed dot. */
@media (max-width: 1024px) {
  .mobile-bottom-nav {
    position: fixed !important;
    inset: auto 0 0 0 !important;
    height: auto !important;
    min-height: 56px !important;
    padding: 0 var(--s-1) env(safe-area-inset-bottom, 0px) !important;
    background: var(--surface) !important;
    border-top: var(--rule) !important;
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
    box-shadow: none !important;
    justify-content: space-evenly !important;
    align-items: stretch !important;
    z-index: 9999 !important;
  }

  .mobile-bottom-nav .nav__item {
    flex: 1 1 0 !important;
    min-width: 0 !important;
    /* 44px is the smallest reliably tappable target. */
    min-height: 56px !important;
    gap: 4px !important;
    padding: var(--s-2) 2px !important;
    color: var(--text-3) !important;
    background: none !important;
    border: 0 !important;
    border-radius: 0 !important;
    position: relative !important;
    transition: color .15s var(--ease) !important;
  }

  .mobile-bottom-nav .nav__item .nav__icon svg {
    width: 21px !important;
    height: 21px !important;
    stroke-width: 1.9 !important;
  }

  .mobile-bottom-nav .nav__item .nav__label {
    font-size: 10.5px !important;
    font-weight: 500 !important;
    letter-spacing: 0 !important;
    line-height: 1.2 !important;
    /* "Time Machine" is two words in a ~70px column — let it shrink
       rather than wrap into the icon. */
    max-width: 100% !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
    white-space: nowrap !important;
  }

  .mobile-bottom-nav .nav__item.is-active {
    color: var(--accent) !important;
  }

  .mobile-bottom-nav .nav__item.is-active .nav__label {
    font-weight: 600 !important;
  }

  /* Replaces the glowing dot: a 2px rule along the top edge of the tab,
     which reads as a selected tab rather than a notification. */
  .mobile-bottom-nav .nav__item.is-active::after {
    content: "" !important;
    position: absolute !important;
    top: 0 !important;
    left: 50% !important;
    transform: translateX(-50%) !important;
    width: 26px !important;
    height: 2px !important;
    bottom: auto !important;
    border-radius: 0 0 2px 2px !important;
    background: var(--accent) !important;
    box-shadow: none !important;
  }

  .mobile-bottom-nav .nav__item:active {
    background: var(--surface-2) !important;
  }
}

/* ── 2. The calendar fits, no side-scroll, not cramped ──
   Per explicit request: the grid must fill the card width on mobile
   instead of scrolling horizontally, and not feel cramped. The 8th
   "Weekly" summary column is dropped on mobile (app.html's inline mobile
   block hides it and switches the grid to 7 columns) so each day gets
   ~1/7 of the width instead of 1/8 — this file just has to agree with
   those sizes instead of shrinking them back down. */
@media (max-width: 820px) {
  .calendar-card,
  #calendarCard,
  .cal-scroll {
    overflow-x: visible !important;
  }

  .calendar-grid {
    min-width: 0 !important;
    width: 100% !important;
    grid-auto-rows: auto !important;
  }

  .cal-dow.is-weekly,
  .cal-day--weekly {
    display: none !important;
  }

  /* Square tiles — height follows each column's own width instead of a
     fixed px, so 7 columns and 5 columns (Weekend Off) both stay 1:1. */
  .cal-day {
    aspect-ratio: 1 / 1 !important;
    height: auto !important;
    min-height: 0 !important;
    max-height: none !important;
    padding: 8px 4px !important;
  }

  /* aspect-ratio:1 makes the box height DEFINITE (computed from its own
     width) — content that needs more room no longer grows the box, it just
     overflows past the edge and gets clipped, invisibly, by overflow:hidden
     on .cal-day. The base desktop styles push the P&L line down with
     padding-top + margin-top:auto, which was enough to shove it below that
     edge on narrower columns. Tighten the gap and shrink the text instead. */
  .cal-day__body {
    margin-top: 2px !important;
    padding-top: 0 !important;
  }
  .cal-day__header {
    flex-shrink: 0 !important;
  }
  .cal-day__num { font-size: 12px !important; }
  .txt-win, .txt-loss, .txt-be { font-size: 11px !important; }
  .cal-day__badge { padding: 1px 4px !important; font-size: 9px !important; }
  .cal-dow { font-size: 10px !important; padding: 8px 3px !important; }
}
.calendar-card__scroll-hint { display: none !important; }

/* ── 3. Sidebar drawer ──
   It already slides over the content and already closes on an outside
   tap. What it lacked was a backdrop in the right stacking context: the
   old one was a ::after on .sidebar itself at z-index:-1, which put the
   dim layer behind the page instead of over it. */
@media (max-width: 1024px) {
  .sidebar {
    width: min(280px, 82vw) !important;
    z-index: 1200 !important;
    box-shadow: none !important;
  }

  .sidebar.is-open::after {
    z-index: -1 !important;
    left: min(280px, 82vw) !important;
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
  }

  /* The drawer must never push the page: content stays put underneath. */
  .main,
  .topbar {
    margin-left: 0 !important;
    transform: none !important;
  }
}

/* ── General small-screen fit ── */
@media (max-width: 820px) {
  /* One long greeting line was eating three rows of the viewport. */
  .view__title {
    font-size: 22px !important;
    line-height: 28px !important;
  }

  .view__subtitle { font-size: 14px !important; line-height: 20px !important; }

  .view__header {
    margin-bottom: var(--s-5) !important;
    padding-bottom: var(--s-4) !important;
    gap: var(--s-3) !important;
  }

  /* Range chips and tool buttons wrap into a scrollable row instead of
     stacking into a tall pile. */
  .view__actions {
    width: 100%;
    overflow-x: auto;
    flex-wrap: nowrap;
    padding-bottom: 2px;
    scrollbar-width: none;
  }

  .view__actions::-webkit-scrollbar { display: none; }
  .view__actions > * { flex: none; }

  .kpi-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: var(--s-2) !important;
  }

  .kpi { padding: var(--s-3) !important; }
  .kpi__value { font-size: 20px !important; line-height: 26px !important; }
  .kpi__label { font-size: 10px !important; }

  .card { padding: var(--s-4) !important; }
  .card__title { font-size: 16px !important; line-height: 22px !important; }

  /* Full-height sheets read better than floating cards on a phone.
     These must out-specify section 16's `#tradeModal .modal`, which sets
     max-width:1040px and width:calc(100vw - 48px) for the desktop
     dialog. Without the id here that rule won at (1,1,0) and the sheet
     came out square-cornered but inset 24px on each side — reading as a
     card that had drifted off-centre rather than a sheet. */
  .modal-backdrop {
    padding: 0 !important;
    align-items: stretch !important;
    justify-items: stretch !important;
  }

  /* Viewport units, not percentages. styles.css sizes these sheets with
     `#tradeModal .modal { width: 94% !important }` and
     `.modal { width: 96% !important; margin: auto !important }`, and a
     percentage resolves against the padded backdrop — which is why the
     sheet kept a few stubborn pixels of gutter no matter what the
     backdrop padding was set to. 100vw/100dvh sidesteps the parent. */
  #tradeModal .modal,
  .modal-backdrop .modal,
  .modal {
    width: 100vw !important;
    max-width: 100vw !important;
    height: 100dvh !important;
    max-height: 100dvh !important;
    border-radius: 0 !important;
    margin: 0 !important;
    inset: 0 !important;
    display: flex !important;
    flex-direction: column !important;
  }

  /* Head and foot pin; only the fields scroll. Without this the Save
     button sits below the fold of a long form. */
  .modal__head { flex: none; }

  .modal__body {
    flex: 1 1 auto;
    min-height: 0;
    max-height: none !important;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }

  .modal__foot {
    flex: none;
    position: sticky;
    bottom: 0;
    background: var(--surface);
    border-top: var(--rule);
    padding-bottom: calc(var(--s-3) + env(safe-area-inset-bottom, 0px)) !important;
  }

  /* Tap targets. */
  .btn, .seg__btn, .icon-btn, .chip {
    min-height: 38px;
    display: inline-flex;
    align-items: center;
  }
}

@media (max-width: 480px) {
  .kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
  .view__title { font-size: 20px !important; line-height: 26px !important; }
  .mobile-bottom-nav .nav__item .nav__label { font-size: 10px !important; }
}

/* ───────── 19. BOTTOM-BAR NEW TRADE ─────────
   Logging a trade was reachable on a phone only by opening the drawer or
   scrolling to the header button. It is the app's primary action, so it
   now sits in the middle of the tab bar — the easiest place to reach
   one-handed — as an accent disc rather than a sixth labelled tab, which
   keeps it read as an action instead of a destination. */

.nav__item--action {
  flex: 0 0 auto !important;
  min-width: 0 !important;
  padding: 0 var(--s-2) !important;
  background: none !important;
  border: 0 !important;
  cursor: pointer;
}

.nav__action-disc {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 14px;
  background: var(--accent);
  color: #fff;
  transition: transform .12s var(--ease), background .15s var(--ease);
}

.nav__item--action:active .nav__action-disc {
  transform: scale(0.94);
  background: var(--accent-2);
}

/* It is an action, so it never takes the active-tab treatment. */
.mobile-bottom-nav .nav__item--action.is-active::after { display: none !important; }

/* Desktop keeps the sidebar's New Trade button; the bar is hidden there
   anyway, but this guards against it leaking into a wide layout. */
@media (min-width: 1025px) {
  .nav__item--action { display: none !important; }
}

/* ───────── 20. TRADES ON SMALL SCREENS ─────────
   Three faults visible on a real handset:

   1. .trade-filters is pinned to `repeat(3, 1fr)` on mobile. At 375px
      each column is ~105px, so the bilingual labels — "MARKET (ตลาด)",
      "SESSION (ช่วงตลาด)" — wrap to two lines at different heights,
      which is what makes the rows look ragged, and the selects clip
      their own text to "All Market" / "All Session".

   2. .trade-table cells are `white-space: nowrap` inside a wrapper that
      was told to shrink instead of scroll, so the right-hand columns are
      cut off with no way to reach them.

   3. The sticky header hardcodes `background: #15151d` / `#131319` —
      dark slabs that stay dark on the light themes. */

@media (max-width: 820px) {
  .trade-filters {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: var(--s-3) !important;
    align-items: start !important;
  }

  .filter-group {
    min-width: 0;
    margin-bottom: 0 !important;
  }

  .filter-label {
    display: block;
    font-size: 10px !important;
    letter-spacing: 0.06em !important;
    line-height: 1.3;
    margin-bottom: 5px;
    /* Two-line labels are what threw the rows out of alignment; reserve
       the same height for every one of them. */
    min-height: 26px;
  }

  .trade-filters .input,
  .trade-filters .select {
    width: 100% !important;
    min-width: 0 !important;
    font-size: 14px !important;
  }

  /* Search spans the row — it is the field people actually type in. */
  .trade-filters .filter-group:first-child { grid-column: 1 / -1; }

  .filter-group--actions {
    grid-column: 1 / -1;
    align-self: start !important;
  }

  .filter-group--actions .btn { width: 100% !important; }

  /* Table scrolls inside its own wrapper; the page never moves sideways. */
  .trade-table-wrap {
    overflow-x: auto !important;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
  }

  .trade-table { min-width: 660px !important; }
  .trade-table th,
  .trade-table td { padding: 10px !important; font-size: 12.5px !important; }
}

@media (max-width: 560px) {
  .trade-filters { grid-template-columns: minmax(0, 1fr) !important; }
  .filter-label { min-height: 0; }
}

/* Sticky header follows the theme instead of two hardcoded greys. */
.trade-table thead th,
.admin-table thead th {
  background: var(--surface) !important;
  color: var(--text-3) !important;
}

/* Destructive action stops competing with the primary one. It sat as a
   filled red slab the same size as "+ New Trade", first in the row and
   directly under the thumb. */
#tradesDeleteAll {
  background: none !important;
  color: var(--loss-ink) !important;
  border: 1px solid var(--border) !important;
  box-shadow: none !important;
  order: 9;
}

#tradesDeleteAll:hover {
  border-color: var(--loss-ink) !important;
  background: var(--loss-soft) !important;
}

@media (max-width: 820px) {
  /* Primary first, destructive last. */
  #tradesNewTrade { order: 1; }
}

/* Trade drawer. Its content measures past the right edge while closed,
   but that is the drawer parked off-screen, not an overflow bug — the
   panel and its content are both 388px on a 412px viewport. Only the
   genuine small-screen needs are handled: a long symbol should wrap
   rather than stretch the header, and the tab strip should scroll. */
@media (max-width: 820px) {
  .trade-drawer__title-group { flex-wrap: wrap; min-width: 0; }
  .trade-drawer__title { word-break: break-word; }

  .trade-drawer__tabs {
    overflow-x: auto;
    scrollbar-width: none;
  }

  .trade-drawer__tabs::-webkit-scrollbar { display: none; }
  .trade-drawer__tabs .trade-drawer__tab { flex: none; }
}

/* ───────── 21. MOBILE TEST FIXES ─────────
   Found by driving the app at 375x812 with touch emulation. */

/* A. Toasts were landing under the tab bar.
   .toast-stack sits at `bottom: 24px` with z-index 3000; the tab bar is
   56px tall at z-index 9999. A single toast measured 734-788px on an
   812px viewport while the bar starts at 751 — 37px of every message,
   including "Sync: added N trades" and the guest-mode notice, was
   covered, and the bar wins the stacking order. */
@media (max-width: 1024px) {
  .toast-stack {
    bottom: calc(56px + env(safe-area-inset-bottom, 0px) + var(--s-3)) !important;
    left: var(--s-3) !important;
    right: var(--s-3) !important;
    z-index: 10000 !important;
    align-items: stretch !important;
  }

  .toast-stack .toast { width: 100% !important; max-width: 100% !important; }
}

/* B. The boot splash keeps hit-testing during its fade.
   `.boot-loader.is-hidden` sets visibility:hidden, but the transition
   delays visibility by 0.6s — so for that window the full-screen splash
   still swallows taps at z-index 9999. Opacity is already 0 by then, so
   the element is invisible while eating input. */
.boot-loader.is-hidden {
  pointer-events: none !important;
}

/* C. Row actions were 28x28 with delete adjacent to edit.
   Four icon buttons per row — view, edit, share, delete — each 28px on a
   touch screen, with the destructive one a couple of pixels from the
   edit control. Enlarged and separated on mobile: mis-tapping delete
   here removes a logged trade. */
@media (max-width: 1024px) {
  .row-actions {
    display: flex !important;
    gap: var(--s-2) !important;
    opacity: 1 !important;
  }

  .row-actions button,
  .row-actions .view-drawer,
  .row-actions .edit,
  .row-actions .share-card,
  .row-actions .danger {
    width: 38px !important;
    height: 38px !important;
    display: grid !important;
    place-items: center !important;
    border-radius: var(--r-control) !important;
  }

  /* Extra separation before the destructive one. */
  .row-actions .danger { margin-left: var(--s-2) !important; }
}

/* D. Portfolio switcher opened off-screen on mobile.
   #accSwitchMenu is a child of .sidebar__footer and is positioned with
   an inline `position: fixed; right: 98px`. On mobile the sidebar is slid
   away with `transform: translateX(-100%)` — and a transformed ancestor
   becomes the containing block for fixed-position descendants. So the
   menu resolved against the off-screen sidebar instead of the viewport
   and rendered at x=-379 on a 375px screen: tapping the chip appeared to
   do nothing at all.

   Two parts to the repair. First, slide the drawer with `left` instead
   of `transform` so it stops creating a containing block. Second, give
   the menu real viewport coordinates — the inline `right: 98px` is
   computed for the desktop topbar, so !important is needed to beat it. */
/* The drawer keeps its transform-based slide — that is the GPU-composited
   one, and swapping it for a `left` transition would repaint the panel
   every frame. The menu is moved to <body> in app.js instead, which is
   the actual root fix. Only a width cap is needed here so the menu cannot
   run past a narrow screen. */
@media (max-width: 1024px) {
  /* app.js right-aligns the menu to the chip, but the chip is not at the
     screen edge — the bell and avatar sit to its right — so a 280px menu
     anchored there hangs 3px off the left of a 375px screen. On a phone
     it becomes a full-width sheet under the topbar instead. The inline
     styles app.js writes are non-important, so these win. */
  #accSwitchMenu,
  .acc-switch-menu {
    left: var(--s-3) !important;
    right: var(--s-3) !important;
    width: auto !important;
    min-width: 0 !important;
    max-width: none !important;
    max-height: calc(100dvh - var(--topbar-h) - 96px) !important;
    overflow-y: auto !important;
    z-index: 1300 !important;
  }
}

/* E. The New Trade action split the tab row.
   Sitting inline as a sixth item, it pushed "Chart" off its centre and
   squeezed "Time Machine" into an ellipsis. Lifted out of the row: the
   five tabs go back to even spacing and the action floats clear of them
   as a circular button, which also reads more clearly as an action than
   a tab. */
/* Specificity note: section 18 sets `.mobile-bottom-nav .nav__item
   { position: relative !important }` at (0,2,0). The action has to match
   that weight or it stays in the row. */
.mobile-bottom-nav .nav__item--action,
.nav__item--action {
  position: fixed !important;
  right: var(--s-4) !important;
  bottom: calc(56px + env(safe-area-inset-bottom, 0px) + var(--s-4)) !important;
  z-index: 9998 !important;
  padding: 0 !important;
  width: auto !important;
  height: auto !important;
  min-height: 0 !important;
  flex: none !important;
}

.nav__action-disc {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  box-shadow: 0 6px 16px rgba(var(--accent-glow), 0.35);
}

@media (min-width: 1025px) {
  .nav__item--action { display: none !important; }
}

/* F. The tab bar sat on top of every modal.
   Backdrops are z-index 1000; the bar is 9999 and the floating action
   9998. On a phone that put the bar directly over the modal footer — the
   Pre-Trade Checklist's "Skip" and "Proceed to Trade" both hit-tested to
   .nav__item instead of themselves, so the checklist could not be
   completed at all.

   Two layers of repair. The z-index correction is the one that always
   applies: dialogs go above the bar, and toasts stay above dialogs so a
   message raised by a modal is still readable. The :has() rules then
   remove the bar and the action button entirely while a dialog is open,
   which is the right behaviour anyway — neither is reachable or
   meaningful behind a modal. Browsers without :has() still get correct
   stacking from the first rule. */

.modal-backdrop {
  z-index: 10010 !important;
}

.toast-stack {
  z-index: 10020 !important;
}

/* The drawer and its scrim also belong above the bar. */
.trade-drawer { z-index: 10005 !important; }
.trade-drawer-backdrop, .drawer-backdrop { z-index: 10004 !important; }

@media (max-width: 1024px) {
  body:has(.modal-backdrop:not([hidden])) .mobile-bottom-nav,
  body:has(.modal-backdrop:not([hidden])) .nav__item--action {
    display: none !important;
  }

  /* A full-height sheet supplies its own bottom padding, so the view's
     tab-bar allowance must not stack on top of it. */
  body:has(.modal-backdrop:not([hidden])) .main {
    padding-bottom: var(--s-6) !important;
  }
}

/* G. The open drawer had the same problem as the modals.
   .sidebar is z-index 1200 against the bar's 9999, so "Pricing",
   "Settings" and the drawer's own "New Trade" button all hit-tested to
   .nav__item — the bottom third of the menu was dead. The bar is also
   redundant while the drawer is open, so it steps aside.

   The PDPA consent banner is fixed at z-index 9999 too and was covering
   "Playbook" and "Risk Lab". The banner is left in place and still
   requires an explicit accept — it is simply no longer stacked over an
   open navigation drawer. */
@media (max-width: 1024px) {
  .sidebar.is-open { z-index: 10015 !important; }
  .sidebar.is-open::after { z-index: -1 !important; }

  body:has(.sidebar.is-open) .mobile-bottom-nav,
  body:has(.sidebar.is-open) .nav__item--action {
    display: none !important;
  }
}

/* ───────── Sync-cap chooser ───────── */

.synccap-note {
  font-size: 13px;
  line-height: 1.6;
  color: var(--text-2);
  margin: 0 0 var(--s-4);
}

.synccap-counter {
  font-size: 12px;
  color: var(--text-3);
  margin-bottom: var(--s-3);
}

.synccap-counter b { color: var(--text-1); font-variant-numeric: tabular-nums; }

.synccap-item:has(input:disabled) { opacity: 0.5; }

.synccap-list {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

.synccap-item {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  border: var(--rule);
  border-radius: var(--r-card);
  cursor: pointer;
  transition: border-color .15s var(--ease), background .15s var(--ease);
}

.synccap-item:hover { border-color: var(--border-strong); }

.synccap-item:has(input:checked) {
  border-color: var(--accent);
  background: var(--surface-2);
}

.synccap-item input { flex: none; }

.synccap-item__body { display: flex; flex-direction: column; min-width: 0; }

.synccap-item__name {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-1);
}

.synccap-item__meta {
  font-size: 12px;
  color: var(--text-3);
  margin-top: 2px;
}

/* ───────── UI scale control ───────── */

.ui-scale {
  padding-bottom: var(--s-5);
  margin-bottom: var(--s-5);
  border-bottom: var(--rule);
}

.ui-scale__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  flex-wrap: wrap;
}

.ui-scale__label {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-1);
}

.ui-scale__hint {
  font-size: 12px;
  color: var(--text-3);
  margin-top: 3px;
}

.ui-scale__note {
  font-size: 12px;
  line-height: 1.5;
  color: var(--warn-ink);
  margin-top: var(--s-2);
  max-width: 46ch;
}

#uiScaleSeg .seg__btn {
  font-variant-numeric: tabular-nums;
  min-width: 52px;
}

@media (max-width: 560px) {
  .ui-scale__head { flex-direction: column; align-items: stretch; }
  #uiScaleSeg { width: 100%; }
  #uiScaleSeg .seg__btn { flex: 1; min-width: 0; }
}

/* ───────── 21. MOTION ───────── */

@media (prefers-reduced-motion: reduce) {
  .card,
  .kpi,
  .btn,
  .chip,
  .seg__btn,
  .icon-btn { transition: none; }
}

