/* =============================================================================
   Theme tokens — clean minimalist blue & white, financial-terminal format.
   Dark is the default; <html data-theme="light"> switches. Both modes are
   deliberately low-chroma: blue is the only hue that carries brand, everything
   else is neutral so the data does the talking.
   ============================================================================= */
:root{
  color-scheme: dark;
  --page:        #0a0e14;
  --surface:     #111720;
  --surface-2:   #171e29;
  --surface-3:   #212a37;
  --border:      rgba(255,255,255,0.07);
  --border-2:    rgba(255,255,255,0.13);
  --ink:         #f2f5f9;
  --ink-2:       #9dadc0;
  /* 5.94:1 on --surface. Was #617083 at 3.56:1, which fails WCAG 1.4.3 AA
     (4.5:1 for body text) — and this token carries hint text, empty states,
     "NA" cells, timestamps and every placeholder in the app. Measured, not
     eyeballed; see mockups/accessibility-preview.html. */
  --ink-muted:   #8496ac;
  --invite:          #a97bf0;
  --invite-bg:       rgba(169,123,240,0.10);
  --invite-bg-hover: rgba(169,123,240,0.17);

  --brand-50:    #101c30;
  --brand-100:   #16263f;
  --brand-300:   #7cb0f2;
  --brand-500:   #3d87ea;
  --brand-600:   #2f76d6;
  --brand-700:   #7cb0f2;   /* on dark, "strong" reads brighter, not darker */
  --brand-900:   #cfe3fb;

  --good:        #16a34a;
  --good-bg:     rgba(22,163,74,0.13);
  --bad:         #ef5f5f;
  --bad-bg:      rgba(239,95,95,0.13);
  --warn:        #eab308;
  --warn-bg:     rgba(234,179,8,0.13);

  /* NOT-VERIFIED orange. The trust split is the one distinction on this app
     that has to survive a glance, and grey did not carry it: grey is what
     every muted thing on the page already is, so a typed-in holding read as
     "quiet" rather than as "this does not count". Orange is the only hue
     left that says caution without saying loss — green and red are taken by
     P/L and must never be spent on provenance.
     These are the same two values .earn-cal-tbd already used; naming them
     here makes it ONE orange instead of two that drift apart. */
  --unv:         #fb923c;
  --unv-bg:      rgba(251,146,60,0.14);

  /* Channel-manager role accent. The role badges use one hue each — admin
     green, lecturer yellow, user brand-blue — and purple was the hue left
     that reads as "elevated" without colliding with good/bad/warn. */
  --role-manager:    #a78bfa;
  --role-manager-bg: rgba(167,139,250,0.14);

  --radius:      10px;
  --radius-sm:   7px;
  --shadow:      0 1px 2px rgba(0,0,0,0.4);
  --font: system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", "Segoe UI Mono", Menlo, monospace;
}

:root[data-theme="light"]{
  color-scheme: light;
  --page:        #f6f8fb;
  --surface:     #ffffff;
  --surface-2:   #f2f6fb;
  --surface-3:   #e6eef8;
  --border:      rgba(15,23,42,0.09);
  --border-2:    rgba(15,23,42,0.16);
  --ink:         #0f172a;
  --ink-2:       #4b5a6d;
  /* 5.14:1 on white. Was #8494a8 at 3.10:1 — the worse of the two failures. */
  --ink-muted:   #5f6f83;
  --invite:          #7c3aed;
  --invite-bg:       rgba(124,58,237,0.08);
  --invite-bg-hover: rgba(124,58,237,0.14);

  --brand-50:    #eef4fd;
  --brand-100:   #dbe8fb;
  --brand-300:   #7cb0f2;
  --brand-500:   #2563eb;
  --brand-600:   #1d4fd0;
  --brand-700:   #17408f;
  --brand-900:   #0d2a5e;

  --good:        #12813c;
  --good-bg:     #e7f6ec;
  --bad:         #c62f2f;
  --bad-bg:      #fdecec;
  --warn:        #a86a00;
  --warn-bg:     #fdf3dd;
  /* A dark-friendly orange washes out on white — the same reason
     .earn-cal-tbd carried a light override. Darker, not just less bright. */
  --unv:         #d9670d;
  --unv-bg:      #fdeee0;
  --role-manager:    #7c5cd6;
  --role-manager-bg: #efe9fb;
  --shadow:      0 1px 2px rgba(15,23,42,0.05);
}

*{box-sizing:border-box;}
html,body{height:100%;}
body{
  margin:0;
  font-family:var(--font);
  background:var(--page);
  color:var(--ink);
  font-size:14px;
  line-height:1.5;
}
img{max-width:100%;display:block;}
a{color:inherit;text-decoration:none;}
button{font-family:inherit;}
::-webkit-scrollbar{width:9px;height:9px;}
::-webkit-scrollbar-track{background:transparent;}
::-webkit-scrollbar-thumb{background:var(--border-2);border-radius:6px;}
::-webkit-scrollbar-thumb:hover{background:var(--ink-muted);}

/* Numerals align in columns — the terminal tell */
.mono, .val, .chg, .kv .v, .metric .v, .earn-when .d,
.trend-table td, .wl-table td{font-variant-numeric:tabular-nums;}

/* ===== Layout ===== */
/* 100dvh, with 100vh kept above it as the fallback for browsers that do not
   know the unit (they ignore the second line and keep the first).
   ---------------------------------------------------------------------------
   On a phone, 100vh is the LARGE viewport: the height the page would have if
   the browser's address bar were hidden. It does not shrink when the bar is
   showing, so a 100vh box is always taller than what you can actually see, and
   its last ~60-100px start life underneath the browser chrome. dvh is the
   DYNAMIC viewport — the height available right now — which is what a
   full-height app shell actually wants. */
#app{display:flex;flex-direction:column;min-height:100vh;min-height:100dvh;}

/* The wrapper carries the stickiness, not the bar itself: a sticky element is
   confined to its parent's box, and #topbar is exactly as tall as .topbar — so
   sticking the inner element gives it zero room to travel and it scrolls away. */
#topbar{
  position:sticky;top:0;z-index:60;
}
/* Single source of truth for the bar's height — the sidebar offsets from it,
   and a hardcoded guess left a 3px seam. */
:root{
  --topbar-h: 52px;
  /* The phone tab bar's height, matching .tabbar-btn's min-height. Everything
     that has to keep clear of the bar reads this instead of guessing, because
     the guess is what gets forgotten: the bar is fixed to the bottom of the
     viewport and it covers whatever is under it, silently, on exactly the
     device nobody develops on. Add the safe-area inset alongside it — that is
     separate, and only non-zero on a notched iPhone. */
  --tabbar-h: 54px;
}
.topbar{
  display:flex;align-items:center;gap:14px;
  background:var(--surface);
  color:var(--ink);
  padding:9px 18px;
  border-bottom:1px solid var(--border);
}
.topbar .logo{
  display:flex;align-items:center;gap:9px;
  font-weight:700;font-size:15px;letter-spacing:-.015em;white-space:nowrap;cursor:pointer;
}
.topbar .logo .logo-word{
  background:linear-gradient(90deg, var(--ink) 0%, var(--ink) 55%, var(--brand-500) 100%);
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:transparent;
}

.search-wrap{position:relative;flex:1;max-width:520px;}
/* Must out-specify the generic input[type=text] rule further down
   (same specificity as input.search-input, so source order would win). */
.topbar input.search-input{
  width:100%;padding:7px 12px;border-radius:var(--radius-sm);
  border:1px solid var(--border-2);
  background:var(--surface-2);color:var(--ink);font-size:13px;
}
.topbar input.search-input::placeholder{color:var(--ink-muted);}
.topbar input.search-input:focus{border-color:var(--brand-500);background:var(--surface);}

.search-results{
  position:absolute;top:calc(100% + 6px);left:0;right:0;
  background:var(--surface);color:var(--ink);
  border-radius:var(--radius-sm);box-shadow:var(--shadow);
  max-height:360px;overflow:auto;z-index:60;border:1px solid var(--border);
}
.search-item{display:flex;align-items:center;gap:10px;padding:9px 12px;cursor:pointer;}
.search-item:hover,
.search-item.active{background:var(--surface-2);}
/* Keyboard highlight needs to read differently from a passing mouse hover. */
.search-item.active{box-shadow:inset 2px 0 0 var(--brand-700);}
.search-item .nm{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.search-item .sym{font-weight:700;color:var(--brand-700);min-width:56px;}
.search-item .nm{color:var(--ink-2);font-size:12.5px;flex:1;}
.search-item .exch{font-size:11px;color:var(--ink-muted);background:var(--surface-2);padding:2px 6px;border-radius:6px;}

.topbar-actions{display:flex;align-items:center;gap:8px;margin-inline-start:auto;}
.persona-btn{
  display:flex;align-items:center;gap:7px;
  background:transparent;border:1px solid var(--border);
  color:var(--ink);padding:5px 10px;border-radius:var(--radius-sm);cursor:pointer;font-size:12.5px;
}
.persona-btn:hover{background:var(--surface-2);border-color:var(--border-2);}
.persona-btn .caret{font-size:9px;color:var(--ink-muted);margin-inline-start:2px;}

/* Account switcher menu */
.account-wrap{position:relative;}
.account-menu{
  position:absolute;inset-inline-end:0;top:calc(100% + 6px);min-width:250px;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);
  box-shadow:0 10px 30px rgba(0,0,0,0.28);z-index:70;overflow:hidden;padding:4px;
}
.account-menu-head{
  font-size:9.5px;font-weight:800;letter-spacing:.07em;text-transform:uppercase;
  color:var(--ink-muted);padding:7px 9px 5px;
}
.account-menu-sep{height:1px;background:var(--border);margin:4px 0;}
.account-row{
  display:flex;align-items:center;gap:9px;padding:7px 9px;cursor:pointer;
  border-radius:calc(var(--radius-sm) - 2px);
}
.account-row:hover{background:var(--surface-2);}
.account-row.active{background:var(--brand-50);}
.account-name{display:flex;flex-direction:column;flex:1;min-width:0;}
.account-name .n{font-size:12.5px;font-weight:600;}
.account-name .e{
  font-size:10.5px;color:var(--ink-muted);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.persona-avatar.sm{width:22px;height:22px;font-size:9px;}
.icon-btn{
  display:flex;align-items:center;justify-content:center;
  width:30px;height:30px;border-radius:var(--radius-sm);
  background:transparent;border:1px solid var(--border);
  color:var(--ink-2);cursor:pointer;font-size:14px;padding:0;
}
.icon-btn:hover{background:var(--surface-2);color:var(--ink);border-color:var(--border-2);}

/* ------------------------------------------------- the market clock -------
   Sits right of the bell. Wider than the square icon buttons because it
   carries text, so it opts out of the fixed 30px width rather than squeezing
   a time and a countdown into it. */
.mkt-clock{
  width:auto; gap:7px; padding:0 9px;
  font-family:var(--mono); font-size:11.5px; font-weight:700;
  white-space:nowrap;
}
/* The clock and its "i" as one unit, so they cannot be separated by a wrap. */
.mkt-wrap{ display:inline-flex; align-items:center; gap:5px; flex:none; }

/* RED shut, GREEN open — the owner's call, replacing the blue this shipped
   with. Red reads as "you cannot trade now" at a glance, which is the question
   the clock is there to answer. The word Open/Closed sits beside the dot, so
   colour is never carrying the meaning on its own. */
.mkt-dot{
  width:7px; height:7px; border-radius:50%; flex:none;
  background:var(--bad);
}
.mkt-clock.is-open .mkt-dot{ background:var(--good); }
.mkt-time{ color:var(--ink); }
/* The state word carries the same colour as the dot and the countdown, so the
   three read as one statement rather than three separate facts. */
.mkt-state{ color:var(--bad); font-weight:800; }
.mkt-until{ color:var(--bad); font-weight:600; }
.mkt-clock.is-open .mkt-state,
.mkt-clock.is-open .mkt-until,
.mkt-clock.is-open .mkt-time{ color:var(--good); }

/* Below the point where the topbar starts fighting for room, the countdown
   goes and the clock keeps its dot and time — the state and the hour are what
   earn the space. The full sentence stays in the tooltip and the aria-label,
   so nothing is lost, only shortened. */
@media (max-width:900px){
  /* The countdown goes first — the state word and the hour are what earn the
     space. The full sentence stays in the tooltip and the aria-label. */
  .mkt-until{ display:none; }
}
@media (max-width:760px){
  /* Then the "i" — its legend is a paragraph, and a paragraph is the wrong
     thing to reach for on a phone toolbar. */
  .mkt-wrap .info-tip{ display:none; }
}
@media (max-width:620px){
  .mkt-wrap{ display:none; }
}
/* ----------------------------------------------------------- Creator Hub --
   The real Option D portal at #/community. */
.hub-kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;margin:12px 0 14px;}
.hub-kpi{background:var(--surface-2);border:1px solid var(--border);border-radius:var(--radius-sm);padding:12px;}
.hub-kpi .l{font-size:10px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-muted);}
.hub-kpi .v{font-size:20px;font-weight:800;font-family:var(--mono);margin-top:3px;}
.hub-kpi .s{font-size:11px;color:var(--ink-muted);margin-top:2px;}
.hub-grid{display:grid;grid-template-columns:2fr 1fr;gap:14px;align-items:start;}
@media (max-width:980px){.hub-grid{grid-template-columns:1fr;}}
.hub-org-chip{
  display:inline-flex;align-items:center;gap:7px;font-size:13px;font-weight:700;
  border:1px solid var(--border-2);border-radius:20px;padding:4px 12px;cursor:pointer;
}
.hub-org-chip:hover{background:var(--surface-2);}
.hub-org-chip .ic{
  width:18px;height:18px;border-radius:5px;background:var(--brand-500);color:#fff;
  display:inline-flex;align-items:center;justify-content:center;font-size:10px;
}
.hub-org-chip .sub{color:var(--ink-muted);font-weight:600;}
.hub-org-edit{background:transparent;border:none;color:var(--ink-muted);cursor:pointer;font-size:12px;padding:0 2px;}
.hub-org-edit:hover{color:var(--ink);}
.hub-ta{
  width:100%;min-height:74px;background:var(--surface-2);border:1px solid var(--border);
  border-radius:var(--radius-sm);color:var(--ink);padding:10px;font-family:inherit;
  font-size:13px;resize:vertical;
}
.hub-rec{
  display:flex;align-items:center;gap:10px;margin-top:8px;font-size:12px;
  border:1px dashed var(--brand-500);border-radius:var(--radius-sm);padding:8px 10px;
  background:color-mix(in srgb, var(--brand-500) 5%, transparent);
}
.hub-rec .s{color:var(--ink-muted);}
/* Parked: the dashed brand border said "new thing, try me", which is the wrong
   invitation for a control that cannot be pressed. Neutral border, flat
   background, no accent. */
.hub-rec.is-parked{
  border-style:solid; border-color:var(--border-2);
  background:var(--surface-2); flex-wrap:wrap;
}
.hub-rec.is-parked .btn[disabled]{
  opacity:.45; cursor:not-allowed; pointer-events:none;
}
/* The tag that explains the greying. --ink-2 on --surface-3, not muted-on-muted:
   the label saying WHY a control is off is the one thing in the row that has to
   stay readable. */
.hub-soon{
  font-size:10px; font-weight:800; letter-spacing:.07em; text-transform:uppercase;
  color:var(--ink-2); background:var(--surface-3);
  border:1px solid var(--border-2); border-radius:999px; padding:2px 8px;
  flex:none; white-space:nowrap;
}
.hub-rec-preview{width:100%;max-height:260px;border-radius:var(--radius-sm);margin-top:8px;background:#000;}
table.hub-perf{width:100%;border-collapse:collapse;font-size:12px;}
.hub-perf th{font-size:9.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--ink-muted);text-align:left;padding:6px 12px;border-bottom:1px solid var(--border);}
.hub-perf td{padding:8px 12px;border-bottom:1px solid var(--border);vertical-align:top;}
.hub-perf td.n{font-family:var(--mono);font-weight:700;}
.hub-perf tbody tr{cursor:pointer;}
.hub-perf tbody tr:hover td{background:var(--surface-2);}
.hub-perf .b{font-weight:600;}
.hub-perf .m{font-size:10.5px;color:var(--ink-muted);margin-top:2px;}
.hub-perf tr:last-child td{border-bottom:none;}
/* Video attachments in channel posts (Hub screen recordings). */
.chx-att-video{width:100%;max-height:320px;border-radius:var(--radius-sm);margin-top:8px;background:#000;}

/* Bell: the dot pins to the button's top-right corner (the button is the
   positioning context), red because it means "something needs you". */
.bell-btn{position:relative;display:inline-flex;align-items:center;justify-content:center;}
.bell-ic{color:var(--ink-muted);display:block;}
.bell-btn:hover .bell-ic{color:var(--ink);}
/* Something is waiting: the bell fills in and the dot appears. Two signals
   rather than one, because a 9px dot alone is easy to miss in a top bar. */
.bell-btn.has-unread .bell-ic{color:var(--ink);}
.bell-btn.has-unread .bell-ic path:first-child{fill:var(--ink);}
.bell-dot{
  position:absolute;top:1px;right:1px;width:9px;height:9px;border-radius:50%;
  background:#ef4444;border:2px solid var(--surface);
  box-shadow:0 0 0 1px rgba(239,68,68,0.35);
}
/* "Coming soon" — a feature that is designed and visible but deliberately
   inert. Muted rather than branded: it must not compete with what works. */
.badge.soon{background:var(--surface-2);color:var(--ink-muted);border:1px solid var(--border);}

/* The mini profile card: who is this, can I follow them, what of theirs is
   public. Anchored to the clicked name, so it is positioned in page
   coordinates rather than being a centred modal. */
.mini-profile{
  position:absolute;z-index:200;width:260px;
  background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius);box-shadow:0 18px 44px rgba(0,0,0,0.28);
  padding:14px;
}
.mp-head{display:flex;gap:10px;align-items:center;margin-bottom:10px;}
.mp-name{font-weight:700;font-size:14px;}
.mp-sub{font-size:11.5px;color:var(--ink-muted);}
.mp-rows{display:flex;flex-direction:column;gap:6px;}
.mp-row{display:flex;align-items:center;justify-content:space-between;font-size:12.5px;}
/* Two buttons side by side under the rows: View profile (always) and Follow.
   They share the width so neither reads as the primary action — looking at
   somebody is as valid an answer as following them. */
.mp-actions{display:flex;gap:8px;margin-top:12px;align-items:center;}
.mp-actions .btn{flex:1;min-width:0;}
.mp-follow{margin-top:0;}
/* The money row. .up/.down are the app-wide green/red, so a profit here is the
   same colour as a profit everywhere else. */
.mp-row .mono{font-weight:700;}
/* An author's name is a button, but it must still read as a name. */
.person-link{
  background:none;border:none;padding:0;font:inherit;color:inherit;
  cursor:pointer;text-align:start;
}
.person-link:hover{color:var(--brand-500);text-decoration:underline;}

/* Public profile (#/u/:id): somebody else's watchlists, one block per list.
   The symbols wrap as chips rather than sitting in a table — a watchlist is a
   bag of tickers, not a grid of measurements. */
.pub-wl{padding:10px 0;border-bottom:1px solid var(--border);}
.pub-wl:last-child{border-bottom:none;padding-bottom:0;}
.pub-wl-head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:8px;}
.pub-wl-syms{display:flex;flex-wrap:wrap;gap:6px;}
.pub-wl-syms .chip{padding:4px 10px;font-size:11.5px;text-decoration:none;}

/* Destructive corner of a settings card: set apart so "delete everything"
   never sits flush against the ordinary controls above it. */
.chx-danger{
  margin-top:16px;padding-top:14px;border-top:1px solid var(--border);
}
.chx-danger-h{font-size:13px;font-weight:700;color:var(--bad);margin-bottom:4px;}
.chx-danger .btn.danger{margin-top:10px;}

/* A tab that needs an answer — Friends → "Sent to me" while requests wait. */
.tab-dot{
  display:inline-block;width:7px;height:7px;border-radius:50%;
  background:#ef4444;margin-inline-start:6px;vertical-align:middle;
  box-shadow:0 0 0 2px rgba(239,68,68,0.22);
}
.persona-avatar{width:20px;height:20px;border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;color:#fff;}

.persona-menu{
  position:absolute;top:56px;inset-inline-end:20px;
  background:var(--surface);color:var(--ink);border-radius:var(--radius);
  box-shadow:var(--shadow);border:1px solid var(--border);
  min-width:260px;z-index:70;overflow:hidden;
}
.persona-menu h4{margin:0;padding:10px 14px;font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-muted);background:var(--surface-2);}
.persona-row{display:flex;align-items:center;gap:10px;padding:10px 14px;cursor:pointer;border-top:1px solid var(--border);}
.persona-row:hover{background:var(--surface-2);}
.persona-row.active{background:var(--brand-50);}
.persona-row .persona-avatar{width:32px;height:32px;font-size:13px;}
.persona-row .meta{flex:1;min-width:0;}
.persona-row .meta .nm{font-weight:600;font-size:13.5px;}
.persona-row .meta .role{font-size:11.5px;color:var(--ink-muted);}

.body-wrap{display:flex;flex:1;min-height:0;}

.sidenav{
  width:206px;flex:none;background:var(--surface);
  border-inline-end:1px solid var(--border);
  padding:10px 8px;display:flex;flex-direction:column;gap:1px;
  transition:width .16s ease;
  position:sticky;top:var(--topbar-h);align-self:flex-start;
  height:calc(100vh - var(--topbar-h));overflow-y:auto;overflow-x:hidden;
}
.sidenav a{
  display:flex;align-items:center;gap:10px;padding:7px 10px;border-radius:var(--radius-sm);
  color:var(--ink-2);font-size:13px;font-weight:500;white-space:nowrap;
}
.sidenav a .ic{width:18px;text-align:center;flex:none;font-size:14px;}
.sidenav a .lbl{overflow:hidden;text-overflow:ellipsis;}
.sidenav a:hover{background:var(--surface-2);color:var(--ink);}
.sidenav a.active{background:var(--brand-50);color:var(--brand-700);font-weight:600;}
.sidenav .sep{height:1px;background:var(--border);margin:7px 6px;}

/* Expandable nav group (parent row + caret) */
.nav-group-row{display:flex;align-items:center;gap:2px;}
.nav-group-row .nav-group-link{flex:1;min-width:0;}
.nav-caret{
  flex:none;width:20px;height:26px;padding:0;
  background:transparent;border:none;cursor:pointer;
  color:var(--ink-muted);font-size:9px;line-height:1;
  display:flex;align-items:center;justify-content:center;border-radius:4px;
}
.nav-caret:hover{background:var(--surface-2);color:var(--ink);}
.sidenav a.nav-child{
  padding-inline-start:26px;font-size:12.5px;
}
.sidenav a.nav-child .ic{font-size:12px;}
.sidenav.collapsed .nav-caret{display:none;}
.sidenav.collapsed a.nav-child{padding-inline-start:0;}

/* Collapse toggle */
.nav-collapse{
  display:flex;align-items:center;justify-content:flex-end;
  margin-bottom:4px;padding:0 2px;
}
.nav-collapse button{
  width:26px;height:26px;border-radius:var(--radius-sm);
  background:transparent;border:1px solid var(--border);
  color:var(--ink-muted);cursor:pointer;font-size:12px;line-height:1;
  display:flex;align-items:center;justify-content:center;
}
.nav-collapse button:hover{background:var(--surface-2);color:var(--ink);}

/* Collapsed state: icons only */
.sidenav.collapsed{width:52px;}
.sidenav.collapsed a{justify-content:center;padding:8px 0;gap:0;}
.sidenav.collapsed a .lbl{display:none;}
.sidenav.collapsed .nav-collapse{justify-content:center;}
/* tooltip so collapsed icons stay identifiable */
.sidenav.collapsed a{position:relative;}
.sidenav.collapsed a::after{
  content:attr(data-label);
  position:absolute;inset-inline-start:calc(100% + 8px);top:50%;transform:translateY(-50%);
  background:var(--ink);color:var(--page);
  padding:4px 8px;border-radius:var(--radius-sm);font-size:11.5px;font-weight:600;
  white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .12s;z-index:90;
}
.sidenav.collapsed a:hover::after{opacity:1;}

main.content{flex:1;min-width:0;padding:18px 22px 60px;max-width:1240px;margin:0 auto;width:100%;}
/* The heatmap is the one page whose value scales with width — 500 tiles in a
   1240px column are slivers. It gets the whole window. */
main.content.wide{max-width:none;}

/* ===== Generic pieces ===== */
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);}
.card-pad{padding:14px 16px;}
.page-title{font-size:18px;font-weight:700;margin:0 0 3px;letter-spacing:-.01em;}
.page-sub{color:var(--ink-2);font-size:12.5px;margin:0 0 16px;}
.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:16px;}
.grid-3{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;}
@media (max-width:900px){.grid-2,.grid-3{grid-template-columns:1fr;}}

.btn{
  display:inline-flex;align-items:center;gap:6px;justify-content:center;
  background:var(--brand-500);color:#fff;border:1px solid var(--brand-500);padding:7px 14px;
  border-radius:var(--radius-sm);font-size:13px;font-weight:600;cursor:pointer;
}
.btn:hover{background:var(--brand-600);border-color:var(--brand-600);}
.btn.secondary{background:transparent;color:var(--ink);border:1px solid var(--border-2);}
.btn.secondary:hover{background:var(--surface-2);}
.btn.ghost{background:transparent;color:var(--brand-700);border-color:transparent;}
.btn.ghost:hover{background:var(--surface-2);}
.btn.sm{padding:4px 10px;font-size:12px;}
.btn:disabled{opacity:.5;cursor:default;}

.badge{display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:700;padding:2px 7px;border-radius:4px;}
.badge.good{background:var(--good-bg);color:var(--good);}
.badge.bad{background:var(--bad-bg);color:var(--bad);}
.badge.brand{background:var(--brand-50);color:var(--brand-700);}
.badge.live{background:var(--bad-bg);color:var(--bad);}
.badge.live .dot{width:6px;height:6px;border-radius:50%;background:var(--bad);animation:pulse 1.4s infinite;}
@keyframes pulse{0%,100%{opacity:1;}50%{opacity:.35;}}

/* .tabs / .tab are GONE — 2026-08-06. Every strip that used them (Admin,
   Chart, Earnings, Heatmap, Profile) is now a segmented control, so what is
   left here is only the spacing each host wants around it. See
   js/components/segmented.js for why, and .segctl below for the styling.

   Deleting the rules rather than leaving them for "something might still use
   it" was checked, not assumed: no class attribute in js/ or the HTML entry
   points matches `.tab` or `.tabs` any more. The similar names that remain —
   .chx-tab in Channels, .tab-dot in Friends, .chart-tab-body — are separate
   selectors these rules never applied to. */
.admin-tabs{margin-bottom:14px;}
.earn-tabs{margin-bottom:14px;}

.avatar{border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;flex:none;}

input[type=text], input[type=number], select, textarea{
  border:1px solid var(--border);border-radius:var(--radius-sm);padding:8px 10px;
  font-size:13.5px;background:var(--surface);color:var(--ink);font-family:inherit;
}
textarea{width:100%;resize:vertical;}
label.field{display:flex;flex-direction:column;gap:4px;font-size:12.5px;color:var(--ink-2);font-weight:600;}

.empty-hint{color:var(--ink-muted);font-size:13px;padding:20px;text-align:center;}

/* The one survivor of the old demo feed's CSS: the symbol pill, still used
   by the Profile page's watchlist/holdings chips. */
.post-tag{font-size:11.5px;font-weight:700;color:var(--brand-700);background:var(--brand-50);padding:2px 8px;border-radius:8px;cursor:pointer;}

/* Watchlist / table */
.wl-table{width:100%;border-collapse:collapse;}
.wl-table th{text-align:start;font-size:11px;text-transform:uppercase;color:var(--ink-muted);padding:6px 10px;border-bottom:1px solid var(--border);}
.wl-table td{padding:9px 10px;border-bottom:1px solid var(--border);font-size:13px;}
.wl-table tr:last-child td{border-bottom:none;}
.wl-table tr:hover td{background:var(--surface-2);}
.chip-row{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px;}
.chip{padding:6px 14px;border-radius:16px;background:var(--surface-2);font-size:12.5px;font-weight:600;cursor:pointer;border:1px solid var(--border-2);color:var(--ink-2);}
.chip:hover{background:var(--surface-3);color:var(--ink);}
.chip.active{background:var(--brand-500);color:#fff;border-color:var(--brand-500);}
/* Reorder arrows inside a watchlist chip — quiet until hovered, and they
   inherit the chip's ink so they stay readable on the active (filled) chip. */
.chip-move{background:none;border:none;cursor:pointer;color:inherit;opacity:.45;font-size:10px;padding:0 3px;}
.chip-move:hover{opacity:1;}
.chip-pin{font-size:11px;}

/* Stock detail */
.stock-head{display:flex;align-items:flex-start;gap:16px;flex-wrap:wrap;}
.stock-price{font-size:30px;font-weight:800;}
.stock-sym{font-size:22px;font-weight:800;color:var(--brand-700);}
.chart-toolbar{display:flex;align-items:flex-end;gap:10px;flex-wrap:wrap;margin-bottom:12px;}
.seg{display:flex;background:var(--surface-2);border-radius:8px;padding:2px;gap:2px;border:1px solid var(--border);}
.seg button{border:none;background:transparent;padding:5px 10px;border-radius:6px;font-size:12px;font-weight:600;color:var(--ink-2);cursor:pointer;}
.seg button:hover{color:var(--ink);}
.seg button.active{background:var(--brand-500);color:#fff;}
.indicator-list{display:flex;gap:6px;flex-wrap:wrap;}

.etf-strip{display:flex;gap:10px;overflow-x:auto;padding-bottom:4px;}
.etf-card{flex:none;width:150px;padding:10px 12px;}

/* The rail (dots + connecting line) runs through the VERTICAL CENTER of the
   strip: date above the dot, EPS + verdict below, and the line segments are
   drawn from each dot outward (::before/::after on the dot itself), so they
   sit exactly on the dots' centerline by construction. */
.earnings-timeline{display:flex;gap:0;overflow-x:auto;padding:6px 0;}
.earn-node{
  flex:none;width:130px;text-align:center;padding:0 6px;
  display:grid;grid-template-rows:1fr auto 1fr;justify-items:center;
}
.earn-node .lbl{align-self:end;font-size:11px;font-weight:700;margin:0 0 7px;}
.earn-meta{align-self:start;display:flex;flex-direction:column;align-items:center;}
.earn-dot{width:10px;height:10px;border-radius:50%;background:var(--brand-500);position:relative;}
.earn-dot::before,.earn-dot::after{
  content:"";position:absolute;top:50%;transform:translateY(-50%);
  height:2px;width:60px;background:var(--border);
}
.earn-dot::before{right:100%;}
.earn-dot::after{left:100%;}
.earn-node:first-child .earn-dot::before{display:none;}
.earn-node:last-child .earn-dot::after{display:none;}
.earn-node.future .earn-dot{background:var(--ink-muted);}
.earn-meta .dt{font-size:10.5px;color:var(--ink-muted);margin-top:7px;}
/* The analyst estimate, directly under the reported figure. Quieter than the
   actual on purpose: the pair reads "here is what happened, and here is what
   was expected", not two equal numbers the reader has to tell apart. */
.earn-meta .est{font-size:9.5px;color:var(--ink-muted);opacity:.8;margin-top:1px;}
/* Three tiers, same three the Financials tab uses — see js/expectations.js.
   These take the app's own semantic tokens rather than the saturated dot
   colours: a filled badge at --warn's brightness would shout, where a 6px dot
   at the same value only reads. */
.badge.earn-tier.beat{background:var(--good-bg);color:var(--good);}
.badge.earn-tier.inline{background:var(--warn-bg);color:var(--warn);}
.badge.earn-tier.miss{background:var(--bad-bg);color:var(--bad);}

/* ---- the upcoming report, last node on the timeline ----------------------
   Amber throughout, and amber is NOT one of the three result colours here —
   green, red and the in-line yellow all describe something that happened. This
   node describes something that has not, so it needs a colour that cannot be
   mistaken for a verdict. The countdown badge is outlined rather than filled
   for the same reason: a solid badge in this row means a result. */
.earn-node.future .lbl{color:var(--warn);}
.earn-node.future .earn-dot{
  background:var(--surface);box-shadow:inset 0 0 0 2px var(--warn);
}
/* The rail INTO this node is dashed, so the break between reported and
   expected is visible along the line itself and not only at its end. */
.earn-node.future .earn-dot::before{
  background:none;
  border-top:2px dashed var(--warn);
  height:0;opacity:.75;
}
.earn-meta .est-eps{color:var(--warn);font-weight:700;}
.earn-node.future .earn-meta .est{color:var(--warn);opacity:.85;}
.badge.earn-tier.upcoming{
  background:transparent;color:var(--warn);
  box-shadow:inset 0 0 0 1px var(--warn);
}

/* Friends / Messages */
.friend-row{display:flex;align-items:center;gap:10px;padding:10px 14px;border-bottom:1px solid var(--border);}
.friend-row:last-child{border-bottom:none;}
.friend-row .meta{flex:1;min-width:0;}
.friend-row .nm{font-weight:700;font-size:13.5px;}
.online-dot{width:8px;height:8px;border-radius:50%;background:#c3c2b7;flex:none;}
.online-dot.on{background:var(--good);}

.msg-shell{display:flex;height:calc(100vh - 150px);min-height:420px;border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;background:var(--surface);}
.conv-list{width:270px;flex:none;border-inline-end:1px solid var(--border);overflow-y:auto;}
.conv-row{display:flex;gap:10px;padding:10px 12px;cursor:pointer;border-bottom:1px solid var(--border);}
.conv-row:hover, .conv-row.active{background:var(--surface-2);}
.conv-row .meta{flex:1;min-width:0;}
.conv-row .nm{font-weight:700;font-size:13px;}
.conv-row .last{font-size:11.5px;color:var(--ink-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.chat-pane{flex:1;display:flex;flex-direction:column;min-width:0;}
.chat-head{padding:12px 16px;border-bottom:1px solid var(--border);font-weight:700;font-size:13.5px;display:flex;align-items:center;gap:10px;}
.chat-msgs{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:10px;}
.bubble{max-width:60%;padding:9px 13px;border-radius:14px;font-size:13px;}
.bubble.me{align-self:flex-end;background:var(--brand-500);color:#fff;border-end-end-radius:4px;}
.bubble.them{align-self:flex-start;background:var(--surface-2);border-end-start-radius:4px;}
.bubble .ts{display:block;font-size:10px;opacity:.7;margin-top:3px;}
.chat-input{display:flex;gap:8px;padding:10px;border-top:1px solid var(--border);}
.chat-input input{flex:1;}

/* Community / rooms */
.room-list{display:flex;flex-direction:column;gap:4px;}
.room-item{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--radius-sm);cursor:pointer;font-size:13px;}
.room-item:hover{background:var(--surface-2);}
.room-item.active{background:var(--brand-50);color:var(--brand-700);font-weight:700;}
.role-card{padding:14px;border:1px solid var(--border);border-radius:var(--radius);}
.role-card h4{margin:0 0 4px;font-size:14px;}
.role-card .price{font-weight:800;color:var(--brand-700);font-size:18px;}

/* Integrations */
.token-box{display:flex;align-items:center;gap:8px;background:var(--surface-2);border:1px dashed var(--brand-300);border-radius:var(--radius-sm);padding:10px 12px;font-family:monospace;font-size:12.5px;word-break:break-all;}
.step{display:flex;gap:12px;padding:12px 0;border-top:1px solid var(--border);}
.step:first-child{border-top:none;}
.step .num{width:26px;height:26px;border-radius:50%;background:var(--brand-500);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:12px;flex:none;}

/* Profile */
.profile-head{display:flex;gap:16px;align-items:center;flex-wrap:wrap;}
.trust-ring{width:60px;height:60px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:15px;flex:none;background:conic-gradient(var(--brand-500) calc(var(--pct)*1%), var(--surface-2) 0);}
.trust-ring span{background:var(--surface);width:46px;height:46px;border-radius:50%;display:flex;align-items:center;justify-content:center;}
.stat-box{text-align:center;padding:10px;}
.stat-box .n{font-size:18px;font-weight:800;color:var(--brand-700);}
.stat-box .l{font-size:11px;color:var(--ink-muted);}

/* ===================== HOME / TERMINAL DASHBOARD ===================== */

.demo-banner{
  display:flex;align-items:center;gap:8px;justify-content:center;
  background:var(--warn-bg);color:var(--warn);
  border:1px solid rgba(250,178,25,0.35);border-radius:var(--radius-sm);
  padding:7px 14px;font-size:12px;font-weight:700;margin-bottom:16px;
  letter-spacing:.02em;
}

/* Hero search */
.hero{padding:26px 0 22px;text-align:center;}
.hero h1{font-size:26px;font-weight:800;margin:0 0 6px;letter-spacing:-.01em;}
.hero p{color:var(--ink-2);font-size:13.5px;margin:0 0 18px;}
.ask-wrap{position:relative;max-width:680px;margin:0 auto;}
.ask-box{
  display:flex;align-items:center;gap:10px;
  background:var(--surface);border:1px solid var(--border-2);
  border-radius:26px;padding:5px 6px 5px 18px;
  box-shadow:var(--shadow);transition:border-color .15s;
}
.ask-box:focus-within{border-color:var(--brand-500);}
.ask-box input{
  flex:1;border:none;background:transparent;color:var(--ink);
  font-size:14.5px;padding:10px 0;font-family:inherit;
}
.ask-box input::placeholder{color:var(--ink-muted);}
.ask-send{
  border:none;background:var(--brand-500);color:#fff;cursor:pointer;
  width:38px;height:38px;border-radius:50%;flex:none;font-size:15px;
  display:flex;align-items:center;justify-content:center;
}
.ask-send:hover{background:var(--brand-600);}
.ask-suggestions{display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin-top:12px;}
.ask-sugg{
  font-size:12px;color:var(--ink-2);background:var(--surface-2);
  border:1px solid var(--border);border-radius:14px;padding:5px 12px;cursor:pointer;
}
.ask-sugg:hover{border-color:var(--brand-500);color:var(--ink);}

/* Index strip */
.index-strip{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;}
@media (max-width:900px){.index-strip{grid-template-columns:repeat(2,1fr);}}
/* Roughly half the height these tiles used to be. Four index quotes are
   orientation, not the point of the page — at the old size they pushed the
   Market Summary and the heatmap below the fold on a laptop. Everything is
   still here, just at a density that matches how long anyone looks at it. */
/* The sparkline sits BESIDE the numbers, not on top of them.
   ---------------------------------------------------------------------------
   It used to be position:absolute at the right edge, which meant it was not
   in the layout at all and the tile had no idea it was there. Measured on a
   real tile: "54,036.93 +151.83 (+0.28%)" is 183px wide inside 176px of usable
   width, with the 52px spark drawn over the last 52 of those — so the percent
   change was always partly behind a green line. Flex makes the overlap
   impossible by construction rather than by choosing the right padding.

   The tile is width:auto in the marquee now (see .idx-marquee .index-tile), so
   a long index name widens its own tile instead of squeezing its numbers. The
   marquee scrolls, so tiles of different widths cost nothing. */
.index-tile{
  background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
  padding:5px 10px;position:relative;overflow:hidden;
  display:flex;align-items:center;gap:10px;
}
.index-tile .idx-txt{min-width:0;}
.index-tile .nm{font-size:9px;color:var(--ink-muted);font-weight:700;letter-spacing:.05em;text-transform:uppercase;line-height:1.2;}
/* Value and change share a line: stacking three rows is what made these tiles
   tall, and the pair reads naturally side by side. nowrap because the tile
   sizes itself to this line — allowing a wrap would let it wrap instead of
   growing, and put the change under the value again. */
.index-tile .idx-nums{white-space:nowrap;line-height:1.2;}
.index-tile .val{font-size:14px;font-weight:800;font-family:var(--mono);margin:0;line-height:1.2;display:inline-block;margin-inline-end:6px;}
.index-tile .chg{font-size:10px;font-weight:700;font-family:var(--mono);line-height:1.2;display:inline-block;}
/* margin-inline-start:auto pins it to the far edge when the tile is wider than
   its content — which is what min-width does to the short ones. */
.index-tile .spark{flex:none;margin-inline-start:auto;opacity:.55;line-height:0;}
.up{color:var(--good);}
.down{color:var(--bad);}

/* ---- Paged panels: the search box and the pager (js/components/paged-panel.js)
   The box lives in the section header, pushed to the far end by margin-inline-
   start:auto — .sec-head is a flex row with space-between, and dropping a third
   child into it would otherwise spread all three evenly and leave the search
   floating in the middle. */
.panel-search{
  display:inline-flex;align-items:center;gap:6px;
  margin-inline-start:auto;
  background:var(--surface-2);border:1px solid var(--border-2);
  border-radius:999px;padding:4px 12px;
}
.panel-search:focus-within{border-color:var(--brand-500);}
.panel-search-ic{font-size:13px;color:var(--ink-muted);line-height:1;}
.panel-search-in{
  background:none;border:none;color:var(--ink);
  font-family:inherit;font-size:12.5px;width:132px;padding:2px 0;
}
.panel-search-in:focus{outline:none;}
.panel-search-in::placeholder{color:var(--ink-muted);}
/* The browser's own clear "x" on type=search is drawn in the UA's colours and
   looks pasted on in dark mode. The field is short enough to clear by hand. */
.panel-search-in::-webkit-search-cancel-button{-webkit-appearance:none;}

.panel-pager{
  display:flex;align-items:center;gap:5px;flex-wrap:wrap;
  margin-top:9px;padding:0 2px;
}
.panel-more{
  display:inline-flex;align-items:center;gap:6px;
  background:none;border:1px solid var(--border-2);border-radius:999px;
  color:var(--ink-2);font-family:inherit;font-size:11.5px;font-weight:700;
  padding:4px 12px;cursor:pointer;
}
.panel-more:hover{border-color:var(--brand-500);color:var(--ink);}
.panel-more .ic{font-size:12px;line-height:1;}
.panel-page{
  min-width:24px;height:24px;padding:0 6px;
  background:none;border:1px solid transparent;border-radius:6px;
  color:var(--ink-2);font-family:inherit;font-size:11.5px;font-weight:700;
  cursor:pointer;
}
.panel-page:hover:not(:disabled){background:var(--surface-2);color:var(--ink);}
.panel-page.on{background:var(--brand-50);color:var(--brand-700);border-color:var(--brand-500);}
/* Disabled ‹ / › keep their space rather than vanishing: buttons that appear
   and disappear make the row jump as you page, and the jump reads as the page
   having changed more than it did. */
.panel-page:disabled{opacity:.32;cursor:default;}
.panel-gap{color:var(--ink-muted);font-size:11.5px;padding:0 2px;}
/* Pushed to the far end, so the page buttons stay together on the left. */
.panel-count{margin-inline-start:auto;font-size:11px;color:var(--ink-muted);}
/* Dimmed while a page is in flight — enough to say "working", not enough to
   make the rows unreadable, because most of these fetches land in under a
   frame or two and a spinner would flash. */
.is-loading{opacity:.55;transition:opacity .12s;}

@media (max-width:560px){
  /* The header stacks on a phone; a full-width box beats a 132px one squeezed
     next to a title. */
  .panel-search{margin-inline-start:0;width:100%;}
  .panel-search-in{width:100%;}
}

/* ---- Overview: quarterly performance, four small charts ------------------
   Small multiples. Two columns on a desktop, one on a phone — the panels are
   independent, so a narrow screen loses nothing but width.

   The old shared-axis overlay is gone, and with it .qmix-readout,
   .qmix-legend, .qmix-modes and their rules: four quantities with different
   volatility cannot share one axis, so the readout, the legend toggles and the
   Numbers/% switch were all machinery for managing a problem that no longer
   exists. See js/charts/quarterly-mix.js. */
.qmix-grid{
  display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:4px;
}
@media (max-width:760px){ .qmix-grid{grid-template-columns:1fr;} }
.qmix-mini{
  border:1px solid var(--border);border-radius:10px;padding:10px 12px 4px;
  /* overflow:visible on the svg lets the end labels sit past the plot; the
     panel has to allow that too or they are clipped at its border. */
  overflow:visible;
}
.qmix-mini-h{
  font-size:10.5px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;
  margin:0 0 2px;
}
.qmix-mini-v{font-size:17px;font-weight:800;font-variant-numeric:tabular-nums;}
/* The year-on-year change beside the headline figure — quieter, because the
   figure is the fact and the change is the comparison. */
.qmix-mini-v .c{
  font-size:11.5px;font-weight:600;color:var(--ink-muted);margin-inline-start:7px;
}

/* ---- Chart right rail: the News section -----------------------------------
   Whole-block links rather than a link inside a row: the headline, the source
   and the timestamp are all one target, which is what a thumb needs. */
.chart-ws-news{display:flex;flex-direction:column;}
.chart-ws-news-item{
  display:block;padding:8px 10px;border-bottom:1px solid var(--border);
  color:var(--ink);text-decoration:none;
}
.chart-ws-news-item:last-child{border-bottom:none;}
.chart-ws-news-item:hover{background:var(--surface-2);}
.chart-ws-news-item:focus-visible{outline:2px solid var(--brand-500);outline-offset:-2px;}
/* Three lines maximum. A rail is narrow and some headlines are a paragraph;
   without the clamp one story can be taller than the four beneath it. */
.chart-ws-news-item .hl{
  font-size:12px;font-weight:600;line-height:1.4;
  display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;
}
.chart-ws-news-item .meta{
  display:flex;gap:5px;align-items:center;flex-wrap:wrap;
  font-size:10.5px;color:var(--ink-muted);margin-top:3px;
}
.chart-ws-news-item .meta .dot{opacity:.6;}

/* Section shell */
.sec{margin-top:22px;}
.sec-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px;flex-wrap:wrap;}
.sec-title{font-size:14px;font-weight:800;display:flex;align-items:center;gap:8px;letter-spacing:-.005em;}
.sec-title .ic{color:var(--brand-500);}
.sec-sub{font-size:11.5px;color:var(--ink-muted);}

/* AI summary cards */
.ai-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:16px 18px;}
.ai-card.accent{border-inline-start:3px solid var(--brand-500);}
.ai-badge{
  display:inline-flex;align-items:center;gap:5px;font-size:10.5px;font-weight:800;
  background:var(--brand-50);color:var(--brand-300);border:1px solid rgba(109,167,236,0.3);
  padding:2px 8px;border-radius:10px;letter-spacing:.04em;
}
.ai-body{font-size:13.5px;line-height:1.75;color:var(--ink-2);margin:10px 0 0;}
.ai-body strong{color:var(--ink);font-weight:700;}
.sources{margin-top:14px;padding-top:12px;border-top:1px solid var(--border);}
.sources-title{font-size:10.5px;font-weight:800;color:var(--ink-muted);letter-spacing:.06em;text-transform:uppercase;margin-bottom:7px;}
.source-row{display:flex;align-items:flex-start;gap:8px;font-size:11.5px;color:var(--ink-2);padding:4px 0;}
.source-row .n{
  flex:none;width:16px;height:16px;border-radius:4px;background:var(--surface-3);
  color:var(--brand-300);font-size:9.5px;font-weight:800;display:flex;align-items:center;justify-content:center;
  font-family:var(--mono);margin-top:1px;
}
.source-row .src{color:var(--brand-300);font-weight:700;}
.source-row .dt{color:var(--ink-muted);}

/* Heatmap */
.heatmap{display:flex;flex-wrap:wrap;gap:3px;}
.heat-tile{
  border-radius:4px;padding:9px 10px;display:flex;flex-direction:column;justify-content:center;
  cursor:pointer;overflow:hidden;transition:transform .1s;min-height:54px;
}
.heat-tile:hover{transform:scale(1.01);outline:1.5px solid var(--ink);}
.heat-tile .sym{font-size:13px;font-weight:700;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;letter-spacing:-.01em;}
.heat-tile .pct{
  font-size:13px;font-weight:600;color:rgba(255,255,255,0.94);
  font-family:var(--mono);font-variant-numeric:tabular-nums;margin-top:2px;
}
.heat-legend{display:flex;align-items:center;gap:6px;font-size:10.5px;color:var(--ink-muted);margin-top:8px;justify-content:center;}
.heat-legend .sw{width:22px;height:9px;border-radius:2px;}

/* Trending table */
.trend-table{width:100%;border-collapse:collapse;}
.trend-table th{
  text-align:start;font-size:10px;text-transform:uppercase;letter-spacing:.06em;
  color:var(--ink-muted);padding:7px 10px;border-bottom:1px solid var(--border);font-weight:800;
}
.trend-table td{padding:9px 10px;border-bottom:1px solid var(--border);font-size:12.5px;}
.trend-table tr:last-child td{border-bottom:none;}
.trend-table tbody tr{cursor:pointer;}
.trend-table tbody tr:hover td{background:var(--surface-2);}
.trend-table .mono{font-family:var(--mono);}
.rank{color:var(--ink-muted);font-family:var(--mono);font-size:11px;}

/* Earnings hub */
.earn-tabs{display:flex;gap:4px;margin-bottom:10px;}
.earn-row{
  display:flex;align-items:center;gap:12px;padding:11px 12px;
  border-bottom:1px solid var(--border);cursor:pointer;
}
.earn-row:last-child{border-bottom:none;}
.earn-row:hover{background:var(--surface-2);}
/* The next call still to happen, in a symbol's year-grouped history — an
   inset shadow rather than a real border so it doesn't add width and throw
   off alignment with the plain rows above/below it in the same card. */
.earn-row.next{box-shadow:inset 0 0 0 2px var(--brand-500);border-radius:var(--radius-sm);}
.earn-when{
  flex:none;width:62px;text-align:center;background:var(--surface-2);
  border:1px solid var(--border);border-radius:6px;padding:4px 0;
}
.earn-when .d{font-size:15px;font-weight:800;font-family:var(--mono);line-height:1.1;}
.earn-when .m{font-size:9.5px;color:var(--ink-muted);text-transform:uppercase;letter-spacing:.05em;}
.earn-info{flex:1;min-width:0;}
.earn-info .s{font-weight:800;font-size:12.5px;}
.earn-info .n{font-size:11px;color:var(--ink-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.earn-metrics{display:flex;gap:14px;flex:none;}
.metric{text-align:end;}
.metric .l{font-size:9.5px;color:var(--ink-muted);text-transform:uppercase;letter-spacing:.05em;}
.metric .v{font-size:12.5px;font-weight:700;font-family:var(--mono);}

/* Earnings calendar tab — a month grid, each day split into before-open /
   after-close / time-unconfirmed buckets (see js/pages/earnings.js). */
.earn-cal-nav{display:flex;align-items:center;gap:8px;}
.earn-cal-nav-label{font-size:13px;font-weight:700;min-width:96px;text-align:center;}
.earn-cal-legend{
  display:flex;gap:16px;margin-bottom:8px;font-size:11px;color:var(--ink-muted);
}
.earn-cal-grid{
  display:grid;grid-template-columns:repeat(7,1fr);gap:1px;
  background:var(--border);border:1px solid var(--border);border-radius:var(--radius);
  overflow:hidden;
}
.earn-cal-dow{
  background:var(--surface-2);padding:6px 8px;font-size:10px;font-weight:700;
  letter-spacing:.05em;color:var(--ink-muted);text-transform:uppercase;
}
.earn-cal-day{
  background:var(--surface);min-height:92px;padding:6px;
}
/* Days outside the shown month, padding the grid to full weeks. */
.earn-cal-day.out{opacity:.4;}
.earn-cal-day.today{box-shadow:inset 0 0 0 2px var(--brand-500);}
.earn-cal-daynum{font-size:11.5px;font-weight:700;color:var(--ink);margin-bottom:3px;}
.earn-cal-day.today .earn-cal-daynum{color:var(--brand-500);}
.earn-cal-bucket{margin-top:4px;}
.earn-cal-bucket-lbl{
  font-size:10.5px;font-weight:700;color:var(--ink);text-transform:uppercase;letter-spacing:.03em;
  margin-bottom:2px;}
/* Timing colors, shared by the day-cell labels and the legend. Yellow rides
   --warn (bright on dark, amber on light); blue rides --brand-500 (per-theme
   values already tuned for contrast); orange rides --unv, which carries the
   per-theme override this rule used to hard-code. Same two values as before —
   they were lifted into a token when the trust split started using orange
   too, so the app has one orange rather than two that drift. */
.earn-cal-bmo{color:var(--warn);}
.earn-cal-amc{color:var(--brand-500);}
.earn-cal-tbd{color:var(--unv);}
.earn-cal-chips{display:flex;flex-wrap:wrap;gap:3px;}
.earn-cal-chip{
  display:inline-flex;align-items:center;gap:4px;background:var(--surface-3);
  border-radius:20px;padding:1px 7px 1px 1px;font-size:10.5px;font-weight:600;
  color:var(--ink);white-space:nowrap;cursor:pointer;
}
.earn-cal-chip:hover{background:var(--surface-2);}
/* .sym-logo's own margin-inline-end is sized for a normal row; the flex gap
   on .earn-cal-chip already spaces the logo from the ticker text. */
.earn-cal-chip .sym-logo{margin-inline-end:0;}
.earn-cal-more{font-size:10.5px;color:var(--ink-muted);align-self:center;}

.transcript{
  background:var(--surface-2);border:1px solid var(--border);border-radius:var(--radius-sm);
  padding:12px;max-height:180px;overflow-y:auto;font-size:12px;line-height:1.7;
}
.transcript .line{display:flex;gap:8px;padding:3px 0;}
.transcript .tm{color:var(--ink-muted);font-family:var(--mono);font-size:10.5px;flex:none;padding-top:2px;}

/* Screener */
.filter-pill{
  display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:700;
  background:var(--brand-50);color:var(--brand-300);border:1px solid rgba(109,167,236,0.3);
  padding:3px 9px;border-radius:12px;font-family:var(--mono);
}

/* Alerts / tasks */
.alert-row{display:flex;align-items:center;gap:10px;padding:10px 12px;border-bottom:1px solid var(--border);}
.alert-row:last-child{border-bottom:none;}
/* Notification inbox rows: whole row is a link; unread carries a brand tint
   and its own dot so scanning for "what's new" needs no reading. */
.notif-row{cursor:pointer;}
.notif-row:hover{background:var(--surface-2);}
.notif-row.unread{background:color-mix(in srgb, var(--brand-500) 7%, transparent);}
.notif-row.unread .t{font-weight:700;}
.notif-unread-dot{
  width:8px;height:8px;border-radius:50%;background:#ef4444;flex:none;
}
.alert-ic{
  width:30px;height:30px;border-radius:8px;flex:none;display:flex;align-items:center;justify-content:center;
  background:var(--surface-2);border:1px solid var(--border);font-size:13px;
}
.alert-info{flex:1;min-width:0;}
.alert-info .t{font-size:12.5px;font-weight:700;}
.alert-info .d{font-size:11px;color:var(--ink-muted);}
.switch{
  width:34px;height:19px;border-radius:10px;background:var(--surface-3);
  border:1px solid var(--border-2);position:relative;cursor:pointer;flex:none;transition:background .15s;
}
.switch::after{
  content:"";position:absolute;top:2px;inset-inline-start:2px;
  width:13px;height:13px;border-radius:50%;background:var(--ink-muted);transition:.15s;
}
.switch.on{background:var(--brand-500);border-color:var(--brand-500);}
.switch.on::after{inset-inline-start:17px;background:#fff;}
/* Per-follow "notify me when they post" control on Profile → Following. */
.following-notify{
  display:flex;align-items:center;gap:6px;cursor:pointer;flex:none;
}
.following-notify .lbl{font-size:11px;color:var(--ink-muted);font-weight:600;}

/* Plaid connect */
.connect-card{
  display:flex;align-items:center;gap:14px;padding:16px 18px;
  background:linear-gradient(135deg, var(--brand-50), var(--surface));
  border:1px solid var(--border-2);border-radius:var(--radius);
}
.connect-card .ic{
  width:42px;height:42px;border-radius:11px;background:var(--brand-500);color:#fff;
  display:flex;align-items:center;justify-content:center;font-size:19px;flex:none;
}

/* ========================= DROPDOWN CONTROL ================================ */
.ctl-wrap{display:flex;flex-direction:column;gap:3px;}
.ctl-label{
  font-size:9.5px;font-weight:800;letter-spacing:.07em;text-transform:uppercase;
  color:var(--ink-muted);
}
.ctl-field{position:relative;display:flex;align-items:center;}
select.ctl-select{
  appearance:none;-webkit-appearance:none;
  background:var(--surface-2);color:var(--ink);
  border:1px solid var(--border-2);border-radius:var(--radius-sm);
  padding:6px 26px 6px 10px;font-size:12.5px;font-weight:600;
  font-family:inherit;cursor:pointer;width:100%;
}
select.ctl-select:hover{border-color:var(--brand-300);}
select.ctl-select:focus{border-color:var(--brand-500);}
.ctl-caret{
  position:absolute;inset-inline-end:9px;pointer-events:none;
  font-size:9px;color:var(--ink-muted);
}

/* Multi-select — matches .ctl-select so a toolbar row stays visually even. */
.ms-wrap{position:relative;}
.ms-trigger{
  display:flex;align-items:center;justify-content:space-between;gap:6px;width:100%;
  background:var(--surface-2);color:var(--ink);
  border:1px solid var(--border-2);border-radius:var(--radius-sm);
  padding:6px 10px;font-size:12.5px;font-weight:600;
  font-family:inherit;cursor:pointer;
}
.ms-trigger:hover{border-color:var(--brand-300);}
.ms-trigger[aria-expanded="true"]{border-color:var(--brand-500);}
.ms-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.ms-label.empty{color:var(--ink-muted);font-weight:500;}
/* Prefixed msi- (not ms-caret/ms-check) because main.css also has a second,
   unrelated multi-select (js/components/multi-select.js, used by Settings)
   that reuses those exact class names — same specificity, so whichever rule
   is later in the file silently wins on both, and the loser's checkbox lost
   its fill and stayed a blank square no matter what was selected. */
.msi-caret{font-size:9px;color:var(--ink-muted);flex-shrink:0;}
/* position:fixed, not absolute — the chart top bar it sits in scrolls
   horizontally (overflow-x:auto), and an absolutely positioned child of a
   scroll container is clipped by it instead of floating over the chart.
   Left/top are set from the trigger's bounding rect in JS on open. */
.ms-menu{
  position:fixed;min-width:160px;
  background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius-sm);box-shadow:0 10px 30px rgba(0,0,0,0.28);
  z-index:80;padding:4px;max-height:320px;overflow-y:auto;
}
.ms-option{
  display:flex;align-items:center;gap:7px;padding:6px 8px;cursor:pointer;
  border-radius:calc(var(--radius-sm) - 2px);font-size:12.5px;white-space:nowrap;
}
.ms-option:hover{background:var(--surface-2);}
.ms-option.on{color:var(--brand-700);font-weight:600;}
.msi-check{
  flex:none;width:14px;height:14px;border-radius:4px;
  border:1px solid var(--border-2);display:flex;align-items:center;justify-content:center;
  font-size:10px;color:#fff;
}
.ms-option.on .msi-check{background:var(--brand-500);border-color:var(--brand-500);}

/* Symbol picker inside the alert modal */
.alert-symbol-wrap{position:relative;}
.search-results.inline{position:absolute;inset-inline:0;top:calc(100% + 3px);}
.alert-picked{font-size:11.5px;color:var(--ink-muted);margin-top:4px;}

/* ============================== SECTORS ==================================== */
.sector-controls{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:14px;}

.sector-summary{
  display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:14px;
}
@media (max-width:900px){.sector-summary{grid-template-columns:1fr;}}
.breadth-bar{display:flex;height:7px;border-radius:4px;overflow:hidden;gap:2px;margin-bottom:8px;}
.breadth-bar .up{background:var(--good);}
.breadth-bar .down{background:var(--bad);}
.breadth-text{display:flex;justify-content:space-between;font-size:11.5px;font-weight:600;}
.breadth-text .up{color:var(--good);}
.breadth-text .down{color:var(--bad);}

.leader-row{
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:3px 0;font-size:12px;
}
.leader-name{color:var(--ink-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.leader-val{font-weight:700;flex:none;font-variant-numeric:tabular-nums;}

.sector-table{width:100%;border-collapse:collapse;font-size:12.5px;}
.sector-table th{
  text-align:start;font-size:10px;text-transform:uppercase;letter-spacing:.06em;
  color:var(--ink-muted);font-weight:800;padding:8px 10px;
  border-bottom:1px solid var(--border);cursor:pointer;white-space:nowrap;user-select:none;
}
.sector-table th:hover{color:var(--ink);}
.sector-table th.num{text-align:end;}
.sector-table th.sorted{color:var(--brand-700);}
.sort-caret{font-size:8px;margin-inline-start:3px;}
.sector-table td{padding:8px 10px;border-bottom:1px solid var(--border);vertical-align:middle;}
.sector-table td.num{text-align:end;font-variant-numeric:tabular-nums;white-space:nowrap;}
.sector-table td.muted{color:var(--ink-muted);}
.sector-table tbody tr{cursor:pointer;}
.sector-table tbody tr:hover td{background:var(--surface-2);}
.sector-table tr.expanded td{background:var(--brand-50);}
.sector-table tr.constituents-row:hover td{background:var(--surface-2);}

.sector-name-cell{display:flex;align-items:center;gap:8px;}
.perf-dot{width:6px;height:6px;border-radius:2px;flex:none;}
.sector-name{font-weight:600;}
.child-row .sector-name{font-weight:500;color:var(--ink-2);padding-inline-start:14px;}
.sector-tag{
  font-size:9px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;
  color:var(--ink-muted);background:var(--surface-2);border:1px solid var(--border);
  padding:1px 5px;border-radius:3px;
}

td.active-tf{background:var(--surface-2);}
.perf-bar{
  display:block;position:relative;height:3px;margin-top:4px;width:100%;
  background:var(--border);border-radius:2px;overflow:hidden;
}
.perf-fill{position:absolute;top:0;height:100%;border-radius:2px;}
.perf-fill.up{background:var(--good);}
.perf-fill.down{background:var(--bad);}

.constituents-cell{padding:10px 12px !important;background:var(--surface-2) !important;}
.constituents{display:flex;gap:8px;flex-wrap:wrap;}
.constituent{
  background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);
  padding:7px 11px;cursor:pointer;min-width:118px;
}
.constituent:hover{border-color:var(--brand-500);}
.c-sym{font-size:12px;font-weight:700;}
.c-name{font-size:10px;color:var(--ink-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:118px;}
.c-chg{font-size:11px;font-weight:600;margin-top:2px;}
.constituents-empty{font-size:11.5px;color:var(--ink-muted);}

.up{color:var(--good);}
.down{color:var(--bad);}

/* ===================== CHART WORKSPACE (TradingView-style) ================== */
.chart-shell{
  display:flex;gap:12px;align-items:stretch;
  /* Clamped: on a tall monitor an unbounded 100vh made the price pane ~1050px,
     which stretches candles into vertical smears and starves the indicator
     panels below it. */
  height:clamp(440px, calc(100vh - 130px), 680px);
}
.chart-main{
  flex:1;min-width:0;display:flex;flex-direction:column;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
  overflow:hidden;
}
.chart-head{
  display:flex;align-items:center;gap:14px;flex-wrap:wrap;
  padding:11px 14px;border-bottom:1px solid var(--border);
}
.chart-sym{display:flex;align-items:baseline;gap:8px;font-size:17px;font-weight:700;letter-spacing:-.01em;}
.chart-exch{font-size:10.5px;font-weight:600;color:var(--ink-muted);text-transform:uppercase;letter-spacing:.05em;}
.chart-name{font-size:11.5px;color:var(--ink-muted);margin-top:1px;}
.chart-price{font-size:19px;font-weight:700;}

/* align-items:end so every control group sits on a shared baseline regardless
   of whether its label wraps */
.chart-tools{
  display:flex;align-items:flex-end;gap:10px;flex-wrap:wrap;
  padding:8px 14px;border-bottom:1px solid var(--border);background:var(--surface-2);
}
.chart-inds{display:flex;flex-direction:column;gap:3px;}
.chart-inds .chip-row-inline{display:flex;gap:5px;flex-wrap:wrap;}
.chip.sm-chip{
  padding:0 10px;height:29px;font-size:11.5px;border-radius:var(--radius-sm);
  display:inline-flex;align-items:center;
}

.chart-body{flex:1;min-height:0;display:flex;}
.chart-canvas-wrap{
  /* min-height, NOT the 0 this used to carry, and it is load-bearing: it is
     the single source of truth for how short the plot may get. The renderer
     measures this box and sizes the canvas to it, so whatever CSS reserves
     here is what the canvas gets — the two can no longer disagree.

     208 = the 200px of plot the chart needs to be worth drawing, plus the 8px
     of vertical padding below, because the canvas lives inside the padding
     box. With min-height:0 the flex column would hand the plot whatever was
     left after the foot row, the renderer's own 200px floor would win anyway,
     and the surplus painted straight over the Range buttons and the E/D key —
     with the time-axis labels landing INSIDE that row. Measured at a 220px
     pane: 41px of canvas over the row, the dates 29px into it.

     overflow:hidden is the guarantee rather than the fix: even if a future
     caller hands the renderer a wrong height, the plot is clipped instead of
     painting on the controls below. Safe for the tooltip, which clamps itself
     to this element's rect with a 4px inset (see candlestick.js). */
  flex:1;min-width:0;min-height:208px;overflow:hidden;padding:4px 6px;
  /* Crosshair at rest, grabbing mid-drag. Crosshair rather than grab because
     at rest the pointer's job is READING — the crosshair chips print the
     price and date under it — and every charting tool people arrive from
     signals that with this cursor. The drag affordance survives: the cursor
     flips to grabbing the moment a pan actually starts (.panning below). */
  cursor:crosshair;touch-action:none;   /* the pane pans; the browser must not */
}
.chart-canvas-wrap.panning{cursor:grabbing;}
/* The way back, visible only once the view has left its fitted state (the
   button's display is driven from paint() in js/pages/chart.js). Positioned
   inside the canvas wrap, clear of the price axis gutter on the right and
   the time axis below. */
.chart-canvas-wrap{position:relative;}
.chart-reset-btn{
  position:absolute;right:62px;bottom:40px;z-index:5;
  padding:4px 10px;font-size:11px;font-weight:600;
  border:1px solid var(--border-2);border-radius:99px;
  background:var(--surface);color:var(--ink-2);cursor:pointer;
  box-shadow:0 2px 8px rgba(0,0,0,.18);
}
.chart-reset-btn:hover{color:var(--ink);border-color:var(--brand-500);}
/* Focusable from the keyboard (tabindex is set in js/pages/chart.js), so it
   needs a focus ring — an invisible tab stop is worse than none, because the
   arrow keys start moving something the user cannot see they have selected.
   :focus-visible rather than :focus, so a mouse click does not outline it. */
.chart-canvas-wrap:focus{outline:none;}
.chart-canvas-wrap:focus-visible{
  outline:2px solid var(--brand-500);outline-offset:-2px;border-radius:var(--radius-sm);
}
/* Sits over the chart body, not inside the canvas wrapper — that wrapper is
   cleared on every repaint. */
.chart-body{position:relative;}
.chart-older{
  position:absolute;left:12px;top:10px;z-index:3;pointer-events:none;
  background:var(--surface-2);border:1px solid var(--border-2);
  border-radius:999px;padding:3px 10px;
  font-size:10.5px;font-weight:600;color:var(--ink-muted);
}

/* Indicator panels are fixed-height rows so they can't compete with the price
   pane for space — the price chart keeps whatever is left. */
.chart-sub{flex:none;border-top:1px solid var(--border);}
.chart-sub-label{
  font-size:9.5px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;
  color:var(--ink-muted);padding:5px 14px 0;
}

/* Right rail */
.chart-rail{
  width:288px;flex:none;display:flex;flex-direction:column;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
  overflow:hidden;
}
.rail-head{
  display:flex;align-items:center;gap:8px;
  padding:10px 12px;border-bottom:1px solid var(--border);
}
.rail-title{
  font-size:10.5px;font-weight:800;letter-spacing:.07em;text-transform:uppercase;
  color:var(--ink-muted);flex:none;
}
.rail-dd{
  display:flex;align-items:center;gap:6px;max-width:160px;
  background:var(--surface-2);border:1px solid var(--border-2);color:var(--ink);
  padding:4px 9px;border-radius:var(--radius-sm);cursor:pointer;font-size:12px;font-weight:600;
}
.rail-dd:hover{border-color:var(--brand-300);}

.rail-add{position:relative;padding:9px 10px;border-bottom:1px solid var(--border);}
.rail-add input{width:100%;font-size:12.5px;padding:6px 9px;}
.rail-note{
  padding:8px 12px;font-size:11px;color:var(--ink-muted);
  border-bottom:1px solid var(--border);background:var(--surface-2);
}

.rail-list{flex:1;overflow-y:auto;min-height:0;}
.rail-row{
  display:flex;align-items:center;gap:8px;position:relative;
  padding:8px 11px;cursor:pointer;border-bottom:1px solid var(--border);
}
.rail-row:hover{background:var(--surface-2);}
.rail-row.active{background:var(--brand-50);box-shadow:inset 2px 0 0 var(--brand-500);}
.rail-sym{font-size:12.5px;font-weight:700;}
.rail-name{font-size:10.5px;color:var(--ink-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.rail-price{font-size:12px;font-weight:600;}
.rail-chg{font-size:10.5px;font-weight:600;}
.rail-remove{
  position:absolute;inset-inline-end:4px;top:50%;transform:translateY(-50%);
  width:18px;height:18px;border-radius:4px;border:none;background:var(--surface-3);
  color:var(--ink-muted);cursor:pointer;font-size:12px;line-height:1;
  display:none;align-items:center;justify-content:center;padding:0;
}
.rail-row:hover .rail-remove{display:flex;}
.rail-remove:hover{background:var(--bad-bg);color:var(--bad);}

/* Stock detail panels under the chart. They sit outside .chart-shell because
   that is height-clamped and overflow:hidden — nesting them would crop them. */
.chart-details{display:flex;flex-direction:column;gap:14px;margin-top:14px;}

@media (max-width:1000px){
  .chart-shell{flex-direction:column;height:auto;}
  .chart-main{min-height:420px;}
  .chart-rail{width:auto;max-height:340px;}
}

/* ---- Chart Overview: instrument header, area chart, period strip --------- */
.chart-page{max-width:1180px;}
/* Expanded chart drops the cap — main.content also carries .wide then. */
.chart-page.fullchart{max-width:none;}

/* Header: logo spans the text rows; one alignment edge for name/ident/price. */
.chart-ov-head{display:grid;grid-template-columns:auto 1fr;gap:0 16px;align-items:start;padding:4px 0 16px;}
.chart-ov-logo{grid-row:1 / span 3;width:52px;height:52px;border-radius:50%;overflow:hidden;flex:none;
  display:flex;align-items:center;justify-content:center;background:var(--surface-2);}
.chart-ov-name{font-size:22px;font-weight:800;letter-spacing:-.02em;line-height:1.2;margin:0 0 4px;}
.chart-ov-ident{display:flex;align-items:center;gap:7px;flex-wrap:wrap;margin-bottom:8px;font-size:12px;}
.chart-ov-exch{background:var(--surface-2);border:1px solid var(--border);border-radius:999px;
  padding:2px 9px;color:var(--ink-2);}
.chart-ov-priceRow{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;}
.chart-ov-price{font-size:30px;font-weight:800;letter-spacing:-.02em;font-variant-numeric:tabular-nums;}
.chart-ov-asof{font-size:11px;color:var(--ink-muted);margin-top:6px;}

.chart-subtabs{margin-bottom:14px;}
.chart-tab-body{min-height:320px;}
.heatmap-subtabs{margin-bottom:14px;}

.chart-ov-actions{display:flex;justify-content:flex-end;margin-bottom:8px;}
.chart-ov-chart{min-height:300px;}

.chart-ov-periods{display:grid;grid-template-columns:repeat(9,1fr);gap:2px;
  border-top:1px solid var(--border);margin-top:10px;padding-top:10px;}
.chart-period{padding:9px 6px;border-radius:var(--radius-sm);text-align:center;cursor:pointer;
  border:1px solid transparent;}
.chart-period:hover{background:var(--surface-2);}
.chart-period.active{background:var(--surface-2);border-color:var(--border);}
.chart-period .l{font-size:11px;color:var(--ink-2);margin-bottom:3px;}
.chart-period.active .l{color:var(--ink);font-weight:600;}
.chart-period .v{font-size:12px;font-weight:700;font-variant-numeric:tabular-nums;}
.chart-period .v.up{color:var(--good);}
.chart-period .v.down{color:var(--bad);}

/* Full chart: collapse the header to a one-line strip (~a third its height),
   keep the menu, let the workspace take the room. */
.chart-page.fullchart .chart-ov-head{display:flex;align-items:center;gap:12px;padding:4px 0 10px;}
.chart-page.fullchart .chart-ov-logo{width:26px;height:26px;grid-row:auto;}
.chart-page.fullchart .chart-ov-name{font-size:15px;margin:0;}
.chart-page.fullchart .chart-ov-ident{display:none;}
.chart-page.fullchart .chart-ov-asof{display:none;}
.chart-page.fullchart .chart-ov-price{font-size:18px;}

@media (max-width:1000px){ .chart-ov-periods{grid-template-columns:repeat(5,1fr);} }
@media (max-width:620px){
  .chart-ov-periods{grid-template-columns:repeat(3,1fr);}
  .chart-ov-head{grid-template-columns:1fr;}
  .chart-ov-logo{grid-row:auto;margin-bottom:8px;}
}

/* ---- Fullscreen chart workspace ------------------------------------------- */
/* Own grid rather than nested flex: the middle row is "whatever height is
   left after the top bar", and grid expresses that as 1fr with no
   min-height:0 dance needed at every level. */
.chart-ws{
  display:grid;grid-template-rows:auto auto 1fr;   /* toolbar / indicator badges / plot */
  height:74vh;min-height:440px;
  border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;
}
/* Expanded mode fills the window: the page also gets .wide (chart.js), so
   the workspace takes every pixel left under the compact header + subtabs. */
.chart-page.fullchart .chart-ws{
  height:calc(100vh - var(--topbar-h) - 148px);
  min-height:480px;
}
.chart-ws-top{
  display:flex;align-items:center;gap:6px;padding:8px 10px;
  border-bottom:1px solid var(--border);background:var(--surface);
  font-size:12px;overflow-x:auto;
}
/* Three columns: drawing rail, chart, context rail. Only the middle one
   flexes — both rails hold a fixed width, because a rail that grows with the
   window is a rail that eats the plot on a small one.
   See js/pages/chart.js renderFullchart. */
/* `minmax(0,1fr)` for the plot, not `1fr`. A bare 1fr has an automatic minimum
   of its content's size, so below roughly 850px the canvas refused to shrink,
   the grid grew wider than the pane, and the whole row overflowed — which is
   what made the drawing rail look like it was sitting on top of the chart.
   Nothing was overlapping; the row was simply wider than the box holding it.
   The context rail gets the same treatment so it gives ground before the
   plot does. */
.chart-ws-mid{
  display:grid; grid-template-columns:auto minmax(0,1fr) minmax(0,auto);
  min-height:0;
  /* The context rail is a fixed 268px and will not give ground, so between
     roughly 760 and 850px the three tracks still add up to more than the pane.
     Scrolling the row is the honest answer: overflowing it spills the columns
     over whatever is beside them, which is what "the rail is hiding the
     content" looked like. Nothing is covered now — it moves out of the way and
     you can reach it.
     Properly stacking the context rail under the plot at that width is the
     better fix and is not done here. */
}

/* NO narrow-width special case here, and the previous commit's `overflow-x:auto`
   on this row is gone — both were solving a problem that does not exist.
   Correcting my own work, 2026-08-06.

   I "measured" the row overflowing below 850px by shrinking the .chart-page
   ELEMENT and reading the geometry. Media queries key off the VIEWPORT, so
   none of them fired during that test: the context rail stayed on screen at
   widths where the real app has already hidden it (the `max-width:1000px`
   block further down sets `display:none` on it, and has for a long time). The
   overflow was manufactured by the test method.

   Re-measured properly, by resizing the viewport: at 900px the row is
   `58px 581px`, the rail is display:none, and neither the row nor the page
   overflows. There was nothing to fix. */

/* ---------------------------------------------------- the drawing rail ----
   Variant B, the labelled one — chosen by the owner over the 42px icon-only
   strip A. It costs about 22px of chart width and buys a rail nobody has to
   hover to understand. See mockups/chart-tools-b-labelled.html.

   58px is not a round number picked for looks: it is the narrowest that fits
   "Horizontal" under a 14px glyph without hyphenating. */
.chart-rail{
  width:58px; flex:none; overflow-y:auto;
  border-inline-end:1px solid var(--border); background:var(--surface);
  display:flex; flex-direction:column; align-items:center; padding:5px 0; gap:1px;
}
.chart-rail-btn{
  width:52px; flex:none; display:flex; flex-direction:column;
  align-items:center; justify-content:center; gap:3px; padding:6px 2px;
  border:none; background:transparent; color:var(--ink-2);
  cursor:pointer; border-radius:var(--radius-sm);
  font-size:14px; line-height:1; font-family:inherit;
}
/* Lowercase, not uppercase, on the owner's call after picking B: uppercase at
   8px reads as shouting and costs roughly 12% more width per word for no
   legibility gain. */
.chart-rail-btn span{
  font-size:8px; font-weight:600; letter-spacing:.015em; line-height:1.05;
  text-transform:none; opacity:.9;
}
.chart-rail-btn:hover{ background:var(--surface-2); color:var(--ink); }
.chart-rail-btn:focus-visible{ outline:2px solid var(--brand-500); outline-offset:1px; }
/* #1d4fd0 rather than --brand-500 for the armed tool, the same substitution the
   segmented control and the timeframe pills make: white on --brand-500 measures
   3.59:1 on dark and fails 1.4.3. */
.chart-rail-btn.active{ background:#1d4fd0; color:#fff; }
:root[data-theme="light"] .chart-rail-btn.active{ background:var(--brand-500); }
/* Destructive actions are not red until they are reachable — a permanently red
   icon in a toolbar becomes wallpaper. Red on hover only. */
.chart-rail-btn.danger:hover{ background:var(--bad); color:#fff; }
/* Delete sits disabled until a drawing is selected. Dimmed AND inert — the
   attribute does the work; this only makes the state visible. */
.chart-rail-btn[disabled]{ opacity:.35; cursor:not-allowed; }
.chart-rail-btn[disabled]:hover{ background:transparent; color:var(--ink-2); }

.chart-rail-div{ width:40px; height:1px; background:var(--border-2); margin:4px 0; flex:none; }
.chart-rail-foot{ margin-top:auto; display:flex; flex-direction:column; align-items:center; gap:1px; }

/* Six swatches, three per row — the rail is wide enough in B for three, which
   keeps the group two rows tall instead of three. */
.chart-rail-palette{
  display:grid; grid-template-columns:repeat(3,1fr); gap:3px;
  padding:5px 4px; justify-items:center;
}
.chart-swatch{
  width:15px; height:15px; padding:0; cursor:pointer;
  border:1px solid var(--border-2); border-radius:3px; background:var(--sw);
}
.chart-swatch:hover{ transform:scale(1.15); }
/* Outlined in --ink rather than filled or ticked: the swatch IS its colour, so
   anything drawn on top of it hides the thing being chosen. */
.chart-swatch.active{ outline:2px solid var(--ink); outline-offset:1px; border-color:transparent; }
.chart-swatch:focus-visible{ outline:2px solid var(--brand-500); outline-offset:2px; }
@media (prefers-reduced-motion:reduce){ .chart-swatch:hover{ transform:none; } }

/* Narrow screens: the plot is the point, and a 58px rail is a tenth of a phone.
   It becomes a horizontal strip above the chart instead of disappearing —
   hiding it would make the tools unreachable rather than merely cramped. */
@media (max-width:760px){
  .chart-ws-mid{ grid-template-columns:1fr; }
  .chart-rail{
    width:auto; flex-direction:row; flex-wrap:wrap; justify-content:flex-start;
    border-inline-end:none; border-bottom:1px solid var(--border); padding:4px 6px;
  }
  .chart-rail-div{ width:1px; height:28px; margin:0 4px; }
  .chart-rail-foot{ margin-top:0; flex-direction:row; }
  .chart-rail-palette{ grid-template-columns:repeat(6,1fr); }
}

/* Same shape as .btn.secondary.sm elsewhere in the app (bordered, transparent
   fill, brand text on hover) rather than the borderless flat-toolbar icon
   look this used to have — every other button on the site has a visible
   border, and this one now does too. */
.chart-ws-btn{
  display:inline-flex;align-items:center;gap:5px;
  border:1px solid var(--border-2);background:transparent;color:var(--ink);
  cursor:pointer;font-family:inherit;font-size:12px;font-weight:600;
  padding:5px 10px;border-radius:var(--radius-sm);white-space:nowrap;
}
.chart-ws-btn:hover{background:var(--surface-2);}
.chart-ws-btn.active{background:var(--surface-3);border-color:var(--border-2);}
.chart-ws-btn.sym{font-size:13px;font-weight:800;}
/* Visually present but not yet wired (see js/pages/chart.js) — dimmed rather
   than hidden, so the eventual capability is legible without pretending it
   already works. */
.chart-ws-btn[disabled]{opacity:.5;cursor:default;}
.chart-ws-btn[disabled]:hover{background:transparent;}
.chart-ws-sep{width:1px;height:20px;background:var(--border-2);margin:0 3px;flex:none;}
.chart-ws-spacer{flex:1;min-width:8px;}

/* A bordered segmented control — the same idiom as the Earnings Hub's
   period toggle — instead of the borderless pill row this used to be. */
.chart-tf{display:inline-flex;border:1px solid var(--border-2);border-radius:var(--radius-sm);overflow:hidden;}
.chart-tf button{
  border:none;border-inline-end:1px solid var(--border-2);
  background:transparent;color:var(--ink-2);
  cursor:pointer;font-family:inherit;font-size:12px;font-weight:700;
  padding:5px 10px;
}
.chart-tf button:last-child{border-inline-end:none;}
.chart-tf button:hover{background:var(--surface-2);color:var(--ink);}
.chart-tf button[disabled]{opacity:.4;cursor:not-allowed;}
.chart-tf button[disabled]:hover{background:transparent;color:var(--ink-2);}

/* overflow-y:auto is what makes "the indicators must not run over the chart"
   true rather than aspirational. The plot carries a min-height and each
   indicator panel is flex:none, so when the pane is shorter than plot +
   panels + foot row something has to give — and without a scroll container
   that something is the layout spilling over whatever sits below it. Scrolled,
   everything keeps its size and stays reachable. */
.chart-ws-chart{position:relative;min-width:0;min-height:0;overflow-y:auto;display:flex;flex-direction:column;background:var(--page);}
.chart-ohlc{
  position:absolute;top:7px;left:10px;z-index:4;
  display:flex;align-items:center;gap:7px;flex-wrap:wrap;
  font-size:11.5px;font-variant-numeric:tabular-nums;pointer-events:none;
}
.chart-ohlc .sym{font-weight:700;color:var(--ink);}
.chart-ohlc .meta{color:var(--ink-muted);}
.chart-ohlc .val{color:var(--ink-2);}

.chart-ws-rail{
  width:268px;flex:none;border-inline-start:1px solid var(--border);
  background:var(--surface);display:flex;flex-direction:column;overflow-y:auto;
}
.chart-ws-rail-sec{border-bottom:1px solid var(--border);}
/* Caption stacked over a full-width trigger — the ctl-label/control idiom
   from the top bar, so the list switcher explains itself. */
.chart-ws-rail-head{
  display:flex;flex-direction:column;align-items:stretch;
  gap:3px;padding:8px 11px;font-size:12px;font-weight:700;color:var(--ink);
}
.chart-ws-rail-head-dd{position:relative;}
/* Same bordered-pill idiom as .chart-ws-btn in the top bar — a borderless
   text trigger read as a section title, not something to click. Full width
   with the caret pushed to the far edge, so it reads as a <select>. */
.chart-ws-rail-head-dd button{
  display:flex;align-items:center;justify-content:space-between;gap:4px;
  width:100%;
  border:1px solid var(--border-2);border-radius:var(--radius-sm);background:transparent;
  color:var(--ink);font-weight:700;font-size:12px;font-family:inherit;cursor:pointer;
  padding:5px 9px;
}
.chart-ws-rail-head-dd button .lbl{
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.chart-ws-rail-head-dd button:hover{background:var(--surface-2);}
.chart-ws-rail-menu{
  position:absolute;top:100%;left:0;z-index:10;min-width:160px;
  background:var(--surface);border:1px solid var(--border-2);border-radius:var(--radius-sm);
  box-shadow:var(--shadow);padding:4px;
}
.chart-ws-rail-menu-item{
  padding:6px 9px;border-radius:6px;font-size:12px;color:var(--ink-2);cursor:pointer;
  display:flex;justify-content:space-between;gap:8px;
}
.chart-ws-rail-menu-item:hover{background:var(--surface-2);}
.chart-ws-rail-menu-item.active{color:var(--brand-700);font-weight:700;}

.chart-wl{width:100%;border-collapse:collapse;table-layout:fixed;font-size:13px;}
.chart-wl th{
  text-align:start;font-weight:600;color:var(--ink-muted);
  font-size:11px;text-transform:uppercase;letter-spacing:.05em;
  padding:4px 11px;border-bottom:1px solid var(--border);
}
.chart-wl th:not(:first-child), .chart-wl td:not(:first-child){text-align:end;}
.chart-wl td{padding:5px 11px;font-variant-numeric:tabular-nums;color:var(--ink-2);cursor:pointer;}
.chart-wl tr:hover td{background:var(--surface-2);}
.chart-wl tr.active td{background:var(--brand-50);}
.chart-wl .sym{display:flex;align-items:center;gap:6px;color:var(--ink);font-weight:600;}

.chart-ws-price{padding:10px 11px;}
.chart-ws-price .p{font-size:22px;font-weight:800;font-variant-numeric:tabular-nums;}
.chart-ws-price .c{font-size:13.5px;font-weight:700;font-variant-numeric:tabular-nums;}
.chart-ws-status{display:flex;align-items:center;gap:5px;font-size:12px;color:var(--ink-muted);margin-top:5px;}
.chart-ws-status i{width:6px;height:6px;border-radius:50%;background:var(--good);display:block;}

.chart-ws-range{padding:7px 11px;}
.chart-ws-range-lbl{
  display:flex;justify-content:space-between;align-items:center;
  font-size:11px;color:var(--ink-muted);margin-bottom:4px;
  text-transform:uppercase;letter-spacing:.05em;
}
.chart-ws-range-lbl b{color:var(--ink-2);font-variant-numeric:tabular-nums;font-size:12px;}
.chart-ws-range-track{position:relative;height:3px;border-radius:2px;background:var(--surface-3);}
.chart-ws-range-fill{position:absolute;inset-block:0;inset-inline-start:0;border-radius:2px;background:var(--brand-500);}
.chart-ws-range-mark{
  position:absolute;top:50%;width:2px;height:9px;
  background:var(--ink);border-radius:1px;transform:translate(-50%,-50%);
}

.chart-ws-kv-list{padding:4px 11px 11px;}
.chart-ws-kv-row{
  display:flex;justify-content:space-between;gap:10px;
  padding:6px 0;font-size:13px;border-bottom:1px solid var(--border);
}
.chart-ws-kv-row:last-child{border-bottom:none;}
.chart-ws-kv-row .k{color:var(--ink-muted);}
.chart-ws-kv-row .v{color:var(--ink);font-weight:600;font-variant-numeric:tabular-nums;}

@media (max-width:1000px){
  .chart-ws{height:auto;min-height:560px;}
  .chart-ws-rail{display:none;}   /* narrow viewport: chart only */
}

/* ---- Chart Financials: statement ribbon + Performance + Seasonals -------- */
.chart-fin-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:14px;margin-top:14px;}
@media (max-width:900px){ .chart-fin-grid{grid-template-columns:1fr;} }

.chart-card-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px;}
.chart-card-title{display:flex;align-items:center;gap:5px;font-size:12.5px;font-weight:700;}
/* .chart-help is GONE (2026-08-10). It was a second info icon — 13px against
   .info-tip's 14, Georgia serif against the app font, no border, and driven by
   the browser's native `title` rather than the app's own bubble. Twelve of
   them across Financials, the Creator Hub and the indicator picker. All are
   infoTip() now; see js/components/tooltip.js. Do not reintroduce a local
   variant: one control, one look, one behaviour. */
.chart-fin-toggle{display:flex;gap:12px;font-size:11.5px;font-weight:600;}
.chart-fin-toggle button{
  border:none;background:transparent;padding:0;cursor:pointer;
  font-family:inherit;font-size:11.5px;font-weight:600;color:var(--ink-muted);
}
.chart-fin-toggle button:hover{color:var(--ink-2);}
.chart-fin-toggle button.active{color:var(--brand-700);border-bottom:1.5px solid var(--brand-500);padding-bottom:1px;}

/* Revenue → Net income waterfall at the bottom of the Financials tab. */
.chart-wf{display:block;width:100%;height:auto;margin-top:4px;}
/* Period chips: walk the last 4 quarters / 3 fiscal years, oldest→newest. */
.chart-wf-periods{display:flex;gap:6px;flex-wrap:wrap;margin:2px 0 8px;}
.chart-wf-chip{
  border:1px solid var(--border-2);border-radius:var(--radius-sm);
  background:transparent;color:var(--ink-2);cursor:pointer;
  font-family:var(--mono);font-size:11px;font-weight:700;padding:3px 9px;
}
.chart-wf-chip:hover{background:var(--surface-2);}
.chart-wf-chip.active{
  border-color:var(--brand-500);color:var(--brand-500);
  background:color-mix(in srgb, var(--brand-500) 10%, transparent);
}

.chart-ribbon{width:100%;border-collapse:collapse;font-size:12.5px;}
.chart-ribbon thead th{
  font-weight:600;color:var(--ink-muted);font-size:10px;text-transform:uppercase;
  letter-spacing:.05em;padding:0 0 9px;border-bottom:1px solid var(--border);vertical-align:bottom;
}
.chart-ribbon th.k{text-align:left;}
.chart-ribbon th.val,.chart-ribbon th.d{text-align:right;}
.chart-ribbon td{padding:9px 0;border-bottom:1px solid var(--border);vertical-align:middle;}
.chart-ribbon tbody tr{cursor:pointer;}
.chart-ribbon tbody tr:hover td{background:var(--surface-2);}
/* display:flex belongs on an inner wrapper, never the <td> itself — a table
   cell whose own `display` is overridden to flex stops sizing the way its
   row-mates do, so its border-bottom lands at a different height than the
   other three cells in the same row and the row's divider reads as broken
   rather than one straight line. */
.chart-ribbon .k{font-weight:600;color:var(--ink-2);}
.chart-ribbon .k-inner{display:flex;align-items:center;gap:5px;}
/* Centred, not left-aligned: the column is wider than the chart it holds, and
   a left-aligned chart just moves the leftover space to one side instead of
   splitting it evenly either side of the years/bars. */
.chart-ribbon .chart{width:364px;text-align:center;}
.chart-ribbon td.chart svg{display:block;width:340px;height:95px;margin:0 auto;}
/* Taller than the bar rows — see EXP_CHART_H in chart-financials.js. */
.chart-ribbon tr.exp td.chart svg{height:130px;}
.chart-ribbon th.chart svg{display:block;width:340px;height:20px;margin:0 auto;}
.chart-ribbon .val{text-align:right;font-variant-numeric:tabular-nums;font-weight:700;color:var(--ink);white-space:nowrap;padding-inline-start:14px;}
/* Unit letter (B/M/K) rides along with the number itself — same size, weight
   and ink as the digits so "394.3B" reads as one value, not number + footnote. */
.chart-ribbon .val .u{font-size:inherit;font-weight:inherit;color:inherit;margin-inline-start:2px;}
.chart-ribbon .d{text-align:right;font-variant-numeric:tabular-nums;font-weight:700;white-space:nowrap;padding-inline-start:14px;}
.chart-ribbon .d.up-strong{color:#22c55e;}
.chart-ribbon .d.up-mild{color:#facc15;}
.chart-ribbon .d.down{color:#ef4444;}
.chart-ribbon .d .arw{font-size:9px;margin-inline-end:1px;}
.chart-ribbon tr.sec td{
  padding:16px 0 5px;border-bottom:none;cursor:default;
  font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-muted);font-weight:700;
}

/* vs-expectations sub-row: nested visually under its parent metric row —
   smaller, indented, muted label so it reads as a detail, not a peer line. */
.chart-ribbon tr.exp .k{font-weight:500;font-size:11.5px;color:var(--ink-muted);padding-inline-start:14px;}
.chart-ribbon tr.exp td{padding-top:4px;padding-bottom:11px;}
.exp-badge{font-weight:700;font-variant-numeric:tabular-nums;padding:2px 7px;border-radius:20px;font-size:11px;}
.exp-badge.beat{color:#22c55e;background:rgba(34,197,94,.15);}
.exp-badge.inline{color:#a87f0a;background:rgba(250,204,21,.18);}
.exp-badge.miss{color:#ef4444;background:rgba(239,68,68,.15);}
.chart-fin-explegend{
  display:flex;align-items:center;flex-wrap:wrap;gap:6px 16px;
  margin-top:10px;font-size:10.5px;color:var(--ink-muted);
}
.chart-fin-explegend span{display:inline-flex;align-items:center;gap:5px;}
.exp-legend-dot{width:7px;height:7px;border-radius:50%;display:block;flex:none;}
.exp-legend-dot.hollow{background:none;border:1.3px solid var(--ink-muted);}
.chart-perf{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;}
.chart-perf-tile{padding:10px 8px;border-radius:var(--radius-sm);text-align:center;}
.chart-perf-tile.neg{background:var(--bad-bg);}
.chart-perf-tile.pos{background:var(--good-bg);}
.chart-perf-tile .v{font-size:14px;font-weight:700;font-variant-numeric:tabular-nums;}
.chart-perf-tile.neg .v{color:var(--bad);}
.chart-perf-tile.pos .v{color:var(--good);}
.chart-perf-tile .l{font-size:10.5px;color:var(--ink-muted);margin-top:2px;}

.chart-fin-legend{display:flex;align-items:center;justify-content:center;gap:15px;flex-wrap:wrap;
  margin-top:9px;font-size:10.5px;color:var(--ink-2);}
.chart-fin-legend span{display:inline-flex;align-items:center;gap:5px;}
.chart-fin-legend i{width:7px;height:7px;border-radius:50%;display:block;flex:none;}

.chart-expandable{cursor:pointer;transition:border-color .12s;}
.chart-expandable:hover{border-color:var(--brand-500);}

/* ---- Chart Technicals: two gauges + analyst consensus --------------------- */
.chart-gauge-wrap{text-align:center;padding:6px 0 2px;}
.chart-consensus{margin-top:16px;padding-top:14px;border-top:1px solid var(--border);}
.chart-consensus-head{
  display:flex;align-items:baseline;justify-content:space-between;
  gap:10px;font-size:11.5px;color:var(--ink-muted);margin-bottom:7px;
}
.chart-consensus-head b{color:var(--ink);font-variant-numeric:tabular-nums;}
.chart-consensus-bar{display:flex;height:8px;border-radius:4px;overflow:hidden;background:var(--surface-3);}
.chart-consensus-bar span{display:block;height:100%;}
.chart-consensus-keys{display:flex;flex-wrap:wrap;gap:5px 13px;margin-top:9px;font-size:10.5px;color:var(--ink-2);}
.chart-consensus-keys span{display:inline-flex;align-items:center;gap:5px;}
.chart-consensus-keys i{width:7px;height:7px;border-radius:2px;display:block;flex:none;}

/* ---- Chart Key stats: dense four-column label/value grids ---------------- */
.chart-h2{display:flex;align-items:center;gap:5px;font-size:19px;font-weight:800;letter-spacing:-.015em;margin:0 0 16px;}
.chart-h2 .chart-chev{color:var(--ink-muted);font-weight:400;}

.chart-sec-block{padding:26px 0;border-bottom:1px solid var(--border);}
.chart-sec-block:first-child{padding-top:0;}
.chart-sec-block:last-child{border-bottom:none;}

.chart-stats-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:20px 24px;}
@media (max-width:900px){.chart-stats-grid{grid-template-columns:repeat(2,1fr);}}
@media (max-width:520px){.chart-stats-grid{grid-template-columns:1fr;}}
.chart-stat .l{
  font-size:11.5px;color:var(--brand-700);margin-bottom:3px;
  display:flex;align-items:center;gap:3px;cursor:default;
}
/* Every label reads the same regardless of whether it carries a help
   tooltip — a pointer cursor on plain text looked like a broken link, and
   the muted "plain" variant read as a second, lesser tier of heading. */
.chart-stat .l.plain{color:var(--brand-700);}
.chart-stat .v{
  font-size:15px;font-weight:700;color:var(--ink);
  font-variant-numeric:tabular-nums;
  display:flex;align-items:baseline;gap:3px;flex-wrap:wrap;
}
/* Unit suffix (B/T/M/K) at the same size as the number it qualifies,
   rather than a small muted afterthought. */
.chart-stat .v .u{font-size:15px;font-weight:700;color:#22c55e;}
.chart-stat .v.pos{color:var(--good);}
.chart-stat .v.neg{color:var(--bad);}

.chart-desc{font-size:12.5px;color:var(--ink-2);line-height:1.7;margin-top:18px;max-width:900px;}
.chart-desc button{
  border:none;background:none;padding:0;margin-inline-start:4px;
  color:var(--brand-700);font-weight:600;font-size:12.5px;font-family:inherit;cursor:pointer;
}
.chart-desc button:hover{text-decoration:underline;}

/* ========================== PORTFOLIO CHARTS =============================== */
/* Composition stays narrow; Allocation is wider because it holds the donut and
   the value-over-time chart side by side. */
/* stretch, not start: the two cards sit side by side and a short Composition
   card next to a tall Allocation card reads as a layout mistake. Equal height
   also lets the composition donut centre itself in the leftover space. */
.pf-charts{
  display:grid;grid-template-columns:minmax(240px,1fr) minmax(360px,1.7fr);
  gap:16px;align-items:stretch;
}
.pf-charts > .card{display:flex;flex-direction:column;}
/* The donut+legend row grows to fill the card so the chart is vertically
   centred rather than pinned to the top with dead space beneath it. */
.pf-charts > .card > div:last-child{flex:1;align-items:center;}
.pf-alloc-row{display:flex;gap:18px;align-items:center;flex-wrap:wrap;}
.pf-alloc-donut{flex:none;}
.pf-alloc-time{flex:1;min-width:280px;}
.pf-alloc-note{
  font-size:11px;color:var(--ink-muted);line-height:1.55;margin-top:8px;
}

/* Hover tooltip for the value line. */
.vl-tip{
  position:absolute;display:none;pointer-events:none;z-index:5;white-space:nowrap;
  background:var(--surface-2);color:var(--ink);border:1px solid var(--border-2);
  font-size:11.5px;font-weight:600;padding:4px 8px;border-radius:6px;
}
/* Multi-series tooltip: the bucket, then one row per visible series. The
   swatch is what ties a row to its line — the name alone would make you read
   the legend to find out which is which, at the moment you are looking
   somewhere else. */
.vl-tip-h{font-size:10.5px;color:var(--ink-muted);font-weight:700;margin-bottom:2px;}
.vl-tip-r{display:flex;align-items:center;gap:6px;line-height:1.5;}
.vl-tip-sw{width:8px;height:8px;border-radius:2px;flex:none;}
.vl-tip-n{color:var(--ink-2);font-weight:600;}
.vl-tip-r b{margin-inline-start:auto;font-variant-numeric:tabular-nums;}

/* Legend, drawn only when there is more than one series. Each entry is a
   toggle; `.off` is the hidden state and dims the whole entry rather than only
   the swatch, so the state is legible without comparing two colours. */
.vl-legend{
  display:flex;flex-wrap:wrap;gap:6px 14px;justify-content:center;
  margin-top:8px;
}
.vl-leg{
  display:inline-flex;align-items:center;gap:6px;
  background:none;border:none;padding:2px 4px;border-radius:5px;
  font-family:inherit;font-size:11.5px;font-weight:700;color:var(--ink-2);
  cursor:pointer;
}
.vl-leg:hover{background:var(--surface-2);color:var(--ink);}
.vl-leg:focus-visible{outline:2px solid var(--brand-500);outline-offset:1px;}
.vl-leg-sw{width:9px;height:9px;border-radius:50%;flex:none;}
/* Hidden: faded, and the swatch becomes a ring. Colour is never the only
   signal — the outline says "off" in a greyscale screenshot too. */
.vl-leg.off{opacity:.45;text-decoration:line-through;}
.vl-leg.off .vl-leg-sw{background:transparent !important;box-shadow:inset 0 0 0 2px var(--ink-muted);}

@media (max-width:900px){
  .pf-charts{grid-template-columns:1fr;}
  .pf-alloc-row{flex-direction:column;align-items:stretch;}
  .pf-alloc-donut{align-self:center;}
}

/* --- Portfolio redesign: values card + statistics card + sortable table ---
   The row uses a soft 1:1.6 split so the chart on the right gets the room to
   read as a chart rather than an inset thumbnail, while the values card on
   the left stays roomy enough for the big number and the toggle underneath.
*/
.pf-row{
  display:grid;grid-template-columns:minmax(300px,1fr) minmax(360px,1.6fr);
  gap:14px;margin-bottom:16px;align-items:stretch;
}
.pf-row > .card{display:flex;flex-direction:column;}
.pf-values-title, .pf-stats-header, .pf-hold-title{
  display:flex;align-items:center;justify-content:space-between;
  gap:10px;margin-bottom:12px;
}
.pf-values-title h3, .pf-stats-header h3, .pf-hold-title h3{
  margin:0;font-size:14px;font-weight:600;color:var(--ink);
}
.pf-values-hero{
  display:flex;align-items:baseline;gap:12px;margin-bottom:6px;flex-wrap:wrap;
}
.pf-values-amount{
  font-size:30px;font-weight:700;letter-spacing:-0.01em;
  font-variant-numeric:tabular-nums;line-height:1.1;
}
.pf-values-chip{
  display:inline-flex;align-items:center;gap:4px;
  padding:3px 9px;border-radius:999px;
  font-size:12px;font-weight:700;font-variant-numeric:tabular-nums;
}
.pf-values-chip.up{background:var(--good-bg);color:var(--good);}
.pf-values-chip.down{background:var(--bad-bg);color:var(--bad);}
.pf-values-narrative{
  color:var(--ink-muted);font-size:13px;line-height:1.55;margin:8px 0 16px;max-width:44ch;
}
/* Segmented pill toggle. The active pill is filled with the brand blue —
   the same tone the chart uses, so top-performer and chart line read as
   part of one design decision. */
/* Best/worst performer — two SEPARATE cards, not a segmented toggle.
   The previous version was one pill split in half with the "Top" side filled
   blue, which read as a selected option in a two-way choice rather than as two
   links. Each is now its own bordered card that lifts on hover and states what
   pressing it does. */
.pf-perf-pair{
  display:grid; grid-template-columns:1fr 1fr; gap:10px;
  margin-top:auto; align-self:stretch;
}
.pf-perf-card{
  display:flex; flex-direction:column; gap:5px; text-align:left; cursor:pointer;
  background:var(--surface-2); border:1px solid var(--border-2);
  border-radius:var(--radius-sm); padding:10px 12px;
  font-family:inherit; font-variant-numeric:tabular-nums;
  transition:border-color .15s ease, transform .15s ease, box-shadow .15s ease;
}
.pf-perf-card:hover{
  border-color:var(--brand-500);
  transform:translateY(-1px);
  box-shadow:0 4px 14px rgba(0,0,0,.18);
}
.pf-perf-card:focus-visible{ outline:2px solid var(--brand-500); outline-offset:2px; }
.pf-perf-label{
  font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:.08em;
  color:var(--ink-muted);
}
.pf-perf-row{ display:flex; align-items:baseline; gap:7px; flex-wrap:wrap; }
.pf-perf-sym{ font-weight:800; font-size:15px; letter-spacing:.02em; color:var(--ink); }
.pf-perf-pct{ font-size:13px; font-weight:700; }
.pf-perf-pct.up{ color:var(--good); }
.pf-perf-pct.down{ color:var(--bad); }
/* The affordance: says what the card is for, and moves on hover so the
   pointer confirms it before the click. */
.pf-perf-cta{
  display:flex; align-items:center; gap:5px;
  font-size:11.5px; font-weight:600; color:var(--brand-500);
}
.pf-perf-arrow{ transition:transform .15s ease; }
.pf-perf-card:hover .pf-perf-arrow{ transform:translateX(3px); }

@media (max-width:520px){ .pf-perf-pair{ grid-template-columns:1fr; } }

/* Native select styled to look like a small pill in the card header. */
.pf-stats-select{
  background:var(--surface-2);border:1px solid var(--border-2);color:var(--ink);
  padding:5px 26px 5px 12px;border-radius:8px;font-size:12.5px;font-weight:500;
  appearance:none;-webkit-appearance:none;cursor:pointer;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--ink-muted) 50%),
    linear-gradient(135deg, var(--ink-muted) 50%, transparent 50%);
  background-position: calc(100% - 12px) 50%, calc(100% - 7px) 50%;
  background-size:5px 5px, 5px 5px;background-repeat:no-repeat;
}
.pf-stats-plot{min-height:220px;}

/* Sortable header glyphs — the arrow next to the label is what tells you
   which column drove the current order, so it stays visible even when the
   column is not active (dim ↕ vs bright ▲/▼). */
.pf-table th.pf-th{cursor:pointer;user-select:none;}
.pf-table th.pf-th .pf-th-glyph{color:var(--ink-muted);font-size:10px;opacity:.6;}
.pf-table th.pf-th.sorted{color:var(--ink);}
.pf-table th.pf-th.sorted .pf-th-glyph{color:var(--brand-500);opacity:1;}
.pf-table th.pf-th:hover{color:var(--ink);}

/* Symbol cell with a logo and two lines of text. The name is muted so the
   ticker still carries the row visually. */
.pf-name-td{cursor:pointer;}
.pf-name-cell{display:inline-flex;align-items:center;gap:10px;}
.pf-name-block{display:inline-flex;flex-direction:column;line-height:1.2;}
.pf-name-sym{font-weight:700;font-size:13.5px;letter-spacing:.01em;}
.pf-name-co{color:var(--ink-muted);font-size:11.5px;margin-top:1px;}
.pf-table-wrap{overflow-x:auto;}

/* Change column colours — the same tokens as up/down badges elsewhere so
   the visual language is consistent. */
.pf-table td .up{color:var(--good);font-weight:600;}
.pf-table td .down{color:var(--bad);font-weight:600;}

@media (max-width:900px){
  .pf-row{grid-template-columns:1fr;}
}

/* ====================== PORTFOLIO TRUST SPLIT (pt-) ========================
   Ported from mockups/portfolio-trust.css after the owner approved the
   integrated mockup (2026-08-08). Everything here is ADDITIVE on top of the
   page's existing classes — a stripe, a tag, a muted state, a divider row —
   so no component the app already owns is restyled. */

/* The two stat-row labels. The verified one is brand-coloured; the
   unverified one is grey with a dashed rule, so even the section headings
   carry the "solid vs provisional" language. */
.pt-statrow-l{
  display:flex;align-items:center;gap:8px;
  font-size:11px;text-transform:uppercase;letter-spacing:.06em;font-weight:700;
  margin:0 0 7px 2px;
}
.pt-statrow-l.v{color:var(--brand-700);}
.pt-statrow-l.u{color:var(--unv);}
.pt-statrow-l .rule{flex:1;height:1px;background:var(--border);}
/* The dashed rule stays dashed — texture carries the split for anyone who
   cannot separate the two hues — but it takes the orange at low opacity so
   the label does not look like it belongs to a different row than its line. */
.pt-statrow-l.u .rule{
  background:none;height:0;
  border-top:1px dashed color-mix(in srgb, var(--unv) 45%, transparent);
}
.pt-statrow-l .sub{font-weight:600;letter-spacing:0;text-transform:none;font-size:11.5px;}

/* The unverified stat tiles: dashed, transparent, and NEVER green or red.
   Green is the app's reward colour, and paying it out for a typed-in number
   would hand an unverified gain exactly the emotional weight the split
   exists to withhold.
   The orange lands on the BORDER and the LABEL, never on the number. That is
   the whole point of the restraint: orange on the value would be emphasis,
   and this set is meant to be readable without being persuasive. The figure
   stays grey. */
.pt-stats-unverified .heatmap-stat{
  border-style:dashed;background:transparent;
  border-color:color-mix(in srgb, var(--unv) 42%, var(--border-2));
}
/* Plain figures (value, count) stay neutral; up/down keep the app's green and
   red. Owner's call, 2026-08-10 — this row used to force EVERYTHING to grey,
   including the P/L, so a member could not tell at a glance whether their
   typed-in holdings were up or down without reading the sign.

   The argument for grey was that green is the app's reward colour and an
   unverified gain has not earned it. That argument loses to a simpler one:
   this row is already fenced off by its own "○ NOT VERIFIED" heading, a
   dashed border and orange labels — three signals that survive a greyscale
   screenshot — so the colour is not what carries the trust claim, and
   withholding it only made the number harder to read. */
.pt-stats-unverified .heatmap-stat .v{color:var(--ink-2);}
.pt-stats-unverified .heatmap-stat .v.up{color:var(--good);}
.pt-stats-unverified .heatmap-stat .v.down{color:var(--bad);}
.pt-stats-unverified .heatmap-stat .l{color:var(--unv);}

/* The "verified" tag beside the hero number. */
.pt-tag{
  display:inline-flex;align-items:center;gap:4px;
  font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
  padding:2px 7px;border-radius:99px;white-space:nowrap;
}
.pt-tag.v{background:var(--brand-50);color:var(--brand-700);}

/* The typed-in money, named right under the verified headline. Dashed rule
   on purpose: provisional, and it survives a greyscale screenshot. */
.pt-hero-legacy{
  margin-top:12px;padding-top:11px;border-top:1px dashed var(--border-2);
  font-size:12.5px;color:var(--ink-muted);line-height:1.6;
}
.pt-hero-legacy .amt{color:var(--ink-2);font-weight:600;}

/* Table rows. The stripe sits on the first cell so it reads as a group
   marker down the table's left edge, not as a border on one row.
   Blue counts, orange does not. The unverified stripe used to be --border-2,
   which is the same grey as every divider on the page — so the rail was
   invisible unless you already knew to look for it, and a marker nobody sees
   is not a marker. The row's TEXT stays muted: the stripe says which set the
   row is in, the muted text says how much weight to give it, and those are
   two different jobs. Painting the cells orange would do the second one
   backwards, by making the rows that count for nothing the loudest on the
   page. */
.pf-table tr.pt-v > td:first-child{box-shadow:inset 3px 0 0 var(--brand-500);}
.pf-table tr.pt-l > td:first-child{box-shadow:inset 3px 0 0 var(--unv);}
.pf-table tr.pt-l .pf-name-sym{color:var(--ink-2);}
.pf-table tr.pt-l td{color:var(--ink-muted);}

/* A group divider inside the table, so the two sets are physically
   separated and named, not merely tinted. The .v/.u modifiers tint the
   heading to match the stripes beneath it; without them both headings were
   the same grey and the coloured rails looked unlabelled. Bare .pt-group
   keeps the grey, so any caller that has not been updated still renders. */
.pf-table tr.pt-group td{
  padding:14px 10px 6px;font-size:10.5px;text-transform:uppercase;
  letter-spacing:.06em;color:var(--ink-muted);font-weight:700;border-bottom:none;
}
.pf-table tr.pt-group.v td{color:var(--brand-700);}
.pf-table tr.pt-group.u td{color:var(--unv);}
.pf-table tr.pt-group.first td{padding-top:2px;}

/* The "not verified" badge. It was a bare .badge — surface grey — which put
   the single most important disclaimer on the row in the quietest paint
   available. Still used where there is no figure to show (no price). */
.badge.unv{background:var(--unv-bg);color:var(--unv);}

/* An unverified P/L: the real figure, with its caveat stacked underneath.
   The figure is --ink-2 and NOT up/down green-red — that restriction is the
   whole point of the split, and it is the one thing showing the number must
   not cost. Stacked rather than side by side because the two halves are read
   in order: what it is, then how much it is worth. */
.pt-pl-unv{display:inline-flex;flex-direction:column;line-height:1.35;}
/* Bare text rather than a filled badge — that is what separates an unverified
   P/L from a verified one now that both carry colour, and unlike the colour it
   survives a greyscale screenshot. Grey stays the fallback for a row whose
   direction is unknown. */
.pt-pl-unv .amt{color:var(--ink-2);font-weight:600;}
.pt-pl-unv .amt.up{color:var(--good);}
.pt-pl-unv .amt.down{color:var(--bad);}
.pt-pl-unv .lbl{
  font-size:9.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;
  color:var(--unv);
}

/* ---- Pending Actions: the same left rail, its own set of colours ----
   The rail here answers a DIFFERENT question than the one on the holdings
   table, so it deliberately does not reuse blue and orange (owner's call,
   2026-08-09). Up there the two hues mean verified / not verified — a fact
   about provenance that never changes. Down here every order is verified by
   construction (the not-verified path writes a holding directly and never
   creates an order), and what varies is where the order is in its life:

     green  filled    — it happened. In the trade log now, permanently.
     yellow pending   — waiting. Not a problem, not a result; the one row you
                        can still act on, which is why it is the one that
                        catches the eye.
     grey   cancelled — over, and it changed nothing.

   Keeping the two systems on separate palettes is the point: seeing orange on
   this page would otherwise invite the reading "this order is not verified",
   which is not a state an order can be in.

   Class names say the status rather than v/u, because that is now literally
   what they encode. Scoped to .wl-table, the orders table. */
.wl-table tr.ord-filled  > td:first-child{box-shadow:inset 3px 0 0 var(--good);}
.wl-table tr.ord-pending > td:first-child{box-shadow:inset 3px 0 0 var(--warn);}
.wl-table tr.ord-cancel  > td:first-child{box-shadow:inset 3px 0 0 var(--border-2);}
.wl-table tr.ord-cancel td{color:var(--ink-muted);}

/* The pending badge. .badge had good/bad/brand but no plain warn variant —
   only the channel-specific ones — so this is the generic missing rung. */
.badge.warn{background:var(--warn-bg);color:var(--warn);}
/* SELL, wherever a trade's side is shown (trade log, public profile).
   Owner's call, 2026-08-10: BUY carried the green badge and SELL was left
   bare, so the two sides of the same column were not reading as a pair —
   one looked like a label and the other like plain text.

   The frame is an INSET SHADOW, not a border: `border` adds 2px to the
   badge's box and BUY has none, so the two would sit at different heights in
   the same cell and the column would look misaligned. An inset shadow paints
   inside the existing box and costs no layout.

   On the yellow: it is --warn, the same token Pending Actions uses for a
   pending row — but those are different tables and the trade log has no
   lifecycle in it (every row already happened), so nothing here can be read
   as "waiting". Colour is never the only signal either: the word SELL is
   right there inside the badge. */
.badge.sell{
  background:var(--warn-bg);color:var(--warn);
  box-shadow:inset 0 0 0 1px var(--warn);
}

/* ---- The trade log ----
   Verified blue, not the green/yellow/grey above it. Those three track where
   a command has got to, and nothing in this table is going anywhere: every
   row already happened and none of them can be edited or deleted. What the
   rail says here is the same thing the holdings table says with it — this
   counts — so a member tracing a position back to the fill that created it
   meets one colour at both ends rather than two. */
.wl-table tr.tl-verified > td:first-child{box-shadow:inset 3px 0 0 var(--brand-500);}

/* The add-asset form's two choice cards — the one irreversible decision on
   the form, drawn as a decision rather than folded into a dropdown. */
.pt-choice{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:14px;}
@media(max-width:560px){ .pt-choice{grid-template-columns:1fr;} }
.pt-choice-opt{
  border:1px solid var(--border);border-radius:var(--radius);padding:11px 13px;cursor:pointer;
}
.pt-choice-opt.on{border-color:var(--brand-500);background:var(--brand-50);}
.pt-choice-opt.alt{border-style:dashed;}
/* Picking "not verified" must not light up in the SAME blue that means
   verified everywhere else on the page — that is the one card where the
   selected colour is itself information. Order matters: this follows .on so
   it wins for the alt card without needing !important. */
.pt-choice-opt.alt.on{border-color:var(--unv);background:var(--unv-bg);}
.pt-choice-t{font-size:12.5px;font-weight:700;color:var(--ink);margin-bottom:3px;}
.pt-choice-d{font-size:11px;color:var(--ink-muted);line-height:1.5;}

/* The dependent-field explanations under each control. */
.pt-lockmsg{font-size:11px;color:var(--ink-muted);margin:-6px 0 10px;line-height:1.5;}

/* The join-date widget: a timeline whose ONLY hard edge is the join date.
   The "before" segment is outlined, not just filled — at --surface-3 against
   a card it was nearly invisible and the bar read as though it began at the
   pin. It has to be legible-but-secondary: the time exists, it just is not
   ours. */
.pt-w-timeline{position:relative;margin:14px 2px 0;}
.pt-w-track{height:8px;border-radius:99px;overflow:hidden;display:flex;}
.pt-w-before{background:var(--unv-bg);border:1px dashed var(--unv);
  border-right:none;border-radius:99px 0 0 99px;box-sizing:border-box;}
.pt-w-after{background:var(--brand-500);flex:1;}
.pt-w-pin{
  position:absolute;top:-5px;width:3px;height:18px;border-radius:2px;
  background:var(--brand-700);
}
.pt-w-legend{display:flex;gap:16px;margin-top:12px;flex-wrap:wrap;font-size:11.5px;}
.pt-w-key{display:flex;align-items:center;gap:6px;color:var(--ink-muted);}
.pt-w-key .vlab{color:var(--brand-700);font-weight:600;}
.pt-w-dot{width:9px;height:9px;border-radius:3px;flex:none;}
.pt-w-dot.v{background:var(--brand-500);}
/* Dashed, not solid: the legend key has to match the bar segment it stands
   for, and that segment is outlined because the time before you joined is
   real but not ours to vouch for. */
.pt-w-dot.l{background:var(--unv-bg);border:1px dashed var(--unv);}

/* ============================ EARNINGS HUB ================================= */
.earn-controls{
  display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin:12px 0 14px;
}
.seg{display:inline-flex;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;}
.seg-btn{
  border:0;background:transparent;color:var(--ink-2);cursor:pointer;
  padding:6px 12px;font-size:12.5px;font-weight:600;
  border-inline-end:1px solid var(--border);
}
.seg-btn:last-child{border-inline-end:0;}
.seg-btn:hover{background:var(--surface-2);}
.seg-btn.active{background:var(--brand-500);color:#fff;}
.earn-range{display:flex;align-items:center;gap:6px;flex-wrap:wrap;}
.earn-filter{
  display:inline-flex;align-items:center;gap:7px;cursor:pointer;
  font-size:12.5px;color:var(--ink-2);
}

/* Day and month headers between the grouped cards. */
.earn-group-head{
  display:flex;align-items:baseline;gap:8px;
  margin:16px 0 7px;
}
.earn-group-head .g-day{
  font-size:12.5px;font-weight:800;letter-spacing:.02em;
}
.earn-group-head .g-count{
  font-size:11px;color:var(--ink-muted);
  border:1px solid var(--border);border-radius:999px;padding:0 7px;
}
.earn-foot{
  margin-top:16px;font-size:11.5px;color:var(--ink-muted);line-height:1.6;
}

/* ============================ CHANNELS v2 ================================== */
/* Slack-shaped: org strip | channel rail | main pane. The shell owns the
   viewport height so the feed scrolls inside it and the composer stays put. */
/* The Channels page goes edge-to-edge: no page padding, no width cap, and
   the shell stretches to fill everything under the topbar. */
main.content.chx-page{
  padding:0;max-width:none;
  display:flex;flex-direction:column;min-height:0;
}
.chx-shell{
  display:flex;align-items:stretch;
  flex:1;min-height:0;
  height:calc(100vh - var(--topbar-h));
  background:var(--surface);
  overflow:hidden;
}

/* ---- org strip (workspace switcher) ---- */
.chx-strip{
  width:56px;flex:none;display:flex;flex-direction:column;align-items:center;
  gap:10px;padding:12px 0;background:var(--surface-2);
  border-inline-end:1px solid var(--border);
}
.chx-org-sq{
  width:38px;height:38px;border:none;border-radius:11px;cursor:pointer;
  display:flex;align-items:center;justify-content:center;position:relative;
  color:#fff;font-weight:800;font-size:12.5px;
}
.chx-org-sq:hover{filter:brightness(1.15);}
.chx-org-sq.on{outline:2px solid var(--ink);outline-offset:2px;}
.chx-org-sq.on::before{
  content:"";position:absolute;inset-inline-start:-9px;top:50%;
  transform:translateY(-50%);width:4px;height:22px;
  border-radius:0 3px 3px 0;background:var(--ink);
}
.chx-org-lock{
  position:absolute;bottom:-3px;inset-inline-end:-3px;font-size:9px;line-height:1;
  background:var(--surface);border:1px solid var(--border-2);border-radius:50%;padding:1px 2px;
}
.chx-org-add{
  width:38px;height:38px;border-radius:11px;border:1px dashed var(--border-2);
  background:transparent;color:var(--ink-muted);font-size:19px;line-height:1;cursor:pointer;
}
.chx-org-add:hover{color:var(--brand-500);border-color:var(--brand-500);}
.chx-org-add:disabled{opacity:.4;cursor:not-allowed;}

/* ---- rail ---- */
.chx-rail{
  width:250px;flex:none;display:flex;flex-direction:column;min-height:0;
  border-inline-end:1px solid var(--border);
}
.chx-rail-head{
  display:flex;align-items:center;gap:8px;padding:12px 14px;
  border-bottom:1px solid var(--border);font-weight:700;font-size:13.5px;
}
.chx-rail-head .tag{margin-inline-start:auto;font-size:9.5px;color:var(--ink-muted);letter-spacing:.06em;}
.chx-rail-searchwrap{padding:10px 12px 4px;}
.chx-rail-search{width:100%;font-size:12.5px;}
.chx-rail-scroll{flex:1;min-height:0;overflow-y:auto;padding:6px 8px 10px;}
.chx-rail-home{
  display:flex;align-items:center;gap:8px;padding:6px 8px;margin-bottom:2px;
  border-radius:var(--radius-sm);color:var(--ink-2);cursor:pointer;font-size:13px;
}
.chx-rail-home:hover{background:var(--surface-2);}
.chx-rail-home.on{background:var(--brand-50);color:var(--brand-500);font-weight:600;}
.chx-grp{margin-top:10px;border-radius:var(--radius-sm);}
.chx-grp-head{
  display:flex;align-items:center;gap:6px;padding:3px 8px;cursor:pointer;user-select:none;
  color:var(--ink-muted);font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
}
.chx-grp-head:hover{color:var(--ink-2);}
.chx-grp-head .tw{font-size:9px;width:10px;transition:transform .12s;}
.chx-grp.closed .tw{transform:rotate(-90deg);}
.chx-grp.closed .chx-ch-list{display:none;}
.chx-grp.drop-hint{outline:1px dashed var(--brand-500);background:var(--brand-50);}
.chx-grp-del{
  margin-inline-start:auto;background:transparent;border:none;color:transparent;
  font-size:10px;cursor:pointer;
}
.chx-grp-head:hover .chx-grp-del{color:var(--ink-muted);}
.chx-grp-del:hover{color:var(--bad) !important;}
.chx-grp-rename{font-size:11px;padding:1px 6px;flex:1;}
.chx-ch-row{
  display:flex;align-items:center;gap:7px;padding:5px 8px 5px 14px;cursor:pointer;
  border-radius:var(--radius-sm);color:var(--ink-2);font-size:13.5px;user-select:none;
}
.chx-ch-row:hover{background:var(--surface-2);}
.chx-ch-row.on{
  background:var(--brand-50);color:var(--brand-500);font-weight:600;
  box-shadow:inset 3px 0 0 var(--brand-500);
}
.chx-ch-row .hash{color:var(--ink-muted);}
.chx-ch-row.on .hash{color:var(--brand-500);}
.chx-ch-row .lock{margin-inline-start:auto;font-size:11px;}
.chx-ch-row.dragging{opacity:.4;}
.chx-rail-note{font-size:10.5px;color:var(--ink-muted);padding:12px 8px 0;}
.chx-rail-foot{border-top:1px solid var(--border);padding:8px;display:flex;flex-direction:column;gap:2px;}
.chx-rail-add{
  display:flex;align-items:center;gap:8px;padding:6px 8px;text-align:start;
  background:transparent;border:none;border-radius:var(--radius-sm);
  color:var(--ink-muted);font-size:13px;cursor:pointer;
}
.chx-rail-add:hover{background:var(--surface-2);color:var(--ink-2);}

/* ---- main pane / channel header / tabs ---- */
.chx-main{flex:1;min-width:0;display:flex;flex-direction:column;min-height:0;}
.chx-head{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  padding:12px 18px 0;border-bottom:1px solid var(--border);
}
.chx-title{display:flex;align-items:center;gap:6px;font-size:16px;font-weight:700;}
.chx-title .hash{color:var(--ink-muted);font-weight:400;}
.badge.chx-type{background:var(--surface-3);color:var(--ink-2);}
.badge.chx-pub{background:var(--good-bg);color:var(--good);}
.badge.chx-priv{background:var(--warn-bg);color:var(--warn);}
.badge.chx-r-admin{background:var(--good-bg);color:var(--good);}
.badge.chx-r-manager{background:var(--role-manager-bg);color:var(--role-manager);}
.badge.chx-r-lecturer{background:var(--warn-bg);color:var(--warn);}
.badge.chx-r-user{background:var(--brand-50);color:var(--brand-500);}
.chx-tabs{display:flex;gap:2px;width:100%;margin-top:8px;}
.chx-tab{
  background:transparent;border:none;border-bottom:2px solid transparent;
  color:var(--ink-muted);padding:7px 14px;font-size:13px;font-weight:600;cursor:pointer;
}
.chx-tab:hover{color:var(--ink-2);}
.chx-tab.on{color:var(--brand-500);border-bottom-color:var(--brand-500);}
.chx-tab-body{flex:1;min-height:0;display:flex;flex-direction:column;}

/* ---- feed + posts ---- */
.chx-feed-wrap{flex:1;min-height:0;display:flex;position:relative;}
.chx-feed-col{flex:1;min-width:0;display:flex;flex-direction:column;min-height:0;}
.chx-feed{
  flex:1;min-height:0;overflow-y:auto;padding:16px 18px;
  display:flex;flex-direction:column;gap:12px;background:var(--page);
}
.chx-post{
  background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius);padding:12px 14px;
}
/* Pinned posts freeze at the top: one sticky wrapper inside the scrolling
   feed. top:-16px cancels the feed's own padding so the block sits flush,
   and the opaque background hides posts scrolling underneath it. */
.chx-pinned-wrap{
  position:sticky;top:-16px;z-index:10;
  display:flex;flex-direction:column;gap:8px;
  background:var(--page);padding:16px 0 10px;margin-top:-16px;
  border-bottom:1px solid var(--border-2);
}
.chx-post.pinned{border-color:var(--warn);}
.chx-pin-tag{
  font-size:10px;font-weight:800;letter-spacing:.05em;color:var(--warn);
  margin-bottom:6px;
}
.chx-post-head{display:flex;align-items:center;gap:9px;margin-bottom:6px;}
.chx-post-nm{display:flex;align-items:center;gap:6px;font-weight:700;font-size:13.5px;}
.chx-post-tm{font-size:11px;color:var(--ink-muted);font-variant-numeric:tabular-nums;}
.chx-post-body{font-size:13.5px;line-height:1.55;overflow-wrap:anywhere;}
.chx-post-body.sm{font-size:12.5px;}
.chx-link{color:var(--brand-500);}
.chx-link:hover{text-decoration:underline;}
.chx-code{
  font-family:var(--mono);font-size:12px;background:var(--surface-2);
  border:1px solid var(--border);border-radius:4px;padding:1px 5px;
}
.chx-att-img{max-width:min(420px,100%);border-radius:var(--radius-sm);border:1px solid var(--border-2);margin-top:8px;}
.chx-att-file{
  display:inline-flex;align-items:center;gap:7px;margin-top:8px;padding:7px 11px;
  background:var(--surface-2);border:1px solid var(--border-2);border-radius:var(--radius-sm);
  font-size:12.5px;color:var(--ink-2);
}
.chx-att-file:hover{border-color:var(--brand-500);}
.chx-labels{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px;}
/* Inline variant: labels share the footer action line (same row as Pin),
   pushed to the far end so the action buttons stay grouped. */
.chx-labels-inline{margin-top:0;margin-inline-start:auto;}
/* Amber, not brand blue: a label is a tag the author chose, and it should not
   read as one more blue control among the links and buttons it sits beside. */
.chx-label{
  font-size:11px;color:var(--warn);background:var(--warn-bg);
  border:1px solid transparent;border-radius:20px;padding:1px 9px;cursor:pointer;
  font-weight:600;
}
.chx-label:hover{border-color:var(--warn);}
.chx-post-foot{display:flex;align-items:center;gap:12px;margin-top:8px;}
.chx-like-btn{
  display:inline-flex;align-items:center;gap:4px;padding:0;cursor:pointer;
  background:transparent;border:none;color:var(--ink-muted);font-size:14px;line-height:1;
}
.chx-like-btn:hover{color:var(--bad);}
.chx-like-btn.liked{color:var(--bad);}
.chx-like-count{font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;}
.chx-reply-foot{display:flex;align-items:center;gap:10px;margin-top:5px;}
.chx-reply-btn{
  background:transparent;border:none;padding:0;cursor:pointer;
  color:var(--brand-500);font-size:12.5px;font-weight:600;
}
.chx-reply-btn:hover{text-decoration:underline;}
.chx-post-act{
  background:transparent;border:none;padding:0;cursor:pointer;
  color:var(--ink-muted);font-size:12px;
}
.chx-post-act:hover{color:var(--ink-2);}
/* Delete was a bare 12px bin glyph among five other muted controls — the one
   irreversible action on the row and the hardest to find. It now carries its
   own word and its own colour, and reads as a button rather than as text. */
.chx-post-act.danger{
  display:inline-flex;align-items:center;gap:5px;
  color:var(--bad);font-size:12.5px;font-weight:600;
  border:1px solid transparent;border-radius:8px;padding:2px 9px;
}
.chx-post-act.danger .ic{font-size:15px;line-height:1;}
.chx-post-act.danger:hover{color:var(--bad);background:var(--bad-bg);border-color:var(--bad);}
.chx-edit-note{font-size:11px;color:var(--ink-muted);}
.chx-edit-ta{width:100%;min-height:64px;resize:vertical;font-size:13px;}
.chx-edit-row{display:flex;gap:8px;align-items:center;margin-top:6px;}
.chx-err{font-size:12px;color:var(--bad);}

/* ---- composer ---- */
.chx-composer{border-top:1px solid var(--border);padding:10px 18px 12px;}
.chx-comp-ta{width:100%;min-height:56px;resize:vertical;font-size:13.5px;}
.chx-comp-ta.sm{min-height:42px;font-size:12.5px;}
.chx-comp-row{display:flex;align-items:center;gap:6px;margin-top:6px;position:relative;flex-wrap:wrap;}
.chx-tool{
  background:var(--surface-2);border:1px solid var(--border-2);border-radius:var(--radius-sm);
  color:var(--ink-2);padding:4px 9px;font-size:12.5px;cursor:pointer;
}
.chx-tool:hover{border-color:var(--brand-500);color:var(--brand-500);}
.chx-label-in{font-size:12px;width:90px;padding:4px 8px;}
.chx-send{margin-inline-start:auto;}
.chx-comp-hint{font-size:11px;color:var(--ink-muted);margin-top:5px;}
.chx-locked{
  border-top:1px solid var(--border);padding:12px 18px;
  font-size:12.5px;color:var(--ink-muted);display:flex;align-items:center;gap:8px;
}
.chx-emoji-pop{
  position:absolute;bottom:110%;inset-inline-start:0;z-index:30;display:none;
  grid-template-columns:repeat(8,1fr);gap:2px;padding:8px;
  background:var(--surface);border:1px solid var(--border-2);border-radius:var(--radius);
  box-shadow:0 8px 24px rgba(0,0,0,.35);
}
.chx-emoji-pop.open{display:grid;}
.chx-emoji-pop button{background:transparent;border:none;font-size:17px;padding:3px;border-radius:6px;cursor:pointer;}
.chx-emoji-pop button:hover{background:var(--surface-2);}

/* ---- polls ----
   The composer button carries a word next to its glyph, so it is wider than
   its neighbours and says so rather than relying on them all being the same
   size. `.on` is the open state, which matches aria-pressed on the element. */
.chx-tool-poll{display:inline-flex;align-items:center;gap:5px;font-weight:600;}
.chx-tool-poll .ic{font-size:13px;}
.chx-tool-poll.on{background:var(--brand-500);border-color:var(--brand-500);color:#fff;}
.chx-tool-poll.on:hover{color:#fff;}

.chx-poll-drawer{
  margin-top:10px;padding-top:10px;border-top:1px dashed var(--border-2);
  display:flex;flex-direction:column;gap:7px;
}
.chx-poll-drawer[hidden]{display:none;}
.chx-poll-drawer-hd{font-size:11.5px;font-weight:700;color:var(--ink-muted);letter-spacing:.02em;}
.chx-poll-in{width:100%;font-size:13px;padding:6px 9px;}
.chx-poll-opts{display:flex;flex-direction:column;gap:5px;}
.chx-poll-opt-row{display:flex;align-items:center;gap:6px;}
.chx-poll-drawer-ft{display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
.chx-poll-dur{
  display:inline-flex;align-items:center;gap:6px;margin-inline-start:auto;
  font-size:11.5px;color:var(--ink-muted);
}
.chx-poll-dur .ctl-select{width:auto;font-size:12px;padding:3px 6px;}

/* The poll inside a post. Each option is a button whose background IS the
   result bar, so voting and reading the result are the same gesture. */
.chx-poll{
  margin:9px 0 4px;padding:10px 11px;border:1px solid var(--border-2);
  border-radius:var(--radius);background:var(--surface-2);
}
.chx-poll-head{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;margin-bottom:8px;}
.chx-poll-badge{font-size:11px;font-weight:700;color:var(--ink-muted);white-space:nowrap;}
.chx-poll-q{font-size:13.5px;font-weight:600;}
.chx-poll-opt{
  position:relative;display:flex;align-items:center;gap:8px;width:100%;
  padding:7px 10px;margin-bottom:5px;text-align:start;cursor:pointer;
  background:var(--surface);border:1px solid var(--border-2);
  border-radius:var(--radius-sm);color:inherit;font-size:12.5px;overflow:hidden;
}
.chx-poll-opt:hover:not(.closed){border-color:var(--brand-500);}
.chx-poll-opt.mine{border-color:var(--brand-500);}
.chx-poll-opt.closed{cursor:default;}
/* Behind the label, never over it — hence the explicit stacking below. */
.chx-poll-fill{
  position:absolute;inset-block:0;inset-inline-start:0;z-index:0;
  background:color-mix(in srgb, var(--brand-500) 22%, transparent);
  transition:width .25s ease;
}
.chx-poll-opt.mine .chx-poll-fill{background:color-mix(in srgb, var(--brand-500) 38%, transparent);}
.chx-poll-lab{position:relative;z-index:1;display:flex;align-items:center;gap:7px;flex:1;min-width:0;}
.chx-poll-tick{color:var(--brand-500);font-size:11px;}
.chx-poll-num{position:relative;z-index:1;font-size:11.5px;color:var(--ink-muted);white-space:nowrap;}
.chx-poll-opt.mine .chx-poll-num{color:var(--ink-2);}
.chx-poll-foot{
  display:flex;align-items:center;gap:6px;flex-wrap:wrap;
  font-size:11px;color:var(--ink-muted);margin-top:2px;
}
.chx-poll-undo{font-style:italic;}

/* ---- thread panel ---- */
.chx-thread{
  width:0;flex:none;display:flex;flex-direction:column;min-height:0;
  border-inline-start:1px solid var(--border);background:var(--surface);
  overflow:hidden;transition:width .15s;
}
.chx-thread.open{width:min(340px,45%);}
.chx-thread-head{
  display:flex;align-items:center;justify-content:space-between;
  padding:11px 14px;border-bottom:1px solid var(--border);font-weight:700;font-size:13.5px;
}
.chx-x{background:transparent;border:none;color:var(--ink-muted);font-size:14px;cursor:pointer;}
.chx-x:hover{color:var(--bad);}
.chx-thread-scroll{flex:1;min-height:0;overflow-y:auto;padding:12px 14px;display:flex;flex-direction:column;gap:10px;}
.chx-thread-root{border-bottom:1px solid var(--border);padding-bottom:10px;}
.chx-reply{display:flex;gap:8px;}
.chx-reply-box{
  flex:1;background:var(--surface-2);border:1px solid var(--border);
  border-radius:var(--radius-sm);padding:7px 10px;
}
.chx-reply-nm{display:flex;align-items:center;gap:6px;font-size:12px;font-weight:700;}
.chx-reply-bd{font-size:12.5px;margin-top:2px;overflow-wrap:anywhere;}
.chx-thread-comp{border-top:1px solid var(--border);padding:10px 12px;}

/* ---- settings / users / cards ---- */
.chx-pane{flex:1;min-height:0;overflow-y:auto;padding:18px;background:var(--page);}
.chx-card{max-width:640px;margin-bottom:12px;}
.chx-card-h{font-weight:700;font-size:13.5px;display:flex;align-items:center;gap:8px;margin-bottom:4px;}
.chx-card-sub{font-size:12px;color:var(--ink-muted);margin-bottom:10px;}
.chx-form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap;}
.chx-form-row input[type=text]{flex:1;min-width:160px;}
.chx-mgr-only{font-size:10px;color:var(--role-manager);font-weight:800;letter-spacing:.04em;}
.chx-locked-note{font-size:12px;color:var(--ink-muted);font-style:italic;margin-top:6px;}
.chx-invite-url{
  font-family:var(--mono);font-size:11.5px;color:var(--brand-500);word-break:break-all;
  background:var(--surface-2);border:1px dashed var(--border-2);
  border-radius:var(--radius-sm);padding:8px 10px;
}
.chx-invite-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:8px;}
.chx-mail-in{flex:1;min-width:150px;}
.chx-member-row{display:flex;align-items:center;gap:10px;padding:8px 2px;border-bottom:1px solid var(--border);}
.chx-member-row:last-child{border-bottom:none;}
.chx-member-nm{font-weight:600;font-size:13.5px;}
.chx-member-ctl{margin-inline-start:auto;display:flex;gap:6px;align-items:center;}
.chx-role-sel{padding:3px 7px;font-size:12px;}
.chx-picker{margin-top:10px;position:relative;}
.chx-picker input{width:100%;}
.chx-picker-results{
  position:absolute;top:100%;inset-inline:0;z-index:20;
  background:var(--surface);border:1px solid var(--border-2);border-radius:var(--radius-sm);
  box-shadow:0 8px 24px rgba(0,0,0,.3);
}
.chx-picker-row{
  display:flex;align-items:center;gap:8px;width:100%;padding:7px 10px;text-align:start;
  background:transparent;border:none;color:var(--ink);font-size:13px;cursor:pointer;
}
.chx-picker-row:hover{background:var(--surface-2);}

/* ---- org home / advanced search ---- */
.chx-home{flex:1;min-height:0;overflow-y:auto;padding:22px 24px;background:var(--page);}
.chx-home h2{margin:0 0 2px;font-size:19px;}
.chx-home-sub{color:var(--ink-muted);font-size:13px;margin-bottom:16px;}
.chx-search-row{display:flex;gap:8px;max-width:680px;}
.chx-search-in{flex:1;font-size:14px;padding:9px 13px;}
.chx-adv-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:10px;max-width:680px;}
.chx-adv-row .lbl{font-size:11.5px;color:var(--ink-muted);}
.chx-adv-row select,.chx-adv-row input{font-size:12px;padding:5px 8px;}
.chx-res-sec{margin-top:20px;max-width:680px;}
.chx-res-sec h5{
  margin:0 0 8px;font-size:11.5px;color:var(--ink-muted);
  text-transform:uppercase;letter-spacing:.06em;
}
.chx-res-ch{
  display:flex;align-items:center;gap:8px;padding:8px 12px;margin-bottom:6px;cursor:pointer;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);font-size:13px;
}
.chx-res-ch:hover{border-color:var(--brand-500);}
.chx-res-ch .hash{color:var(--ink-muted);}
.chx-res-post{
  padding:10px 12px;margin-bottom:6px;cursor:pointer;font-size:12.5px;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);
}
.chx-res-post:hover{border-color:var(--brand-500);}
.chx-res-meta{font-size:11px;color:var(--ink-muted);margin-bottom:2px;}
.chx-res-tags{margin-inline-start:auto;font-size:11px;color:var(--brand-500);}

@media (max-width:1000px){
  .chx-shell{flex-direction:column;height:auto;}
  .chx-strip{flex-direction:row;width:auto;border-inline-end:none;border-bottom:1px solid var(--border);padding:8px 12px;}
  .chx-rail{width:auto;max-height:280px;border-inline-end:none;border-bottom:1px solid var(--border);}
  .chx-main{min-height:480px;}
  .chx-thread.open{position:absolute;inset-inline-end:0;top:0;bottom:0;z-index:20;box-shadow:-8px 0 24px rgba(0,0,0,.3);}
}

/* What the treemap's area encodes. It looks like every market-cap heatmap, so
   when it is not one that has to be stated next to the legend. */
.heatmap-axis-note{
  font-size:11.5px;color:var(--ink-muted);margin-top:9px;
}

/* ============================== TREEMAP ==================================== */
/* The viewport clips and scrolls; the board inside it is laid out at
   width x zoom, so zooming enlarges real tiles rather than magnifying pixels. */
.tm-viewport{
  position:relative;width:100%;overflow:auto;border-radius:var(--radius-sm);
  overscroll-behavior:contain;   /* panning the map must not scroll the page */
  cursor:grab;
}
.tm-viewport.panning{cursor:grabbing;}
.tm-viewport::-webkit-scrollbar{width:9px;height:9px;}
.treemap{position:relative;overflow:hidden;}

/* Zoom controls, parked under the map rather than floating over tiles. */
.tm-zoom{display:flex;align-items:center;gap:6px;margin-top:8px;}
.tm-zoom button{
  width:28px;height:26px;border-radius:var(--radius-sm);
  border:1px solid var(--border-2);background:var(--surface-2);color:var(--ink);
  font-size:14px;font-weight:700;cursor:pointer;line-height:1;
}
.tm-zoom button.wide{width:auto;padding:0 10px;font-size:11.5px;font-weight:600;}
.tm-zoom button:hover{background:var(--border);}
.tm-zoom-label{
  font-size:11px;color:var(--ink-muted);min-width:40px;text-align:center;
  font-variant-numeric:tabular-nums;
}

/* Logo beside the ticker on tiles with room for both. */
.tm-head{display:flex;align-items:center;gap:5px;min-width:0;}
.tm-head .sym-logo{flex:none;}
.tm-name{
  font-size:9.5px;opacity:.82;margin-top:2px;line-height:1.2;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%;
}
.tm-tile{
  position:absolute;border-radius:3px;overflow:hidden;cursor:pointer;
  padding:5px 7px;display:flex;flex-direction:column;justify-content:center;
  transition:outline-color .1s;outline:1.5px solid transparent;
}
.tm-tile:hover{outline-color:var(--ink);z-index:2;}
.tm-sym{
  font-weight:700;color:#fff;line-height:1.15;letter-spacing:-.01em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.tm-pct{
  color:rgba(255,255,255,0.92);font-family:var(--mono);
  font-variant-numeric:tabular-nums;line-height:1.2;margin-top:1px;
}
.tm-legend{
  display:flex;align-items:center;gap:5px;justify-content:center;
  font-size:10.5px;color:var(--ink-muted);margin-top:10px;
}
.tm-legend .sw{width:26px;height:9px;border-radius:2px;}

.heatmap-stamp{
  display:flex;align-items:center;gap:10px;margin-inline-start:auto;
}
.stamp-label{
  font-size:9.5px;font-weight:800;letter-spacing:.07em;text-transform:uppercase;
  color:var(--ink-muted);
}
.stamp-value{font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;}

/* Provenance of the figures: saved snapshot vs. a live provider pull. */
.stamp-source{
  font-size:10.5px;color:var(--ink-muted);
  border:1px solid var(--border);border-radius:999px;padding:2px 8px;white-space:nowrap;
}
.stamp-source.stale{color:var(--warn);border-color:currentColor;}

/* "Update data" — the button plus the outcome line underneath it. */
.update-wrap{display:flex;flex-direction:column;align-items:flex-end;gap:3px;}
.update-status{font-size:10.5px;color:var(--ink-muted);max-width:280px;text-align:end;}
.update-status.good{color:var(--good);}
.update-status.bad{color:var(--bad);}

/* The stamp row carries more than a timestamp now, so let it wrap on narrow
   windows instead of pushing the page into a horizontal scroll. */
.heatmap-stamp{flex-wrap:wrap;justify-content:flex-end;row-gap:6px;}

.heatmap-stats{display:flex;gap:10px;flex-wrap:wrap;margin-top:12px;}

/* In the Home rail the four portfolio figures sit in a narrow column, where
   flex-wrap stacked them one per line into a tall strip. A 2x2 grid makes the
   block square and keeps all four readable at a glance. min-width:0 overrides
   the 130px floor, which is what forced the single column in the first place. */
.home-rail .heatmap-stats{display:grid;grid-template-columns:1fr 1fr;gap:8px;}

/* Generated summaries. Set apart from the user's own writing so the two are
   never mistaken for each other. */
.ai-body{font-size:13.5px;line-height:1.7;color:var(--ink-2);white-space:pre-wrap;}
.ai-foot{
  display:flex;align-items:center;gap:10px;justify-content:space-between;
  margin-top:10px;padding-top:9px;border-top:1px solid var(--border);
  font-size:10.5px;color:var(--ink-muted);line-height:1.5;
}
.ai-foot button{flex:none;}

.home-rail .heatmap-stat{min-width:0;padding:8px 10px;}
.heatmap-stat{
  background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius-sm);padding:9px 14px;min-width:130px;
}
.heatmap-stat .l{
  font-size:9.5px;font-weight:800;letter-spacing:.07em;text-transform:uppercase;
  color:var(--ink-muted);
}
.heatmap-stat .v{font-size:15px;font-weight:700;margin-top:2px;font-variant-numeric:tabular-nums;}

/* ============================ HOME LAYOUT ================================== */
/* The row starts at Market Summary, so the rail's first item lines up with
   that section's heading rather than sitting beside the hero. */
.home-layout{display:flex;gap:16px;align-items:flex-start;margin-top:22px;}
.home-rail{
  width:300px;flex:none;display:flex;flex-direction:column;
  position:sticky;top:calc(var(--topbar-h) + 18px);
  max-height:calc(100vh - var(--topbar-h) - 36px);
  /* The rail now stacks several sections; when they exceed the viewport cap
     it must scroll INSIDE itself — without this the overflow painted over
     the sections below and the columns visually collided. */
  overflow-y:auto;
}
/* First section inside the column loses its top margin so both columns
   share one baseline. */
.home-main > .sec:first-child{margin-top:0;}
.home-rail .card{overflow-y:auto;}
.home-main{flex:1;min-width:0;}
@media (max-width:1000px){
  .home-layout{flex-direction:column;}
  .home-rail{width:auto;position:static;max-height:none;}
}

/* ============================ EMPTY STATES ================================= */
.empty-panel{
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:7px;text-align:center;padding:26px 20px;min-height:120px;
  border:1px dashed var(--border-2);border-radius:var(--radius);
  background:var(--surface-2);
}
.empty-panel.loading{
  flex-direction:row;gap:9px;border-style:solid;
  color:var(--ink-muted);font-size:12.5px;min-height:80px;
}
.empty-panel.loading .sp{
  width:14px;height:14px;border:2px solid var(--border-2);border-top-color:var(--brand-500);
  border-radius:50%;animation:spin .7s linear infinite;flex:none;
}
.empty-icon{font-size:20px;color:var(--ink-muted);opacity:.75;line-height:1;}
.empty-title{font-size:13px;font-weight:700;color:var(--ink-2);}
.empty-detail{
  font-size:11.5px;color:var(--ink-muted);line-height:1.6;max-width:420px;
}
.empty-action{margin-top:4px;}
.empty-panel.error{border-color:rgba(239,95,95,0.4);}
.empty-panel.error .empty-icon{color:var(--bad);}
.empty-panel.plan .empty-icon{color:var(--warn);}
.empty-panel.quota{border-color:rgba(234,179,8,0.45);background:var(--warn-bg);}
.empty-panel.quota .empty-icon{color:var(--warn);}
.empty-panel.quota .empty-title{color:var(--warn);}

.empty-inline{
  font-size:11.5px;color:var(--ink-muted);padding:14px 12px;text-align:center;
}

/* ============================== ADMIN ====================================== */
.admin-bar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:12px;}
input.admin-search{width:250px;font-size:12.5px;padding:6px 10px;}
select.ctl-select.mini{padding:4px 22px 4px 8px;font-size:11.5px;font-weight:600;}

.admin-notice{
  display:flex;align-items:center;gap:8px;
  padding:8px 12px;border-radius:var(--radius-sm);margin-bottom:12px;
  font-size:12px;font-weight:600;
}
.admin-notice.ok{background:var(--good-bg);color:var(--good);}
.admin-notice.warn{background:var(--warn-bg);color:var(--warn);}

.admin-table{width:100%;border-collapse:collapse;font-size:12.5px;}
.admin-table th{
  text-align:start;font-size:10px;text-transform:uppercase;letter-spacing:.06em;
  color:var(--ink-muted);font-weight:800;padding:8px 10px;
  border-bottom:1px solid var(--border);white-space:nowrap;
}
.admin-table th.num, .admin-table td.num{text-align:end;}
.admin-table td{padding:7px 10px;border-bottom:1px solid var(--border);vertical-align:middle;}
.admin-table tbody tr:last-child td{border-bottom:none;}
.admin-table tbody tr:hover td{background:var(--surface-2);}
.admin-table td.muted{color:var(--ink-muted);}

.admin-user{display:flex;align-items:center;gap:9px;}
.admin-avatar{
  width:26px;height:26px;border-radius:6px;flex:none;color:#fff;
  display:flex;align-items:center;justify-content:center;
  font-size:10px;font-weight:700;
}
.admin-name{font-weight:600;}
.admin-count{font-size:11px;color:var(--ink-muted);margin-top:8px;}

.btn.ghost.danger{color:var(--bad);}
.btn.ghost.danger:hover{background:var(--bad-bg);}
.btn.danger-btn{background:var(--bad);border-color:var(--bad);}
.btn.danger-btn:hover{background:var(--bad);filter:brightness(1.1);}

.admin-delete-target{
  display:flex;align-items:center;gap:10px;margin-top:10px;
  padding:10px 12px;background:var(--surface-2);
  border:1px solid var(--border);border-radius:var(--radius-sm);
}

/* ============================== MODAL ====================================== */
.modal-overlay{
  position:fixed;inset:0;z-index:200;
  background:rgba(4,8,14,0.62);
  display:flex;align-items:center;justify-content:center;padding:20px;
}
.modal-box{
  background:var(--surface);border:1px solid var(--border-2);
  border-radius:var(--radius);width:100%;max-height:86vh;
  display:flex;flex-direction:column;overflow:hidden;
  box-shadow:0 20px 60px rgba(0,0,0,.45);
}
.modal-head{
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:13px 16px;border-bottom:1px solid var(--border);
}
.modal-title{font-weight:700;font-size:14px;}
.modal-x{
  background:transparent;border:none;color:var(--ink-muted);
  cursor:pointer;font-size:14px;line-height:1;padding:4px 6px;border-radius:5px;
}
.modal-x:hover{background:var(--surface-2);color:var(--ink);}
.modal-body{padding:16px;overflow-y:auto;display:flex;flex-direction:column;gap:12px;}
.modal-field{display:flex;flex-direction:column;gap:4px;}
.modal-field-label{
  font-size:9.5px;font-weight:800;letter-spacing:.07em;text-transform:uppercase;
  color:var(--ink-muted);
}
.modal-field input, .modal-field select{width:100%;font-size:13px;}
.modal-field-hint{font-size:11px;color:var(--ink-muted);}
.modal-error{
  background:var(--bad-bg);color:var(--bad);
  padding:8px 11px;border-radius:var(--radius-sm);font-size:12px;font-weight:600;
}
.modal-actions{
  display:flex;justify-content:flex-end;gap:8px;
  padding-top:12px;border-top:1px solid var(--border);margin-top:2px;
}

/* ============================== TOOLTIP ==================================== */
.info-tip{
  display:inline-flex;align-items:center;justify-content:center;
  width:14px;height:14px;border-radius:50%;flex:none;
  background:var(--surface-3);border:1px solid var(--border-2);
  color:var(--ink-muted);font-size:9px;font-weight:800;font-style:italic;
  cursor:help;margin-inline-start:5px;vertical-align:middle;
}
.info-tip:hover, .info-tip:focus{
  background:var(--brand-500);border-color:var(--brand-500);color:#fff;
}
.tip-bubble{
  position:fixed;z-index:300;max-width:280px;
  background:var(--ink);color:var(--page);
  padding:9px 11px;border-radius:var(--radius-sm);
  font-size:11.5px;line-height:1.55;
  box-shadow:0 8px 24px rgba(0,0,0,.4);pointer-events:none;
}
.tip-title{font-weight:800;margin-bottom:3px;font-size:11px;}
/* pre-line, so a tip written as several lines arrives as several lines.
   Some of these texts are a sentence followed by bullets (the Creator Hub
   upgrade explainer is four), and they used to rely on the browser's native
   `title` tooltip, which honours newlines. Moving them onto this bubble
   without this would have run them together into one paragraph. Runs of
   spaces still collapse, so indentation in the source does not leak out. */
.tip-text{opacity:.92;white-space:pre-line;}

/* Settings */
.settings-group-title{
  font-size:10.5px;font-weight:800;letter-spacing:.07em;text-transform:uppercase;
  color:var(--ink-muted);margin-bottom:7px;
}
.settings-row{
  display:flex;align-items:center;gap:16px;flex-wrap:wrap;
  padding:14px 16px;border-bottom:1px solid var(--border);
}
.settings-row:last-child{border-bottom:none;}
.settings-row-text{flex:1;min-width:170px;}
.settings-row-text .t{font-size:13px;font-weight:600;}
.settings-row-text .d{font-size:11.5px;color:var(--ink-muted);margin-top:2px;}

/* Data-source table on Settings. Padded to sit flush with .settings-row above
   it, and scrollable on its own so a narrow window doesn't scroll the page. */
.source-table-wrap{padding:4px 16px 14px;overflow-x:auto;}
.source-table{min-width:420px;}
.source-table td:first-child{width:34%;}
.source-table .src-never{color:var(--ink-muted);font-size:12px;}
.source-table .mono{font-family:var(--mono);font-size:12px;color:var(--ink-muted);}
.source-table .src-note{font-size:11px;color:var(--ink-muted);margin-top:2px;}
.source-table tr:hover td{background:transparent;}

.theme-picker{display:flex;gap:10px;flex-wrap:wrap;}
/* Compact cards (Guy asked for half the original height): slim swatch strip
   with the label tucked right under it. */
.theme-option{
  border:1px solid var(--border-2);border-radius:var(--radius-sm);
  padding:4px 8px 5px;cursor:pointer;width:112px;background:var(--surface-2);
  transition:border-color .12s;
}
.theme-option:hover{border-color:var(--brand-300);}
.theme-option.active{border-color:var(--brand-500);background:var(--brand-50);}
.theme-swatch{display:flex;height:12px;border-radius:4px;overflow:hidden;border:1px solid var(--border);}
.theme-swatch span{flex:1;}
.theme-label{
  display:flex;align-items:center;gap:5px;
  font-size:11px;font-weight:600;margin-top:3px;color:var(--ink-2);
}
.theme-option.active .theme-label{color:var(--brand-700);}
.dot-check{font-size:10px;}

/* Ask / answer page */
.answer-q{font-size:20px;font-weight:800;margin:0 0 4px;line-height:1.35;}
.answer-meta{font-size:11.5px;color:var(--ink-muted);margin-bottom:16px;display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.answer-body{font-size:14px;line-height:1.85;color:var(--ink-2);}
.answer-body p{margin:0 0 12px;}
.answer-body strong{color:var(--ink);}
.answer-kv{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:10px;margin:16px 0;}
.kv{background:var(--surface-2);border:1px solid var(--border);border-radius:var(--radius-sm);padding:10px 12px;}
.kv .l{font-size:10px;color:var(--ink-muted);text-transform:uppercase;letter-spacing:.05em;font-weight:800;}
.kv .v{font-size:16px;font-weight:800;font-family:var(--mono);margin-top:2px;}
.thinking{display:flex;align-items:center;gap:9px;color:var(--ink-muted);font-size:13px;padding:20px 0;}
.thinking .sp{
  width:14px;height:14px;border:2px solid var(--border-2);border-top-color:var(--brand-500);
  border-radius:50%;animation:spin .7s linear infinite;
}
@keyframes spin{to{transform:rotate(360deg);}}


/* ===================== THE PHONE SHELL ====================================
   Bottom tab bar + drawer, from mockups/mobile-shell.html.

   The three pieces below (.burger, .nav-scrim, .tabbar) are built on every
   screen by js/components/mobile-shell.js and hidden here above the phone
   breakpoint. Hidden rather than conditionally built: a resize from desktop to
   phone width then needs no JavaScript at all, and there is no window-resize
   listener to leak or to fire during a drag. */
.burger, .nav-scrim, .tabbar{ display:none; }

/* Signed out, at any width. main.js sets this when it shows the login card.
   Written OUTSIDE the phone breakpoint and with !important because the rule
   inside it turns .tabbar back on, and this has to win at every width without
   depending on which block a future edit happens to sit in. */
body.signed-out .burger,
body.signed-out .nav-scrim,
body.signed-out .tabbar{ display:none !important; }

@media (max-width:760px){
  /* THE APP SHELL IS THE SCROLLER ON A PHONE, and this is what actually keeps
     the bottom bar still.
     ------------------------------------------------------------------------
     `position:fixed; bottom:0` pins an element to the LAYOUT viewport. On iOS
     Safari and Android Chrome the layout viewport is NOT what you can see
     while a page is being scrolled: the address bar collapses and expands, and
     the browser only reconciles the two once the gesture settles. So the bar
     drifts, or slides under the browser's own chrome, precisely during the
     scroll where it is most conspicuous. Nothing about the bar is wrong, which
     is why three rounds of adjusting the bar did not fix it.

     When the DOCUMENT never scrolls, the address bar never collapses, the
     layout viewport never changes, and the bar cannot move. #app is exactly
     one viewport tall, and main.content scrolls inside it.

     The cost, paid in js/scroller.js: window.scrollTo and window.scrollY stop
     meaning anything here, and four places called them. Anything added later
     that wants the scroll position must ask that module, not the window. */
  html, body{ height:100%; overflow:hidden; }
  #app{
    height:100vh;      /* fallback first */
    height:100dvh;
    overflow:hidden;
  }
  main.content{
    flex:1; min-height:0;
    overflow-y:auto;
    -webkit-overflow-scrolling:touch;
    /* Stops a scroll that reaches the end of the page from becoming a
       pull-to-refresh or a rubber-band of the whole document behind it —
       which would move the bar again, by a different route. */
    overscroll-behavior:contain;
  }

  .sidenav{position:fixed;inset-inline-start:-220px;top:0;bottom:0;transition:inset-inline-start .2s;z-index:80;}
  .sidenav.open{inset-inline-start:0;}
  main.content{padding:14px;}
  .search-wrap{max-width:none;}

  .burger{ display:flex; flex:none; }

  /* Behind the drawer, above everything else. Kept in the DOM permanently and
     switched by opacity rather than display, so it can fade; pointer-events
     is what actually makes it inert when closed — an invisible full-screen
     div that still swallowed clicks would break the whole app. */
  .nav-scrim{
    display:block; position:fixed; inset:0; z-index:70;
    background:rgba(0,0,0,.5);
    opacity:0; pointer-events:none; transition:opacity .2s;
  }
  body.drawer-open .nav-scrim{ opacity:1; pointer-events:auto; }
  /* The drawer must not scroll the page behind it. */
  body.drawer-open{ overflow:hidden; }

  .tabbar{
    display:grid; grid-template-columns:repeat(5,1fr);
    position:fixed; inset-inline:0; bottom:0; z-index:75;
    background:var(--surface); border-top:1px solid var(--border);
    /* Clears the iPhone home indicator. Falls back to 0 everywhere else. */
    padding-bottom:env(safe-area-inset-bottom, 0px);
  }
  /* The bar must never be see-through. .tabbar sits over whatever scrolls
     beneath it, and --surface is a solid colour in both themes today — but if
     it ever gains alpha, text would scroll visibly through the bar. Stating
     the opacity here makes that a deliberate change rather than an accident.

     `contain:layout paint` also keeps the bar off the main scroll's paint
     path: it is fixed and repaints on every frame of a scroll otherwise. */
  .tabbar{ opacity:1; contain:layout paint; }
  .tabbar-btn{
    display:flex; flex-direction:column; align-items:center; justify-content:center;
    gap:2px; min-height:54px; padding:6px 2px;
    border:none; background:transparent; color:var(--ink-2);
    font-family:inherit; cursor:pointer;
  }
  .tabbar-ic{ font-size:17px; line-height:1; }
  .tabbar-lb{ font-size:9.5px; font-weight:700; letter-spacing:.01em; }
  .tabbar-btn:active{ background:var(--surface-2); }
  .tabbar-btn:focus-visible{ outline:2px solid var(--brand-500); outline-offset:-2px; }
  /* The current tab is marked by colour AND weight, never colour alone. */
  .tabbar-btn.active{ color:var(--brand-700); }
  .tabbar-btn.active .tabbar-lb{ font-weight:800; }

  /* Room for the bar, or it sits on top of the last thing on every page.
     The bar plus the safe area plus a little air. */
  main.content{
    padding-bottom:calc(var(--tabbar-h) + 16px + env(safe-area-inset-bottom, 0px));
  }
  /* SAME clearance, restated at .chx-page's specificity.
     ------------------------------------------------------------------------
     `main.content.chx-page` (0,2,1) sets `padding:0` and outranks
     `main.content` (0,1,1) no matter which comes later or which media query
     it lives in — so Channels, one of the five tabs on the bar, was the one
     page where the rule above did nothing and the bar covered the bottom of
     the thread. Reported by Guy, 2026-08-10.
     Only the bottom is restored; the zero on the other three sides is what
     lets the channel shell run edge to edge, and is deliberate. */
  main.content.chx-page{
    padding-bottom:calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px));
  }
  /* Fixed panels that run to bottom:0 stop above the bar instead of under it.
     .fd-thread is z-index 60 against the bar's 75, so without this the bar
     draws straight over its composer — you could see the reply box but not
     reach it. */
  .fd-thread{
    bottom:calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px));
  }
  /* NOT capping .dm-shell / .chx-shell here, deliberately. Both already drop
     to `height:auto` at their own breakpoints and both carry
     `overflow:hidden`, so a max-height would clip the bottom of a conversation
     with no way to scroll to it — a worse bug than the one being fixed. They
     grow, the page scrolls, and main.content's padding-bottom above is what
     keeps the last message clear of the bar. */
}

/* ============================== LOGIN ====================================== */
.login-shell{
  min-height:100vh;display:flex;align-items:center;justify-content:center;
  padding:24px;background:var(--bg);
}
.login-card{
  width:100%;max-width:380px;background:var(--surface);
  border:1px solid var(--border);border-radius:var(--radius);
  padding:30px 28px;box-shadow:0 18px 50px rgba(0,0,0,0.22);
}
.login-brand{display:flex;align-items:center;gap:9px;justify-content:center;}
.login-word{font-size:19px;font-weight:800;letter-spacing:-0.02em;}
.login-sub{
  text-align:center;color:var(--ink-muted);font-size:12.5px;
  margin:6px 0 22px;
}
.login-field{display:flex;flex-direction:column;gap:5px;margin-bottom:14px;}
.login-field > span{
  font-size:10.5px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;
  color:var(--ink-muted);
}
.login-field input{
  background:var(--surface-2);color:var(--ink);
  border:1px solid var(--border-2);border-radius:var(--radius-sm);
  padding:10px 12px;font-size:13.5px;font-family:inherit;width:100%;
}
.login-field input:focus{border-color:var(--brand-500);}
.login-submit{width:100%;margin-top:6px;padding:11px;font-size:13.5px;}
.login-error{
  background:var(--bad-bg);color:var(--bad);
  border-radius:var(--radius-sm);padding:9px 11px;font-size:12px;margin-bottom:12px;
}
/* Registration additions: the sign-up view shares the card; these are the
   pieces sign-in never needed. */
.login-two{display:grid;grid-template-columns:1fr 1fr;gap:10px;}
.login-hint{font-size:10.5px;color:var(--ink-muted);line-height:1.45;}
/* The live password requirements. Two columns, because five stacked rows push
   the Create account button below the fold on a laptop — and a rule you have
   to scroll to is a rule you find out about from the error message. */
.pw-req{
  list-style:none;margin:-6px 0 14px;padding:0;
  display:grid;grid-template-columns:1fr 1fr;gap:2px 10px;
  font-size:10.5px;line-height:1.5;color:var(--ink-muted);
}
.pw-req li{display:flex;align-items:center;gap:5px;}
/* Fixed-width marker so the labels stay on a common left edge as ○ becomes ✓
   — otherwise every tick nudges its own row and the list appears to twitch as
   you type, which reads as a glitch rather than as progress. */
.pw-req-m{width:9px;flex:none;text-align:center;font-size:9.5px;}
/* Colour is the reward, but it is never the only signal: the marker itself
   changes shape, so this reads the same to anyone who cannot separate the two
   hues. */
.pw-req li.ok{color:var(--good);}
@media (max-width:420px){ .pw-req{grid-template-columns:1fr;} }
.login-ok{
  background:var(--good-bg);color:var(--good);
  border-radius:var(--radius-sm);padding:9px 11px;font-size:12px;margin-bottom:12px;
}
.login-alt{
  margin-top:16px;text-align:center;font-size:12px;color:var(--ink-muted);
}
.login-alt a{color:var(--brand-700);font-weight:700;text-decoration:none;}
.login-alt a:hover{text-decoration:underline;}
/* The support address, last thing in the card on every login view. Separated
   by a rule rather than by whitespace so it reads as a footer and not as one
   more instruction — it is only for the person the form has failed, and it
   should be findable without being loud. Deliberately quieter than
   .login-alt: that link is the path most people want, this one is the exit. */
.login-support{
  margin:14px 0 0;padding-top:12px;border-top:1px solid var(--border);
  text-align:center;font-size:11px;line-height:1.5;color:var(--ink-muted);
}
.login-support a{color:var(--brand-700);font-weight:700;text-decoration:none;}
.login-support a:hover{text-decoration:underline;}
.login-check{text-align:center;font-size:13px;line-height:1.6;margin-bottom:14px;}
.login-check p{margin:0 0 8px;}
.login-check-ic{
  width:52px;height:52px;margin:2px auto 12px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;font-size:22px;
  background:var(--brand-100);color:var(--brand-700);
}

/* A standing statement of fact under a page title — currently the one saying
   the portfolio is virtual. Quieter than a warning banner because it is not a
   problem to fix, but permanent because it never stops being true. */
.page-note{
  display:flex;gap:9px;align-items:flex-start;
  background:var(--surface-2);border:1px solid var(--border);
  border-inline-start:3px solid var(--brand-500);
  border-radius:var(--radius-sm);padding:9px 12px;margin-bottom:14px;
  font-size:12.5px;color:var(--ink-muted);line-height:1.55;
}
.page-note .ic{color:var(--brand-500);font-size:13px;line-height:1.5;}
.page-note b{color:var(--ink);}

/* Order-type explanation inside the Add command modal */
.order-explain{
  background:var(--surface-2);border:1px solid var(--border);
  border-radius:var(--radius-sm);padding:9px 11px;margin-bottom:14px;
}
.order-explain-title{font-size:12px;font-weight:700;margin-bottom:3px;}
.order-explain-text{font-size:11.5px;color:var(--ink-muted);line-height:1.5;}

/* Sector ETF cell — a link to that ETF's chart, not dead text. */
.etf-link{
  cursor:pointer;font-weight:600;color:var(--brand-700);
  border-bottom:1px dashed currentColor;
}
.etf-link:hover{opacity:.75;}

/* Company logo beside a ticker. Initials show until the image decodes. */
.sym-logo{
  display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;
  border-radius:5px;background:var(--surface-2);color:var(--ink-muted);
  font-weight:700;letter-spacing:-.02em;overflow:hidden;
  margin-inline-end:8px;vertical-align:middle;
}
.sym-logo.has-img{background:#fff;}
.sym-logo img{width:100%;height:100%;object-fit:contain;}

/* ------------------------------------------------------- multi-select ----
   Checkbox dropdown used by the Settings data-source table.

   The panel is position:fixed, not absolute, and js/components/multi-select.js
   sets its coordinates on open. That is deliberate: the control sits inside
   .source-table-wrap, which is an overflow:auto scroll container, and an
   absolutely positioned popover would be clipped at the cell edge. */
.ms{position:relative;display:inline-block;max-width:100%;}
.ms-btn{
  display:inline-flex;align-items:center;gap:6px;justify-content:space-between;
  min-width:150px;max-width:100%;padding:5px 9px;
  background:var(--surface-2);border:1px solid var(--border-2);border-radius:var(--radius-sm);
  color:var(--ink);font-size:12px;font-weight:600;font-family:inherit;cursor:pointer;
}
.ms-btn:hover{border-color:var(--brand-500);}
.ms-btn-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.ms-caret{font-size:10px;color:var(--ink-muted);flex:none;}
.ms.open .ms-btn{border-color:var(--brand-500);}

.ms-panel{
  display:none;position:fixed;z-index:80;
  background:var(--surface);border:1px solid var(--border-2);
  border-radius:var(--radius-sm);box-shadow:0 10px 30px rgba(0,0,0,.28);
  padding:4px;max-height:320px;overflow-y:auto;
}
.ms.open .ms-panel{display:block;}

.ms-opt{
  display:flex;align-items:flex-start;gap:8px;padding:7px 8px;
  border-radius:6px;cursor:pointer;font-size:12.5px;
}
.ms-opt:hover{background:var(--surface-2);}
.ms-opt.disabled{opacity:.5;cursor:not-allowed;}
.ms-opt.disabled:hover{background:transparent;}
.ms-check{
  flex:none;width:15px;height:15px;margin-top:1px;border-radius:4px;
  border:1px solid var(--border-2);display:flex;align-items:center;justify-content:center;
  font-size:10px;color:#fff;
}
.ms-opt.on .ms-check{background:var(--brand-500);border-color:var(--brand-500);}
.ms-opt-text{display:flex;flex-direction:column;gap:2px;min-width:0;}
.ms-opt-label{font-weight:600;color:var(--ink);}
.ms-opt-hint{font-size:11px;color:var(--ink-muted);line-height:1.35;}
.ms-opt-badge{
  flex:none;margin-inline-start:auto;font-size:10px;font-weight:700;
  color:var(--ink-muted);white-space:nowrap;
}

/* ------------------------------------------------- grouped source table ----
   One header row per data type carrying the picker, then one indented row per
   chosen provider with its own last-pull time and status. */
.source-table .src-group td{border-top:1px solid var(--border-2);padding-top:12px;}
.source-table .src-type{font-weight:600;color:var(--ink);}
.source-table .src-child td{padding-top:4px;padding-bottom:4px;border-top:none;}
.source-table .src-child-type{
  color:var(--ink-muted);font-size:12px;padding-inline-start:16px;
  border-inline-start:2px solid var(--border);
}
.source-table .src-rank{
  display:inline-block;min-width:62px;font-size:10px;font-weight:700;
  text-transform:uppercase;letter-spacing:.04em;color:var(--ink-muted);
}
.source-table .src-fail{font-size:11px;color:var(--bad);margin-top:2px;}

/* ------------------------------------------------------------ logs tab ----- */
.log-bar{
  display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;
  gap:10px;padding:0 16px 12px;
}
.log-filters{display:inline-flex;flex-wrap:wrap;border:1px solid var(--border);
  border-radius:var(--radius-sm);overflow:hidden;}
.log-actions{display:flex;gap:6px;}

.log-table{min-width:640px;width:100%;}
.log-table td{vertical-align:top;font-size:12.5px;}
.log-table .log-day td{
  background:var(--surface-2);font-size:11px;font-weight:700;
  text-transform:uppercase;letter-spacing:.05em;color:var(--ink-muted);
  padding:6px 10px;
}
.log-row td{padding-top:6px;padding-bottom:6px;}
/* A left edge coloured by severity: scanning three days of rows for the two
   that went wrong should not require reading any of them. */
.log-row.lvl-warn td:first-child{box-shadow:inset 3px 0 0 var(--brand-500);}
.log-row.lvl-error td:first-child{box-shadow:inset 3px 0 0 var(--bad);}
.log-origin{
  display:inline-block;margin-top:3px;font-size:10px;font-weight:700;
  color:var(--ink-muted);border:1px solid var(--border);border-radius:3px;
  padding:0 4px;
}

/* Method line under the Sectors summary strip. The "vs S&P 500" column is only
   interpretable if you know what the benchmark was measured through, so the
   answer sits next to it rather than in a tooltip. */
.sector-bench-note{
  font-size:11.5px;color:var(--ink-muted);margin:-4px 0 12px;
}

/* ------------------------------------------------- chart indicator row ----
   A legend under the chart toolbar naming what is drawn on the canvas. The
   dropdown that selects indicators is closed almost all of the time, and once
   it is, an unlabelled blue line and orange line are just two lines. */
.chart-ind-badges{
  display:flex;flex-wrap:wrap;align-items:center;gap:6px;
  padding:6px 12px;min-height:24px;
  border-bottom:1px solid var(--border);background:var(--surface);
}
/* Square symbols by request — the code (initials + parameter numbers) sits
   inside a squared chip, not a pill. */
/* ---------------------------------------------------------------- Messages
   Direct conversations: roster left, thread right (the approved design —
   mockups/messages-a-inbox.html, where these carried the ma- prefix). */
.dm-shell{
  display:grid;grid-template-columns:300px 1fr;
  height:calc(100vh - 250px);min-height:460px;
  border:1px solid var(--border);border-radius:12px;overflow:hidden;
  background:var(--surface);
}
.dm-rail{display:flex;flex-direction:column;border-inline-end:1px solid var(--border);min-width:0;}
.dm-rail-head{padding:12px 12px 8px;border-bottom:1px solid var(--border);}
.dm-rail-title{display:flex;align-items:center;gap:8px;margin-bottom:9px;}
.dm-rail-title b{font-size:14px;}
.dm-new{
  margin-inline-start:auto;border:1px solid var(--border-2);background:var(--surface-2);
  color:var(--ink-2);border-radius:6px;padding:3px 9px;font-size:11.5px;font-weight:600;cursor:pointer;
}
.dm-new:hover{background:var(--surface-3);color:var(--ink);}
.dm-search{width:100%;font-size:12.5px;}
.dm-filters{display:flex;gap:5px;padding:9px 12px;border-bottom:1px solid var(--border);flex-wrap:wrap;}
.dm-filter{
  border:1px solid var(--border-2);background:var(--surface-2);color:var(--ink-2);
  border-radius:14px;padding:3px 11px;font-size:11px;font-weight:600;cursor:pointer;
}
.dm-filter:hover{background:var(--surface-3);color:var(--ink);}
.dm-filter.on{background:var(--brand-500);border-color:var(--brand-500);color:#fff;}
.dm-list{overflow-y:auto;flex:1;}

.dm-row{
  display:flex;gap:10px;align-items:flex-start;padding:11px 12px;cursor:pointer;
  border-bottom:1px solid var(--border);border-inline-start:2px solid transparent;
}
.dm-row:hover{background:var(--surface-2);}
.dm-row.on{background:var(--surface-2);border-inline-start-color:var(--brand-500);}
.dm-av{
  width:34px;height:34px;border-radius:8px;flex:none;
  display:flex;align-items:center;justify-content:center;
  font-size:12.5px;font-weight:800;color:#fff;
}
.dm-av.sm{width:30px;height:30px;font-size:11.5px;}
.dm-row-mid{flex:1;min-width:0;}
.dm-row-top{display:flex;align-items:baseline;gap:6px;}
.dm-row-nm{font-size:12.5px;font-weight:700;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.dm-row-tm{margin-inline-start:auto;font-size:10.5px;color:var(--ink-muted);flex:none;}
.dm-row-pv{
  font-size:11.5px;color:var(--ink-muted);margin-top:2px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.dm-row.unread .dm-row-pv{color:var(--ink-2);font-weight:600;}
.dm-pill{
  flex:none;min-width:18px;height:18px;border-radius:9px;background:var(--brand-500);
  color:#fff;font-size:10.5px;font-weight:800;display:flex;align-items:center;
  justify-content:center;padding:0 5px;align-self:center;
}

.dm-thread{display:flex;flex-direction:column;min-width:0;background:var(--page);}
.dm-th-head{
  display:flex;align-items:center;gap:10px;padding:11px 16px;
  border-bottom:1px solid var(--border);background:var(--surface);
}
.dm-th-nm{font-size:13.5px;font-weight:700;}
.dm-th-sub{font-size:11.5px;color:var(--ink-muted);}
.dm-th-actions{margin-inline-start:auto;display:flex;gap:6px;}
.dm-scroll{flex:1;overflow-y:auto;padding:16px;}

.dm-day{display:flex;align-items:center;gap:10px;margin:14px 0 12px;}
.dm-day::before,.dm-day::after{content:"";flex:1;height:1px;background:var(--border);}
.dm-day span{font-size:10.5px;color:var(--ink-muted);font-weight:600;letter-spacing:.4px;text-transform:uppercase;}

.dm-grp{display:flex;gap:10px;margin-bottom:14px;}
.dm-grp-body{flex:1;min-width:0;}
.dm-grp-head{display:flex;align-items:baseline;gap:7px;margin-bottom:3px;}
.dm-grp-nm{font-size:12.5px;font-weight:700;}
.dm-grp-tm{font-size:10.5px;color:var(--ink-muted);}
.dm-msg{font-size:13px;line-height:1.6;color:var(--ink);padding:2px 0;max-width:min(620px,100%);}

.dm-comp{border-top:1px solid var(--border);background:var(--surface);padding:10px 16px 12px;}
.dm-ta{width:100%;min-height:48px;resize:vertical;font-size:13px;line-height:1.5;font-family:inherit;}
.dm-comp-row{display:flex;align-items:center;gap:6px;margin-top:7px;}
.dm-hint{font-size:11px;color:var(--ink-muted);}
.dm-send{margin-inline-start:auto;}

/* The unread count beside the Messages entry in the side nav. */
.nav-badge{
  margin-inline-start:auto;min-width:17px;height:17px;border-radius:9px;
  background:var(--brand-500);color:#fff;font-size:10px;font-weight:800;
  display:flex;align-items:center;justify-content:center;padding:0 5px;flex:none;
}
.sidenav.collapsed .nav-badge{display:none;}

@media (max-width: 900px){
  .dm-shell{grid-template-columns:1fr;height:auto;}
  .dm-rail{border-inline-end:none;border-bottom:1px solid var(--border);max-height:280px;}
  .dm-scroll{min-height:320px;}
}

/* The "you're signed in as someone else" banner on social pages. */
.auth-mismatch-note{
  background:color-mix(in srgb, var(--warn) 12%, var(--surface));
  border:1px solid color-mix(in srgb, var(--warn) 45%, var(--border-2));
  border-radius:10px;padding:10px 14px;margin-bottom:14px;
  font-size:12.5px;line-height:1.55;color:var(--ink);
}
.auth-mismatch-note b{display:block;margin-bottom:2px;}

/* The heatmap's board-size toggle, right-aligned under Update data. */
.heatmap-count-row{display:flex;gap:6px;margin-top:6px;justify-content:flex-end;}

/* Event markers over the price pane: D = dividend ex-date (green circle),
   E = earnings report (yellow square). The two upcoming dates are not glyphs
   at all -- they are named boxes pinned to the right edge (see below).
   The layer never grabs the pointer — only the badges do, for their
   tooltips — so chart drag/zoom keeps working everywhere else. */
.chart-evt-layer{position:absolute;inset:0;pointer-events:none;z-index:4;}
.chart-evt{
  position:absolute;width:14px;height:14px;line-height:14px;
  pointer-events:auto;cursor:help;text-align:center;
  font-size:9px;font-weight:800;font-family:var(--mono);color:#fff;
  user-select:none;
}
.chart-evt.div{background:#16a34a;border-radius:50%;}
.chart-evt.earn{background:#eda100;border-radius:3px;color:#3a2c00;}

/* Outcome colours. Earnings: beat / miss / roughly in line. Dividends: raised
   / cut / held. The badge answers "how did it go?" at a glance instead of only
   marking that something happened -- amber stays the neutral case so a quarter
   that merely landed on the estimate is not painted as a win. */
.chart-evt.earn.beat{background:#22c55e;color:#04260f;}
.chart-evt.earn.miss{background:#ef4444;color:#2b0505;}
.chart-evt.earn.inline{background:#facc15;color:#3a2c00;}
.chart-evt.div.raised{background:#22c55e;}
.chart-evt.div.cut{background:#ef4444;}
.chart-evt.div.held{background:#64748b;}

/* The two upcoming dates, pinned to the right edge. As bare 14px glyphs these
   were the least visible marks on a chart full of history, and they are the
   only ones a holder plans around -- so they spell the word out, carry their
   date, and read without hovering. Rectangular (3px, same as the historical
   earnings square) rather than a rounded pill, so they read as chart
   annotations and not as buttons somebody is meant to click. */
/* Doubled class on purpose: `.chart-evt.div` above is two classes and would
   otherwise win the radius, rounding this box into an ellipse. */
.chart-evt.chart-evt-next{
  width:auto;height:auto;line-height:1.2;padding:4px 9px;border-radius:3px;
  display:inline-flex;align-items:center;gap:6px;font-size:10.5px;
  box-shadow:0 2px 8px rgba(0,0,0,.25);white-space:nowrap;
}
/* The word is the label, so it carries the weight; the date sits beside it. */
.chart-evt-next b{font-size:10.5px;font-weight:800;letter-spacing:.02em;opacity:1;}
.chart-evt-next .nx{font-weight:700;letter-spacing:.01em;}
.chart-evt-next.earn.future{
  background:var(--surface);border:1.5px dashed #eda100;color:#eda100;
}
.chart-evt-next.div.future{
  background:var(--surface);border:1.5px dashed #16a34a;color:#16a34a;
}
/* The same upcoming-earnings pill, placed inline inside the Overview's
   Earnings-timeline card. Reuses .chart-evt-next.earn.future verbatim so the
   chart and the card cannot drift apart in colour or shape; the only override
   is positioning, because inside a card it flows with the content instead of
   floating over a canvas. cursor:default because there is no tooltip here. */
.earn-next-inline{
  position:static;transform:none;margin-bottom:10px;cursor:default;
}

/* The shared marker tooltip — instant on hover, positioned by JS. */
.chart-evt-tip{
  position:absolute;display:none;z-index:6;pointer-events:none;
  background:var(--surface);border:1px solid var(--border-2);border-radius:8px;
  padding:7px 10px;font-size:11.5px;line-height:1.55;color:var(--ink);
  box-shadow:0 6px 18px rgba(0,0,0,.18);white-space:nowrap;
}
.chart-evt-tip .t{font-weight:700;}

/* The custom MA's period editor, living inside its badge. */
.chart-ind-malen{
  width:44px;padding:0 2px;border:1px solid var(--border-2);border-radius:3px;
  background:var(--surface);color:inherit;
  font-size:10.5px;font-weight:700;font-family:var(--mono);
}
.chart-ind-badge{
  display:inline-flex;align-items:center;gap:5px;
  background:var(--surface-2);border:1px solid var(--border-2);
  border-radius:4px;padding:2px 8px;
  font-size:10.5px;font-weight:700;font-family:var(--mono);
  color:var(--ink);letter-spacing:.02em;cursor:default;
}
/* The swatch is the whole point of the badge for overlay indicators: it ties
   the code to the exact line colour on the canvas. Sourced from
   INDICATOR_COLORS so the two cannot drift apart. */
.chart-ind-badge .dot{width:8px;height:8px;border-radius:2px;flex:none;}
.chart-ind-none{font-size:10.5px;color:var(--ink-muted);font-style:italic;}

/* ================================ FEED ===================================== */
/* Stocktwits-shaped: widgets on both rails, a center stream, the portfolio
   card pinned and frozen on top of it. Classes mirror mockups/feed.html —
   that file is the approved design reference. */
main.content.fd-page{max-width:none;}
.fd-layout{
  display:grid;grid-template-columns:260px minmax(0,660px) 300px;
  gap:16px;justify-content:center;align-items:start;
}
.fd-rail{position:sticky;top:calc(var(--topbar-h) + 10px);display:flex;flex-direction:column;gap:12px;}
.fd-rail .sec{margin:0;}
.fd-wl-row{display:flex;align-items:center;gap:8px;padding:6px 2px;border-bottom:1px solid var(--border);font-size:13px;cursor:pointer;}
.fd-wl-row:last-child{border-bottom:none;}
.fd-wl-row:hover .s{color:var(--brand-500);}
.fd-wl-row .s{font-weight:700;}
.fd-wl-row .p{margin-inline-start:auto;font-variant-numeric:tabular-nums;}
.fd-wl-chg{font-size:11.5px;font-weight:600;font-variant-numeric:tabular-nums;}
.fd-tiles{display:grid;grid-template-columns:1fr 1fr;gap:8px;}
.fd-tile{background:var(--surface-2);border:1px solid var(--border);border-radius:var(--radius-sm);padding:7px 9px;}
.fd-tile .l{font-size:10px;color:var(--ink-muted);text-transform:uppercase;letter-spacing:.05em;}
.fd-tile .v{font-size:14px;font-weight:800;font-variant-numeric:tabular-nums;}
.fd-earn-row{display:flex;align-items:center;gap:9px;padding:6px 0;border-bottom:1px solid var(--border);font-size:12.5px;cursor:pointer;}
.fd-earn-row:last-child{border-bottom:none;}
.fd-earn-date{
  width:40px;flex:none;text-align:center;background:var(--surface-2);
  border:1px solid var(--border);border-radius:var(--radius-sm);
  padding:2px 0;font-size:10px;line-height:1.3;
}
.fd-earn-date b{display:block;font-size:13px;}
.fd-headline{
  display:block;padding:6px 2px;border-bottom:1px solid var(--border);
  font-size:12px;color:var(--ink-2);
}
.fd-headline:last-child{border-bottom:none;}
.fd-headline:hover{color:var(--brand-500);}

/* center stream */
.fd-center{min-width:0;display:flex;flex-direction:column;gap:12px;}
.fd-stream{display:flex;flex-direction:column;gap:12px;}
.fd-new-pill{
  position:sticky;top:calc(var(--topbar-h) + 6px);z-index:20;align-self:center;
  background:var(--brand-500);color:#fff;font-size:12px;font-weight:700;
  border:none;border-radius:20px;padding:6px 16px;cursor:pointer;
  box-shadow:0 4px 14px rgba(0,0,0,.35);display:none;
}
.fd-new-pill.show{display:block;}
/* The portfolio card is PINNED: frozen while the stream scrolls beneath it. */
.fd-pinned{position:sticky;top:calc(var(--topbar-h) + 6px);z-index:15;}
.fd-pinned .fd-card{box-shadow:0 6px 18px rgba(0,0,0,.28);border-color:var(--border-2);}

.fd-card{
  background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius);padding:13px 15px;
}
@keyframes fd-slide{from{opacity:0;transform:translateY(-14px);}to{opacity:1;transform:none;}}
@keyframes fd-glow{from{box-shadow:0 0 0 2px var(--brand-500);}to{box-shadow:0 0 0 0 transparent;}}
.fd-card.fresh{animation:fd-slide .35s ease, fd-glow 2.2s ease .3s;}
.fd-card-top{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:7px;}
.fd-kind{font-size:9.5px;font-weight:800;letter-spacing:.07em;border-radius:4px;padding:2px 7px;}
.fd-kind.portfolio{background:var(--role-manager-bg);color:var(--role-manager);}
.fd-kind.news{background:var(--brand-50);color:var(--brand-500);}
.fd-kind.creator{background:var(--good-bg);color:var(--good);}
.fd-kind.x{background:var(--ink);color:var(--surface);}
.fd-kind.popular{background:var(--bad-bg);color:var(--bad);}
.fd-kind.watchlist{background:transparent;color:var(--ink-2);border:1px dashed var(--border-2);}
.fd-sym{
  display:inline-flex;align-items:center;background:var(--surface-2);
  border:1px solid var(--border-2);border-radius:4px;padding:1px 7px;cursor:pointer;
  font-size:11px;font-weight:800;font-family:var(--mono);color:var(--ink);
}
.fd-sym:hover{border-color:var(--brand-500);color:var(--brand-500);}
.fd-when{margin-inline-start:auto;font-size:11px;color:var(--ink-muted);font-variant-numeric:tabular-nums;}
.fd-title{font-weight:700;font-size:14px;margin-bottom:3px;display:block;color:var(--ink);}
.fd-link:hover{color:var(--brand-500);}
.fd-body{font-size:13px;color:var(--ink-2);line-height:1.55;overflow-wrap:anywhere;}
.fd-src{font-size:11px;color:var(--ink-muted);margin-top:6px;}
.fd-post-head{display:flex;align-items:center;gap:8px;margin-bottom:5px;font-size:13px;}
.fd-foot{display:flex;align-items:center;gap:14px;margin-top:9px;}
.fd-like{
  display:inline-flex;align-items:center;gap:4px;background:transparent;border:none;
  color:var(--ink-muted);font-size:14px;cursor:pointer;padding:0;line-height:1;
}
.fd-like:hover,.fd-like.liked{color:var(--bad);}
.fd-like .n{font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;}
.fd-dislike{font-size:13px;}
.fd-dislike:hover,.fd-dislike.on{color:var(--warn);}
.fd-reply{background:transparent;border:none;padding:0;cursor:pointer;color:var(--brand-500);font-size:12.5px;font-weight:600;}
.fd-reply:hover{text-decoration:underline;}
.fd-na{color:var(--ink-muted);}

/* portfolio table */
.fd-pf-table{width:100%;border-collapse:collapse;font-size:12.5px;margin-top:4px;}
.fd-pf-table th{font-size:10px;color:var(--ink-muted);text-transform:uppercase;letter-spacing:.05em;text-align:end;padding:3px 6px;}
.fd-pf-table th:first-child{text-align:start;}
.fd-pf-table td{padding:4px 6px;border-top:1px solid var(--border);text-align:end;font-variant-numeric:tabular-nums;}
.fd-pf-table td:first-child{text-align:start;font-weight:700;}
@keyframes fd-flash-good{from{background:var(--good-bg);}to{background:transparent;}}
@keyframes fd-flash-bad{from{background:var(--bad-bg);}to{background:transparent;}}
.fd-pf-table td.flash-good{animation:fd-flash-good 1.2s ease;}
.fd-pf-table td.flash-bad{animation:fd-flash-bad 1.2s ease;}

/* thread panel (fixed slide-in; reuses the channels reply/composer classes) */
.fd-thread{
  position:fixed;top:var(--topbar-h);bottom:0;inset-inline-end:0;z-index:60;
  width:min(360px,92vw);display:none;flex-direction:column;
  background:var(--surface);border-inline-start:1px solid var(--border-2);
  box-shadow:-10px 0 30px rgba(0,0,0,.35);
}
.fd-thread.open{display:flex;}
.fd-thread-head{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-bottom:1px solid var(--border);font-weight:700;font-size:13.5px;}
.fd-thread-src{padding:10px 14px;border-bottom:1px solid var(--border);font-size:12.5px;color:var(--ink-2);}
.fd-thread-scroll{flex:1;min-height:0;overflow-y:auto;padding:12px 14px;display:flex;flex-direction:column;gap:10px;}

@media (max-width:1100px){
  .fd-layout{grid-template-columns:minmax(0,1fr);}
  .fd-rail{position:static;flex-direction:row;flex-wrap:wrap;}
  .fd-rail .sec{flex:1;min-width:250px;}
}

/* ============================ HOME v2 ====================================== */
/* The scrolling index ribbon. The track holds every tile twice; the keyframes
   travel exactly one copy's width (-50% -> 0 = left-to-right), so when the
   loop wraps the second copy stands where the first was — seamless. Hover
   pauses it so the numbers can actually be read. */
.idx-marquee{overflow:hidden;position:relative;}
.idx-marquee-track{
  display:flex;gap:10px;width:max-content;
  animation:idx-scroll 50s linear infinite;
}
.idx-marquee:hover .idx-marquee-track{animation-play-state:paused;}
@keyframes idx-scroll{
  from{transform:translateX(-50%);}
  to{transform:translateX(0);}
}
/* width:auto, min-width instead of a fixed 196px. The fixed width was what
   forced the sparkline to overlap: the content needs ~265px with the spark
   beside it, and no amount of positioning fits 265px of content into 196px.
   Tiles now size to their own content and the short ones still line up at a
   common minimum. */
.idx-marquee .index-tile{width:auto;min-width:196px;flex:none;}

/* The hero band is gone — Pulse is a left-column widget and the portfolio
   leads the right rail. The rail widens so the top-5 table breathes, and the
   pulse card lays its pieces in one compact row. */
.home-rail .fd-card{margin-top:10px;}
.home-rail .heatmap-stats{grid-template-columns:1fr 1fr;}
/* The side-by-side mini heatmap/sectors pair: each cell must be allowed to
   shrink (grids default to min-width:auto, which lets a wide treemap blow
   the cell out over the neighbouring column) and clip its own card. */
.home-duo > .sec{min-width:0;margin-top:0;}
.home-duo .card{overflow:hidden;}

/* Compact one-row widget: session · verdict · breadth bar · VIX. */
.pulse-card{display:flex;flex-wrap:wrap;align-items:center;gap:8px 16px;}
.pulse-card .pulse-verdict{font-size:19px;}
.pulse-card .pulse-bar{flex:1 1 140px;}
.pulse-session{display:flex;align-items:center;gap:7px;font-size:12.5px;color:var(--ink-2);font-weight:600;}
.pulse-dot{width:8px;height:8px;border-radius:50%;background:var(--ink-muted);flex:none;}
.pulse-dot.live{background:var(--good);animation:pulse 1.4s infinite;}
.pulse-verdict{font-size:22px;font-weight:800;letter-spacing:-.01em;}
.pulse-verdict.bullish{color:var(--good);}
.pulse-verdict.bearish{color:var(--bad);}
.pulse-verdict.mixed{color:var(--warn);}
/* Breadth: green fill over a red base — the split IS the message. */
.pulse-bar{height:8px;border-radius:4px;background:var(--bad-bg);overflow:hidden;}
.pulse-bar .fill{height:100%;border-radius:4px;background:var(--good);}
.pulse-line{font-size:12px;color:var(--ink-muted);}

/* Mini sectors: the two ends of the board, three rows each. */
.mini-sector-row{
  display:flex;align-items:center;gap:8px;padding:6px 2px;cursor:pointer;
  border-bottom:1px solid var(--border);font-size:13px;
}
.mini-sector-row:last-child{border-bottom:none;}
.mini-sector-row:hover{background:var(--surface-2);}
.mini-sector-row .perf-dot{width:8px;height:8px;}
.mini-sector-row .nm{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.mini-sector-gap{text-align:center;color:var(--ink-muted);font-size:10px;line-height:1;padding:2px 0;}

/* Rail: headlines + watchlist rows. */
.home-headline{display:block;padding:7px 2px;border-bottom:1px solid var(--border);}
.home-headline:last-child{border-bottom:none;}
.home-headline:hover .h{color:var(--brand-500);}
.home-headline .h{font-size:12.5px;font-weight:600;line-height:1.45;}
.home-headline .s{font-size:10.5px;color:var(--ink-muted);margin-top:2px;}
.home-wl-row{
  display:flex;align-items:center;gap:8px;padding:6px 2px;cursor:pointer;
  border-bottom:1px solid var(--border);font-size:13px;
}
.home-wl-row:last-child{border-bottom:none;}
.home-wl-row:hover{background:var(--surface-2);}
.home-wl-row .sym{font-weight:700;flex:1;}

@media (max-width:1000px){
  .home-hero{grid-template-columns:1fr;}
}

/* ------------------------------------------------------------------ profile --
   Concept B (mockups/profile-redesign.html, chosen 2026-08-03): banner across
   the top, identity rail on the left, performance and activity on the right.

   Only the rail rides up over the banner. Pulling the whole grid up drags the
   performance card under it and clips its header — found while building the
   mockup, and the reason for the negative margin sitting on .prof-rail alone
   rather than on .prof-split.                                                */
.prof-shell{display:block;}
.prof-banner{
  height:150px;border-radius:var(--radius);background:var(--surface-2);
  border:1px solid var(--border);position:relative;overflow:hidden;
  background-size:cover;background-position:center;
}
.prof-banner.has-img{border-color:transparent;}
.prof-upload{
  position:absolute;inset-inline-end:12px;bottom:12px;z-index:2;
  padding:6px 12px;border-radius:14px;border:1px solid var(--border-2);
  background:var(--surface);color:var(--ink-2);font-size:12px;font-weight:600;
  cursor:pointer;
}
.prof-upload:hover{color:var(--ink);}
.prof-upload.bad{color:var(--bad);border-color:var(--bad);}

.prof-split{display:grid;grid-template-columns:320px 1fr;gap:16px;margin-top:14px;}
.prof-rail{margin-top:-56px;min-width:0;}
.prof-col{min-width:0;}
.prof-rail > div + div{margin-top:14px;}
.prof-col  > div + div{margin-top:14px;}

.prof-id{text-align:center;}
.prof-av-wrap{position:relative;display:inline-block;margin:0 auto 12px;}
.prof-av{
  width:92px;height:92px;border-radius:50%;object-fit:cover;display:block;
  border:3px solid var(--surface);
}
.prof-av-ini{
  display:flex;align-items:center;justify-content:center;
  color:#fff;font-weight:800;font-size:30px;
}
/* The avatar's own picker is a small round button on the picture itself, not
   the wide pill used on the banner. */
.prof-av-wrap .prof-upload{
  inset-inline-end:-2px;bottom:-2px;padding:5px 8px;border-radius:50%;font-size:13px;
}
.prof-name{font-weight:800;font-size:19px;}
.prof-handle{font-size:13px;color:var(--brand-500);font-weight:600;margin-top:1px;}
.prof-badges{display:flex;justify-content:center;gap:6px;margin-top:9px;flex-wrap:wrap;}
.prof-bio{text-align:start;margin:12px 0 0;font-size:13px;color:var(--ink-2);line-height:1.55;}
.prof-meta{
  text-align:start;margin-top:12px;display:flex;flex-direction:column;gap:5px;
  font-size:12px;color:var(--ink-muted);
}
.prof-links{display:flex;flex-wrap:wrap;gap:6px;margin-top:12px;justify-content:center;}
.prof-links .chip{text-decoration:none;padding:4px 11px;font-size:11.5px;}
.prof-actions{display:flex;gap:8px;margin-top:14px;}
.prof-actions .btn{flex:1;}
.prof-counts{
  display:flex;justify-content:space-around;margin-top:16px;padding-top:14px;
  border-top:1px solid var(--border);
}
.prof-count .v{font-weight:800;font-size:17px;font-variant-numeric:tabular-nums;}
.prof-count .l{
  font-size:10.5px;color:var(--ink-muted);text-transform:uppercase;
  letter-spacing:.08em;font-weight:700;margin-top:2px;
}

/* edit mode — the same card with fields in it, not a separate settings page */
.prof-editing{text-align:start;}
.prof-editing .prof-av-wrap{display:block;margin:0 auto 14px;width:max-content;}
.prof-field{display:block;margin-top:10px;}
.prof-field > span{
  display:block;font-size:11.5px;font-weight:700;color:var(--ink-muted);
  text-transform:uppercase;letter-spacing:.06em;margin-bottom:4px;
}
.prof-field input,.prof-field textarea{width:100%;}
.prof-hint{font-size:11.5px;color:var(--ink-muted);margin-top:5px;line-height:1.5;}
.prof-link-row{display:grid;grid-template-columns:1fr 1.6fr;gap:6px;margin-top:6px;}
.prof-link-row input{width:100%;font-size:12.5px;}
.prof-status{font-size:12px;margin-top:9px;color:var(--ink-muted);}
.prof-status.bad{color:var(--bad);}

.prof-perf-head{margin-bottom:4px;}
.prof-perf-value{font-size:28px;font-weight:800;}
.prof-perf-delta{font-weight:700;font-size:13.5px;margin-top:2px;}
.prof-stats{margin-top:14px;}
.prof-vis-row{
  display:flex;justify-content:space-between;align-items:center;
  font-size:13px;margin-bottom:7px;
}
.prof-vis-row:last-child{margin-bottom:0;}
.prof-tabs{margin-top:16px;}
.prof-post{padding:11px 0;border-bottom:1px solid var(--border);}
.prof-post:last-child{border-bottom:none;padding-bottom:0;}
.prof-post-who{font-size:11.5px;color:var(--ink-muted);font-weight:600;}
.prof-post-body{margin-top:5px;font-size:13px;line-height:1.55;}

/* Concept B collapses worst on a phone: 320px of rail beside anything is two
   unreadable columns. Below 900 the rail goes back on top, and its negative
   margin has to go with it or the avatar climbs off the banner. */
@media (max-width:900px){
  .prof-split{grid-template-columns:1fr;}
  .prof-rail{margin-top:-48px;}
  .prof-banner{height:110px;}
  .prof-link-row{grid-template-columns:1fr;}
}

/* ------------------------------------------------- channels: opt-in list --
   The sidebar lists only channels you've joined, so it needs a line telling
   you where the rest are. Without it a two-item rail reads as a fault. */
.chx-discover{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.chx-discover-btn{
  background:none;border:none;padding:0;cursor:pointer;font:inherit;
  color:var(--brand-500);font-weight:700;
}
.chx-discover-btn:hover{text-decoration:underline;}

/* ---------------------------------------------- creator hub: upgrade -----
   Head row for the discovery view: title left, (i) + button right. The (i)
   comes first in DOM order as well as visually, because the explanation has
   to be reachable before the control that changes your account. */
.hub-disc-head{
  display:flex;align-items:flex-start;justify-content:space-between;
  gap:14px;flex-wrap:wrap;margin-bottom:14px;
}
.hub-upgrade{position:relative;display:flex;flex-direction:column;align-items:flex-end;gap:6px;}
.hub-upgrade-row{display:flex;align-items:center;gap:8px;}
.hub-info-btn{
  width:24px;height:24px;flex:none;border-radius:50%;cursor:pointer;
  border:1px solid var(--border-2);background:var(--surface-2);color:var(--ink-2);
  font-weight:800;font-size:12px;font-family:inherit;line-height:1;
}
.hub-info-btn:hover{color:var(--ink);border-color:var(--brand-500);}
.hub-info-btn:focus-visible{outline:2px solid var(--brand-500);outline-offset:2px;}
.hub-info-panel{
  position:absolute;top:34px;inset-inline-end:0;z-index:40;width:min(340px,90vw);
  background:var(--surface);border:1px solid var(--border-2);border-radius:var(--radius);
  padding:13px 15px;box-shadow:0 12px 30px rgba(0,0,0,.28);text-align:start;
}
.hub-info-panel[hidden]{display:none;}
.hub-info-h{font-weight:800;font-size:13px;margin-bottom:7px;}
.hub-info-panel ul{margin:0;padding-inline-start:17px;}
.hub-info-panel li{font-size:12.5px;line-height:1.55;color:var(--ink-2);margin-bottom:5px;}
.hub-info-note{
  margin-top:8px;padding-top:8px;border-top:1px solid var(--border);
  font-size:11.5px;line-height:1.5;color:var(--ink-muted);
}
.hub-upgrade-status{font-size:12px;color:var(--ink-muted);}
.hub-upgrade-status.bad{color:var(--bad);}

@media (max-width:700px){
  .hub-disc-head{flex-direction:column;}
  .hub-upgrade{align-items:stretch;width:100%;}
  .hub-info-panel{inset-inline-end:auto;inset-inline-start:0;}
}

/* Friends rows: the avatar is a button now (it opens the mini profile), so it
   needs the button resets the span never did. */
.persona-avatar.person-open{
  border:none;cursor:pointer;font-family:inherit;padding:0;
}
.persona-avatar.person-open:hover{filter:brightness(1.15);}
.persona-avatar.person-open:focus-visible{outline:2px solid var(--brand-500);outline-offset:2px;}

/* Notification rows carry a dismiss button that only shows on hover/focus, so
   a full inbox isn't a wall of X's — but it stays reachable by keyboard. */
.notif-del{
  background:none;border:none;cursor:pointer;font-family:inherit;font-size:13px;
  color:var(--ink-muted);padding:4px 6px;border-radius:6px;opacity:0;
  transition:opacity .12s ease;flex:none;
}
.notif-row:hover .notif-del,
.notif-del:focus-visible{opacity:1;}
.notif-del:hover{color:var(--bad);background:var(--surface-2);}
.notif-del:focus-visible{outline:2px solid var(--brand-500);outline-offset:-2px;}
@media (hover:none){ .notif-del{opacity:1;} }

/* The rule between notification SETTINGS and the notification INBOX. They are
   two different kinds of thing and were reading as one stack — a switch and a
   received alert looked like sibling rows. The label rides on the line so the
   separator also says what is below it. */
.notif-divider{
  display:flex;align-items:center;gap:10px;margin:18px 0 10px;
  font-size:10.5px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  color:var(--ink-muted);
}
.notif-divider::before,
.notif-divider::after{content:"";height:1px;background:var(--border-2);flex:1;}
.notif-divider::before{flex:0 0 0;}

/* The red-dot explainer shows a live dot inline, so the thing being described
   is the thing on screen rather than a description of it. */
.notif-help{display:flex;align-items:flex-start;gap:8px;}
.notif-help .notif-unread-dot{margin-top:6px;flex:none;}

/* Screen-reader-only text: visible to assistive tech, not on screen. Used
   where a visual cue (a coloured border) carries meaning that a border alone
   cannot convey. */
.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0;
}

/* A channel somebody added you to, which you have not opened yet. Purple
   because every other state in this rail is already spoken for — blue is
   "active", the amber dot is unread activity — so a third colour is the only
   way this reads as a different KIND of thing rather than a louder version of
   an existing one. Cleared on first open (mark_channel_seen). */
.chx-ch-row.invited{
  border:1px solid var(--invite);
  border-radius:var(--radius-sm);
  background:var(--invite-bg);
}
.chx-ch-row.invited:hover{background:var(--invite-bg-hover);}
.chx-new-dot{
  width:6px;height:6px;border-radius:50%;background:var(--invite);
  margin-inline-start:auto;flex:none;
}

/* Current price under the symbol box in Add command. */
.order-price{
  margin:-4px 0 10px;font-size:12.5px;display:flex;align-items:center;gap:2px;
  min-height:18px;
}
.order-price .muted{color:var(--ink-muted);}

/* Accept / Decline on a friend-request notification, answered where you read
   it. Sits before the dismiss button so the destructive control stays last. */
.notif-fr-actions{display:flex;gap:6px;flex:none;align-items:center;}

/* Attached-file chip in the composer, with its remove control. */
.chx-file-chip{display:inline-flex;align-items:center;gap:6px;}
.chx-chip-x{
  background:none;border:none;padding:0 2px;cursor:pointer;font-family:inherit;
  color:var(--ink-muted);font-size:11px;line-height:1;
}
.chx-chip-x:hover{color:var(--bad);}
.chx-chip-x:focus-visible{outline:2px solid var(--brand-500);outline-offset:1px;border-radius:3px;}

/* =============================================================================
   User guide (#/docs)
   -----------------------------------------------------------------------------
   The guide moved out of a standalone page and into the app on 2026-08-04, so
   it now has to live inside the shell rather than owning the window. Two
   columns: a sticky contents rail and an article column capped at a readable
   measure, because prose at full terminal width is unreadable however wide the
   screen is.
   ========================================================================== */
.docs-shell{
  display:grid; grid-template-columns:262px minmax(0,1fr); gap:38px;
  align-items:start; padding:4px 0 60px;
}
.docs-loading{ padding:40px 0; color:var(--ink-muted); }

/* The contents rail. Sticky and independently scrollable — `overscroll-behavior`
   stops a scroll that reaches the end of the rail from continuing into the
   article behind it, which felt like the page jumping. */
/* Sticky BELOW the topbar, not below the viewport.
   ---------------------------------------------------------------------------
   This read `top:12px` and `max-height:calc(100vh - 40px)`, neither of which
   knew the topbar exists. #topbar is `position:sticky; top:0; z-index:60` and
   --topbar-h tall (52px), so once the page scrolled the rail parked 12px from
   the viewport top — forty pixels UNDERNEATH the bar. The part that vanished
   was the top of the list: the "Start here" heading and chapter 1. Reported by
   Guy, 2026-08-06.

   The height was wrong by the same amount, so the bottom of the rail was
   pushed past the fold and the last chapters could not be scrolled to either.

   Every other sticky rail in this file already offsets from --topbar-h — the
   sidenav, .fd-rail, the feed's pinned bar. This one was the odd one out. */
.docs-toc{
  position:sticky; top:calc(var(--topbar-h) + 12px);
  max-height:calc(100vh - var(--topbar-h) - 24px);
  overflow-y:auto; overscroll-behavior:contain;
  padding-right:6px; scrollbar-gutter:stable;
}
.docs-toc ul{ list-style:none; margin:0; padding:0; }
/* The part headings that group the chapters ("Getting started", "Your money",
   …). Coloured on the owner's call, 2026-08-06 — they were --ink-muted, the
   same grey as the chapter numbers beneath them, so the rail read as one flat
   list of thirty-odd rows with no visible structure at all.

   --brand-700 rather than --brand-500: the token exists precisely for this,
   and inverts per theme (bright #7cb0f2 on dark, deep #17408f on light) so a
   10px uppercase label clears 4.5:1 in both. --brand-500 does not on dark.

   The rule underneath does as much work as the colour: it turns a line of text
   into a section boundary, which is what makes the chapters below it read as
   belonging to something. */
.docs-toc-part{
  font-size:10px; font-weight:800; text-transform:uppercase; letter-spacing:.1em;
  color:var(--brand-700); margin:20px 0 7px 9px;
  padding-bottom:5px; border-bottom:1px solid var(--border);
  padding-right:9px;
}
.docs-toc-part:first-child{ margin-top:0; }
.docs-toc a{
  display:flex; gap:8px; padding:5px 9px; border-radius:var(--radius-sm);
  color:var(--ink-2); font-size:13px; line-height:1.4;
}
.docs-toc a:hover{ background:var(--surface-2); color:var(--ink); }
.docs-toc a.here{ background:var(--brand-50); color:var(--brand-700); font-weight:700; }
.docs-toc-num{ font-family:var(--mono); font-size:11px; color:var(--ink-muted); min-width:20px; flex:none; }
.docs-toc a.here .docs-toc-num{ color:var(--brand-500); }
/* Sub-sections are present from the start and revealed for the chapter being
   read; building them on scroll would reflow the list under the cursor. */
.docs-toc-sub{ display:none; margin:2px 0 6px 18px; border-left:1px solid var(--border); padding-left:6px; }
.docs-toc-item.open > .docs-toc-sub{ display:block; }
.docs-toc-sub a{ font-size:12.5px; padding:3px 8px; }

.docs-article{ max-width:76ch; min-width:0; }
.docs-head{ margin-bottom:26px; }
.docs-search-wrap{ margin-top:14px; }
.docs-search-box{ position:relative; max-width:420px; }
.docs-search{
  width:100%; height:36px; padding:0 12px; background:var(--surface-2); color:var(--ink);
  border:1px solid var(--border-2); border-radius:999px; font-family:var(--font); font-size:13.5px;
}
.docs-search:focus{ border-color:var(--brand-500); background:var(--surface); }
.docs-results{
  position:absolute; top:42px; left:0; right:0; z-index:40; padding:6px;
  background:var(--surface); border:1px solid var(--border-2); border-radius:var(--radius);
  box-shadow:0 18px 48px rgba(0,0,0,.28); max-height:60vh; overflow-y:auto;
}
.docs-res{ display:flex; gap:9px; align-items:baseline; padding:8px 10px; border-radius:var(--radius-sm); color:var(--ink); }
.docs-res:hover{ background:var(--surface-2); }
.docs-res-num{
  font-family:var(--mono); font-size:11px; font-weight:700; color:var(--brand-500);
  background:var(--brand-50); border-radius:4px; padding:0 5px; flex:none;
}
.docs-res-t{ font-weight:600; font-size:13.5px; }
.docs-res-p{ font-size:11px; color:var(--ink-muted); margin-left:auto; text-transform:uppercase; letter-spacing:.05em; }
.docs-res-none{ padding:12px; font-size:13px; color:var(--ink-2); }

.docs-part{
  margin:46px 0 6px; padding-top:22px; border-top:2px solid var(--border-2);
  font-size:11px; font-weight:800; text-transform:uppercase; letter-spacing:.14em; color:var(--brand-500);
}
/* Clears the topbar too. jumpTo() scrolls a chapter with block:"start", which
   puts its heading at the very top of the VIEWPORT — i.e. behind the 52px
   sticky topbar. 16px was not enough to clear it, so clicking a contents entry
   landed you just under the title you had asked for. Same root cause as the
   rail above: a sticky offset written as if the bar were not there. */
.docs-sec{ scroll-margin-top:calc(var(--topbar-h) + 16px); }
.docs-chapter > .docs-h{ font-size:23px; line-height:1.25; margin:22px 0 10px; letter-spacing:-.015em; }
.docs-topic  > .docs-h{ font-size:16px; margin:22px 0 7px; }
.docs-h{ display:flex; align-items:baseline; gap:9px; }
.docs-num{ font-family:var(--mono); color:var(--brand-500); font-weight:700; font-size:.72em; flex:none; }
.docs-edit{
  margin-left:auto; background:none; border:1px solid var(--border); color:var(--ink-muted);
  border-radius:6px; padding:1px 7px; font-size:12px; cursor:pointer; opacity:0; flex:none;
}
.docs-sec:hover .docs-edit, .docs-edit:focus{ opacity:1; }
.docs-edit:hover{ background:var(--surface-2); color:var(--ink); }

.docs-body{ color:var(--ink); }
.docs-body p{ margin:0 0 12px; line-height:1.65; }
.docs-body ul, .docs-body ol{ margin:0 0 13px; padding-left:21px; }
.docs-body li{ margin-bottom:5px; }
.docs-body dl{ margin:0 0 14px; }
.docs-body dt{ font-weight:700; margin-top:11px; }
.docs-body dd{ margin:2px 0 0; color:var(--ink-2); }
.docs-body table{ width:100%; border-collapse:collapse; margin:0 0 14px; font-size:13px; }
.docs-body th, .docs-body td{ text-align:start; padding:7px 10px; border-bottom:1px solid var(--border); vertical-align:top; }
.docs-body th{ font-size:10.5px; text-transform:uppercase; letter-spacing:.07em; color:var(--ink-muted); }
/* A wide table scrolls inside itself; the page never scrolls sideways. */
.docs-body .scroller{ overflow-x:auto; margin-bottom:14px; }
.docs-body .scroller table{ margin-bottom:0; min-width:520px; }
.docs-body figure{ margin:0 0 18px; }
.docs-body figure img{
  display:block; width:100%; height:auto; background:var(--surface);
  border:1px solid var(--border-2); border-radius:var(--radius);
}
.docs-body figcaption{ margin-top:7px; font-size:12px; color:var(--ink-muted); }
.docs-body code{
  background:var(--surface-2); border:1px solid var(--border); border-radius:5px;
  padding:1px 5px; font-family:var(--mono); font-size:.87em;
}
.docs-body kbd{
  background:var(--surface-2); border:1px solid var(--border-2); border-bottom-width:2px;
  border-radius:5px; padding:1px 6px; font-family:var(--mono); font-size:.82em; font-weight:600;
}
.docs-body .ui{
  background:var(--surface-2); border:1px solid var(--border); border-radius:5px;
  padding:1px 7px; font-size:.88em; font-weight:600; white-space:nowrap;
}
.docs-body .note{
  border:1px solid var(--border-2); border-left:3px solid var(--brand-500);
  background:var(--surface); border-radius:var(--radius-sm); padding:11px 14px; margin:0 0 14px;
}
.docs-body .note.warn{ border-left-color:var(--warn); background:var(--warn-bg); }
.docs-body .note p:last-child{ margin-bottom:0; }
.docs-body .note b.lbl{ display:block; margin-bottom:3px; }
.docs-body .steps{ counter-reset:step; list-style:none; padding-left:0; }
.docs-body .steps > li{ counter-increment:step; position:relative; padding-left:32px; margin-bottom:9px; }
.docs-body .steps > li::before{
  content:counter(step); position:absolute; left:0; top:0;
  width:22px; height:22px; border-radius:50%; background:var(--brand-50);
  color:var(--brand-500); border:1px solid var(--brand-100);
  font-size:11.5px; font-weight:800; display:flex; align-items:center; justify-content:center;
}

/* Per-chapter feedback. Quiet until you want it — a loud box under every
   chapter would read as a survey rather than an offer. */
.docs-fb{
  display:flex; align-items:center; gap:12px; flex-wrap:wrap;
  margin:18px 0 6px; padding:11px 14px;
  border:1px dashed var(--border-2); border-radius:var(--radius-sm); background:var(--surface-2);
}
.docs-fb-q{ font-size:12.5px; color:var(--ink-2); }

/* The section a contents entry or a search result just landed on. */
@keyframes docsFlash{ from{ background:var(--brand-50); } to{ background:transparent; } }
.docs-flash{ animation:docsFlash 1.5s ease-out; border-radius:var(--radius); }

@media (max-width: 1000px){
  .docs-shell{ grid-template-columns:1fr; gap:14px; }
  .docs-toc{ position:static; max-height:none; border-bottom:1px solid var(--border); padding-bottom:12px; }
  .docs-toc-sub{ display:none !important; }
}
@media (prefers-reduced-motion: reduce){
  .docs-flash{ animation:none; outline:2px solid var(--brand-500); outline-offset:4px; }
}

/* The existing position in the chosen symbol, under the live price in the
   order ticket. Quiet but always present — "0 shares" is an answer, and an
   empty gap would read as "still loading". */
.order-holding{ margin:-4px 0 12px; font-size:12.5px; }
.order-holding .muted{ color:var(--ink-muted); }

/* =============================================================================
   The newspaper (#/news)
   -----------------------------------------------------------------------------
   "The Ledger", the design the owner chose from three mockups and then tuned:
   index ticker across the top, ivory paper, TRADE TOGETHER in brand blue,
   headlines in serif but BODY COPY IN THE UI SANS - the one deliberate break
   from newspaper convention, because Georgia at 13px on a screen is harder
   work than it looks and the owner asked for legibility over authenticity.

   The paper keeps its own light surface in BOTH themes. A newspaper that
   inverts to black is no longer a newspaper, and the masthead stops meaning
   anything. It is the one screen in the app that does not follow the theme.
   ========================================================================== */
.paper{
  --pp:#f7f4ec; --pp2:#efeadb; --pink:#141412; --pink2:#45443f; --pmut:#807d72;
  --prule:#cdc8b6; --pbrand:#1d4fd0;
  background:var(--pp); color:var(--pink);
  border:1px solid var(--prule); border-radius:var(--radius);
  padding:0 0 34px; overflow:hidden; max-width:1180px; margin:0 auto;
  font-family:Georgia,"Times New Roman",serif;
}
.paper-ticker{
  display:flex; gap:26px; justify-content:center; flex-wrap:wrap;
  background:#141412; color:#efe9da; padding:7px 14px;
  font-family:var(--mono); font-size:11.5px; font-variant-numeric:tabular-nums;
}
.paper-ticker .up{ color:#7fd49a; } .paper-ticker .down{ color:#f09a93; }
.paper-ears{
  display:flex; justify-content:space-between; padding:10px 32px 0;
  font-family:var(--font); font-size:10.5px; color:var(--pink2);
  text-transform:uppercase; letter-spacing:.08em;
}
.paper-masthead{ text-align:center; padding:10px 32px 12px; }
.paper-masthead h1{ font-size:clamp(28px,5vw,54px); font-weight:700; letter-spacing:.01em; margin:0; }
.paper-masthead h1 span{ color:var(--pbrand); }
.paper-dateline{
  display:flex; justify-content:space-between; align-items:center; gap:14px; flex-wrap:wrap;
  margin:0 32px; padding:6px 2px;
  border-top:2.5px solid var(--pink); border-bottom:1px solid var(--pink);
  font-family:var(--font); font-size:11px; color:var(--pink2);
  text-transform:uppercase; letter-spacing:.09em;
}
.paper-dateline b{ color:var(--pink); }
.paper-dateline .up{ color:#12813c; } .paper-dateline .down{ color:#a3271e; }
.paper-tabs{
  display:flex; justify-content:center; gap:34px; padding:10px 0;
  margin:0 32px; border-bottom:1px solid var(--prule);
}
.paper-tabs button{
  background:none; border:none; cursor:pointer; padding:2px 0 7px;
  font-family:var(--font); font-size:12px; letter-spacing:.1em;
  text-transform:uppercase; color:var(--pink2); border-bottom:2px solid transparent;
}
.paper-tabs button:hover{ color:var(--pink); }
.paper-tabs button.on{ color:var(--pbrand); border-bottom-color:var(--pbrand); font-weight:700; }
.paper-tabs button:focus-visible{ outline:2px solid var(--pbrand); outline-offset:3px; border-radius:4px; }

.paper-body{ padding:0 32px; }
.paper-note{
  margin:16px 0 0; padding:10px 14px; background:var(--pp2);
  border-left:3px solid var(--pbrand); border-radius:0 5px 5px 0;
  font-family:var(--font); font-size:12.5px; color:var(--pink2);
}
.paper-grid{ display:grid; grid-template-columns:2fr 1fr; gap:0 32px; padding-top:20px; }
.paper-col{ min-width:0; }

.paper-kick{
  font-family:var(--mono); font-size:10.5px; font-weight:700; letter-spacing:.1em;
  text-transform:uppercase; color:var(--pbrand);
}
.paper-lead h2{
  font-size:clamp(21px,2.6vw,32px); line-height:1.14; font-weight:800;
  letter-spacing:-.012em; margin:5px 0 9px;
}
/* Body copy in the UI sans - see the header note. */
.paper-deck{ font-family:var(--font); font-size:14.5px; line-height:1.58; color:var(--pink2); margin:0 0 8px; }
.paper-duo{ display:grid; grid-template-columns:1fr 1fr; gap:0 28px; margin-top:18px; }
.paper-story{ border-top:1px solid var(--prule); padding-top:12px; margin-top:14px; }
.paper-story h3{ font-size:18px; line-height:1.24; font-weight:700; margin:3px 0 5px; }
.paper-story p{ font-family:var(--font); font-size:12.5px; line-height:1.55; color:var(--pink2); margin:0; }

.paper-rail{ border-left:2.5px solid var(--pink); padding-left:22px; min-width:0; }
.paper-rail-h{
  font-family:var(--font); font-size:11px; font-weight:700; letter-spacing:.14em;
  text-transform:uppercase; border-bottom:1px solid var(--pink);
  padding-bottom:6px; margin-bottom:10px;
}
.paper-brief{ border-top:1px solid var(--prule); padding:9px 0 3px; }
.paper-brief:first-of-type{ border-top:none; padding-top:0; }
.paper-brief h4{ font-size:14.5px; line-height:1.3; margin:0 0 3px; font-weight:700; }

/* The source credit - a real link to the publisher's article. */
.paper-credit{ font-family:var(--font); font-size:10.5px; color:var(--pmut); margin-top:6px; }
.paper-credit a{ color:inherit; text-decoration:none; border-bottom:1px dotted var(--pmut); }
.paper-credit a:hover{ color:var(--pbrand); border-bottom-color:var(--pbrand); }
.paper-credit a:focus-visible{ outline:2px solid var(--pbrand); outline-offset:2px; border-radius:3px; }
.paper-credit b{ color:var(--pink2); font-weight:600; }

.paper-chips{ display:flex; gap:6px; flex-wrap:wrap; margin-top:10px; }
.paper-chip{
  font-family:var(--mono); font-size:11px; font-weight:700; cursor:pointer;
  color:var(--pbrand); background:rgba(29,79,208,.07);
  border:1px solid rgba(29,79,208,.25); border-radius:4px;
  min-height:24px; padding:0 9px;     /* WCAG 2.5.8 - 24px minimum target */
}
.paper-chip:hover{ background:rgba(29,79,208,.14); }
.paper-chip:focus-visible{ outline:2px solid var(--pbrand); outline-offset:2px; }

.paper-colophon{
  border-top:1px solid var(--prule); margin-top:26px; padding-top:10px;
  font-family:var(--font); font-size:10.5px; color:var(--pmut); text-align:center;
}
.paper .thinking{ padding:40px 0; color:var(--pmut); }

@media (max-width:900px){
  .paper-grid, .paper-duo{ grid-template-columns:1fr; }
  .paper-rail{ border-left:none; padding-left:0; border-top:2.5px solid var(--pink); padding-top:16px; margin-top:20px; }
  .paper-body, .paper-ears, .paper-masthead{ padding-left:18px; padding-right:18px; }
  .paper-dateline, .paper-tabs{ margin-left:18px; margin-right:18px; }
}

/* Docs feedback bar - two per page (top and bottom), not one per chapter. */
.docs-fb-top{ margin:0 0 22px; }
.docs-fb-bottom{ margin-top:30px; }

/* The publisher name under a news card - a link to the article, not a caption.
   Quiet by default so it reads as attribution; brand blue on hover so the hand
   knows before the click. */
.fd-src-link{ color:inherit; text-decoration:none; border-bottom:1px dotted currentColor; }
.fd-src-link:hover{ color:var(--brand-500); border-bottom-color:var(--brand-500); }
.fd-src-link:focus-visible{ outline:2px solid var(--brand-500); outline-offset:2px; border-radius:3px; }

/* =============================================================================
   Accessibility layer
   -----------------------------------------------------------------------------
   Added 2026-08-04 against WCAG 2.1 AA, which is what both the ADA (via DOJ
   guidance and Section 508) and the European Accessibility Act / EN 301 549
   point at. Deliberately LAST in the file so it wins without !important -
   these rules have to beat component styles, and a specificity war fought with
   !important is one nobody can debug later.
   ========================================================================== */

/* ---- 2.4.7 / 2.4.11 Focus Visible ------------------------------------------
   One ring for the whole app. Six `outline:none` declarations were deleted to
   make room for it; each had replaced the outline with a border-colour change,
   which is a colour-only signal and fails 1.4.1 as well.

   :focus-visible, not :focus - a mouse click on a button should not leave a
   ring behind it. The browser decides, which is the entire point of the
   pseudo-class. */
:focus-visible{
  outline:2px solid var(--brand-500);
  outline-offset:2px;
  border-radius:3px;
}
/* Inputs sit flush inside their container, so the ring goes inward or it is
   clipped by the parent's overflow. */
input:focus-visible, textarea:focus-visible, select:focus-visible{
  outline-offset:-1px;
}

/* ---- 2.4.1 Bypass Blocks ---------------------------------------------------
   Without this a keyboard user tabs through all 15 sidebar entries on every
   single page load before reaching anything they came for. */
.skip-link{
  position:absolute; left:-9999px; top:0; z-index:999;
  background:var(--brand-500); color:#fff;
  padding:10px 18px; border-radius:0 0 var(--radius) 0;
  font-size:13px; font-weight:700; text-decoration:none;
}
.skip-link:focus{ left:0; }

/* ---- 2.5.8 Target Size (Minimum) -------------------------------------------
   24x24 CSS pixels. The dismiss X on a notification was 18x18, and the compact
   chips were smaller still. */
.btn.sm, .btn.ghost.sm, .notif-del, .chx-label-x, .icon-btn, .paper-chip{
  min-width:24px; min-height:24px;
}
.notif-del{ display:inline-flex; align-items:center; justify-content:center; }

/* ---- 2.3.3 Animation from Interactions -------------------------------------
   The app flashes price cells green and red on every refresh, slides panels
   and animates the feed. For a reader with a vestibular disorder that is not
   decoration, it is a symptom trigger. One global rule beats remembering it
   per component - which we demonstrably did not: before today the whole
   stylesheet contained exactly one prefers-reduced-motion block. */
@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{
    animation-duration:.001ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
    scroll-behavior:auto !important;
  }
}

/* ---- 1.4.1 Use of Colour ---------------------------------------------------
   Screen-reader-only text, for the places where colour or position carries
   meaning that has no visible words. Clip-path rather than display:none:
   hidden elements are removed from the accessibility tree, which would defeat
   the purpose. */
.sr-only{
  /* top/left pin it. Without them an absolutely-positioned box with no offsets
     sits at its STATIC position — and #a11y-live is the last element in <body>,
     so its 1px box landed just past the bottom of the viewport and made the
     document 1px scrollable. On a desktop that is invisible. On a phone it is
     not: one scrollable pixel is enough for the browser to treat the page as
     scrollable and start collapsing its address bar, which is the exact thing
     the app-shell scroller above exists to prevent. Found by measuring
     documentElement.scrollHeight (813) against innerHeight (812). */
  position:absolute !important; top:0; inset-inline-start:0;
  width:1px; height:1px;
  padding:0; margin:-1px; overflow:hidden;
  clip:rect(0,0,0,0); clip-path:inset(50%); white-space:nowrap; border:0;
}

/* ---- 4.1.3 Status Messages -------------------------------------------------
   The live region announce() writes into. Present in the DOM from boot,
   because a live region added at the same moment as its text is not announced
   by most screen readers. */
#a11y-live{
  /* top/left, for the reason spelled out on .sr-only above: this is the LAST
     element in <body>, and an absolutely-positioned box with no offsets sits
     at its static position — so its single pixel landed just past the bottom
     of the viewport and made the whole document one pixel scrollable. One
     scrollable pixel is enough for a mobile browser to treat the page as
     scrollable and start collapsing its address bar, which moves the bottom
     tab bar. Measured: documentElement.scrollHeight 813 against an
     innerHeight of 812. */
  position:absolute !important; top:0; inset-inline-start:0;
  width:1px; height:1px;
  overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap;
}

/* ---- The segmented control (js/components/segmented.js) --------------------
   The "C + D" design: boxed options with counts, plus a sliding indicator.
   The grid is load-bearing - equal columns are what let the slider stay
   CSS-only once the counts make the labels different widths. */
.segctl{
  position:relative; display:grid; gap:0;
  background:var(--surface-2); border:1px solid var(--border-2);
  border-radius:var(--radius-sm); padding:4px; width:max-content; max-width:100%;
}
.segctl-2{ grid-template-columns:repeat(2,1fr); }
.segctl-3{ grid-template-columns:repeat(3,1fr); }
.segctl-4{ grid-template-columns:repeat(4,1fr); }
.segctl-5{ grid-template-columns:repeat(5,1fr); }
.segctl-6{ grid-template-columns:repeat(6,1fr); }
.segctl-7{ grid-template-columns:repeat(7,1fr); }
/* Eight is Admin, and only Admin. Eight equal columns sized to the widest
   label ("System Integrations") would be over 1000px and overflow the page, so
   this one wraps to two rows by design rather than by accident. Its slider is
   dropped below for the same reason the narrow-screen rule drops the others'. */
.segctl-8{ grid-template-columns:repeat(4,1fr); width:100%; }
.segctl input{ position:absolute; opacity:0; width:1px; height:1px; }
.segctl label{
  position:relative; z-index:1; cursor:pointer; user-select:none;
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  min-height:34px; padding:0 15px; border-radius:5px;   /* clears 2.5.8 */
  font-size:13px; font-weight:600; color:var(--ink-2);
  transition:color .18s ease; white-space:nowrap;
}
.segctl label:hover{ color:var(--ink); }
.segctl input:checked + label{ color:#fff; }
.segctl input:focus-visible + label{ outline:2px solid var(--brand-500); outline-offset:3px; }

.segctl-count{
  font-family:var(--mono); font-size:11px; font-weight:700;
  background:var(--surface-3); color:var(--ink-2);
  border-radius:999px; padding:1px 7px; transition:background .18s, color .18s;
}
.segctl input:checked + label .segctl-count{ background:rgba(255,255,255,.22); color:#fff; }

/* White on --brand-500 is 3.59:1 and fails 1.4.3. This one is 6.81:1. */
.segctl-slider{
  position:absolute; top:4px; bottom:4px; left:4px;
  background:#1d4fd0; border-radius:5px;
  transition:transform .22s cubic-bezier(.4,0,.2,1);
}
:root[data-theme="light"] .segctl-slider{ background:var(--brand-500); }  /* 5.17:1 */
.segctl-2 .segctl-slider{ width:calc((100% - 8px)/2); }
.segctl-3 .segctl-slider{ width:calc((100% - 8px)/3); }
.segctl-4 .segctl-slider{ width:calc((100% - 8px)/4); }
.segctl-5 .segctl-slider{ width:calc((100% - 8px)/5); }
.segctl-6 .segctl-slider{ width:calc((100% - 8px)/6); }
.segctl-7 .segctl-slider{ width:calc((100% - 8px)/7); }
.segctl:has(input:nth-of-type(2):checked) .segctl-slider{ transform:translateX(100%); }
.segctl:has(input:nth-of-type(3):checked) .segctl-slider{ transform:translateX(200%); }
.segctl:has(input:nth-of-type(4):checked) .segctl-slider{ transform:translateX(300%); }
.segctl:has(input:nth-of-type(5):checked) .segctl-slider{ transform:translateX(400%); }
.segctl:has(input:nth-of-type(6):checked) .segctl-slider{ transform:translateX(500%); }
.segctl:has(input:nth-of-type(7):checked) .segctl-slider{ transform:translateX(600%); }

/* The wrapped-grid treatment, factored out of the media query because .segctl-8
   needs it at every width, not only on phones. Selected state moves from the
   slider onto the label itself. */
.segctl-8 .segctl-slider{ display:none; }
.segctl-8 input:checked + label{ background:#1d4fd0; border-radius:5px; }
:root[data-theme="light"] .segctl-8 input:checked + label{ background:var(--brand-500); }

@media (max-width:560px){
  .segctl-4, .segctl-5, .segctl-6, .segctl-7{ grid-template-columns:repeat(2,1fr); width:100%; }
  .segctl-8{ grid-template-columns:repeat(2,1fr); }
  /* A sliding indicator across a wrapped grid points at the wrong option, and
     a wrong indicator is worse than none. */
  .segctl-4 .segctl-slider, .segctl-5 .segctl-slider,
  .segctl-6 .segctl-slider, .segctl-7 .segctl-slider{ display:none; }
  .segctl-4 input:checked + label, .segctl-5 input:checked + label,
  .segctl-6 input:checked + label, .segctl-7 input:checked + label{
    background:#1d4fd0; border-radius:5px;
  }
  :root[data-theme="light"] .segctl-4 input:checked + label,
  :root[data-theme="light"] .segctl-5 input:checked + label,
  :root[data-theme="light"] .segctl-6 input:checked + label,
  :root[data-theme="light"] .segctl-7 input:checked + label{ background:var(--brand-500); }
}

/* The legal footer at the foot of the user guide. */
.docs-legal{
  margin-top:26px; padding-top:14px; border-top:1px solid var(--border);
  font-size:12.5px; color:var(--ink-muted);
}
.docs-legal a{ color:var(--ink-2); }
.docs-legal a:hover{ color:var(--brand-500); }

/* The generated contents chapter (js/pages/docs.js buildContents). */
.docs-contents{ margin-top:6px; }
.docs-contents-part{
  font-size:10.5px; font-weight:800; text-transform:uppercase; letter-spacing:.11em;
  color:var(--ink-muted); margin:20px 0 8px; padding-bottom:5px;
  border-bottom:1px solid var(--border);
}
.docs-contents-part:first-child{ margin-top:0; }
.docs-contents-row{ margin-bottom:11px; }
.docs-contents-ch{
  display:flex; gap:10px; align-items:baseline;
  font-size:15px; font-weight:700; color:var(--ink); text-decoration:none;
  min-height:24px;                      /* WCAG 2.5.8 */
}
.docs-contents-ch:hover{ color:var(--brand-500); }
.docs-contents-num{
  font-family:var(--mono); font-size:12px; font-weight:700; color:var(--brand-500);
  background:var(--brand-50); border-radius:4px; padding:1px 6px; flex:none; min-width:30px; text-align:center;
}
.docs-contents-subs{ display:flex; flex-wrap:wrap; gap:6px 14px; margin:5px 0 0 40px; }
.docs-contents-subs a{
  font-size:12.5px; color:var(--ink-2); text-decoration:none;
  display:inline-flex; align-items:center; min-height:24px;
}
.docs-contents-subs a:hover{ color:var(--brand-500); }

/* A chapter with sub-sections, in the contents rail. The caret is the only
   thing telling a reader that fourteen of these chapters have a dozen entries
   folded inside them — so it has to actually read as a control.

   It used to be a bare "›" in --ink-muted at the edge of the row. That measures
   about 3.5:1 on dark, which scrapes past 1.4.11 for a graphic and looks like
   punctuation: at a glance it is indistinguishable from the kind of decorative
   chevron that sits at the end of every list row and means nothing. Nobody
   opened the chapters, which was the owner's report — 2026-08-06.

   The fix is a chip, not a brighter glyph. A shape with its own background is
   read as "this does something"; a coloured character is read as a character.
   The chip also gives the rotation something visible to rotate. */
.docs-toc a.has-subs{ position:relative; padding-right:28px; }
.docs-toc-caret{
  position:absolute; right:6px; top:50%;
  width:17px; height:17px; border-radius:5px;
  display:flex; align-items:center; justify-content:center;
  font-size:13px; font-weight:700; line-height:1;
  /* The glyph sits a hair right of centre inside its own box — "›" is drawn
     with its weight to the left. Nudged back rather than left looking off. */
  padding-right:1px;
  background:var(--surface-3); color:var(--ink);
  transform:translateY(-50%);
  transition:transform .18s ease, background .18s ease, color .18s ease;
}
/* #1d4fd0, not --brand-500. Measured while building this: white on
   --brand-500 is 3.59:1 on dark and fails 1.4.3 — the same number this
   codebase already records for the segmented control and the timeframe pills,
   which is why they use this fill too. #1d4fd0 measures 6.81:1. */
.docs-toc a.has-subs:hover .docs-toc-caret{ background:#1d4fd0; color:#fff; }
/* Points down, and fills, once the chapter's sections are showing — the two
   states have to differ by more than 90 degrees of a small grey arrow. */
.docs-toc-item.open > a.has-subs .docs-toc-caret{
  transform:translateY(-50%) rotate(90deg);
  background:#1d4fd0; color:#fff;
}
.docs-toc a.here .docs-toc-caret{ background:#1d4fd0; color:#fff; }
/* No light-theme override here, deliberately. #1d4fd0 measures 6.81:1 against
   white in BOTH themes, so unlike the segmented control — whose slider sits on
   a light track and needs --brand-500 to look right — this chip needs nothing
   swapped. A rule that changes no measured value is a rule that only adds
   places to look. */
@media (prefers-reduced-motion:reduce){
  .docs-toc-caret{ transition:none; }
}
/* The revealed list gets a rule down its left edge so it reads as belonging to
   the chapter above it rather than as more chapters. */
.docs-toc-item.open > .docs-toc-sub{
  border-left:2px solid var(--brand-100);
}

/* ------------------------------------------- Admin → Last update tab ----
   Reuses .source-table wholesale, so the two Admin tables read as one family.
   Only what the page-oriented view adds is defined here.

   .lu-note repeats .src-note's look because that rule is scoped to
   .source-table and the footnote sits outside the table. Copied rather than
   widened: broadening the original would restyle every stray element that
   happens to carry the class elsewhere. */
.source-table a.src-type{ color:var(--brand-600); text-decoration:none; }
.source-table a.src-type:hover{ text-decoration:underline; }
.src-actions{ display:flex; gap:8px; margin-top:12px; }
.lu-note{ font-size:11px; color:var(--ink-muted); margin-top:10px; max-width:70ch; }

/* ------------------------------------------- chart range bar (below plot) ----
   The Range control moved out of the crowded top toolbar to sit under the
   plot, left-aligned, on the owner's call: it is the control you reach for
   most often, and it belongs next to the thing it changes.

   width:max-content, NOT a stretched grid. .segctl is display:grid with equal
   columns, so left unbounded it would span the full chart width and turn seven
   short labels into seven enormous buttons. */
.chart-rangebar{ display:flex; justify-content:flex-start; padding:8px 0 2px; }

/* Made to match .chart-tf, the TIMEFRAME pills in the top toolbar, on the
   owner's call — two controls that pick one option out of several should not
   look like two different kinds of thing.

   Matched by RESTYLING the segmented control rather than by rebuilding it out
   of .chart-tf's plain <button>s. The buttons are not keyboard-navigable as a
   group; the radio group underneath this gives arrow keys, Home/End, a single
   tab stop and "3 of 7" announced, for free. Copying the look is cheap.
   Copying the accessibility failure is not. */
.chart-rangebar .segctl{
  display:inline-flex; width:max-content; gap:0; padding:0; background:transparent;
  border:1px solid var(--border-2); border-radius:var(--radius-sm); overflow:hidden;
}
/* The sliding indicator assumes equal-width grid columns and a padded track.
   Both are gone here, and .chart-tf has no such thing anyway — the fill sits
   flat on the selected pill. */
.chart-rangebar .segctl-slider{ display:none; }
.chart-rangebar .segctl label{
  border:none; border-inline-end:1px solid var(--border-2); border-radius:0;
  background:transparent; color:var(--ink-2);
  font-size:12px; font-weight:700; padding:5px 10px;
}
.chart-rangebar .segctl label:last-of-type{ border-inline-end:none; }
.chart-rangebar .segctl label:hover{ background:var(--surface-2); color:var(--ink); }

/* --brand-500 rather than the slider's #1d4fd0 would have been the literal
   match to .chart-tf, and white on it measures 3.59:1 in dark — below the
   4.5:1 minimum. Both controls now use the accessible fill, so they match each
   other AND pass. The light theme keeps --brand-500, which measures 5.17:1. */
.chart-rangebar .segctl input:checked + label,
.chart-tf button.active{ background:#1d4fd0; color:#fff; }
:root[data-theme="light"] .chart-rangebar .segctl input:checked + label,
:root[data-theme="light"] .chart-tf button.active{ background:var(--brand-500); }

/* -------------------------------------------- chart event legend (E / D) ----
   Sits under the Range bar and explains the badges ON the plot. The swatches
   reuse .chart-evt and its modifiers verbatim, so the palette is defined once:
   change a colour above and the chart and this key move together.

   Every swatch is paired with a word, so colour is never the only carrier
   (WCAG 1.4.1). It matters more than usual here — "beat" and "raised" are the
   same green, and only the letter and the label tell them apart.

   position:static overrides .chart-evt's absolute positioning, which exists to
   pin a badge to a date on the plot and is meaningless in a flowing row. */
/* Range + the E/D key, one row, pinned to the foot of the chart pane.
   `margin-top:auto` inside the pane's flex column is what makes it FIXED: the
   plot above absorbs every pixel an indicator panel adds, so turning RSI or
   MACD on no longer walks this row down the screen.

   It scrolls sideways rather than wrapping. A row that wraps is a row whose
   height changes with the window, which puts us straight back to the thing
   being fixed here. */
.chart-ws-footrow{
  margin-top:auto; flex:none;
  display:flex; align-items:center; gap:18px;
  padding:6px 6px 8px; overflow-x:auto; overflow-y:hidden;
  border-top:1px solid var(--border);
}
.chart-ws-footrow .chart-rangebar{ padding:0; flex:none; }
/* nowrap has to reach the GROUPS too, not just the legend. .chart-legend-group
   carries its own flex-wrap:wrap, so without this the row was one line made of
   things that were themselves three lines tall. */
.chart-ws-footrow .chart-legend{ padding:0; flex-wrap:nowrap; gap:0 18px; }
.chart-ws-footrow .chart-legend-group{ flex-wrap:nowrap; }
/* The RANGE caption above the buttons stacked the row to 53px. Inline here, so
   the whole strip is one line of controls rather than a captioned block. */
.chart-ws-footrow .ctl-wrap{ flex-direction:row; align-items:center; gap:8px; }
.chart-ws-footrow .ctl-label{ margin:0; }
/* The hint used to claim a whole line of its own (flex-basis:100%). Inline, and
   it is the first thing dropped when the row runs out of room. */
.chart-ws-footrow .chart-legend-note{ flex-basis:auto; white-space:nowrap; }

.chart-legend{
  display:flex; flex-wrap:wrap; align-items:center; gap:6px 20px;
  padding:6px 0 10px;
}
.chart-legend-group{ display:flex; flex-wrap:wrap; align-items:center; gap:5px 12px; }
.chart-legend-title{
  font-size:11px; font-weight:700; color:var(--ink-2);
  text-transform:uppercase; letter-spacing:.04em;
}
.chart-legend-item{
  display:inline-flex; align-items:center; gap:5px;
  font-size:12px; color:var(--ink-muted); white-space:nowrap;
}
.chart-legend .chart-evt{ position:static; transform:none; flex:none; }
.chart-legend-note{
  flex-basis:100%; margin:0; font-size:11px; color:var(--ink-muted);
}

/* Account deletion: the consequences, stated before the button is reachable. */
.admin-delete-damage{
  margin:12px 0 4px; padding:10px 12px; font-size:12.5px; line-height:1.55;
  border:1px solid var(--bad); border-radius:var(--radius-sm);
  background:color-mix(in srgb, var(--bad) 8%, transparent); color:var(--ink-2);
}
.admin-delete-damage ul{ margin:6px 0; padding-inline-start:20px; }
.admin-delete-damage li{ margin:2px 0; }
.admin-delete-damage b{ color:var(--ink); }

/* ---------------------------------------------------- backer badges -------
   js/components/backer-badge.js. Designs and the full reasoning live in
   mockups/campaign-badges.html; this is the shipped half.

   Sized in `em` off the badge's own font-size, so the mark can never end up a
   different size from the word beside it. The palette has no blue and no
   violet — blue is the app's brand colour and a tier wearing it reads as
   "official" rather than as a rung on a ladder. */
.tt-badge{
  display:inline-flex; align-items:center; gap:5px; vertical-align:middle;
  border-radius:999px; white-space:nowrap;
  font-size:10.5px; font-weight:800; letter-spacing:.01em;
  padding-inline:6px 8px; padding-block:2px;   /* logical, so RTL needs no rule */
  background:var(--tt-bg); color:var(--tt-ink); border:1px solid var(--tt-edge);
}
/* currentColor is what lets one sprite serve five tiers. */
.tt-badge svg{ width:1em; height:1em; flex:none; fill:currentColor; display:block; }
.tt-compact{ padding:3px; gap:0; }

.tt-early_believer  { --tt-bg:#1e2a2b; --tt-ink:#7fd4c4; --tt-edge:#2e4344; }
.tt-founding_member { --tt-bg:#2a1f14; --tt-ink:#f0a35e; --tt-edge:#44321f; }
.tt-founding_creator{ --tt-bg:#2b1420; --tt-ink:#f871a0; --tt-edge:#462134; }
.tt-design_partner  { --tt-bg:#16291a; --tt-ink:#7bd45c; --tt-edge:#26422a; }
.tt-patron          { --tt-bg:#2e2410; --tt-ink:#fbbf24; --tt-edge:#584010;
                      box-shadow:inset 0 1px 0 rgba(255,255,255,.14); }

:root[data-theme="light"] .tt-early_believer  { --tt-bg:#e2f4f0; --tt-ink:#0f6b5c; --tt-edge:#bde4dc; }
:root[data-theme="light"] .tt-founding_member { --tt-bg:#fdeedd; --tt-ink:#9a5514; --tt-edge:#f4d7b6; }
:root[data-theme="light"] .tt-founding_creator{ --tt-bg:#fce8ef; --tt-ink:#a4184f; --tt-edge:#f6c7d8; }
:root[data-theme="light"] .tt-design_partner  { --tt-bg:#e9f6e2; --tt-ink:#3d6b1f; --tt-edge:#c9e6ba; }
:root[data-theme="light"] .tt-patron          { --tt-bg:#fdf3d9; --tt-ink:#7d5708; --tt-edge:#f0ddac;
                                                box-shadow:inset 0 1px 0 rgba(255,255,255,.6); }

/* Below this the word goes and the mark stays. A feed row on a phone has no
   room for "Founding Creator" beside a name, and the mark plus its aria-label
   carries the same information. */
@media (max-width:560px){
  .tt-badge > span{ display:none; }
  .tt-badge{ padding:3px; gap:0; }
}

/* A table that is wider than its column scrolls instead of being clipped.
   -------------------------------------------------------------------------
   Shared, because this was solved twice already under two names — .pf-table-wrap
   on Portfolio and an overflow on Admin's .card — and the third table simply
   never got one. Measured at 375px, the Watchlists table ran 62px past the
   right edge with no gesture that reached it.

   `overscroll-behavior-x:contain` matters on a phone: without it, swiping past
   the end of the table hands the gesture to the page and navigates back in the
   browser, which is a far worse outcome than a table that stops. */
.table-scroll{
  overflow-x:auto; overscroll-behavior-x:contain;
  -webkit-overflow-scrolling:touch;
}

/* Admin → Users: the tier picker and a live preview of what it awards. */
.admin-tier{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
