/* Penny Trading Dashboard — design tokens and component classes.

   Typography note: this deliberately uses the platform UI font stack rather
   than fetching Inter from a CDN. A webfont @import here was render-blocking
   on a third-party host, broke the dashboard's typography entirely when
   offline, added latency over a tunnel, and leaked a request per visitor —
   all for a face the system stack already approximates closely. */

:root {
  --color-bg: #161826;
  --color-surface: #232532;
  --color-text: #e9e9ed;
  --color-accent: #9184d9;
  --color-accent-2: #a7a1db;
  --color-divider: color-mix(in srgb, #e9e9ed 16%, transparent);

  --color-neutral-100: #f3f5fe;
  --color-neutral-200: #e4e7f5;
  --color-neutral-300: #cfd3e5;
  --color-neutral-400: #b2b6ca;
  --color-neutral-500: #9397ab;
  --color-neutral-600: #75798c;
  --color-neutral-700: #595d6c;
  --color-neutral-800: #3f424d;
  --color-neutral-900: #292b31;

  --color-accent-100: #f5f4ff;
  --color-accent-200: #e7e5fe;
  --color-accent-300: #d2cefd;
  --color-accent-400: #b5abfc;
  --color-accent-500: #968ae0;
  --color-accent-600: #796cbf;
  --color-accent-700: #5d5294;
  --color-accent-800: #423a6a;
  --color-accent-900: #2b2741;

  --color-positive: oklch(74% 0.14 155);
  --color-negative: oklch(70% 0.16 25);
  --color-warning: oklch(80% 0.14 85);

  --font-ui: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-heading: var(--font-ui);
  --font-heading-weight: 600;
  --font-body: var(--font-ui);
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  --space-1: 2.8px;
  --space-2: 5.6px;
  --space-3: 8.4px;
  --space-4: 11.2px;
  --space-6: 16.8px;
  --space-8: 22.4px;

  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 14px;

  --shadow-sm: 0 0 0 1px #3f424d;
  --shadow-md: 0 0 0 1px #595d6c, 0 6px 18px rgba(0,0,0,0.55);
  --shadow-lg: 0 0 0 1px #9397ab, 0 16px 40px rgba(0,0,0,0.65);
}

@keyframes pulse-dot { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }

::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: var(--color-neutral-800); border-radius: 8px; }

*, *::before, *::after { box-sizing: border-box; }
/* .page owns the scrolling (see below) and needs a genuine height, not just
   a minimum, to bound it — which means html/body need a real height to
   inherit from too, or "100%" here resolves to nothing. */
html, body { height: 100%; }
body {
  margin: 0; font-size: 15px; line-height: 1.55; font-weight: 400;
  background: var(--color-bg); color: var(--color-text); font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  line-height: 1.12; letter-spacing: -0.015em; margin: 0 0 var(--space-2);
}
h1 { font-size: 28px; }
h4 { font-size: 20px; margin-bottom: 12px; }
a { color: var(--color-accent); text-underline-offset: 3px; }
code { font-family: var(--font-mono); font-size: 0.92em; }
.text-muted { color: color-mix(in srgb, var(--color-text) 55%, transparent); }
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
::selection { background: color-mix(in srgb, var(--color-accent) 30%, transparent); }
.hidden { display: none !important; }

/* Visually hidden but still announced by screen readers. */
.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;
}

/* — buttons — */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  cursor: pointer; text-decoration: none;
  font-family: var(--font-heading); font-weight: 500;
  font-size: 13px; line-height: 1.2; color: var(--color-text);
  background: transparent; border: 1px solid transparent;
  padding: var(--space-2) calc(var(--space-3) * 1.2);
  border-radius: var(--radius-md);
}
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-primary { color: var(--color-accent); border-color: var(--color-accent); }
.btn-primary:hover:not(:disabled) { background: color-mix(in srgb, var(--color-accent) 12%, transparent); }
.btn-secondary { border-color: var(--color-divider); }
.btn-secondary:hover:not(:disabled) { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.btn-sm { font-size: 12px; padding: 2px 8px; }

/* — forms — */
.input {
  width: 100%; min-height: 36px; padding: 6px 10px; font: inherit;
  font-size: 14px; color: var(--color-text); caret-color: var(--color-accent);
  background: var(--color-surface);
  border: 1px solid var(--color-divider); border-radius: var(--radius-md);
}
.input:hover { border-color: color-mix(in srgb, var(--color-text) 45%, transparent); }
.input:focus-visible { border-color: var(--color-accent); outline-offset: 0; }
.input-inline { width: auto; }
input[type="search"].input-inline { min-width: 150px; }

.controls { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* — cards — */
.card {
  display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-3); border-radius: var(--radius-md); background: var(--color-surface);
}
.card-kicker { font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-accent); }
.card-title { font-family: var(--font-heading); font-weight: var(--font-heading-weight); font-size: 17px; line-height: 1.2; }
.stat-value { font-size: 26px; font-variant-numeric: tabular-nums; }
.card-meta { display: flex; align-items: center; gap: 6px; font-size: 11px; color: color-mix(in srgb, var(--color-text) 50%, transparent); }
.elev-sm { box-shadow: var(--shadow-sm); }
.elev-md { box-shadow: var(--shadow-md); }

/* — tags — */
.tag { display: inline-flex; align-items: center; font-size: 11px; letter-spacing: 0.02em; padding: 3px 10px; border-radius: calc(var(--radius-md) * 0.75); }
.tag-accent { background: var(--color-accent-800); color: var(--color-accent-100); }
.tag-neutral { background: var(--color-neutral-800); color: var(--color-neutral-100); }
.tag-outline { border: 1px solid var(--color-accent); color: var(--color-accent); }

/* — navigation — */
/* Three columns so the clock can sit at true dead-center regardless of how
   wide the left (tabs) or right (status) groups are — a plain flex row can
   only center something if both sides happen to match in width. */
.nav {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  gap: var(--space-4); border-bottom: 1px solid var(--color-divider); padding: 14px 28px;
}
.nav-left { display: flex; align-items: center; gap: var(--space-4); justify-self: start; }
.nav-right { display: flex; align-items: center; gap: 10px; justify-self: end; }

/* Every nav link (including the PENNY brand link, which doubles as the
   Overview tab) reserves its hover/active indicator as a fixed-width
   transparent border from the start, so turning it visible on hover never
   changes the link's box size — nothing else in the bar shifts. A prior
   version used a font-weight jump for this, which resized the text and
   pushed every link after it sideways. */
.nav a {
  color: inherit; text-decoration: none; font-size: 14px;
  border-bottom: 2px solid transparent; padding-bottom: 2px; line-height: 1.2;
}
.nav a:hover, .nav a.active { color: var(--color-accent); border-bottom-color: var(--color-accent); }
.nav a.nav-brand {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 18px; letter-spacing: 0.04em;
}

.nav-clock {
  justify-self: center;
  font-family: var(--font-heading); font-variant-numeric: tabular-nums;
  font-size: 19px; font-weight: 700; color: var(--color-text);
}

/* Today's realised P&L, sat just right of the clock. First item in the right
   group so it reads as adjacent to the clock without pushing the clock off
   the centre column it deliberately occupies. */
.nav-pnl { display: inline-flex; align-items: baseline; gap: 6px; }
.nav-pnl-label { font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: color-mix(in srgb, var(--color-text) 45%, transparent); }
.nav-pnl-value { font-family: var(--font-heading); font-weight: 600; font-size: 14px; font-variant-numeric: tabular-nums; }

/* Connection state. The dot only pulses while genuinely connected — a
   dashboard that looks live while its socket is down is worse than one
   that plainly says it is stale. */
.conn-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; flex: none; }
.conn-dot[data-state="live"] { background: var(--color-positive); animation: pulse-dot 1.6s ease-in-out infinite; }
.conn-dot[data-state="connecting"] { background: var(--color-warning); }
.conn-dot[data-state="down"] { background: var(--color-negative); }
/* The dot lives inside the tracked-count tag now, pinned to its right edge. */
#trackedTag { gap: 6px; }

/* Timezone toggle. Unpressed = local time (the default); pressed = NY
   exchange time, called out in accent color so it reads as "on". */
.tz-toggle {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border-radius: var(--radius-md);
  border: 1px solid var(--color-divider); background: transparent;
  color: color-mix(in srgb, var(--color-text) 70%, transparent);
  font-family: var(--font-heading); font-weight: 500; font-size: 12px;
  cursor: pointer;
}
.tz-toggle:hover { border-color: color-mix(in srgb, var(--color-text) 45%, transparent); color: var(--color-text); }
.tz-toggle[aria-pressed="true"] {
  border-color: var(--color-accent); color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 12%, transparent);
}
.tz-icon { width: 14px; height: 14px; flex: none; }

/* — banner — */
.banner {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 8px 28px; font-size: 13px;
  background: color-mix(in srgb, var(--color-negative) 18%, var(--color-bg));
  border-bottom: 1px solid color-mix(in srgb, var(--color-negative) 45%, transparent);
}

/* — tables — */
.table { width: 100%; border-collapse: collapse; font-size: 14px; }
.table th {
  text-align: left; font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
  color: color-mix(in srgb, var(--color-text) 60%, transparent);
  padding: var(--space-2); border-bottom: 1px solid var(--color-divider);
  background: var(--color-bg);
}
.table td { padding: var(--space-2); border-bottom: 1px solid color-mix(in srgb, var(--color-text) 8%, transparent); }
.table tbody tr:hover { background: color-mix(in srgb, var(--color-text) 4%, transparent); }
.table .num { font-variant-numeric: tabular-nums; }

.table th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
.table th.sortable:hover { color: var(--color-text); }
.table th.sortable.sort-active { color: var(--color-accent); }
.table th .sort-arrow { display: inline-block; width: 10px; font-size: 9px; margin-left: 2px; }

/* — scrollable lists — */
.scroll-list { max-height: 60vh; overflow-y: auto; border-radius: var(--radius-md); }
.scroll-list .table thead th { position: sticky; top: 0; z-index: 1; }
.scroll-list .live-grid { padding-right: 2px; }
.load-more-row { display: flex; justify-content: center; }

/* Live Trades only: its scroll area fills whatever vertical space is left in
   the viewport instead of capping at a fixed 60vh like the other tables.
   Needs a matching flex chain all the way up (.page -> .content -> this
   section), with min-height:0 at each level so the flex items are actually
   allowed to shrink below their content size and hand overflow to the inner
   .scroll-list rather than growing past the viewport.

   That in turn needs .page to be a genuinely bounded box (height, not just
   min-height) — otherwise "flex:1, fill remaining space" has no upper limit
   to fill and everything just keeps growing with the content instead of
   clipping. .page owns the scrolling itself so other tabs (whose content can
   still validly exceed one screen) keep scrolling exactly as before; only
   Live's inner list is meant to be the thing that scrolls instead of the
   whole page. */
#tab-live.stack { flex: 1; min-height: 0; }
#tab-live .scroll-list { flex: 1; min-height: 0; max-height: none; }

/* — layout — */
.page { height: 100vh; overflow-y: auto; display: flex; flex-direction: column; }
/* min-height:0 overrides the flex-item default of "auto", which otherwise
   inflates .content to fit its tallest possible child (an unbounded Live
   Trades grid) and defeats the whole point of bounding .page above. */
.content { flex: 1; min-height: 0; display: flex; flex-direction: column; padding: 28px 28px 48px; max-width: 1280px; width: 100%; margin: 0 auto; }
.stack { display: flex; flex-direction: column; gap: 22px; }
.row-between { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.subhead { font-size: 13px; margin: 0; color: color-mix(in srgb, var(--color-text) 55%, transparent); }
.stat-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 14px; }
.live-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 16px; }
.trade-card-closed { opacity: 0.72; }
/* A closed card opens its trade dialog, so it reads as pressable. */
.trade-card.row-clickable:hover { opacity: 1; box-shadow: var(--shadow-lg); }

/* Planned entries aren't positions — outlined rather than filled, so they read
   as pending at a glance instead of competing with live P&L cards. */
.trade-card-planned {
  background: transparent;
  border: 1px dashed color-mix(in srgb, var(--color-accent) 45%, transparent);
  box-shadow: none;
}
.planned-price { font-family: var(--font-heading); font-size: 24px; font-weight: 600; font-variant-numeric: tabular-nums; }
.trade-detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; font-size: 12px; }
.trade-card-head { display: flex; align-items: center; justify-content: space-between; }
.trade-card-sym { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.trade-symbol { font-family: var(--font-heading); font-size: 19px; font-weight: 600; }
.trade-pnl-row { display: flex; align-items: baseline; gap: 12px; }
.trade-pnl { font-size: 30px; font-weight: 600; font-family: var(--font-heading); font-variant-numeric: tabular-nums; }
.trade-pnl-pct { font-size: 14px; font-variant-numeric: tabular-nums; }
.detail-label { margin-bottom: 2px; }
.detail-value { font-family: var(--font-heading); font-variant-numeric: tabular-nums; }
.tp-head { display: flex; justify-content: space-between; font-size: 11px; margin-bottom: 6px; }
.tp-row { display: flex; gap: 4px; }
.tp-seg-wrap { flex: 1; display: flex; flex-direction: column; gap: 6px; }
.tp-seg { height: 6px; border-radius: 3px; background: var(--color-neutral-800); }
.tp-seg.filled { background: var(--color-accent-400); }
.tp-seg-label { font-size: 11px; color: color-mix(in srgb, var(--color-text) 70%, transparent); font-variant-numeric: tabular-nums; }
.tp-seg-label.filled { color: var(--color-accent-300); }
.grace-note { font-size: 11px; }
.empty-state { padding: 32px; text-align: center; }
.row-clickable { cursor: pointer; }

/* — dialog — */
.dialog-backdrop {
  position: fixed; inset: 0; z-index: 100; display: grid; place-items: center;
  padding: var(--space-4);
  background: color-mix(in srgb, var(--color-neutral-900) 55%, transparent);
}
.dialog {
  width: min(480px, 100%); display: flex; flex-direction: column; gap: var(--space-3);
  padding: var(--space-4); border-radius: var(--radius-lg);
  background: var(--color-surface); box-shadow: var(--shadow-lg);
}
.dialog-wide { width: min(720px, 100%); max-height: 90vh; overflow-y: auto; }
.dialog-title { font-family: var(--font-heading); font-weight: var(--font-heading-weight); font-size: 20px; }
.dialog-body { font-size: 14px; }
.dialog-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-2); }

/* — trade dialog — */
/* Chart on the left, the trade's story on the right. Padding lives on the
   inner panels rather than .dialog so the header rule and the column divider
   can run edge to edge. */
.trade-dialog {
  width: min(1060px, 100%); max-height: 92vh; padding: 0; gap: 0; overflow: hidden;
  background: linear-gradient(180deg, #1e2031, #191b28);
}
.trade-dialog-head {
  display: flex; align-items: center; gap: 24px;
  padding: 14px 18px 14px 22px; border-bottom: 1px solid var(--color-divider);
}
.trade-dialog-ident { display: flex; flex-direction: column; gap: 3px; }
.trade-dialog-symline { display: flex; align-items: center; gap: 9px; }
.trade-dialog-symbol { font-family: var(--font-heading); font-size: 22px; font-weight: 500; line-height: 1; letter-spacing: -0.015em; }
.trade-dialog-when { font-size: 11px; font-variant-numeric: tabular-nums; color: color-mix(in srgb, var(--color-text) 50%, transparent); }
.trade-dialog-x { margin-left: auto; padding: 6px; line-height: 0; }

.trade-dialog-body { display: grid; grid-template-columns: minmax(0, 1fr) 300px; min-height: 0; overflow: hidden; }
.trade-dialog-chart { padding: 16px 20px 18px; border-right: 1px solid var(--color-divider); min-width: 0; overflow-y: auto; }
.trade-dialog-side { display: flex; flex-direction: column; min-height: 0; overflow-y: auto; }
.trade-dialog-pnlblock { padding: 16px 20px 14px; border-bottom: 1px solid var(--color-divider); }
.trade-dialog-pnl { font-family: var(--font-heading); font-size: 30px; line-height: 1.15; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.trade-dialog-metrics { display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 8px; font-size: 12px; font-variant-numeric: tabular-nums; }
.trade-dialog-metrics .m-label { color: color-mix(in srgb, var(--color-text) 50%, transparent); margin-right: 4px; }
.trade-dialog-flow { padding: 16px 20px 20px; display: flex; flex-direction: column; gap: 12px; }

/* One row per path event, oldest first. Stop moves are indented and smaller
   than fills — they're context for the fills around them, not peers. */
.flow-list { display: flex; flex-direction: column; gap: 10px; }
.flow-row { display: grid; grid-template-columns: 14px minmax(0, 1fr) auto; gap: 0 10px; align-items: start; font-variant-numeric: tabular-nums; }
.flow-row.is-stop { margin-left: 16px; }
.flow-dot { width: 7px; height: 7px; border-radius: 50%; margin: 6px 0 0 3px; }
.flow-row.is-stop .flow-dot { width: 5px; height: 5px; margin-top: 6px; }
.flow-title { font-size: 14px; line-height: 1.25; }
.flow-row.is-stop .flow-title { font-size: 12px; color: color-mix(in srgb, var(--color-text) 72%, transparent); }
.flow-sub { font-size: 11px; color: color-mix(in srgb, var(--color-text) 50%, transparent); line-height: 1.3; }
.flow-row.is-stop .flow-sub { font-size: 10px; }
.flow-right { font-size: 11px; white-space: nowrap; }
.flow-open .flow-title { color: var(--color-accent-300); }

/* — trade chart — */
.chart-block { margin-top: 16px; border-top: 1px solid var(--color-divider); padding-top: 12px; }
.chart-canvas { min-height: 40px; }
.chart-toolbar { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.chart-rule { flex: 1; height: 1px; background: linear-gradient(to right, var(--color-divider), transparent); }
.chart-legend { font-size: 11px; display: flex; gap: 14px; flex-wrap: wrap; color: color-mix(in srgb, var(--color-text) 55%, transparent); }
.chart-legend .lg { display: inline-flex; align-items: center; gap: 5px; }
.chart-legend b { font-weight: 500; }
.chart-note { font-size: 11px; margin-top: 10px; }
.chart-empty { padding: 24px 0; text-align: center; font-size: 12px; }

@media (max-width: 1100px) {
  .stat-grid { grid-template-columns: repeat(3, 1fr); }
  /* Scanner's six columns are already tight here — drop the two least
     load-bearing before the layout starts squeezing prices. */
  #tab-scanner .table th:nth-child(4), #tab-scanner .table td:nth-child(4),
  #tab-scanner .table th:nth-child(5), #tab-scanner .table td:nth-child(5) { display: none; }
}
@media (max-width: 900px) {
  /* 300px of sidebar next to a chart stops being readable here — stack them
     and let the dialog itself scroll. */
  .trade-dialog-body { grid-template-columns: minmax(0, 1fr); overflow-y: auto; }
  .trade-dialog-chart { border-right: none; border-bottom: 1px solid var(--color-divider); overflow-y: visible; }
  .trade-dialog-side { overflow-y: visible; }
}
@media (max-width: 760px) {
  .content { padding: 20px 16px 40px; }
  /* The 3-column grid gets cramped once the left group wraps, so fall back
     to a plain wrapping flex row: tabs+toggle and status stay on the first
     line, the clock drops to its own full-width line below (still
     centered) rather than fighting them for space. */
  .nav { display: flex; flex-wrap: wrap; align-items: center; padding: 12px 16px; row-gap: 8px; }
  .nav-left { flex-wrap: wrap; row-gap: 8px; }
  .nav-right { margin-left: auto; } /* justify-self is a grid-only property, ignored once .nav is flex here */
  .nav-clock { order: 3; width: 100%; text-align: center; }
  .banner { padding: 8px 16px; }
  .live-grid { grid-template-columns: 1fr; }
}
@media (max-width: 560px) {
  .stat-grid { grid-template-columns: 1fr; }
  .trade-detail-grid { grid-template-columns: 1fr 1fr; }

  /* Scanner is dense at 6 columns — phones only need the ticker + status. */
  #tab-scanner .table th:nth-child(n+2):nth-child(-n+5),
  #tab-scanner .table td:nth-child(n+2):nth-child(-n+5) { display: none; }
}

/* ─────────────────────────────────────────────────────────────────────────
   Login

   Its own body class rather than reusing .page: the sign-in screen is one
   centred card with nothing to scroll, so it wants the viewport centred
   rather than .page's scroll container.
   ───────────────────────────────────────────────────────────────────────── */
.login-body { display: grid; place-items: center; padding: var(--space-6); }
.login-shell { width: 100%; max-width: 380px; display: flex; flex-direction: column; gap: var(--space-4); }
.login-card { gap: var(--space-6); padding: var(--space-8); }
.login-brand { display: flex; flex-direction: column; gap: var(--space-1); }
.login-mark {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 26px; letter-spacing: 0.12em;
}
.login-blurb { font-size: 13px; margin: 0; }
.login-field { display: flex; flex-direction: column; gap: var(--space-2); }
.login-submit { width: 100%; min-height: 38px; }
.login-error {
  margin: 0; font-size: 13px; color: var(--color-negative);
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-negative) 12%, transparent);
}
.login-foot { font-size: 11px; text-align: center; margin: 0; }

/* ─────────────────────────────────────────────────────────────────────────
   Control tab — ts-server bot orchestration

   Reuses the dashboard's card/tag/button vocabulary rather than inventing a
   second one: the operator is looking at one site, and a control panel that
   looked like a different product would be the tell that it was bolted on.
   ───────────────────────────────────────────────────────────────────────── */
.ctl-accounts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(420px, 1fr)); gap: 16px;
  /* Cards size to their own content. Without this a grid row stretches
     every card to match its tallest sibling, so an empty account renders
     as a large box of nothing next to a busy one. */
  align-items: start;
}
.ctl-account { gap: var(--space-4); }
.ctl-account-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); }
.ctl-account-tags { display: flex; gap: var(--space-2); flex-wrap: wrap; }
.ctl-account-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; padding-top: var(--space-2); border-top: 1px solid var(--color-divider); }

.ctl-bots { display: flex; flex-direction: column; gap: var(--space-2); }
.ctl-bot {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--space-1) var(--space-4);
  align-items: center; padding: var(--space-3);
  border: 1px solid var(--color-divider); border-radius: var(--radius-md);
}
.ctl-bot-ident { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.ctl-bot-name { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ctl-bot-sub { font-size: 11px; font-family: var(--font-mono); }
/* Meta sits under the name, actions stay on the right across both rows. */
.ctl-bot-meta { grid-column: 1; font-size: 11px; }
.ctl-bot-actions { grid-column: 2; grid-row: 1 / span 2; display: flex; gap: var(--space-2); flex-wrap: wrap; justify-content: flex-end; }

/* Same shape as the header's connection dot, different source of truth: this
   one is a bot process, not a socket. */
.ctl-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.ctl-dot[data-state="running"] { background: var(--color-positive); animation: pulse-dot 1.6s ease-in-out infinite; }
.ctl-dot[data-state="stopped"] { background: var(--color-neutral-600); }

.ctl-tag-good { background: color-mix(in srgb, var(--color-positive) 22%, transparent); color: var(--color-positive); }
.ctl-tag-bad { background: color-mix(in srgb, var(--color-negative) 20%, transparent); color: var(--color-negative); }

.ctl-empty { padding: var(--space-3); font-size: 13px; }
.ctl-empty-card { padding: var(--space-8); text-align: center; font-size: 13px; }

.ctl-session { gap: var(--space-2); }
.ctl-session-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; font-size: 13px; }

/* Destructive actions get their own colour. Everything on this tab moves real
   money, so "delete" must not look like "cancel". */
.btn-danger { color: var(--color-negative); border-color: color-mix(in srgb, var(--color-negative) 60%, transparent); }
.btn-danger:hover:not(:disabled) { background: color-mix(in srgb, var(--color-negative) 14%, transparent); }

/* — control dialogs — */
.ctl-dialog { width: min(520px, 100%); gap: var(--space-4); }
.ctl-dialog-narrow { width: min(420px, 100%); }
.ctl-dialog-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); }
.ctl-dialog-head h4 { margin: 0; }
.ctl-dialog-note { font-size: 12px; margin: 0; line-height: 1.5; }
.ctl-dialog-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-2); }
.ctl-form-fields { display: flex; flex-direction: column; gap: var(--space-4); }
.ctl-textarea { min-height: 120px; font-family: var(--font-mono); font-size: 12px; resize: vertical; }
.ctl-check { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; cursor: pointer; }

/* — log viewer — */
.ctl-log-dialog { width: min(1000px, 100%); max-height: 88vh; }
.ctl-log-ident { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
.ctl-log-title { font-family: var(--font-heading); font-weight: var(--font-heading-weight); font-size: 16px; }
.ctl-log-body {
  margin: 0; padding: var(--space-4); flex: 1; min-height: 320px; max-height: 65vh;
  overflow: auto; white-space: pre-wrap; word-break: break-word;
  font-family: var(--font-mono); font-size: 12px; line-height: 1.5;
  background: var(--color-bg); border-radius: var(--radius-md);
  border: 1px solid var(--color-divider);
}

@media (max-width: 720px) {
  .ctl-accounts { grid-template-columns: 1fr; }
  .ctl-bot { grid-template-columns: 1fr; }
  .ctl-bot-actions { grid-column: 1; grid-row: auto; justify-content: flex-start; }
}
.login-warning {
  margin: 0; font-size: 12px; line-height: 1.5; color: var(--color-warning);
  padding: var(--space-3); border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-warning) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-warning) 40%, transparent);
}
