/* ============================================================================
   Convex Nexus Capital — read-only observer dashboard
   Dark, modern hedge-fund theme. Standard CSS only, system font stack.
   ========================================================================== */

/* ----------------------------------------------------------------------------
   Design tokens
   -------------------------------------------------------------------------- */
:root {
  /* Surfaces — deep charcoal/navy with subtle elevation steps */
  --bg-0:        #0b0f17;            /* page base */
  --bg-1:        #0e131d;            /* slight lift */
  --bg-2:        #131a26;            /* panel base */
  --surface:     rgba(22, 30, 44, 0.66);   /* glassy card fill */
  --surface-2:   rgba(28, 38, 56, 0.72);   /* nested / hover */
  --surface-3:   rgba(34, 46, 67, 0.80);   /* tiles, inputs */

  /* Borders & lines */
  --border:      rgba(148, 170, 205, 0.12);
  --border-soft: rgba(148, 170, 205, 0.08);
  --border-str:  rgba(148, 170, 205, 0.20);
  --line:        rgba(148, 170, 205, 0.10); /* table row separators */

  /* Text */
  --text:        #e7ecf4;           /* primary */
  --text-dim:    #aab4c6;           /* secondary */
  --text-mute:   #707d93;           /* tertiary / notes */
  --text-faint:  #4b566a;           /* disabled-ish */

  /* Accent — crisp teal/cyan */
  --accent:      #2dd4bf;
  --accent-2:    #38bdf8;
  --accent-deep: #0e7c70;
  --accent-glow: rgba(45, 212, 191, 0.35);
  --accent-soft: rgba(45, 212, 191, 0.12);

  /* Semantic */
  --pos:         #34d399;           /* gains */
  --pos-soft:    rgba(52, 211, 153, 0.12);
  --neg:         #f87171;           /* losses */
  --neg-soft:    rgba(248, 113, 113, 0.12);
  --warn:        #fbbf24;           /* stale / caution */
  --warn-soft:   rgba(251, 191, 36, 0.13);
  --danger:      #ef4444;

  /* Radii */
  --r-xs: 6px;
  --r-sm: 9px;
  --r-md: 13px;
  --r-lg: 18px;
  --r-pill: 999px;

  /* Shadows / elevation */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.30);
  --shadow:    0 6px 22px rgba(0, 0, 0, 0.38), 0 1px 0 rgba(255, 255, 255, 0.03) inset;
  --shadow-lg: 0 18px 50px rgba(0, 0, 0, 0.50), 0 1px 0 rgba(255, 255, 255, 0.04) inset;

  /* Typography */
  --font: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
          Arial, "Noto Sans", sans-serif;
  --mono: ui-monospace, "SF Mono", "Cascadia Code", "Roboto Mono", Menlo, Consolas,
          "Liberation Mono", monospace;

  /* Layout rhythm */
  --gap:   18px;
  --pad:   20px;
  --topbar-h: 62px;
  --fresh-strip-h: 27px;   /* height of the injected #data-freshness strip
                              (min-height 26px + 1px bottom border); the private
                              banner parks below it via top:calc(...) so the two
                              sticky bars never share a slot. */

  /* Component surfaces reused below */
  --tip-bg:      #060a12;            /* tooltip body */
  --seg-bg:      rgba(255, 255, 255, 0.035);  /* segmented-control track */
}

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

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

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;               /* stops the footer hiding under the mobile URL bar */
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.5;
  color: var(--text);
  background:
    radial-gradient(1100px 600px at 78% -8%, rgba(56, 189, 248, 0.10), transparent 60%),
    radial-gradient(900px 520px at 8% 0%,   rgba(45, 212, 191, 0.08), transparent 55%),
    linear-gradient(180deg, #0c111b 0%, var(--bg-0) 38%, #090d14 100%);
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-feature-settings: "tnum" 1, "cv05" 1;
}

a { color: var(--accent-2); text-decoration: none; }
a:hover { color: var(--accent); }

h1, h2, h3, h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }

::selection { background: var(--accent-soft); color: #eafffb; }

/* Scrollbars */
* { scrollbar-width: thin; scrollbar-color: rgba(148, 170, 205, 0.28) transparent; }
*::-webkit-scrollbar { height: 10px; width: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: rgba(148, 170, 205, 0.22);
  border-radius: var(--r-pill);
  border: 2px solid transparent;
  background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background: rgba(148, 170, 205, 0.38); background-clip: content-box; }

/* Numeric tabular alignment for any element that needs it.
   Covers both the legacy (.val/.label) and the live runtime (.kpi-num /
   .col-num that app.js actually emits) so every figure lines up digit-for-digit
   and never reflows as values tick. */
.tnum,
.kpi .val,
.kpi .value,
.kpi .kpi-num,
.kpi .kpi-cur,
.ptable td,
.ptable th,
.ptable td.col-num,
.ptable th.col-num {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
}

/* ============================================================================
   TOP BAR
   ========================================================================== */
.topbar {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 20px;
  height: var(--topbar-h);
  padding: 0 24px;
  background: linear-gradient(180deg, rgba(13, 18, 28, 0.92), rgba(11, 15, 23, 0.80));
  backdrop-filter: saturate(150%) blur(14px);
  -webkit-backdrop-filter: saturate(150%) blur(14px);
  border-bottom: 1px solid var(--border);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.03) inset, 0 8px 24px rgba(0, 0, 0, 0.28);
}

/* Brand (left) */
.brand {
  display: flex;
  align-items: center;
  gap: 11px;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.015em;
  color: var(--text);
  white-space: nowrap;
}
.brand::before {
  content: "";
  width: 22px;
  height: 22px;
  border-radius: 7px;
  background:
    conic-gradient(from 210deg, var(--accent), var(--accent-2), var(--accent));
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.10) inset, 0 0 16px var(--accent-glow);
  flex: 0 0 auto;
}
.brand small,
.brand .sub {
  font-weight: 500;
  color: var(--text-mute);
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Nav (center) — link pills */
.nav {
  display: flex;
  align-items: center;
  gap: 4px;
  margin: 0 auto;
  padding: 4px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border-soft);
}
.nav a {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 15px;
  border-radius: var(--r-pill);
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 550;
  line-height: 1;
  white-space: nowrap;
  transition: background-color .15s ease, color .15s ease, box-shadow .15s ease;
}
.nav a:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.05);
}
.nav a.active,
.nav a[aria-current="page"] {
  color: #06231f;
  background: linear-gradient(180deg, var(--accent), #1fb9a6);
  box-shadow: 0 2px 10px var(--accent-glow), 0 1px 0 rgba(255, 255, 255, 0.25) inset;
  font-weight: 650;
}

/* Right cluster */
.topbar-right {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-left: auto;
  white-space: nowrap;
}
/* A faint divider between functional groups in the right cluster keeps the
   account switcher, identity and actions visually separated. */
.topbar-right > .topbar-sep,
.topbar-right .sep {
  width: 1px;
  height: 22px;
  background: var(--border);
  flex: 0 0 auto;
}
.user-email {
  font-size: 12.5px;
  color: var(--text-dim);
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.user-email::before {
  content: "";
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-right: 7px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 8px var(--accent-glow);
  vertical-align: middle;
}

/* ----------------------------------------------------------------------------
   Account switcher — segmented control (.acct-switch > .acct-btn[.active])
   A tidy iOS-style segmented control for the right of the topbar: one pill
   track holding equal-weight segments, the selected one raised in accent teal.
   Works as <div class="acct-switch"> with <button class="acct-btn"> children
   (an <a> works too); add .active to the current account.
   -------------------------------------------------------------------------- */
.acct-switch {
  display: inline-flex;
  align-items: stretch;
  gap: 3px;
  padding: 3px;
  border-radius: var(--r-pill);
  background: var(--seg-bg);
  border: 1px solid var(--border-soft);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.03) inset;
  flex: 0 0 auto;
}
.acct-switch .acct-label {
  /* optional leading "Account" caption inside the control */
  display: inline-flex;
  align-items: center;
  padding: 0 8px 0 9px;
  font-size: 10px;
  font-weight: 650;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-mute);
  white-space: nowrap;
}
.acct-btn {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-width: 38px;
  padding: 6px 13px;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.01em;
  white-space: nowrap;
  cursor: pointer;
  text-decoration: none;
  font-variant-numeric: tabular-nums;
  transition: background-color .15s ease, color .15s ease,
              box-shadow .15s ease, border-color .15s ease;
}
.acct-btn:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.06);
}
.acct-btn:focus-visible {
  outline: 2px solid var(--accent-2);
  outline-offset: 2px;
}
.acct-btn:active { transform: translateY(0.5px); }
.acct-btn.active,
.acct-btn[aria-pressed="true"],
.acct-btn[aria-current="true"] {
  color: #06231f;
  background: linear-gradient(180deg, var(--accent), #1fb9a6);
  border-color: transparent;
  box-shadow: 0 2px 9px var(--accent-glow), 0 1px 0 rgba(255, 255, 255, 0.25) inset;
  font-weight: 700;
  cursor: default;
}
.acct-btn.active:hover { color: #06231f; }   /* don't dim the selected segment */
/* An "All accounts" / aggregate segment reads a touch wider + lighter weight */
.acct-btn.all { font-weight: 650; letter-spacing: 0.02em; }
.acct-btn[disabled],
.acct-btn:disabled { opacity: 0.4; cursor: not-allowed; }

#logoutBtn {
  appearance: none;
  border: 1px solid var(--border-str);
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-dim);
  font: inherit;
  font-size: 12.5px;
  font-weight: 550;
  padding: 7px 13px;
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}
#logoutBtn:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.08);
  border-color: var(--border-str);
}
#logoutBtn:active { transform: translateY(1px); }

/* ----------------------------------------------------------------------------
   iOS-style toggle switch (.switch wrapping <input id="privateToggle">)
   -------------------------------------------------------------------------- */
.switch {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  user-select: none;
  font-size: 12.5px;
  color: var(--text-dim);
  white-space: nowrap;
}
.switch > input {
  /* Hide the native checkbox but keep it accessible & focusable */
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  flex: 0 0 auto;
  width: 44px;
  height: 25px;
  margin: 0;
  border-radius: var(--r-pill);
  background: rgba(120, 135, 160, 0.30);
  border: 1px solid var(--border-str);
  cursor: pointer;
  transition: background-color .22s ease, border-color .22s ease, box-shadow .22s ease;
  vertical-align: middle;
}
.switch > input::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 3px;
  width: 19px;
  height: 19px;
  transform: translateY(-50%);
  border-radius: 50%;
  background: #f4f7fb;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45), 0 0 0 0.5px rgba(0, 0, 0, 0.10);
  transition: left .22s cubic-bezier(.32, 1.4, .55, 1);
}
.switch > input:checked {
  background: linear-gradient(180deg, var(--accent), var(--accent-deep));
  border-color: transparent;
  box-shadow: 0 0 0 1px var(--accent-glow), 0 2px 8px var(--accent-glow) inset;
}
.switch > input:checked::after { left: 22px; }
.switch > input:focus-visible {
  outline: 2px solid var(--accent-2);
  outline-offset: 2px;
}
.switch:hover > input:not(:checked) { background: rgba(120, 135, 160, 0.42); }

/* ============================================================================
   LAYOUT — responsive panel grid
   ========================================================================== */
#panels {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
  gap: var(--gap);
  max-width: 1640px;
  margin: 0 auto;
  padding: 26px 24px 56px;
  align-items: start;
}

/* Charts / big panels can span two columns */
.panel.wide { grid-column: span 2; }

/* On grids that can't fit two columns, don't let .wide overflow */
@media (max-width: 860px) {
  .panel.wide { grid-column: span 1; }
}

/* ============================================================================
   PANEL CARD
   ========================================================================== */
.panel {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: calc(var(--pad) + 2px) var(--pad) var(--pad);
  border-radius: var(--r-lg);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.025), rgba(255, 255, 255, 0)) ,
    var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
  backdrop-filter: blur(10px) saturate(130%);
  -webkit-backdrop-filter: blur(10px) saturate(130%);
  transition: border-color .18s ease, box-shadow .18s ease, transform .18s ease;
}
.panel::before {
  /* faint top-edge sheen */
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.12), transparent);
  pointer-events: none;
}
.panel:hover {
  border-color: var(--border-str);
  box-shadow: var(--shadow-lg);
}

/* Panel header — strong, scannable title row with the badge pushed right.
   A short accent tick before each title gives the eye an anchor down the
   left edge of the grid and reinforces the panel boundary. */
.panel-h {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  min-height: 28px;
  padding-bottom: 13px;
  margin-bottom: 1px;
  border-bottom: 1px solid var(--border-soft);
}
.panel-h h2,
.panel-h h3,
.panel-h .title {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 15.5px;
  font-weight: 680;
  letter-spacing: -0.012em;
  color: var(--text);
  margin-right: auto;          /* push badges to the right */
}
.panel-h h2::before,
.panel-h h3::before,
.panel-h .title::before {
  content: "";
  width: 3px;
  height: 15px;
  border-radius: var(--r-pill);
  background: linear-gradient(180deg, var(--accent), var(--accent-deep));
  box-shadow: 0 0 8px var(--accent-glow);
  flex: 0 0 auto;
}
.panel-h .sub,
.panel-h small {
  font-size: 12px;
  font-weight: 500;
  color: var(--text-mute);
}

/* ----------------------------------------------------------------------------
   Badges (freshness / stale)
   -------------------------------------------------------------------------- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: var(--r-pill);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  line-height: 1;
  white-space: nowrap;
  border: 1px solid var(--border-soft);
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-dim);
}
.badge::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex: 0 0 auto;
}
.badge.fresh {
  color: var(--accent);
  background: var(--accent-soft);
  border-color: rgba(45, 212, 191, 0.30);
}
.badge.fresh::before { box-shadow: 0 0 8px var(--accent-glow); }
.badge.stale {
  color: var(--warn);
  background: var(--warn-soft);
  border-color: rgba(251, 191, 36, 0.30);
}
.badge.stale.bad,
.badge.stale[data-level="bad"] {
  color: var(--neg);
  background: var(--neg-soft);
  border-color: rgba(248, 113, 113, 0.30);
}
/* "no data" / neutral badge — explicit, calmer than a freshness state */
.badge.nodata,
.badge.none {
  color: var(--text-mute);
  background: rgba(255, 255, 255, 0.04);
  border-color: var(--border-soft);
}
/* subtle pulse so a fresh tag feels live */
@keyframes badge-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.45; }
}
.badge.fresh::before { animation: badge-pulse 2.4s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .badge.fresh::before { animation: none; }
}

/* ----------------------------------------------------------------------------
   Stale panel treatment (.panel.is-stale — toggled per-panel by app.js)
   Make staleness obvious without hiding the (last-good) data: warm-dim the
   card, swap the title tick to amber, and let the stale badge read loud.
   -------------------------------------------------------------------------- */
.panel.is-stale {
  border-color: rgba(251, 191, 36, 0.26);
}
.panel.is-stale::after {
  /* thin amber rail down the left edge = "this card is stale" */
  content: "";
  position: absolute;
  top: 14px;
  bottom: 14px;
  left: 0;
  width: 2px;
  border-radius: 0 var(--r-pill) var(--r-pill) 0;
  background: linear-gradient(180deg, var(--warn), rgba(251, 191, 36, 0.25));
  pointer-events: none;
}
.panel.is-stale .panel-h h2::before,
.panel.is-stale .panel-h h3::before,
.panel.is-stale .panel-h .title::before {
  background: linear-gradient(180deg, var(--warn), rgba(251, 191, 36, 0.4));
  box-shadow: 0 0 8px var(--warn-soft);
}
/* gently mute the figures on a stale card so the eye reads "not live" */
.panel.is-stale .kpi .kpi-num,
.panel.is-stale .kpi .val,
.panel.is-stale .ptable tbody td { color: var(--text-dim); }
.panel.is-stale .badge.stale { box-shadow: 0 0 0 1px var(--warn-soft); }

/* ============================================================================
   KPI ROW
   ========================================================================== */
.kpis {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
}
.kpi {
  /* Pack a touch denser: panels with many KPIs (onepager has ~11) wrapped into a
     tall, ragged block at 220/200px. 178px lets tiles fit more-per-row while the
     content box (~178 − 36px padding ≈ 142px) still clears a 25px-bold tabular
     "$1,234,567" (~130px); longer book-aggregate values clip to ellipsis + title
     via the existing #panels .kpi .kpi-num{flex:0 1 auto;min-width:0} safety net,
     so no money value is ever cramped or lost. */
  flex: 1 1 178px;
  min-width: 178px;
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding: 16px 18px 15px;
  border-radius: var(--r-md);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0)),
    var(--surface-3);
  border: 1px solid var(--border-soft);
  transition: border-color .15s ease, background-color .15s ease;
}
.kpi:hover { border-color: var(--border); }

/* The big number. Both the legacy (.val/.value) and the live runtime
   (.kpi-value wrapping .kpi-cur + .kpi-num) structures get the same strong,
   tightly-tracked treatment so the figure dominates its tile. */
.kpi .val,
.kpi .value,
.kpi .kpi-value {
  display: flex;
  align-items: baseline;
  flex-wrap: nowrap;
  gap: 4px;
  font-size: 22px;
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--text);
  order: 1;                       /* value sits above label visually */
  white-space: nowrap;
}
.kpi .kpi-num {
  font-size: 22px;
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--text);
  white-space: nowrap;
}
/* Currency / unit prefix beside the number reads quieter than the figure. */
.kpi .unit,
.kpi .kpi-cur {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-mute);
  letter-spacing: 0;
}
/* Labels: small, calm, uppercase caption beneath the number. */
.kpi .label,
.kpi .lbl,
.kpi .kpi-label {
  order: 2;
  margin-top: 2px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-transform: none;
  color: var(--text-dim, #aab4c6);
  white-space: normal;
  line-height: 1.34;
}
.kpi .sub,
.kpi .delta {
  order: 3;
  font-size: 11.5px;
  font-weight: 550;
  color: var(--text-mute);
}
/* good/bad emphasis must survive into the KPI number itself */
.kpi .kpi-num.pos,
.kpi .val .pos,
.kpi .value .pos { color: var(--pos); }
.kpi .kpi-num.neg,
.kpi .val .neg,
.kpi .value .neg { color: var(--neg); }

/* Accent tint variant for the hero KPI in a panel */
.kpi.accent {
  border-color: rgba(45, 212, 191, 0.28);
  background:
    linear-gradient(180deg, var(--accent-soft), rgba(45, 212, 191, 0.02)),
    var(--surface-3);
}

/* ============================================================================
   CHART CONTAINER
   ========================================================================== */
.chart {
  position: relative;
  width: 100%;
  min-height: 260px;
  border-radius: var(--r-md);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(0, 0, 0, 0.10)),
    rgba(10, 14, 22, 0.45);
  border: 1px solid var(--border-soft);
  overflow: hidden;
  padding: 6px;
}
.chart canvas,
.chart svg,
.chart > img { display: block; width: 100% !important; height: auto; max-width: 100%; }
/* empty / loading state — a quiet skeleton, NOT an error.
   On a cold load every chart slot is briefly empty for up to ~10s; a loud
   "Loading data…" string on each one reads as broken. Instead show a calm,
   low-contrast shimmer sweeping across the empty slot so a cold load looks
   intentional. No text, no JS — purely the empty ::after.
   ::after draws a faint left-to-right highlight band that animates across;
   ::before lays a barely-there base wash so the slot doesn't read as a hole. */
.chart:empty::before,
.chart.loading::before {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(148, 170, 205, 0.025);   /* whisper base, well under the data */
  pointer-events: none;
}
.chart:empty::after,
.chart.loading::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    100deg,
    transparent 30%,
    rgba(148, 170, 205, 0.07) 50%,
    transparent 70%
  );
  background-size: 220% 100%;
  background-repeat: no-repeat;
  animation: chart-skeleton 1.6s ease-in-out infinite;
}
@keyframes chart-skeleton {
  0%   { background-position: 130% 0; }
  100% { background-position: -30% 0; }
}
/* Reduced-motion: drop the sweep, keep a static, calm placeholder wash so the
   slot still reads as "pending" without movement. */
@media (prefers-reduced-motion: reduce) {
  .chart:empty::after,
  .chart.loading::after {
    animation: none;
    background: rgba(148, 170, 205, 0.04);
  }
}

/* ============================================================================
   TABLE (.ptable) — clean, zebra-free, thin separators, sticky header
   ========================================================================== */
.ptable-wrap {
  width: 100%;
  overflow-x: auto;
  border-radius: var(--r-md);
  border: 1px solid var(--border-soft);
}
.ptable {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
  background: rgba(10, 14, 22, 0.30);
}
.ptable thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: linear-gradient(180deg, #141b28, #111723);
  color: var(--text-mute);
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-align: left;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.ptable tbody td {
  padding: 10px 14px;
  border-bottom: 1px solid var(--line);
  color: var(--text-dim);
  white-space: nowrap;
}
.ptable tbody tr:last-child td { border-bottom: 0; }
.ptable tbody tr { transition: background-color .12s ease; }
.ptable tbody tr:hover td { background: rgba(255, 255, 255, 0.03); }

/* First column / account column reads as a bright label */
.ptable tbody td:first-child,
.ptable tbody td.col-acct { color: var(--text); font-weight: 600; }

/* Numeric cells read a shade brighter than label text so figures pop, and
   stay locked to tabular figures (app.js tags these .col-num). */
.ptable tbody td.col-num { color: var(--text); }

/* Right-align numbers: any th/td after the first, or explicit .num */
.ptable th.num,
.ptable td.num,
.ptable thead th:not(:first-child),
.ptable tbody td:not(:first-child) {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
/* …but allow opt-out for text columns */
.ptable th.text,
.ptable td.text { text-align: left; }

/* compact density modifier */
.ptable.compact thead th { padding: 7px 11px; }
.ptable.compact tbody td { padding: 6px 11px; }

/* ============================================================================
   NOTE (muted footnote)
   ========================================================================== */
.note {
  font-size: 11.5px;
  font-style: italic;
  color: var(--text-mute);
  line-height: 1.45;
  margin-top: 2px;
}
.note::before {
  content: "ⓘ";
  font-style: normal;
  margin-right: 6px;
  color: var(--text-faint);
}

/* ============================================================================
   VALUE COLORING + MASKING
   ========================================================================== */
/* Unmistakable good=green / bad=red. Signed figures carry a touch more weight
   than neutral text so a gain/loss is legible at a glance, not just by hue. */
.pos { color: var(--pos); font-weight: 600; }
.neg { color: var(--neg); font-weight: 600; }
.neutral,
.flat { color: var(--text-dim); }
.pos.strong,
.neg.strong { font-weight: 700; }

/* Optional directional caret (opt-in: add .dir alongside .pos/.neg).
   Pure CSS, no markup change needed beyond the class. */
.pos.dir::before { content: "\25B2"; font-size: 0.72em; margin-right: 4px; vertical-align: 0.06em; }
.neg.dir::before { content: "\25BC"; font-size: 0.72em; margin-right: 4px; vertical-align: 0.06em; }

/* tiny chip variants if a value needs a background */
.chip-pos { color: var(--pos); background: var(--pos-soft); padding: 2px 9px; border-radius: var(--r-pill); font-weight: 650; border: 1px solid rgba(52, 211, 153, 0.22); }
.chip-neg { color: var(--neg); background: var(--neg-soft); padding: 2px 9px; border-radius: var(--r-pill); font-weight: 650; border: 1px solid rgba(248, 113, 113, 0.22); }
.chip-neutral { color: var(--text-dim); background: rgba(255, 255, 255, 0.05); padding: 2px 9px; border-radius: var(--r-pill); font-weight: 600; }

/* Masked private value "***" — looks deliberately redacted, not broken. */
.mask {
  font-family: var(--mono);
  color: var(--text-faint);
  letter-spacing: 0.18em;
  font-weight: 700;
  user-select: none;
}
/* In a KPI tile a mask should still hold the number's visual weight so the
   layout doesn't jump between public/private. */
.kpi .kpi-num.mask,
.kpi .mask { color: var(--text-mute); letter-spacing: 0.16em; }

/* ============================================================================
   PRIVATE MODE (body.private-on)
   ========================================================================== */
body.private-on .pos,
body.private-on .neg,
body.private-on .kpi-num.pos,
body.private-on .kpi-num.neg,
body.private-on .col-num.pos,
body.private-on .col-num.neg {
  /* keep numbers legible but drain the money emphasis (red/green) */
  color: var(--text-dim) !important;
  font-weight: 550 !important;
}
body.private-on .chip-pos,
body.private-on .chip-neg {
  color: var(--text-dim) !important;
  background: rgba(255, 255, 255, 0.05) !important;
  border-color: var(--border-soft) !important;
}
body.private-on .kpi.accent {
  border-color: var(--border-soft);
  background: var(--surface-3);
}
/* dim the money-heavy hero KPI values a touch (still readable) */
body.private-on .kpi .val,
body.private-on .kpi .kpi-num { color: var(--text); }

/* "PRIVATE VIEW" indicator strip under the topbar — reads as a deliberate
   privacy banner (lock glyph + amber, distinct from the teal "live" accent).
   The injected freshness strip (#data-freshness, app.js) is ALSO sticky at
   top:var(--topbar-h); both this ::before and that strip would otherwise pin to
   the SAME slot and overlap (the strip, z-index 49, paints over us, z-index 40).
   Park the private banner directly BELOW the freshness strip by adding its
   height (~26px min-height + 1px border = 27px) to the offset, so the two stack
   cleanly: topbar → freshness strip → private banner. (--fresh-strip-h is a
   single source of truth; the mobile block zeroes the whole offset.) */
body.private-on::before {
  content: "\1F512  PRIVATE VIEW — money values hidden";
  position: sticky;
  top: calc(var(--topbar-h) + var(--fresh-strip-h));
  z-index: 40;
  display: block;
  text-align: center;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--warn);
  padding: 5px 0;
  background: linear-gradient(180deg, rgba(251, 191, 36, 0.15), rgba(251, 191, 36, 0.03));
  border-bottom: 1px solid rgba(251, 191, 36, 0.28);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

/* Mark the private toggle + its label as clearly engaged when on. */
body.private-on .switch-label,
body.private-on .switch { color: var(--warn); }
body.private-on .switch > input:checked {
  background: linear-gradient(180deg, var(--warn), #d99413);
  box-shadow: 0 0 0 1px var(--warn-soft), 0 2px 8px var(--warn-soft) inset;
}

/* ============================================================================
   AUTH PAGES
   ========================================================================== */
.auth-wrap,
body.auth {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 32px 18px;
}
.auth-card {
  width: 100%;
  max-width: 392px;
  padding: 30px 28px 26px;
  border-radius: var(--r-lg);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)),
    var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
}
.auth-card .brand {
  justify-content: center;
  margin-bottom: 4px;
  font-size: 17px;
}
.auth-card h1 {
  text-align: center;
  font-size: 20px;
  font-weight: 680;
  margin-bottom: 4px;
}
.auth-card .lead,
.auth-card .subtitle {
  text-align: center;
  color: var(--text-mute);
  font-size: 13px;
  margin-bottom: 22px;
}

/* Form fields */
.auth-card form,
form.auth-form { display: flex; flex-direction: column; gap: 14px; }

label {
  display: block;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-mute);
  margin-bottom: 7px;
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="search"],
select,
textarea {
  width: 100%;
  font: inherit;
  font-size: 14px;
  color: var(--text);
  background: rgba(8, 12, 19, 0.65);
  border: 1px solid var(--border-str);
  border-radius: var(--r-sm);
  padding: 11px 13px;
  transition: border-color .15s ease, box-shadow .15s ease, background-color .15s ease;
}
input::placeholder,
textarea::placeholder { color: var(--text-faint); }
input:hover,
select:hover,
textarea:hover { border-color: rgba(148, 170, 205, 0.32); }
input:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--accent);
  background: rgba(8, 12, 19, 0.85);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
input:-webkit-autofill {
  -webkit-text-fill-color: var(--text);
  -webkit-box-shadow: 0 0 0 1000px rgba(13, 18, 28, 0.95) inset;
  caret-color: var(--text);
}

/* ----------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */
.btn {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font: inherit;
  font-size: 13.5px;
  font-weight: 640;
  line-height: 1;
  padding: 11px 18px;
  border-radius: var(--r-sm);
  border: 1px solid transparent;
  color: #052420;
  background: linear-gradient(180deg, var(--accent), #1cb6a3);
  box-shadow: 0 4px 14px var(--accent-glow), 0 1px 0 rgba(255, 255, 255, 0.25) inset;
  cursor: pointer;
  transition: filter .15s ease, transform .08s ease, box-shadow .15s ease, background-color .15s ease;
}
.btn:hover { filter: brightness(1.07); box-shadow: 0 6px 20px var(--accent-glow), 0 1px 0 rgba(255, 255, 255, 0.30) inset; }
.btn:active { transform: translateY(1px); filter: brightness(0.98); }
.btn:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; }
.btn[disabled],
.btn:disabled { opacity: 0.5; cursor: not-allowed; box-shadow: none; filter: none; }

/* full-width primary (auth submit) */
.auth-card .btn,
.btn.block { width: 100%; padding: 12px 18px; }

/* Ghost / secondary */
.btn-ghost {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1;
  padding: 10px 16px;
  border-radius: var(--r-sm);
  color: var(--text-dim);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--border-str);
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease, border-color .15s ease, transform .08s ease;
}
.btn-ghost:hover { color: var(--text); background: rgba(255, 255, 255, 0.08); }
.btn-ghost:active { transform: translateY(1px); }
.btn-ghost:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; }

/* danger variant of ghost (Reject) */
.btn-ghost.danger {
  color: var(--neg);
  border-color: rgba(248, 113, 113, 0.35);
  background: var(--neg-soft);
}
.btn-ghost.danger:hover {
  color: #fff;
  background: var(--danger);
  border-color: var(--danger);
}

/* solid danger if needed */
.btn.danger {
  color: #fff;
  background: linear-gradient(180deg, #f05252, var(--danger));
  box-shadow: 0 4px 14px rgba(239, 68, 68, 0.35), 0 1px 0 rgba(255, 255, 255, 0.20) inset;
}

/* Auth helper row */
.auth-card .form-links,
.auth-card .links {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 14px;
  font-size: 12.5px;
  color: var(--text-mute);
}

/* ----------------------------------------------------------------------------
   Form messages
   -------------------------------------------------------------------------- */
.form-error,
.form-ok {
  font-size: 12.5px;
  font-weight: 550;
  padding: 10px 13px;
  border-radius: var(--r-sm);
  border: 1px solid transparent;
  line-height: 1.4;
}
.form-error {
  color: #fecaca;
  background: var(--neg-soft);
  border-color: rgba(248, 113, 113, 0.32);
}
.form-error::before { content: "⚠ "; }
.form-ok {
  color: #bbf7e6;
  background: var(--accent-soft);
  border-color: rgba(45, 212, 191, 0.30);
}
.form-ok::before { content: "✓ "; }

/* ============================================================================
   ADMIN
   ========================================================================== */
.admin-wrap { max-width: 1160px; margin: 0 auto; padding: 26px 24px 56px; }
.admin-wrap h1 { font-size: 20px; margin-bottom: 4px; }
.admin-wrap .lead { color: var(--text-mute); font-size: 13px; margin-bottom: 22px; }

.admin-table-wrap {
  width: 100%;
  overflow-x: auto;
  border-radius: var(--r-lg);
  border: 1px solid var(--border);
  background: var(--surface);
  box-shadow: var(--shadow);
}
.admin-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13.5px;
}
.admin-table thead th {
  position: sticky;
  top: 0;
  background: linear-gradient(180deg, #141b28, #111723);
  color: var(--text-mute);
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-align: left;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.admin-table tbody td {
  padding: 12px 16px;
  border-bottom: 1px solid var(--line);
  color: var(--text-dim);
  vertical-align: middle;
}
.admin-table tbody tr:last-child td { border-bottom: 0; }
.admin-table tbody tr:hover td { background: rgba(255, 255, 255, 0.025); }
.admin-table td:first-child,
.admin-table .email-cell { color: var(--text); font-weight: 550; }

/* action cell keeps buttons tidy */
.admin-table .actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  white-space: nowrap;
}
.admin-table .actions .btn,
.admin-table .actions .btn-ghost { padding: 7px 14px; font-size: 12.5px; }

/* ----------------------------------------------------------------------------
   Status pills (pending / approved / rejected)
   -------------------------------------------------------------------------- */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 11px;
  border-radius: var(--r-pill);
  font-size: 11px;
  font-weight: 650;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  line-height: 1;
  border: 1px solid var(--border-soft);
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-dim);
}
.pill::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.pill.approved,
.pill.active   { color: var(--pos);  background: var(--pos-soft);  border-color: rgba(52, 211, 153, 0.30); }
.pill.pending  { color: var(--warn); background: var(--warn-soft); border-color: rgba(251, 191, 36, 0.30); }
.pill.rejected,
.pill.revoked  { color: var(--neg);  background: var(--neg-soft);  border-color: rgba(248, 113, 113, 0.30); }

/* ============================================================================
   MISC UTILITIES
   ========================================================================== */
.muted { color: var(--text-mute); }
.mono  { font-family: var(--mono); }
.right { text-align: right; }
.center { text-align: center; }
.nowrap { white-space: nowrap; }
.hidden,
[hidden] { display: none !important; }
.spacer { flex: 1 1 auto; }

/* ----------------------------------------------------------------------------
   Tooltips (opt-in: any element with a [data-tip="..."] attribute)
   A polished, high-contrast bubble — does NOT touch native title= tooltips, so
   existing title attributes (badge as-of, switch, conn-status) keep working.
   Add data-tip-pos="bottom" to flip it below the element.
   -------------------------------------------------------------------------- */
[data-tip] { position: relative; }
[data-tip]::after,
[data-tip]::before {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 9px);
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, 4px);
  transition: opacity .14s ease, transform .14s ease;
  z-index: 80;
}
[data-tip]::after {
  content: attr(data-tip);
  white-space: pre-line;
  max-width: 280px;
  width: max-content;
  padding: 8px 11px;
  border-radius: var(--r-sm);
  background: var(--tip-bg);
  color: var(--text);
  border: 1px solid var(--border-str);
  box-shadow: var(--shadow-lg);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: 0;
  text-transform: none;
}
[data-tip]::before {              /* little pointer */
  content: "";
  bottom: calc(100% + 4px);
  width: 9px;
  height: 9px;
  background: var(--tip-bg);
  border-right: 1px solid var(--border-str);
  border-bottom: 1px solid var(--border-str);
  transform: translate(-50%, 4px) rotate(45deg);
}
[data-tip]:hover::after,
[data-tip]:focus-visible::after { opacity: 1; transform: translate(-50%, 0); }
[data-tip]:hover::before,
[data-tip]:focus-visible::before { opacity: 1; transform: translate(-50%, 0) rotate(45deg); }
/* below-element variant */
[data-tip][data-tip-pos="bottom"]::after { bottom: auto; top: calc(100% + 9px); }
[data-tip][data-tip-pos="bottom"]::before {
  bottom: auto; top: calc(100% + 4px);
  border: 0; border-left: 1px solid var(--border-str); border-top: 1px solid var(--border-str);
}
/* A faint dotted underline marks text that has an explanatory tooltip */
.has-tip,
[data-tip].hint { cursor: help; }
.has-tip { border-bottom: 1px dotted var(--text-faint); }

hr {
  border: 0;
  border-top: 1px solid var(--border-soft);
  margin: 16px 0;
}

/* Connection indicator polish — a status dot + elevation so "live" vs
   "connection lost" reads instantly. (Colors/positioning come from
   app-compat.css, loaded after; here we only add the dot + shadow, which it
   doesn't set, so nothing is overridden.) */
.conn-status {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-weight: 600;
  letter-spacing: 0.01em;
  box-shadow: var(--shadow);
}
.conn-status::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex: 0 0 auto;
}
.conn-status.ok::before {
  box-shadow: 0 0 8px var(--accent-glow);
  animation: badge-pulse 2.4s ease-in-out infinite;
}
.conn-status.lost::before { box-shadow: 0 0 8px var(--warn-soft); }
@media (prefers-reduced-motion: reduce) {
  .conn-status.ok::before { animation: none; }
}

/* read-only banner / lock affordance, if used anywhere */
.readonly-tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-mute);
  padding: 3px 9px;
  border: 1px solid var(--border-soft);
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.03);
}
.readonly-tag::before { content: "🔒"; font-size: 11px; }

/* ============================================================================
   RESPONSIVE
   ========================================================================== */
@media (max-width: 1024px) {
  .nav a { padding: 7px 12px; font-size: 12.5px; }
  .user-email { max-width: 150px; }
  .acct-btn { padding: 6px 11px; }
  .acct-switch .acct-label { display: none; }   /* drop the caption, keep segments */
}

@media (max-width: 820px) {
  .topbar {
    /* Un-stick the wrapped bar: at ~120-160px tall a sticky topbar pins the
       freshness strip (top:var(--topbar-h)), the private banner and the
       section-head scroll offsets ~66px down, hiding them behind itself.
       position:static removes all three hazards at once. */
    position: static;
    height: auto;
    flex-wrap: wrap;
    gap: 12px;
    /* respect the notch: never let the bar's content slide under a top/side
       cutout (env() resolves to 0 on non-notch devices, so base stays 12/16). */
    padding: max(12px, env(safe-area-inset-top))
             max(16px, env(safe-area-inset-right))
             12px
             max(16px, env(safe-area-inset-left));
  }
  .nav {
    order: 3;
    width: 100%;
    margin: 0;
    justify-content: center;
    flex-wrap: wrap;
  }
  .topbar-right { margin-left: auto; gap: 12px; flex-wrap: wrap; justify-content: flex-end; }
  .topbar-right > .topbar-sep,
  .topbar-right .sep { display: none; }
  #panels { grid-template-columns: 1fr; padding: 18px 14px 48px; }
  .panel.wide { grid-column: span 1; }
  /* Topbar is position:static here (height dynamic), so reset the banner's whole
     sticky offset — including the desktop +--fresh-strip-h stack — back to 0. */
  body.private-on::before { top: 0; }
}

@media (max-width: 520px) {
  body { font-size: 13.5px; }
  .kpi { flex: 1 1 100%; }
  .kpi .val { font-size: 21px; }
  /* On touch screens there is no hover to reveal a clipped value, and tiles are
     already full-width here -- so let long TEXT values (e.g. the vol-regime line
     "NORMAL -- VXN 27.3 (88%ile 1yr)", "n/a -- market closed", a date range) wrap
     at spaces instead of clipping. Numbers carry no internal spaces, so they stay
     on a single line. */
  .kpi .kpi-num { white-space: normal; }
  .user-email { display: none; }        /* keep the right cluster compact */
  .auth-card { padding: 24px 20px; }
  .admin-table .actions { flex-direction: column; align-items: stretch; }
}

/* Honor reduced-motion globally for transitions that animate position */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
  }
}

/* ============================================================================
   ADDITIVE POLISH — runtime-class hardening (2026-06-13)
   ----------------------------------------------------------------------------
   Everything below is purely ADDITIVE. It targets the classes app.js /
   charts.js actually emit at runtime (.kpi-num/.kpi-cur/.kpi-label,
   .col-num/.col-acct/.col-text, .panel.is-stale, body.private-on,
   body.acct-acct{1,2,3}) and only sharpens hierarchy, spacing, numeric
   alignment, stale/private treatments, the new wide panels, the topbar-right
   cluster, and a per-account accent hook. No selector above is modified or
   removed; nothing here changes app.js-injected behaviour (that <style> is
   appended to <head> at runtime and wins on equal specificity by order — we
   never try to out-rank its account-switcher chrome).
   ========================================================================== */

/* ----------------------------------------------------------------------------
   1) Tabular numerals everywhere a figure ticks
   Lock EVERY numeric runtime class to fixed-width digits so a value that
   updates every poll never reflows its column or nudges its neighbours. Covers
   the live emitters (.kpi-num/.kpi-cur, td/th.col-num, the account-id column
   .col-acct) plus signed money (.pos/.neg) wherever they land.
   -------------------------------------------------------------------------- */
#panels .kpi-num,
#panels .kpi-cur,
#panels .ptable th.col-num,
#panels .ptable td.col-num,
#panels .ptable td.col-acct,
#panels .ptable td.pos,
#panels .ptable td.neg,
#panels .kpi-num.pos,
#panels .kpi-num.neg {
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
}
/* The big KPI figure: tighten tracking + baseline so the number truly leads
   the tile, and keep the currency/unit prefix subordinate to it. */
#panels .kpi .kpi-value { align-items: baseline; gap: 3px; line-height: 1.05; }
#panels .kpi .kpi-num   { font-size: 25px; font-weight: 720; letter-spacing: -0.02em; }
#panels .kpi .kpi-cur   { font-size: 14px; font-weight: 600; color: var(--text-mute); }
#panels .kpi .kpi-label { letter-spacing: 0.07em; }
/* KPI number is a flex child of .kpi-value; without min-width:0 the ellipsis on
   .kpi-num never engages (flex items refuse to shrink below content), so a long
   value bleeds past the tile. Allow it to shrink + clip (title set in JS). */
#panels .kpi .kpi-num { flex: 0 1 auto; min-width: 0; }
/* Column-heavy tables (greeks / onepager / stress matrix) hold nowrap cells; let
   the table itself scroll horizontally instead of overrunning the card edge. */
#panels .ptable { display: block; overflow-x: auto; max-width: 100%; }

/* ----------------------------------------------------------------------------
   2) Unmistakable good / bad colouring (KPI numbers + table cells)
   The account-switcher <style> app.js injects sets the base pos/neg hue; we add
   the WEIGHT (and a hairline chip option) so a gain/loss reads at a glance by
   more than hue alone — but never with !important, so the private-mode drain
   (which IS !important, below) still wins and money emphasis disappears in
   Private view exactly as before.
   -------------------------------------------------------------------------- */
#panels .kpi-num.pos,
#panels .ptable td.pos { font-weight: 700; }
#panels .kpi-num.neg,
#panels .ptable td.neg { font-weight: 700; }
/* A faint tint behind a signed table cell makes the good/bad split scannable
   down a dense column without shouting. Kept subtle and pulled off in private
   view by the existing body.private-on rules + the override block below. */
#panels .ptable tbody td.pos { background: rgba(52, 211, 153, 0.06); }
#panels .ptable tbody td.neg { background: rgba(248, 113, 113, 0.06); }
#panels .ptable tbody tr:hover td.pos { background: rgba(52, 211, 153, 0.11); }
#panels .ptable tbody tr:hover td.neg { background: rgba(248, 113, 113, 0.11); }

/* ----------------------------------------------------------------------------
   3) Stronger stale-panel treatment (.panel.is-stale, set by app.js)
   app-compat dims a stale card to opacity .93 and style.css adds an amber rail;
   here we make "this data is old" unmistakable: a warm corner badge-glow, a
   clearer amber header underline, and a faint diagonal wash so a glance across
   the grid separates live cards from stale ones instantly. The last-good
   numbers stay readable (we dim, never hide).
   -------------------------------------------------------------------------- */
#panels .panel.is-stale {
  border-color: rgba(251, 191, 36, 0.34);
  box-shadow: var(--shadow), 0 0 0 1px rgba(251, 191, 36, 0.10) inset;
  background:
    linear-gradient(180deg, rgba(251, 191, 36, 0.05), rgba(255, 255, 255, 0)),
    var(--surface);
}
#panels .panel.is-stale .panel-h { border-bottom-color: rgba(251, 191, 36, 0.30); }
/* The widened amber rail (the base rail comes from .panel.is-stale::after). */
#panels .panel.is-stale::after { width: 3px; }
/* Make the stale badge itself read loud + steady (no live pulse on stale). */
#panels .panel.is-stale .badge,
#panels .panel.is-stale .badge.stale {
  color: var(--warn);
  background: var(--warn-soft);
  border-color: rgba(251, 191, 36, 0.40);
}
#panels .panel.is-stale .badge.stale::before { animation: none; }

/* ----------------------------------------------------------------------------
   4) New / wide panels render well
   The HTML marks #panel-trades and #panel-riskcharts as `.panel.wide`
   (full-width via the existing .panel.wide rule); #panel-theta_dte is a normal
   single-column panel. We pin those intentions by id so they survive any future
   grid tweak, and give the two wide, chart-led panels a taller plot so a fills
   table / correlation+drawdown chart has room to breathe.
   -------------------------------------------------------------------------- */
#panels #panel-trades,
#panels #panel-riskcharts { grid-column: span 2; }
#panels #panel-theta_dte  { grid-column: span 1; }

/* Wide chart panels: give the chart real height so dense series are legible. */
#panels #panel-riskcharts .chart { min-height: 320px; }
#panels #panel-trades .chart:empty { min-height: 0; padding: 0; border: 0; background: none; }
/* The recent-fills panel is table-led (it ships no chart series): when its
   chart slot is empty, collapse it so the table sits right under the header.
   Suppress BOTH skeleton pseudo-elements (base wash + shimmer) so the collapsed
   slot stays truly empty rather than painting a loading shimmer on a panel that
   never loads a chart. */
#panels #panel-trades .chart:empty::after,
#panels #panel-trades .chart:empty::before { content: none; }

/* On a viewport that can't hold two columns, never let a wide panel overflow. */
@media (max-width: 860px) {
  #panels #panel-trades,
  #panels #panel-riskcharts { grid-column: span 1; }
}

/* ----------------------------------------------------------------------------
   5) Topbar-right cluster — graceful, polished wrapping
   The right cluster holds (left→right) the injected account segmented control,
   the Private toggle, the signed-in email, an optional Admin link, and Log out.
   On narrow widths these should wrap as a tidy block, not crush or clip. We add
   a row-gap so wrapped rows don't collide, keep the cluster right-aligned, and
   let the account switch wrap to its own line first (it's the widest child).
   We DON'T restyle .acct-switch__btn — app.js owns that chrome.
   -------------------------------------------------------------------------- */
.topbar-right {
  flex-wrap: wrap;
  row-gap: 10px;
  justify-content: flex-end;
}
/* Keep the email from ever dominating the row; it truncates first, drops last. */
.topbar-right .user-email { min-width: 0; flex: 0 1 auto; }
/* The injected switcher is the widest control — let it take a full row early so
   the identity + actions stay together and aligned. */
@media (max-width: 1180px) {
  .topbar-right { gap: 12px; }
  .topbar-right .user-email { max-width: 170px; }
}
@media (max-width: 1024px) {
  .topbar-right #acctSwitch,
  .topbar-right .acct-switch { order: -1; flex: 1 1 100%; justify-content: flex-start; }
}
/* Tidy the actions so Log out (+ Admin) sit as a neat trailing pair. */
.topbar-right .nav-admin { white-space: nowrap; }
.topbar-right #logoutBtn { flex: 0 0 auto; }

/* ----------------------------------------------------------------------------
   6) Per-account accent hook (body.acct-acct1 / acct2 / acct3)
   app.js sets a body class for the focused account (and body.acct-all / the
   default keep the house teal). We expose a single --acct-accent var per class
   and lean on it for CHROME ONLY — the panel title tick, the focus chip, a top
   hairline, and the hero-KPI accent border. We deliberately DO NOT recolour
   .pos/.neg money figures (good/bad must stay green/red), and we guard the tick
   so a stale panel keeps its amber tick. Default (no acct class) is untouched.
   -------------------------------------------------------------------------- */
body.acct-acct1 { --acct-accent: #38bdf8; --acct-accent-soft: rgba(56, 189, 248, 0.14); --acct-accent-glow: rgba(56, 189, 248, 0.32); }
body.acct-acct2 { --acct-accent: #34d399; --acct-accent-soft: rgba(52, 211, 153, 0.14); --acct-accent-glow: rgba(52, 211, 153, 0.32); }
body.acct-acct3 { --acct-accent: #a78bfa; --acct-accent-soft: rgba(167, 139, 250, 0.16); --acct-accent-glow: rgba(167, 139, 250, 0.34); }

/* Title tick adopts the account accent (skip stale cards → they keep amber). */
body.acct-acct1 #panels .panel:not(.is-stale) .panel-h .title::before,
body.acct-acct2 #panels .panel:not(.is-stale) .panel-h .title::before,
body.acct-acct3 #panels .panel:not(.is-stale) .panel-h .title::before {
  background: linear-gradient(180deg, var(--acct-accent), var(--accent-deep));
  box-shadow: 0 0 8px var(--acct-accent-glow);
}
/* The "Acct N" focus chip app.js stamps on each title (its injected <style>
   draws the ::after chip in house teal; recolour it to match the focus). */
body.acct-acct1.acct-filtered #panels .panel-h .title::after,
body.acct-acct2.acct-filtered #panels .panel-h .title::after,
body.acct-acct3.acct-filtered #panels .panel-h .title::after {
  color: var(--acct-accent);
  background: var(--acct-accent-soft);
}
/* A whisper-thin accent line along the very top of each card when an account is
   in focus — enough to tint the whole grid toward that account, not garish. */
body.acct-acct1 #panels .panel:not(.is-stale)::before,
body.acct-acct2 #panels .panel:not(.is-stale)::before,
body.acct-acct3 #panels .panel:not(.is-stale)::before {
  background: linear-gradient(90deg, transparent, var(--acct-accent-glow), transparent);
}
/* The hero/accent KPI tile borrows the focus colour too (default stays teal). */
body.acct-acct1 #panels .kpi.accent,
body.acct-acct2 #panels .kpi.accent,
body.acct-acct3 #panels .kpi.accent {
  border-color: var(--acct-accent-soft);
  background:
    linear-gradient(180deg, var(--acct-accent-soft), rgba(255, 255, 255, 0.01)),
    var(--surface-3);
}

/* ----------------------------------------------------------------------------
   7) Private-view reinforcement (body.private-on)
   The base privacy rules already drain pos/neg emphasis (with !important) and
   paint the amber banner; we add a touch more: kill the signed-cell tint from
   §2 so the private grid reads flat/neutral, and make the banner + toggle a
   shade more prominent so "money hidden" is impossible to miss on a shared
   screen. These use !important only where they must beat the §2 tint.
   -------------------------------------------------------------------------- */
body.private-on #panels .ptable tbody td.pos,
body.private-on #panels .ptable tbody td.neg,
body.private-on #panels .ptable tbody tr:hover td.pos,
body.private-on #panels .ptable tbody tr:hover td.neg {
  background: transparent !important;
}
/* Drain the per-account top-hairline + hero tint in private view too, so a
   screen-share doesn't carry a coloured account signal over hidden money. */
body.private-on #panels .panel::before {
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.12), transparent) !important;
}
/* Make the existing PRIVATE banner read a touch louder. */
body.private-on::before { letter-spacing: 0.18em; }

/* ============================================================================
   READABILITY PASS — calm, uncramped, legible on a laptop (2026-06-13)
   ----------------------------------------------------------------------------
   The owner reported the dashboard read "cramped / hard to read." The KPI tiles
   were widened separately; this block does the rest of the readability work and
   is purely ADDITIVE / REFINING — it only adjusts spacing, row-height, line-
   height, weight and contrast on EXISTING selectors. No selector above is
   removed; the private-mode (body.private-on) and stale-panel (.panel.is-stale)
   treatments are preserved and, where touched here, reinforced. Higher source
   order = wins on equal specificity, which is why these refinements land.
   ========================================================================== */

/* ----------------------------------------------------------------------------
   A) Tables (.ptable) — the densest, hardest-to-read surface. Give rows real
   breathing room, raise the cell line-height so digits and labels don't crush,
   make the uppercase headers a touch larger / less squeezed, and lift the data
   contrast a notch on the dark fill. Numbers stay tabular + right-aligned (set
   above). A subtle zebra makes long tables scannable row-to-row.
   -------------------------------------------------------------------------- */
.ptable { font-size: 13.5px; line-height: 1.5; }
.ptable thead th {
  font-size: 11px;
  letter-spacing: 0.05em;
  padding: 12px 16px;
  color: var(--text-dim);          /* was text-mute — header was too faint */
}
.ptable tbody td {
  padding: 12px 16px;              /* roomier rows (was 10px 14px) */
  line-height: 1.45;
  color: #c7d0df;                  /* a shade brighter than --text-dim */
}
/* A whisper-faint zebra so the eye can track across a wide row without rules
   shouting. Kept under the hover + signed-cell tints, which still win. */
.ptable tbody tr:nth-child(even) td { background: rgba(255, 255, 255, 0.018); }
.ptable tbody tr:hover td { background: rgba(255, 255, 255, 0.045); }
/* Account / first-column label sits a touch bigger so the row has an anchor. */
.ptable tbody td:first-child,
.ptable tbody td.col-acct { font-size: 13.5px; }
/* The compact modifier should still be comfortable, just tighter than default. */
.ptable.compact thead th { padding: 9px 13px; }
.ptable.compact tbody td { padding: 9px 13px; line-height: 1.4; }
/* Give a scrolling table a hair of inner breathing room from its frame. */
.ptable-wrap { background: rgba(10, 14, 22, 0.22); }

/* Keep the runtime zebra from fighting the §2 signed-cell tints (those are set
   on td.pos / td.neg with higher-priority #panels selectors and must win). */
#panels .ptable tbody tr:nth-child(even) td.pos { background: rgba(52, 211, 153, 0.06); }
#panels .ptable tbody tr:nth-child(even) td.neg { background: rgba(248, 113, 113, 0.06); }

/* ----------------------------------------------------------------------------
   B) Panel cards + headers — more internal air, calmer title, clearer rule.
   The grid felt tight; widen the gutter and give each card a bit more padding
   and more space between its header and body. Titles read slightly larger with
   easier tracking; the header underline is a touch more present so each panel
   has a clear "lid."
   -------------------------------------------------------------------------- */
#panels { gap: 22px; padding: 28px 26px 60px; }
.panel { gap: 18px; padding: calc(var(--pad) + 4px) calc(var(--pad) + 2px) calc(var(--pad) + 2px); }
.panel-h {
  gap: 11px;
  padding-bottom: 15px;
  border-bottom-color: var(--border);   /* was border-soft — too faint */
}
.panel-h h2,
.panel-h h3,
.panel-h .title {
  font-size: 16px;
  letter-spacing: -0.008em;
  line-height: 1.25;
}
.panel-h .sub,
.panel-h small { font-size: 12.5px; }

/* ----------------------------------------------------------------------------
   C) Notes / footnotes — were small and italic-faint. Bump size + contrast and
   relax the leading so an explanatory line is actually readable, not skimmed
   past. (Stays clearly secondary to the data.)
   -------------------------------------------------------------------------- */
.note {
  font-size: 12.5px;
  color: var(--text-dim);
  line-height: 1.55;
  margin-top: 4px;
}
.note::before { color: var(--text-mute); margin-right: 7px; }

/* ----------------------------------------------------------------------------
   D) Topbar — give the bar a little more height + breathing room and lift the
   nav / identity contrast so the chrome doesn't feel pinched against the grid.
   -------------------------------------------------------------------------- */
/* Desktop only: the taller bar + wider gutter must NOT win on phones, where the
   topbar wraps to ~120-160px and a fixed 66px height clips it. Gate behind the
   821px floor so the mobile `height:auto` (the max-width:820px block above) stays
   authoritative. */
@media (min-width: 821px) {
  /* side insets keep the bar clear of a landscape notch; env() is 0 elsewhere. */
  .topbar {
    height: 66px;
    gap: 22px;
    padding: 0 max(26px, env(safe-area-inset-right)) 0 max(26px, env(safe-area-inset-left));
  }
}
.nav a { padding: 8px 16px; font-size: 13.5px; color: var(--text); }
.nav a:not(.active):not([aria-current="page"]) { color: var(--text-dim); }
.user-email { font-size: 13px; color: var(--text); max-width: 240px; }
:root { --topbar-h: 66px; }   /* keep the private banner's sticky offset in sync */

/* ----------------------------------------------------------------------------
   E) Account switcher — a touch larger + roomier so the segments are easy to
   read and hit, and the selected one reads unmistakably without crowding.
   -------------------------------------------------------------------------- */
.acct-switch { gap: 4px; padding: 4px; }
.acct-btn { padding: 7px 15px; font-size: 13px; min-width: 40px; }
.acct-switch .acct-label { font-size: 10.5px; padding: 0 9px 0 10px; }

/* ----------------------------------------------------------------------------
   F) Freshness strip / badges — make the as-of / fresh-stale tags a hair larger
   and clearer so the data-recency signal is legible at a glance.
   -------------------------------------------------------------------------- */
.badge {
  font-size: 11.5px;
  padding: 5px 11px;
  letter-spacing: 0.015em;
}
.conn-status { font-size: 12.5px; }
.readonly-tag { font-size: 11px; }

/* ----------------------------------------------------------------------------
   G) Overall contrast on the dark theme — nudge the dim/mute text tiers up a
   step so secondary copy stops disappearing into the background. These only
   redefine the token VALUES (every consumer inherits the lift); the private and
   stale treatments reference the same tokens, so they track automatically and
   their relative hierarchy is preserved.
   -------------------------------------------------------------------------- */
:root {
  --text-dim:  #b4bdce;   /* secondary copy — was #aab4c6 */
  --text-mute: #9aa6bb;   /* tertiary / notes — lifted for WCAG AA (was #8390a6 → #707d93) */
  --line:      rgba(148, 170, 205, 0.13);   /* table separators a touch clearer */
}

/* ============================================================================
   ACCESSIBILITY PASS — focus rings, tap targets, notch insets (2026-06-13)
   ----------------------------------------------------------------------------
   Purely additive. The performance-control buttons are inline-styled by
   performance.js with NO focus affordance; give them the keyboard ring + a
   comfortable hit height. Then enlarge the small chrome controls to a 44px-ish
   target on coarse (touch) pointers, and lift the connection pill clear of the
   home-indicator / bottom notch. The #conn-status bottom uses the ID so it
   beats app-compat.css's later `.conn-status{bottom:10px}` on specificity.
   ========================================================================== */
.perf-btn { min-height: 32px; }
.perf-btn:focus-visible {
  outline: 2px solid var(--accent-2);
  outline-offset: 2px;
}

/* Coarse pointer (touch): bump the small controls to a finger-friendly size. */
@media (pointer: coarse) {
  .cnc-help-btn,
  .cnc-help-close { width: 40px; height: 40px; }
  #logoutBtn,
  .acct-switch__btn,
  .perf-btn { min-height: 40px; }
}

/* Keep the floating connection pill above the bottom safe area (home bar). */
#conn-status { bottom: calc(10px + env(safe-area-inset-bottom)); }

/* ============================================================================
   WHITE THEME OVERRIDE -- investor-grade light interface (2026-06-14)
   ----------------------------------------------------------------------------
   This late block deliberately overrides the older charcoal theme without
   removing it. Auth/admin pages load auth.css after this file, so selectors that
   must beat hard-coded auth dark fills use higher specificity rather than
   changing that stylesheet outside this lane.
   ========================================================================== */
:root {
  color-scheme: light;

  --bg-0:        #f5f7fa;
  --bg-1:        #eef2f7;
  --bg-2:        #ffffff;
  --surface:     #ffffff;
  --surface-2:   #f8fafc;
  --surface-3:   #f2f6fb;

  --border:      #d8e1ec;
  --border-soft: #e7edf4;
  --border-str:  #b8c6d5;
  --line:        #e2e8f0;

  --text:        #111827;
  --text-dim:    #475569;
  --text-mute:   #64748b;
  --text-faint:  #8a97a8;

  --accent:      #0f766e;
  --accent-2:    #2563eb;
  --accent-deep: #115e59;
  --accent-glow: rgba(15, 118, 110, 0.18);
  --accent-soft: rgba(15, 118, 110, 0.09);

  --pos:         #047857;
  --pos-soft:    rgba(5, 150, 105, 0.10);
  --neg:         #b42318;
  --neg-soft:    rgba(180, 35, 24, 0.09);
  --warn:        #b45309;
  --warn-soft:   rgba(217, 119, 6, 0.12);
  --danger:      #dc2626;

  --r-xs: 4px;
  --r-sm: 6px;
  --r-md: 8px;
  --r-lg: 10px;

  --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.06);
  --shadow:    0 8px 24px rgba(15, 23, 42, 0.07);
  --shadow-lg: 0 18px 42px rgba(15, 23, 42, 0.12);

  --tip-bg:      #ffffff;
  --seg-bg:      #eef3f8;
  --placeholder-text: #6b7280;
}

html { color-scheme: light; }

body {
  color: var(--text);
  background: var(--bg-0);
}

a { color: var(--accent-2); }
a:hover { color: #1d4ed8; }
::selection { background: rgba(37, 99, 235, 0.14); color: var(--text); }

* { scrollbar-color: rgba(100, 116, 139, 0.34) transparent; }
*::-webkit-scrollbar-thumb { background: rgba(100, 116, 139, 0.28); background-clip: content-box; }
*::-webkit-scrollbar-thumb:hover { background: rgba(100, 116, 139, 0.42); background-clip: content-box; }

html body .topbar {
  background: rgba(255, 255, 255, 0.96);
  border-bottom-color: var(--border);
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06);
  backdrop-filter: saturate(130%) blur(10px);
  -webkit-backdrop-filter: saturate(130%) blur(10px);
}
html body .brand,
html body .topbar .brand { color: var(--text); }
html body .brand::before,
html body .topbar .brand::before,
html body.auth-body .brand-mark {
  color: #ffffff;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.08) inset, 0 8px 18px rgba(37, 99, 235, 0.18);
}
html body .brand small,
html body .brand .sub,
html body .topbar .brand .sub { color: var(--text-mute); }

html body .nav,
html body .topbar .nav,
html body .acct-switch {
  background: var(--seg-bg);
  border-color: var(--border);
  box-shadow: none;
}
html body .nav a,
html body .topbar .nav a {
  color: var(--text-dim);
}
html body .nav a:hover,
html body .topbar .nav a:hover {
  color: var(--text);
  background: #ffffff;
}
html body .nav a.active,
html body .nav a[aria-current="page"],
html body .topbar .nav a[aria-current="page"] {
  color: #ffffff;
  background: var(--accent-2);
  box-shadow: 0 1px 3px rgba(37, 99, 235, 0.22);
}

html body .user-email,
html body .topbar-right .user-email { color: var(--text-dim); }
html body .user-email::before,
html body .topbar-right .user-email:not(:empty)::before {
  background: var(--pos);
  box-shadow: none;
}

html body .switch { color: var(--text-dim); }
html body .switch > input {
  background: #dbe3ed;
  border-color: #c6d2df;
}
html body .switch > input::after {
  background: #ffffff;
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.24);
}
html body .switch > input:checked {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

html body .btn,
html body .btn-primary {
  color: #ffffff;
  background: var(--accent-2);
  box-shadow: 0 8px 18px rgba(37, 99, 235, 0.18);
}
html body .btn:hover,
html body .btn-primary:hover { box-shadow: 0 10px 22px rgba(37, 99, 235, 0.20); }
html body .btn-ghost,
html body #logoutBtn,
html body #logoutBtn.btn-ghost {
  color: var(--text-dim);
  background: #ffffff;
  border-color: var(--border-str);
  box-shadow: none;
}
html body .btn-ghost:hover,
html body #logoutBtn:hover {
  color: var(--text);
  background: var(--surface-2);
  border-color: var(--border-str);
}

html body #panels {
  background: transparent;
}
html body .panel,
html body main.admin .panel {
  background: var(--surface);
  border-color: var(--border);
  box-shadow: var(--shadow);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
html body .panel::before,
html body main.admin .panel::before {
  background: linear-gradient(90deg, transparent, rgba(37, 99, 235, 0.20), transparent);
}
html body .panel:hover {
  border-color: var(--border-str);
  box-shadow: var(--shadow-lg);
}
html body .panel-h {
  border-bottom-color: var(--border-soft);
}
html body .panel-h h2,
html body .panel-h h3,
html body .panel-h .title,
html body main.admin .panel-h .title {
  color: var(--text);
}
html body .panel-h h2::before,
html body .panel-h h3::before,
html body .panel-h .title::before,
html body main.admin .panel-h .title::before {
  background: linear-gradient(180deg, var(--accent-2), var(--accent));
  box-shadow: none;
}

html body .badge,
html body .user-row__badge {
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--text-dim);
}
html body .badge.fresh {
  color: var(--pos);
  background: var(--pos-soft);
  border-color: rgba(5, 150, 105, 0.22);
}
html body .badge.stale,
html body .badge--pending {
  color: var(--warn);
  background: var(--warn-soft);
  border-color: rgba(217, 119, 6, 0.26);
}
html body .badge.stale.bad,
html body .badge--rejected {
  color: var(--neg);
  background: var(--neg-soft);
  border-color: rgba(180, 35, 24, 0.24);
}

html body .kpi {
  background: var(--surface-2);
  border-color: var(--border-soft);
  box-shadow: none;
}
html body .kpi:hover {
  border-color: var(--border);
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
}
html body .kpi .val,
html body .kpi .value,
html body .kpi .kpi-value,
html body .kpi .kpi-num {
  color: var(--text);
}
html body .kpi .kpi-cur,
html body .kpi .kpi-label { color: var(--text-mute); }
html body #panels .kpi .kpi-num {
  overflow: hidden;
  text-overflow: ellipsis;
}
html body #panels .kpi-num.pos,
html body #panels .ptable td.pos,
html body .pos { color: var(--pos); }
html body #panels .kpi-num.neg,
html body #panels .ptable td.neg,
html body .neg { color: var(--neg); }
html body #panels .ptable tbody td.pos { background: var(--pos-soft); }
html body #panels .ptable tbody td.neg { background: var(--neg-soft); }

html body .chart,
html body .perf-chart,
html body .perf-mini {
  background: #ffffff;
  border-color: var(--border-soft);
  box-shadow: inset 0 0 0 1px rgba(226, 232, 240, 0.45);
}
html body .chart:empty::before,
html body .chart.loading::before {
  background: #f8fafc;
  border-color: var(--border-soft);
}
html body .chart:empty::after,
html body .chart.loading::after {
  background: linear-gradient(90deg, transparent, rgba(148, 163, 184, 0.18), transparent);
}

html body .ptable-wrap {
  background: #ffffff;
  border-color: var(--border);
}
html body .ptable thead th {
  color: var(--text-mute);
  background: #f8fafc;
  border-bottom-color: var(--border);
}
html body .ptable tbody td {
  color: var(--text-dim);
  border-bottom-color: var(--line);
}
html body .ptable tbody td:first-child,
html body .ptable tbody td.col-acct,
html body .ptable tbody td.col-num,
html body .ptable tbody td:not(:first-child) {
  color: var(--text);
}
html body .ptable tbody tr:nth-child(even) td { background: #fbfdff; }
html body .ptable tbody tr:hover td { background: #f2f7fc; }

html body .note,
html body main.admin .note,
html body .auth-subtext,
html body .auth-message,
html body .auth-foot {
  color: var(--text-mute);
}
html body .footer { color: var(--text-mute); }

html body .panel.is-stale,
html body #panels .panel.is-stale {
  border-color: rgba(217, 119, 6, 0.30);
  background: #fffaf0;
  box-shadow: var(--shadow);
}
html body .panel.is-stale::after {
  background: linear-gradient(180deg, var(--warn), rgba(217, 119, 6, 0.22));
}

html body.private-on::before {
  color: #7c2d12;
  background: #fff7ed;
  border-bottom-color: rgba(217, 119, 6, 0.26);
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
}
html body.private-on .kpi .val,
html body.private-on .kpi .kpi-num {
  color: var(--text);
}

html body .conn-status,
html body #conn-status.conn-status {
  color: var(--text-dim);
  background: rgba(255, 255, 255, 0.94);
  border-color: var(--border);
  box-shadow: 0 8px 18px rgba(15, 23, 42, 0.10);
}
html body .conn-status.ok,
html body #conn-status.conn-status.ok { color: var(--pos); }
html body .conn-status.lost,
html body #conn-status.conn-status.lost {
  color: var(--warn);
  border-color: rgba(217, 119, 6, 0.28);
}

html body [data-tip]::after {
  background: var(--tip-bg);
  color: var(--text);
  border-color: var(--border);
  box-shadow: var(--shadow-lg);
}
html body [data-tip]::before {
  background: var(--tip-bg);
  border-color: var(--border);
}

html body.auth-body {
  background: var(--bg-0);
}
html body.auth-body .auth-card {
  background: var(--surface);
  border-color: var(--border);
  box-shadow: var(--shadow-lg);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
html body.auth-body .auth-card::before {
  background: linear-gradient(90deg, transparent, rgba(37, 99, 235, 0.18), transparent);
}
html body.auth-body .brand-name,
html body.auth-body .auth-title { color: var(--text); }
html body.auth-body .brand-tagline,
html body.auth-body .form-field label,
html body.auth-body .form-hint { color: var(--text-mute); }
html body.auth-body .form-optional { color: var(--text-faint); }
html body.auth-body .form-field input,
html body input[type="text"],
html body input[type="email"],
html body input[type="password"],
html body input[type="number"],
html body input[type="search"] {
  color: var(--text);
  background: #ffffff;
  border-color: var(--border-str);
  box-shadow: none;
}
html body.auth-body .form-field input:hover,
html body input[type="text"]:hover,
html body input[type="email"]:hover,
html body input[type="password"]:hover,
html body input[type="number"]:hover,
html body input[type="search"]:hover { border-color: #9fb0c4; }
html body.auth-body .form-field input:focus,
html body input[type="text"]:focus,
html body input[type="email"]:focus,
html body input[type="password"]:focus,
html body input[type="number"]:focus,
html body input[type="search"]:focus {
  border-color: var(--accent-2);
  background: #ffffff;
  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.14);
}
html body.auth-body .form-field input:-webkit-autofill,
html body input[type="text"]:-webkit-autofill,
html body input[type="email"]:-webkit-autofill,
html body input[type="password"]:-webkit-autofill,
html body input[type="number"]:-webkit-autofill,
html body input[type="search"]:-webkit-autofill {
  -webkit-text-fill-color: var(--text);
  -webkit-box-shadow: 0 0 0 1000px #ffffff inset;
  caret-color: var(--text);
}
html body.auth-body .auth-notice {
  background: #f8fafc;
  border-color: var(--border);
  color: var(--text-dim);
}
html body.auth-body .auth-foot {
  border-top-color: var(--border-soft);
}
html body.auth-body .auth-status-icon {
  color: var(--warn);
  background: var(--warn-soft);
  border-color: rgba(217, 119, 6, 0.26);
}
html body.auth-body .form-error {
  color: var(--neg);
  background: var(--neg-soft);
  border-color: rgba(180, 35, 24, 0.24);
}
html body.auth-body .form-ok {
  color: var(--pos);
  background: var(--pos-soft);
  border-color: rgba(5, 150, 105, 0.22);
}

html body .streambar {
  color: var(--text-mute);
}
html body .streambar.is-error { color: var(--neg); }
html body .streambar.is-ok { color: var(--pos); }
html body .streambar.is-busy { color: var(--text-mute); }

html body .view-switch .nav-admin,
html body .nav-admin {
  color: var(--accent-2);
  background: #ffffff;
  border-color: var(--border);
}
html body .view-switch .nav-admin:hover,
html body .nav-admin:hover {
  color: #1d4ed8;
  background: var(--surface-2);
}
html body .view-switch .nav-admin[aria-current="page"] {
  color: #ffffff;
  background: var(--accent-2);
  border-color: var(--accent-2);
}

html body .user-row {
  background: var(--surface-2);
  border-color: var(--border-soft);
}
html body .user-row:hover { border-color: var(--border); }
html body .user-row__name { color: var(--text); }
html body .user-row__email,
html body .user-row__meta,
html body .user-row__when { color: var(--text-mute); }
html body .user-row__ua { color: var(--text-faint); }
html body .user-row--empty {
  color: var(--text-mute);
  background: #fbfdff;
}
html body .badge--approved,
html body .badge--admin {
  color: var(--pos);
  background: var(--pos-soft);
  border-color: rgba(5, 150, 105, 0.22);
}
html body .badge--investor {
  color: #1d4ed8;
  background: rgba(37, 99, 235, 0.10);
  border-color: rgba(37, 99, 235, 0.24);
}
html body .btn--approve {
  color: #ffffff;
  background: var(--pos);
  border-color: var(--pos);
  box-shadow: 0 8px 18px rgba(5, 150, 105, 0.18);
}
html body .btn--reject {
  color: var(--neg);
  background: #ffffff;
  border-color: rgba(180, 35, 24, 0.32);
}
html body .btn--reject:hover:not(:disabled) {
  color: #ffffff;
  background: var(--danger);
  border-color: var(--danger);
}

html body .mask {
  color: var(--text-mute);
}
