/* ---------------------------------------------------------------------------
   Palette. Every colour the dashboard uses is a role token declared here, so
   light/dark swap in one place. Values come from the validated reference
   palette: categorical slots 1 (blue), 2 (orange), 3 (aqua) and the blue<->red
   diverging pair with a neutral gray midpoint.
   --------------------------------------------------------------------------- */
:root,
:root[data-theme='dark'] {
  color-scheme: dark;

  --surface-1: #1a1a19;
  --page: #0d0d0d;
  --text-primary: #ffffff;
  --text-secondary: #c3c2b7;
  --text-muted: #898781;
  --gridline: #2c2c2a;
  --baseline: #383835;
  --border: rgba(255, 255, 255, 0.1);

  /* Entities keep the same hue everywhere on the page. */
  --series-up: #3987e5;    /* slot 1 - blue   */
  --series-down: #d95926;  /* slot 2 - orange */
  --series-btc: #199e70;   /* slot 3 - aqua   */
  /* The TWAP is a derived view of --series-btc, not a fourth entity: muted
     violet, drawn dashed, so it never competes with the price it averages. */
  --series-twap: #9c92e6;
  /* The locally rebuilt average: the same violet stepped back toward the
     background, drawn dotted. It sits on top of --series-twap whenever both
     are healthy, so it has to stay legible without ever winning that overlap. */
  --series-twap-local: #6f68a8;

  /* Diverging: bid-heavy <-> ask-heavy */
  --diverge-pos: #3987e5;
  --diverge-neg: #e66767;
  --diverge-mid: #383835;

  --status-good: #0ca30c;
  --status-warning: #fab219;
  --status-critical: #d03b3b;
  --success-text: #0ca30c;

  /* Wash behind the row whose window is on screen. */
  --row-selected: rgba(57, 135, 229, 0.2);

  --radius: 10px;
  --font: system-ui, -apple-system, 'Segoe UI', sans-serif;
}

:root[data-theme='light'] {
  color-scheme: light;
  --surface-1: #fcfcfb;
  --page: #f9f9f7;
  --text-primary: #0b0b0b;
  --text-secondary: #52514e;
  --text-muted: #898781;
  --gridline: #e1e0d9;
  --baseline: #c3c2b7;
  --border: rgba(11, 11, 11, 0.1);
  --series-up: #2a78d6;
  --series-down: #eb6834;
  --series-btc: #1baf7a;
  --series-twap: #7a6fd0;
  --series-twap-local: #a79fe0;
  --diverge-pos: #2a78d6;
  --diverge-neg: #e34948;
  --diverge-mid: #f0efec;
  --success-text: #006300;
  --row-selected: rgba(42, 120, 214, 0.1);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--page);
  color: var(--text-primary);
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.45;
}

.wrap {
  max-width: 1440px;
  margin: 0 auto;
  padding: 20px 20px 56px;
}

/* ------------------------------------------------------------------ header */

.topbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-bottom: 18px;
}

.topbar h1 {
  font-size: 17px;
  font-weight: 600;
  margin: 0;
  letter-spacing: -0.01em;
}

.topbar .sub {
  color: var(--text-secondary);
  font-size: 13px;
  margin-right: auto;
}

.pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-1);
  font-size: 12px;
  color: var(--text-secondary);
  white-space: nowrap;
}

.pill .dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-muted);
  flex: none;
}

.pill.ok .dot { background: var(--status-good); }
.pill.bad .dot { background: var(--status-critical); }
.pill.warn .dot { background: var(--status-warning); }

button.ghost {
  font: inherit;
  font-size: 12px;
  color: var(--text-secondary);
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 12px;
  cursor: pointer;
}
button.ghost:hover { color: var(--text-primary); }
button.ghost[disabled] { opacity: 0.55; cursor: progress; }

/* The stop switch: red while the bot is armed, green once it is stopped --
   the colour is the action on offer, not the state. */
button.ghost.danger { color: var(--status-critical); border-color: var(--status-critical); }
button.ghost.danger:hover {
  color: var(--status-critical);
  background: color-mix(in srgb, var(--status-critical) 14%, transparent);
}
button.ghost.go { color: var(--success-text); border-color: var(--status-good); }
button.ghost.go:hover {
  color: var(--success-text);
  background: color-mix(in srgb, var(--status-good) 14%, transparent);
}

/* ------------------------------------------------------------ hero + tiles */

.hero-row {
  display: grid;
  /* Two heroes -- the price and the average it settles against -- then the
     four tiles that support them. */
  grid-template-columns: repeat(2, minmax(225px, 1.2fr)) repeat(4, minmax(115px, 0.72fr));
  gap: 12px;
  margin-bottom: 14px;
}

/* TWAP off: the second hero is not rendered at all, so the row goes back to
   the one-hero split rather than leaving a column of air behind it. */
.hero-row.no-twap {
  grid-template-columns: minmax(260px, 1.15fr) repeat(4, minmax(130px, 1fr));
}

.card {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
}

.card h2 {
  margin: 0 0 2px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-secondary);
  letter-spacing: 0.01em;
}

.card .hint {
  margin: 0 0 10px;
  font-size: 11.5px;
  color: var(--text-muted);
}

/* Sized against its own card, so "$118,234.12" fits whichever share of the row
   the grid hands the hero -- full width on a narrow screen, half of it when the
   price and its TWAP sit side by side. */
.hero { container-type: inline-size; }

.hero .label {
  font-size: 12px;
  color: var(--text-secondary);
  display: flex;
  align-items: center;
  gap: 7px;
}

.hero .value {
  font-size: clamp(28px, 14cqw, 48px);
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -0.02em;
  margin-top: 2px;
}

/* Which way the average is heading, in the hero's label row. Tinted rather
   than filled: it sits directly above the price, and a solid block of colour
   up there reads as an alert. `flat` is deliberately the quiet one -- it is
   the resting state, and it should not compete with the two that mean
   something is happening. */
.dir-chip {
  margin-left: auto;
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  border: 1px solid transparent;
  color: var(--text-muted);
  margin-top:5px;
}

.dir-chip .rate { font-weight: 500; opacity: 0.85; }

.dir-chip.rising {
  color: var(--success-text);
  background: color-mix(in srgb, var(--success-text) 12%, transparent);
  border-color: color-mix(in srgb, var(--success-text) 30%, transparent);
}

.dir-chip.dropping {
  color: var(--status-critical);
  background: color-mix(in srgb, var(--status-critical) 12%, transparent);
  border-color: color-mix(in srgb, var(--status-critical) 30%, transparent);
}

.dir-chip.flat {
  color: var(--text-secondary);
  background: var(--surface-1);
  border-color: var(--border);
}

.hero .delta { font-size: 13px; color: var(--text-secondary); margin-top: 2px; }
.hero .foot { font-size: 11.5px; color: var(--text-muted); margin-top: 3px; }

/* The touch-the-open risk meter, under the TWAP hero's delta. It is a risk
   figure, so it is drawn in the diverging pair's danger end as it grows, and
   never competes with the price above it: one line of type and a 4px bar. */
.hero .touch { margin-top: 8px; }

.hero .touch-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  font-size: 12px;
  color: var(--text-secondary);
}

.hero .touch-value {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--touch-ink, var(--text-primary));
}

.hero .meter {
  position: relative;
  margin-top: 4px;
  height: 4px;
  border-radius: 2px;
  background: var(--gridline);
  overflow: hidden;
}

/* Settlement risk, notched into the touch fill rather than drawn as a second
   bar: the two are the same quantity measured at two moments, and stacking
   them would read as two unrelated figures. It cuts in the tile's own
   background, so it reads as a gap in the fill at any --touch-ink. */
.hero .meter b {
  position: absolute;
  top: 0;
  height: 100%;
  width: 2px;
  /* The left edge is the value; -1px centres the notch on it, and the 100%
     case would otherwise hang half outside the bar. */
  margin-left: -1px;
  background: var(--surface-1);
  transition: left 0.6s ease;
}

/* The bot's own bar. Drawn as a full-height rule in the text colour rather than
   as a cut in the fill: it is not a measurement of the market like the other
   two marks, it is where the gate sits, and the thing worth reading at a glance
   is whether the settlement notch is left of it. */
.hero .meter u {
  position: absolute;
  top: 0;
  height: 100%;
  width: 1px;
  margin-left: -0.5px;
  background: var(--text-secondary);
  transition: left 0.6s ease;
}

.hero .meter i {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 2px;
  background: var(--touch-ink, var(--text-muted));
  /* One second between recomputes, so the bar walks to the new value instead
     of snapping -- a jump reads as new information, and most ticks are not. */
  transition: width 0.6s ease, background-color 0.6s ease;
}

.hero .touch-foot {
  font-size: 11.5px;
  color: var(--text-muted);
  margin-top: 3px;
  font-variant-numeric: tabular-nums;
}

.tile .label { font-size: 12px; color: var(--text-secondary); }
.tile .value {
  font-size: 26px;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin-top: 4px;
}
.tile .foot { font-size: 11.5px; color: var(--text-muted); margin-top: 2px; }

.key-dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex: none;
}
.key-line {
  display: inline-block;
  width: 14px;
  height: 2px;
  border-radius: 1px;
  flex: none;
}

/* The prediction key. A triangle, because the mark on the chart is one: a
   round swatch would promise the dot the candles' own direction wears, and
   these are annotation rather than price. Hueless on purpose -- the arrow
   takes Up blue or Down orange from the call it is making, so the key can
   only stand for the shape. */
.key-tri {
  display: inline-block;
  width: 0;
  height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-bottom: 8px solid var(--text-muted);
  flex: none;
}

/* The models' standing, under the candle card's copy: how many answered, when
   they were last read, and which is ahead on this store's own history. One
   line of muted type -- it is a footnote to the chart, not a panel. */
#candle-models {
  font-size: 11.5px;
  color: var(--text-muted);
  margin: -4px 0 8px;
  min-height: 14px;
}

.up-ink { color: var(--success-text); }
.down-ink { color: var(--status-critical); }

/* --------------------------------------------------------------- filterbar */

.filterbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
}

.segmented {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: 999px;
  overflow: hidden;
  background: var(--surface-1);
}

.segmented button {
  font: inherit;
  font-size: 12px;
  padding: 5px 12px;
  border: 0;
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
}
.segmented button[aria-pressed='true'] {
  background: var(--text-primary);
  color: var(--surface-1);
}

.filterbar label { font-size: 12px; color: var(--text-muted); }

select {
  font: inherit;
  font-size: 12px;
  padding: 5px 10px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-1);
  color: var(--text-primary);
  /* Wide enough for a whole option -- stamp, market name and status -- so the
     closed select doesn't clip the name mid-word. Still shrinks on narrow
     viewports, where the filter bar wraps anyway. */
  max-width: min(100%, 560px);
}

/* ------------------------------------------------------------------ charts */

.grid-1 {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 12px;
  margin-bottom: 12px;
}

.grid-2 {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  margin-bottom: 12px;
}

.grid-3 {
  display: grid;
  /* The history card carries the widest text on the page (a full market name
     per row), so it gets the extra width rather than the two ladders. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.55fr);
  gap: 12px;
  margin-bottom: 12px;
}

.chart-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 2px;
}
.chart-head h2 { margin: 0; }

/* A control living in a card heading rather than the page filter bar (the
   history date picker). Pushed to the trailing edge so the heading still reads
   as the heading. */
.head-filter {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 6px;
}
.head-filter label { font-size: 11px; color: var(--text-muted); }

input[type='date'] {
  font: inherit;
  font-size: 12px;
  padding: 3px 10px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-1);
  color: var(--text-primary);
  /* :root already declares color-scheme per theme, so the native calendar
     popup and the clear/spin affordances follow the page rather than staying
     light on a dark card. */
}
input[type='date']:focus-visible { outline: 2px solid var(--diverge-pos); outline-offset: 1px; }

.legend {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  margin-left: auto;
  font-size: 11.5px;
  color: var(--text-secondary);
}
.legend span { display: inline-flex; align-items: center; gap: 6px; }
/* An author `display` outranks the UA's `[hidden] { display: none }`, so the
   keys the page hides -- the TWAP ones, on a window recorded without it --
   have to be told a second time or they sit there naming a line that is not
   on the chart. */
.legend span[hidden] { display: none; }

.plot { width: 100%; display: block; touch-action: none; }
.plot text { font-family: var(--font); }

.tooltip {
  position: fixed;
  z-index: 40;
  pointer-events: none;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 12px;
  color: var(--text-primary);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
  min-width: 148px;
  opacity: 0;
  transition: opacity 0.08s;
}
.tooltip.on { opacity: 1; }
.tooltip .tt-head {
  color: var(--text-muted);
  font-size: 11px;
  margin-bottom: 5px;
  font-variant-numeric: tabular-nums;
}
.tooltip .tt-row {
  display: flex;
  align-items: center;
  gap: 7px;
  justify-content: space-between;
}
.tooltip .tt-row b {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  margin-left: auto;
}
.tooltip .tt-name { display: inline-flex; align-items: center; gap: 6px; color: var(--text-secondary); }

/* The prediction drill-down inside a candle's tooltip. Ruled off rather than
   spaced off: above it are this window's own four prices, below it is what a
   dozen models made of them, and the two are different kinds of fact. */
.tooltip .tt-split {
  margin: 7px 0 4px;
  padding-top: 6px;
  border-top: 1px solid var(--border);
  color: var(--text-muted);
  font-size: 11px;
}
/* A model's own words under its verdict. Wraps, because a reason is a
   sentence and not a number, and the tooltip is the only place it fits. */
.tooltip .tt-sub {
  margin: -1px 0 4px;
  font-size: 10.5px;
  line-height: 1.35;
}
.tooltip .tt-sub .tt-name {
  display: block;
  color: var(--text-muted);
  max-width: 260px;
}

/* -------------------------------------------------------------------- book */

.ladder { width: 100%; font-size: 12px; border-collapse: collapse; }
.ladder th {
  text-align: right;
  font-weight: 500;
  color: var(--text-muted);
  font-size: 11px;
  padding: 0 6px 5px;
}
.ladder th:first-child { text-align: left; }
.ladder td {
  padding: 2px 6px;
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--text-secondary);
  position: relative;
}
.ladder td:first-child { text-align: left; color: var(--text-primary); font-weight: 500; }
.ladder .bar {
  position: absolute;
  inset: 2px auto 2px 0;
  border-radius: 0 4px 4px 0;
  opacity: 0.16;
}
.ladder tr.bid .bar { background: var(--diverge-pos); }
.ladder tr.ask .bar { background: var(--diverge-neg); }
.ladder .mid td {
  border-top: 1px solid var(--baseline);
  border-bottom: 1px solid var(--baseline);
  color: var(--text-muted);
  font-size: 11px;
  padding: 4px 6px;
}

/* ---------------------------------------------------------------- history */

/* Unfiltered this holds 24 rows and never scrolls; a filtered day is up to 288
   (one per 5 minutes), which would otherwise stretch the card far past the two
   ladders beside it. The cap is about the height of a full ladder, so the row
   of three cards stays level. */
#history { max-height: 420px; overflow-y: auto; }

.history { width: 100%; border-collapse: collapse; font-size: 12px; }
.history th {
  text-align: left;
  font-weight: 500;
  color: var(--text-muted);
  font-size: 11px;
  padding: 0 8px 6px;
  border-bottom: 1px solid var(--gridline);
  /* Stays put while a long day scrolls under it. */
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--surface-1);
}
.history td {
  padding: 5px 8px;
  border-bottom: 1px solid var(--gridline);
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
}
.history tr:last-child td { border-bottom: 0; }
.history tr[data-window] { cursor: pointer; }
.history tr[data-window]:hover td { background: var(--page); }

/* The window currently on the charts. An inset rule marks it instead of a
   border so the row's text doesn't shift by 2px when the selection moves. */
.history tr.selected td { background: var(--row-selected); }
.history tr.selected:hover td { background: var(--row-selected); }
.history tr.selected td:first-child { box-shadow: inset 2px 0 0 var(--diverge-pos); }
.history tr.selected td.name { font-weight: 600; }

.history td.name { color: var(--text-primary); font-variant-numeric: normal; }
.history td.name .stamp { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Polymarket's name for the window, under its start stamp. Every row repeats
   the same prefix, so it reads as the secondary line it is rather than
   competing with the numbers to its right. */
.history td.name .market {
  display: block;
  margin-top: 1px;
  font-size: 11px;
  font-weight: 400;
  color: var(--text-muted);
}
.history .tag {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: 10.5px;
  border: 1px solid var(--border);
  color: var(--text-secondary);
}
.history .tag.live { border-color: var(--status-good); color: var(--success-text); }

/* ------------------------------------------------------------ tracked wallet */

/* A quiet caption in a card head -- what the tracker is watching, beside the
   title rather than stealing a line from the table. */
.head-note {
  font-size: 11.5px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* Two swatches, one label: a wallet mark takes the colour of the outcome it
   traded, so its legend key has to show both rather than invent a third hue.
   They sit as a pair, which the legend's own 6px gap would break up. */
.legend span.key-wallet { gap: 2px; }
.key-wallet .key-dot + .key-dot { margin-right: 4px; }

/* The TWAP key is a dash, not a dot -- the line on the chart is dashed, and a
   round swatch would promise a solid stroke that isn't there. */
.key-dash {
  display: inline-block;
  width: 14px;
  height: 2px;
  flex: none;
}

/* About a dozen rows before it scrolls, so the card can't run away with the
   page when a busy wallet fills it. Seven columns is wider than a phone, so
   the overflow is both ways -- the page itself must never scroll sideways. */
#wallet { max-height: 340px; overflow: auto; }

.wallet { width: 100%; border-collapse: collapse; font-size: 12px; }
.wallet th {
  text-align: right;
  font-weight: 500;
  color: var(--text-muted);
  font-size: 11px;
  padding: 0 8px 6px;
  border-bottom: 1px solid var(--gridline);
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--surface-1);
  white-space: nowrap;
}
.wallet th:first-child, .wallet th:nth-child(2) { text-align: left; }
.wallet td {
  padding: 5px 8px;
  text-align: right;
  border-bottom: 1px solid var(--gridline);
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
  white-space: nowrap;
}
.wallet tr:last-child td { border-bottom: 0; }
.wallet tr[data-wtx] { cursor: pointer; }
.wallet tr[data-wtx]:hover td { background: var(--page); }
/* A transaction whose window was never recorded is not a link. The row is
   complete either way, so it is not dimmed -- the context tag already says
   what is missing, and greying the row would claim more than that. */
.wallet tr.inert { cursor: default; }

/* The window this row would load is already on the charts. */
.wallet tr[aria-current='true'] td { background: var(--row-selected); }
.wallet tr[aria-current='true'] td:first-child { box-shadow: inset 2px 0 0 var(--diverge-pos); }

.wallet td.when, .wallet td.act { text-align: left; }
.wallet td.when { color: var(--text-primary); }
/* The full date + time is one unit; wrapping it mid-stamp would read as two
   different times stacked. */
.wallet td.when .stamp { font-variant-numeric: tabular-nums; white-space: nowrap; }
.wallet td.when .into {
  display: block;
  font-size: 11px;
  color: var(--text-muted);
}
.wallet td.act { color: var(--text-primary); font-variant-numeric: normal; }
.wallet td.act .usd { color: var(--text-muted); font-variant-numeric: tabular-nums; }

/* Polarity ink for the imbalance column, matching the diverging chart's poles
   rather than the good/bad status palette -- bid-heavy is not "good". */
.bid-ink { color: var(--diverge-pos); }
.ask-ink { color: var(--diverge-neg); }

.wallet .tag {
  display: inline-block;
  padding: 1px 6px;
  margin-right: 4px;
  border-radius: 999px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  border: 1px solid var(--border);
  color: var(--text-secondary);
}
/* Direction of the trade. Buying is the wallet taking a position, which is the
   event this whole card exists for, so it is the one that carries a hue. */
.wallet .tag.buy { border-color: var(--diverge-pos); color: var(--diverge-pos); }
.wallet .tag.sell { border-color: var(--diverge-neg); color: var(--diverge-neg); }

/* How the market context was obtained. `none` is a gap in the record and has
   to look like one; the rest are just provenance and stay recessive. */
.wallet .tag.src { font-weight: 500; letter-spacing: 0; text-transform: none; }
.wallet .tag.src.none { border-style: dashed; color: var(--text-muted); }

.empty {
  color: var(--text-muted);
  font-size: 12.5px;
  padding: 26px 0;
  text-align: center;
}

/* ========================================================================= */
/* The bot                                                                   */
/* ========================================================================= */

/* Five even tiles: the bot row has no hero, so nothing claims extra width. */
.bot-row {
  grid-template-columns: repeat(4, minmax(110px, 0.75fr)) repeat(2, minmax(185px, 1.25fr));
}

/* Signal and Position carry a sentence, not a figure, so they read at text
   size rather than at the 26px a single number gets. */
.tile.wide .value { font-size: 17px; line-height: 1.25; }

.tile .value.flat { color: var(--text-muted); }
.tile .value.armed { color: var(--success-text); }
/* The signal cleared, but something else refused the trade -- neither the
   green of an order going out nor the grey of nothing to do. */
.tile .value.held { color: var(--status-warning); }

/* --------------------------------------------------------- signal readout */

.sig {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  font-variant-numeric: tabular-nums;
}
.sig .sig-side {
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  font-size: 13px;
}
.sig .sig-side.up { color: var(--series-up); }
.sig .sig-side.down { color: var(--series-down); }
.sig .sig-gate {
  font-size: 12px;
  color: var(--text-muted);
  font-variant-numeric: normal;
}

/* ---------------------------------------------------------------- tables */

/* Every list on the page -- fills, traded windows, orders, skips, history --
   is the same table, so it is styled once. */
#fills, #botwindows, #orders, #skips, #history { max-height: 240px; overflow: auto; }
#botwindows { max-height: 420px; }

table.rows {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
table.rows th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--surface-1);
  text-align: right;
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  padding: 0 8px 6px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
table.rows td {
  text-align: right;
  padding: 6px 8px;
  border-bottom: 1px solid var(--gridline);
  color: var(--text-secondary);
  white-space: nowrap;
}
table.rows th:first-child,
table.rows td:first-child,
table.rows th.l,
table.rows td.l { text-align: left; }
table.rows tr:last-child td { border-bottom: 0; }
table.rows tr[data-window] { cursor: pointer; }
table.rows tr[data-window]:hover td { background: var(--page); }
table.rows tr[aria-current='true'] td { background: var(--row-selected); }
table.rows tr[aria-current='true'] td:first-child {
  box-shadow: inset 2px 0 0 var(--diverge-pos);
}
table.rows td.when { color: var(--text-primary); }
table.rows td.when .stamp { white-space: nowrap; }
table.rows td.when .into {
  color: var(--text-muted);
  font-size: 11px;
  margin-left: 6px;
}
table.rows td.name {
  color: var(--text-primary);
  font-variant-numeric: normal;
  max-width: 320px;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ------------------------------------------------------------------- tags */

.tag {
  display: inline-block;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 1px 6px;
  border-radius: 999px;
  border: 1px solid var(--border);
  color: var(--text-secondary);
  vertical-align: 1px;
}
.tag.up   { border-color: var(--series-up);   color: var(--series-up); }
.tag.down { border-color: var(--series-down); color: var(--series-down); }
.tag.win  { border-color: var(--status-good); color: var(--success-text); }
.tag.loss { border-color: var(--status-critical); color: var(--status-critical); }
.tag.live { border-color: var(--status-good); color: var(--success-text); }
.tag.open { border-style: dashed; color: var(--text-muted); }
.tag.maker { border-style: dashed; }
.tag.taker { border-color: var(--diverge-neg); color: var(--diverge-neg); }

/* Order-log actions. Placing and filling are things going right; cancelling
   and rejecting are the bot backing out, and they read differently. */
.tag.place  { border-color: var(--diverge-pos); color: var(--diverge-pos); }
.tag.amend  { color: var(--text-muted); }
.tag.cancel { border-color: var(--status-warning); color: var(--status-warning); }
.tag.fill   { border-color: var(--status-good); color: var(--success-text); }
.tag.reject { border-color: var(--status-critical); color: var(--status-critical); }
.tag.settle { border-color: var(--text-secondary); color: var(--text-secondary); }

.bid-ink { color: var(--diverge-pos); }
.ask-ink { color: var(--diverge-neg); }

.head-note {
  font-size: 11px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* A skip reason is a share of the time inside the entry band; the bar behind
   the row is that share, so the dominant reason is visible without reading. */
.skipbar {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--diverge-pos);
  opacity: 0.1;
  border-radius: 3px;
}
table.rows td.reason {
  position: relative;
  color: var(--text-primary);
  font-variant-numeric: normal;
}
table.rows td.reason span { position: relative; }

/* A stand-down is not a gate: the signal was good and something downstream
   refused it. Those rows sort to the top and carry the warning colour, so a
   window that was held back never reads like a window with nothing in it. */
table.rows tr.stood td.reason { color: var(--status-warning); }
table.rows tr.stood .skipbar { background: var(--status-warning); opacity: 0.16; }
.tag.stood { border-color: var(--status-warning); color: var(--status-warning); margin-left: 6px; }

/* --------------------------------------------------------------- backtest */

.nav {
  display: inline-flex;
  gap: 2px;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 2px;
}
.nav a {
  font-size: 12px;
  font-weight: 500;
  padding: 3px 12px;
  border-radius: 999px;
  color: var(--text-secondary);
  text-decoration: none;
}
.nav a:hover { color: var(--text-primary); }
.nav a[aria-current='page'] {
  background: var(--diverge-pos);
  color: #fff;
  font-weight: 600;
}

.gates { margin-bottom: 12px; }
.gategrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 8px 12px;
}
.gategrid label {
  display: flex;
  flex-direction: column;
  gap: 3px;
  font-size: 11px;
  color: var(--text-muted);
}
.gategrid input,
.gategrid select {
  font: inherit;
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  padding: 4px 7px;
  border-radius: 7px;
  border: 1px solid var(--border);
  background: var(--surface-1);
  color: var(--text-primary);
}
.gategrid input:focus-visible,
.gategrid select:focus-visible {
  outline: 2px solid var(--diverge-pos);
  outline-offset: 1px;
}
.gateactions {
  display: flex;
  gap: 8px;
  margin-top: 12px;
}

.compare-row { grid-template-columns: repeat(5, minmax(0, 1fr)); }

#rows { max-height: 560px; overflow: auto; }
#models, #reasons { overflow-x: auto; }

/* A run's parameters are prose, not figures: they wrap rather than scroll. */
#params table.rows td { white-space: normal; }
#params table.rows td:first-child {
  width: 100px;
  color: var(--text-muted);
  vertical-align: top;
}
#params code { font-size: 11.5px; word-break: break-all; }

/* The per-window table pairs two actors over the same window, so the header is
   two rows: who, then what. The group rule is what keeps a strategy column from
   being read as a wallet one. */
table.rows th.group {
  text-align: center;
  font-size: 10px;
  letter-spacing: 0.08em;
  padding-bottom: 2px;
}
table.rows th.group.strategy { color: var(--series-btc); }
table.rows th.group.wallet   { color: var(--series-up); }
table.rows td.sep, table.rows th.sep { border-left: 1px solid var(--gridline); }
table.rows td.dim, table.rows .dim { color: var(--text-muted); }
/* The wallet is not a fourth model -- it is what happened. Its own rule. */
table.rows tr.sep-top td { border-top: 1px solid var(--baseline); }

.tag.agree { border-color: var(--status-good); color: var(--success-text); }
.tag.opposed { border-color: var(--status-critical); color: var(--status-critical); }
.tag.solo { border-style: dashed; color: var(--text-muted); }

/* The headline model is the one driving the tiles, the chart and the table;
   the other two stay legible but recede. */
table.rows tr.model-on td { background: var(--row-selected); font-weight: 600; }

.runbanner {
  font-size: 12.5px;
  color: var(--text-secondary);
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-1);
  margin-bottom: 12px;
}
.runbanner.bad { border-color: var(--status-critical); color: var(--status-critical); }

@media (max-width: 1100px) {
  /* .no-twap is the more specific selector, so it has to be named here too or
     the five-column desktop split would survive the breakpoint. */
  .hero-row,
  .hero-row.no-twap { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .hero-row .hero { grid-column: 1 / -1; }
  .bot-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .compare-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid-2, .grid-3 { grid-template-columns: minmax(0, 1fr); }
}

/* ------------------------------------------------------------------ replay */

/* The transport for playing a closed window back. It sits between the window
   picker and the charts it drives, because that is the order the two are used
   in: pick the window, then play it. */
.replay { margin-bottom: 14px; }

.replay-transport {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}

/* The scrubber takes whatever the buttons leave, down to a width where the
   handle is still worth dragging; below that the row wraps instead. */
.replay-transport input[type='range'] {
  flex: 1 1 220px;
  min-width: 160px;
  accent-color: var(--diverge-pos);
  cursor: pointer;
}
.replay-transport input[type='range'][disabled] { opacity: 0.4; cursor: default; }

.replay-clock {
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
  white-space: nowrap;
}
.replay-clock b { color: var(--text-primary); font-weight: 600; }

/* One row of small readouts under the transport: the whole decision at the
   instant on the clock, so the window can be read without looking away from
   the thing being scrubbed. */
.replay-hud {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px;
  border-top: 1px solid var(--border);
  padding-top: 12px;
}
/* `display` here beats the UA rule behind the `hidden` attribute, so the panel
   needs telling twice or it survives its own hiding. */
.replay-hud[hidden] { display: none; }

.rcell { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.rlabel { font-size: 11px; color: var(--text-secondary); }
.rvalue {
  font-size: 15px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.rvalue.rmuted, .rvalue .rmuted {
  color: var(--text-muted);
  font-weight: 400;
  font-size: 13px;
}
.rfoot { font-size: 11px; color: var(--text-muted); }
