:root {
  --bg: #0f1218;
  --panel: #1a1f2a;
  --panel-2: #232a39;
  --border: #2a3242;
  --text: #d6dce8;
  --muted: #8a93a6;
  --accent: #4f9eff;
  --green: #36c46f;
  --teal: #38b2ac;
  --red: #ef5350;
  --gray: #6b7280;
  --warn: #f5a623;
}

* { box-sizing: border-box; }
/* The `hidden` HTML attribute must always win, even on elements that
   declare a custom `display` (grid/flex/etc) further down. Without
   this, .login-view / .trader-view / .modal-backdrop with hidden=true
   would still render. */
[hidden] { display: none !important; }
html, body { height: 100%; margin: 0; }
body {
  background: var(--bg);
  color: var(--text);
  font: 13px/1.4 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
  /* Fase 1 (#397). The app-shell (topbar + tablist) is now a body-level
     sibling of each view, not nested inside trader-view. Stack them so
     the shell pins to the top and the active view fills the remaining
     viewport height. */
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
.view { flex: 1 1 auto; min-height: 0; }
/* App-shell topbar pinned at the top; views render below it. */
#app-shell { flex: 0 0 auto; }
.trader-view { display: flex; flex-direction: column; }

/* ---------- Login ---------- */
.login-view { display: grid; place-items: center; }
.login-card {
  background: var(--panel);
  border: 1px solid var(--border);
  padding: 2rem;
  border-radius: 8px;
  width: min(360px, 90vw);
  display: flex; flex-direction: column; gap: .9rem;
}
.login-card h1 { margin: 0; font-size: 1.3rem; }
.login-card .subtitle { margin: 0; color: var(--muted); font-size: .85rem; }
.login-card label { display: grid; gap: .25rem; font-size: .8rem; color: var(--muted); }
.login-card label.row { font-size: .7rem; }
.login-card input, .login-card select {
  background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
  border-radius: 4px; padding: .5rem .6rem; font: inherit;
}
.login-card button {
  background: var(--accent); color: #0b1320; border: 0; padding: .6rem; border-radius: 4px;
  font-weight: 600; cursor: pointer; font: inherit;
}
.login-card button:hover { filter: brightness(1.1); }
.login-card button[disabled] { opacity: .6; cursor: not-allowed; filter: none; }
.login-advanced { font-size: .75rem; color: var(--muted); }
.login-advanced summary { cursor: pointer; padding: .15rem 0; user-select: none; }
.login-advanced summary:hover { color: var(--text); }
.login-advanced > .row { margin-top: .5rem; }
.error { color: var(--red); font-size: .85rem; margin: 0; }
.login-switch {
  font-size: .8rem; color: var(--muted); margin: .2rem 0 0; text-align: center;
}
.login-switch .link-button {
  background: none; color: var(--accent); border: 0; padding: 0; cursor: pointer;
  font: inherit; text-decoration: underline;
}
.login-switch .link-button:hover { filter: brightness(1.2); }

/* ---------- Trader shell ---------- */
/* `min-height: 100vh` + no overflow trap: at viewports tall enough to
   fit all four grid rows the layout still feels "fit-to-viewport", but
   when the minimum row sizes exceed the viewport (small laptops,
   browser zoom, dense fonts) the page scrolls vertically instead of
   trapping content under the topbar. The previous `height: 100vh;
   overflow: hidden` swallowed the bottom row (Executions Log + Market
   Data) and let the Tape panel paint over the Blotter. */
/* `.trader-view` flex layout moved earlier (next to body shell rules)
   in Fase 1 (#397) — keep the comment above for context. */
.topbar {
  display: flex; justify-content: space-between; align-items: center;
  padding: .6rem 1rem; background: var(--panel); border-bottom: 1px solid var(--border);
}
.brand { font-weight: 600; }
.pill {
  background: #2a3a55; color: #cfe1ff; padding: .1rem .5rem;
  border-radius: 999px; font-size: .7rem; margin-left: .4rem;
}
.status { display: flex; align-items: center; gap: .8rem; }
.status-pill {
  padding: .15rem .55rem; border-radius: 999px; font-size: .75rem;
  text-transform: uppercase; letter-spacing: .04em;
}
.status-connected    { background: rgba(54,196,111,.18); color: var(--green); }
.status-connecting   { background: rgba(245,166,35,.18); color: var(--warn); }
.status-disconnected { background: rgba(239,83,80,.18);  color: var(--red); }
/* `not_ready` means the WebSocket handshake completed but the server
   declared the upstream feed unavailable (gateway down, snapshot
   broker stalled). Distinct from `connecting` (link still negotiating):
   reconnecting alone won't fix it — operator/server action is needed.
   #342: subdued brick red so the trader reads it as "advisory error",
   not the warm cyan/blue we used previously which read too close to
   the in-progress `connecting` tone. */
.status-not_ready    { background: rgba(217,122,74,.18); color: #d97a4a; }
.user-label { color: var(--muted); }
/* #385. Live cash balance pill in the topbar. Same muted palette as
   the other status badges so it doesn't fight for attention with the
   WS / market-data pills; switches to a warning tone when the
   available balance goes negative. */
.user-balance {
  display: inline-flex; align-items: center;
  padding: .15rem .5rem;
  border: 1px solid var(--border); border-radius: 4px;
  background: var(--panel-2); color: var(--text);
  font-variant-numeric: tabular-nums;
  font-size: .85rem;
}
.user-balance.balance-negative {
  background: rgba(217,122,74,.18);
  color: #d97a4a;
  border-color: rgba(217,122,74,.35);
}
.symbol-select {
  display: inline-flex; align-items: center; gap: .35rem;
  font-size: .72rem; color: var(--muted); text-transform: uppercase;
  letter-spacing: .04em;
}
.symbol-select select {
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 4px;
  padding: .15rem .35rem; font-size: .85rem; text-transform: none; letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}
.symbol-tag {
  display: inline-block; margin-left: .35rem;
  padding: 0 .35rem; border-radius: 4px;
  background: var(--panel-2); color: var(--text);
  font-size: .75rem; font-weight: 500; font-variant-numeric: tabular-nums;
  border: 1px solid var(--border);
}
.symbol-tag--muted { color: var(--muted); }
/* #342: DOB sub-header with mid + spread. Same tabular-nums alignment
 * as the symbol tag so the numbers don't dance on each update. */
.dob-spread {
  display: inline-block; margin-left: .5rem;
  font-size: .72rem; font-weight: 400; font-variant-numeric: tabular-nums;
  color: var(--muted);
}
.tape-all-toggle {
  display: inline-flex; align-items: center; gap: .25rem;
  font-size: .7rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em;
}
.tape-all-toggle input { margin: 0; }
.link-button {
  background: transparent; border: 0; color: var(--accent); cursor: pointer; font: inherit;
}

/* ---------- Trader layout (Fase 4 / #400) ----------
   Vertical shell stacking the Quick ticket strip, the sub-tab band
   (Markets / Watchlist / Auctions) and the lower-band (Working orders
   / Executions) tabbed panel. Replaced the legacy 3×4 named grid so
   the trader never shows more than 4 panels at the same time. */
.trader-layout {
  flex: 1;
  display: flex; flex-direction: column;
  gap: 8px; padding: 8px; min-height: 0;
}
.panel {
  background: var(--panel); border: 1px solid var(--border); border-radius: 6px;
  padding: .8rem; display: flex; flex-direction: column;
  min-height: 0; min-width: 0;
}
.panel > table,
.panel .table-scroll {
  display: block; overflow-x: auto; max-width: 100%;
}
.panel h2 {
  margin: 0 0 .6rem 0; font-size: .85rem; font-weight: 600; color: var(--muted);
  text-transform: uppercase; letter-spacing: .06em;
  display: flex; justify-content: space-between; align-items: center;
}
.panel h2 .count {
  background: var(--panel-2); color: var(--text); border-radius: 999px;
  padding: 0 .5rem; font-size: .7rem;
}
/* Cancel-all panic button (T3). Lives in the blotter h2. Subdued
   until clicked — bright enough to find quickly under stress, not so
   bright the trader hits it by accident. Hidden via [hidden] when
   there's nothing to cancel. */
.cancel-all-btn {
  background: transparent; color: #ff6a6a;
  border: 1px solid #c53030; border-radius: 4px;
  padding: .15rem .55rem; font-size: .7rem; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; cursor: pointer;
  line-height: 1.4;
}
.cancel-all-btn:hover { background: rgba(197, 48, 48, .15); }
.cancel-all-btn:disabled { opacity: .5; cursor: not-allowed; }
.blotter h2 .blotter-title { display: inline-flex; align-items: center; gap: .5rem; }
/* Stale-data overlay (T2). Dim the data area but keep the header
   readable so the trader can still see the panel title + the bright
   "stale" tag explaining why interaction is risky. The data zone is
   pointer-events:none so accidental clicks on frozen rows can't
   issue actions during a flap. */
.panel--stale > table,
.panel--stale .table-scroll,
.panel--stale .executions-log,
.panel--stale .dob-grid,
.panel--stale .chart-canvas,
.panel--stale .tape-list {
  opacity: .5; pointer-events: none; transition: opacity .15s ease-in;
}
.stale-tag {
  margin-left: .5rem; padding: 0 .5rem;
  background: var(--warn, #c97a1a); color: #fff;
  border-radius: 999px; font-size: .7rem; font-weight: 600;
  letter-spacing: .04em; text-transform: none; line-height: 1.4;
}
/* Fase 4 (#400). The legacy `grid-template-areas` rules previously laid
   out every trader panel in a fixed 3×4 grid; the new shell instead
   stacks ticket → sub-panel → bottom-band vertically (see
   `.trader-layout` below), so panels only need to opt into overflow
   behaviour, not a grid slot. */
.ticket     { overflow-y: auto; overflow-x: hidden; min-width: 0; }
.blotter    { min-height: 0; }
.executions { min-height: 0; }
.market-data .empty { color: var(--muted); font-size: .8rem; }
.md-h2-controls { display: inline-flex; gap: .25rem; align-items: center; }

/* #71: Volume heatmap. Compact grid of cells living at the top of the
 * Market data panel; cell colour encodes rolling 10s volume normalised
 * by the per-render global max. Click a cell → selectedSymbol. */
.heatmap-panel {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(72px, 1fr));
  gap: 4px; margin-bottom: .55rem;
}
.heatmap-empty {
  grid-column: 1 / -1; font-size: .75rem; margin: 0;
}
.heatmap-cell {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: .15rem;
  padding: .35rem .4rem; min-height: 44px;
  background: rgba(255,255,255,.04);
  color: var(--text);
  border: 1px solid var(--border); border-radius: 4px;
  font: inherit; cursor: pointer; text-align: left;
  transition: transform .08s ease-out;
}
.heatmap-cell:hover { transform: translateY(-1px); }
.heatmap-cell:focus-visible {
  outline: 2px solid var(--accent, #4aa3ff); outline-offset: 1px;
}
.heatmap-cell--selected {
  outline: 2px solid var(--text); outline-offset: -2px;
}
.heatmap-cell-sym {
  font-size: .72rem; font-weight: 600; letter-spacing: .03em;
}
.heatmap-cell-vol {
  font-size: .68rem; font-variant-numeric: tabular-nums;
  color: rgba(255,255,255,.85);
}
/* `.dob` (the flex-column panel wrapping the title + `.dob-grid`) also
   needs an explicit non-zero `min-height`, not just `.dob-grid` below:
   `.markets-layout`'s <=1280px `grid-template-rows: auto auto auto`
   sizes the dob *row track* off `.dob`'s own intrinsic height during
   grid auto-track sizing, which does not "see through" into
   `.dob-grid`'s min-height once `.dob-grid` establishes its own
   overflow/scroll context — verified empirically: with only
   `.dob-grid { min-height: 180px }` and `.dob { min-height: 0 }`, the
   grid row locked at the collapsed ~28px title-only height and
   `.dob-grid` rendered 180px tall but overflowed *outside* `.dob`'s
   box into the Tape panel below. `230px` covers `.dob`'s real content
   floor (title ~19px + top/bottom padding ~25.6px + dob-grid's 180px
   min-height, ~225px, rounded up for safety). */
.dob { min-height: 230px; }
/* `.dob-grid th` is `position: sticky; top: 0` (generic `th` rule
   below), which needs a scrolling ancestor to stick *within* — every
   other sticky-header table on the page gets one via `.table-scroll`
   (blotter/executions/etc), but the DOB tables were never wrapped in
   one. Without a local scroll container the sticky header escapes to
   the nearest ancestor that actually scrolls, which on mobile's
   single-column stack (markets-layout collapses to `grid-template-rows:
   auto auto auto` at <=1280px) is the whole page — so as the trader
   scrolls, "Cum Qty Bid | Ask Qty Cum" detaches and floats over
   whatever panel happens to be at the top of the viewport (typically
   the Working Orders table), hiding both.

   `min-height: 180px` (not `0`) + `max-height` + `overflow-y: auto` is
   deliberate: any non-visible `overflow` value forces a flex/grid
   item's *automatic minimum size* in its main axis to 0 per spec
   (verified empirically: with `min-height: 0` this collapsed to a
   computed `height: 0px`, hiding the DOB entirely on the <=1280px
   single-column stack where `.markets-layout` rows are `auto` — i.e.
   content-sized with nothing to resolve the ambiguity, so the browser
   picked the automatic-minimum-size floor of 0. Desktop's definite
   `minmax(0, 1fr)` grid row masked the same collapse when an earlier
   `flex: 1` version of this rule was tried). An explicit non-zero
   `min-height` (matching `.trader-bottom-band`'s 180px convention)
   guarantees a real, non-zero height in both contexts. */
.dob-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: .5rem;
  min-height: 180px; max-height: min(45vh, 420px); overflow-y: auto;
  /* Grid items default to `align-self: stretch`, so the bid/ask
     `<table>`s were stretched to fill the full grid-row height (the
     180-420px above) regardless of how many rows they actually have —
     with a thin book (e.g. one bid, no asks) the lone `<tr>` absorbed
     all the leftover space and rendered as one absurdly tall row.
     `align-items: start` lets each table keep its natural
     content-based height and leaves any extra space blank below. */
  align-items: start;
}
.dob-grid table { width: 100%; border-collapse: collapse; font-size: .8rem; }
.dob-grid th { color: var(--muted); font-weight: 500; padding: .15rem .35rem; }
.dob-grid td { padding: .12rem .35rem; }
.dob-grid .num { text-align: right; font-variant-numeric: tabular-nums; }
.dob-bid td:last-child { color: #1f9d55; font-weight: 600; } /* bid price */
.dob-ask td:first-child { color: #c53030; font-weight: 600; } /* ask price */
.dob-grid .muted-cell { color: var(--muted); text-align: center; padding: .8rem; }

/* ---------- Chart panel (T3) ---------- */
.chart { min-height: 220px; } /* was grid-area: chart; — see Fase 4 (#400) */
.chart h2 { display: flex; align-items: center; gap: .55rem; }
.chart .chart-controls { display: inline-flex; gap: .35rem; }
.chart h2 select {
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 4px;
  padding: .15rem .3rem; font: inherit; font-size: .8rem;
}
.chart-canvas { position: relative; flex: 1; min-height: 180px; }
.chart-canvas svg { width: 100%; height: 100%; display: block; background: var(--bg); border-radius: 4px; }
.chart-empty {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  color: var(--muted); margin: 0; pointer-events: none;
}
.chart-empty[hidden] { display: none; }
.candle-up   { stroke: #1f9d55; fill: #1f9d55; }
.candle-down { stroke: #c53030; fill: #c53030; }
.candle-wick { stroke-width: 1; }

/* ---------- Trade tape (T4) ---------- */
.tape { min-height: 200px; } /* was grid-area: tape; — see Fase 4 (#400) */
.tape h2 { display: flex; align-items: center; gap: .55rem; }
.tape-paused {
  font-size: .65rem; color: var(--warn);
  background: rgba(250, 192, 64, .12); border: 1px solid var(--warn);
  border-radius: 999px; padding: 0 .45rem;
}
.tape-list {
  list-style: none; margin: 0; padding: 0;
  flex: 1; min-height: 0; overflow-y: auto;
  font-size: .78rem; font-variant-numeric: tabular-nums;
}
.tape-list li {
  display: grid; grid-template-columns: 64px 1fr 1fr 1fr 56px;
  gap: .35rem; padding: .12rem .35rem;
  border-bottom: 1px solid var(--border);
}
.tape-list li:last-child { border-bottom: 0; }
.tape-list .tape-num { text-align: right; }
.tape-list .tape-up   { color: #1f9d55; }
.tape-list .tape-down { color: #c53030; }
.tape-list .tape-flat { color: var(--muted); }
.tape-list .tape-busted { text-decoration: line-through; opacity: .55; }
.tape-list .tape-empty { color: var(--muted); text-align: center; padding: .8rem; display: block; }

/* ---------- Order ticket ---------- */
.ticket-form { display: flex; flex-direction: column; gap: .55rem; }
.ticket-form label { display: grid; gap: .2rem; font-size: .75rem; color: var(--muted); }
.ticket-form input, .ticket-form select {
  background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
  border-radius: 4px; padding: .4rem .5rem; font: inherit;
  /* Inputs inherit a default intrinsic width from the user-agent
     (≈200px from `size=20`). Inside the row-two grid, a 1fr column on
     the 280px ticket panel ends up ~130px wide — narrower than the
     intrinsic min, so the input overflows the box on the right side
     (visible in the ticket panel). `width:100%` + `min-width:0` lets
     the input shrink with the grid track instead of clipping out. */
  width: 100%;
  min-width: 0;
}
.ticket-form .row-two { display: grid; grid-template-columns: 1fr 1fr; gap: .5rem; }
.ticket-form button {
  margin-top: .3rem; background: var(--accent); color: #0b1320; border: 0;
  padding: .5rem; border-radius: 4px; font-weight: 600; cursor: pointer; font: inherit;
}
.ticket-form button:disabled { opacity: .5; cursor: progress; }
.feedback { margin: .3rem 0 0; font-size: .8rem; }
.feedback.ok    { color: var(--green); }
.feedback.error { color: var(--red); }

/* ---------- Tables ---------- */
.table-scroll { overflow: auto; flex: 1; min-height: 0; }
table { width: 100%; border-collapse: collapse; font-size: .8rem; }
th {
  position: sticky; top: 0; background: var(--panel-2); color: var(--muted);
  text-align: left; padding: .4rem .5rem; font-weight: 500; border-bottom: 1px solid var(--border);
}
td { padding: .35rem .5rem; border-bottom: 1px solid #20283a; }
.num { text-align: right; font-variant-numeric: tabular-nums; }
.cancel-btn {
  background: transparent; border: 1px solid var(--red); color: var(--red);
  padding: .15rem .5rem; border-radius: 3px; cursor: pointer; font: inherit; font-size: .75rem;
}
.cancel-btn:disabled { opacity: .35; cursor: not-allowed; }
.cancel-btn.cancelling { opacity: .55; cursor: progress; }
.modify-btn {
  background: transparent; border: 1px solid var(--accent); color: var(--accent);
  padding: .15rem .5rem; border-radius: 3px; cursor: pointer; font: inherit; font-size: .75rem;
}
.modify-btn:disabled { opacity: .35; cursor: not-allowed; }
.modify-btn.modifying { opacity: .55; cursor: progress; }
.md-cell-stale { color: var(--warn); }
.status-cell-Working   { color: var(--green); }
.status-cell-PendingNew { color: var(--warn); }
.status-cell-Cancelled { color: var(--gray); }
.status-cell-Filled    { color: var(--accent); }
.status-cell-Rejected  { color: var(--red); }
.status-cell-Replaced  { color: var(--gray); }

/* Slice 3 of #132. Order-row staleness overlay. The row is dimmed
 * (row dim mirrors the panel--stale convention used elsewhere) and a
 * compact badge is appended next to the status cell. Cancel/Modify
 * buttons fall back to the standard :disabled style because the JS
 * sets `disabled` on them when IsStale is true — backend already 409s,
 * but gating client-side avoids the round-trip and tells the trader
 * unambiguously that the venue may not know the order. */
tr.row-stale > td { opacity: .55; }
tr.row-stale > td.row-stale-actions { opacity: 1; }
.order-stale-badge {
  display: inline-block;
  margin-left: .35rem;
  padding: 0 .35rem;
  border: 1px solid var(--warn);
  border-radius: 3px;
  color: var(--warn);
  font-size: .65rem;
  font-weight: 600;
  letter-spacing: .03em;
  text-transform: uppercase;
  vertical-align: middle;
  cursor: help;
}

/* ---------- Executions log ---------- */
.executions-log {
  list-style: none; padding: 0; margin: 0; overflow: auto; flex: 1;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .75rem;
  position: relative;
}
.executions-log .vlist-spacer { width: 1px; pointer-events: none; }
.executions-log .vlist-window { position: absolute; top: 0; left: 0; right: 0; will-change: transform; }
.executions-log .exec-row { height: 24px; box-sizing: border-box; padding: .25rem .4rem; border-bottom: 1px solid #20283a; display: flex; gap: .6rem; align-items: center; }
.executions-log li { padding: .25rem .4rem; border-bottom: 1px solid #20283a; display: flex; gap: .6rem; }
.executions-log .ts    { color: var(--muted); flex: 0 0 7em; }
.executions-log .kind  { font-weight: 600; flex: 0 0 7em; }
.executions-log .Fill        { color: var(--green); }
.executions-log .PartialFill { color: var(--teal); }
.executions-log .Rejected    { color: var(--red); }
.executions-log .Canceled    { color: var(--gray); }
.executions-log .New         { color: var(--accent); }
.executions-log .Replaced    { color: var(--gray); }
.executions-log .ReplacedNew { color: var(--accent); }
.executions-log .Expired     { color: #888; }
.executions-log .meta { color: var(--muted); }
.executions-log .badge {
  display: inline-block;
  padding: 0 .35em;
  margin-left: .25em;
  border-radius: .25em;
  font-size: .7rem;
  font-weight: 600;
  vertical-align: baseline;
}
.executions-log .badge.stp-native { background: #5a3a14; color: #ffd28a; }
.executions-log .badge.stp-local  { background: #2a3a5a; color: #b8d2ff; }

/* ---------- Q1.4 (#256) Working-orders type chip ---------- */
.type-chip {
  display: inline-block;
  padding: 0 .35em;
  border-radius: .25em;
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .03em;
  vertical-align: baseline;
  background: var(--panel-2, #20283a);
  color: var(--text, #e6edf7);
}
.type-chip.chip-lim  { background: #1f3550; color: #b8d2ff; }
.type-chip.chip-mkt  { background: #2a3f2a; color: #c8e6c9; }
.type-chip.chip-stp  { background: #5a3a14; color: #ffd28a; }
.type-chip.chip-stpl { background: #4a2455; color: #f0c8ff; }
.type-chip.chip-mwl  { background: #145a4a; color: #8af0d4; }

/* Option badges for Call/Put labels (#497) */
.option-badge {
 display: inline-block;
 font-size: 0.75rem;
 font-weight: 600;
 padding: 0 4px;
 border-radius: 3px;
 margin-left: 4px;
 vertical-align: middle;
}
.option-call { background: #22c55e; color: white; }
.option-put { background: #ef4444; color: white; }

/* Status cell coloring reserved for potential future OrderStatus.Expired
   addition; not currently rendered (backend OrderStatus has no Expired
   member — GTD expiry terminalises as Cancelled). Kept so a future
   slice that adds the enum value gets the styling for free. */
.status-cell-Expired { color: #888; }

/* ---------- Responsive ---------- */
@media (max-width: 1280px) {
  .grid {
    grid-template-columns: 1fr 280px;
    grid-template-rows: minmax(200px, 1fr) minmax(160px, .9fr) minmax(160px, .8fr) minmax(220px, 1.4fr) minmax(140px, .8fr);
    grid-template-areas:
      "chart     dob"
      "tape      dob"
      "ticket    positions"
      "blotter   blotter"
      "execs     md";
  }
}
@media (max-width: 720px) {
  .grid {
    grid-template-columns: 1fr;
    grid-template-rows: none;
    grid-template-areas:
      "ticket"
      "dob"
      "chart"
      "tape"
      "positions"
      "blotter"
      "execs"
      "md";
  }
}

/* ---------- Market data panel ---------- */
.market-data h2 {
  display: flex; align-items: center; justify-content: space-between; gap: .55rem;
}
.market-data h2 .md-title { display: inline-flex; align-items: center; gap: .55rem; }
.icon-button {
  background: transparent; border: 1px solid var(--border); color: var(--muted);
  border-radius: 4px; padding: .05rem .4rem; cursor: pointer; font: inherit;
  font-size: .9rem; line-height: 1;
}
.icon-button:hover { color: var(--text); border-color: var(--accent); }
.md-settings-card { width: min(420px, 90vw); gap: .8rem; }
.md-settings-card label { display: grid; gap: .2rem; font-size: .75rem; color: var(--muted); }
.md-settings-card input {
  background: var(--bg); color: var(--text); border: 1px solid var(--border);
  border-radius: 4px; padding: .4rem .55rem; font: inherit;
}

/* ---------- Operability badges (added in frontend hardening pass) ---------- */
.reconnect-countdown {
  font-size: .7rem; color: var(--warn); font-variant-numeric: tabular-nums;
}
.role-badge {
  background: rgba(79,158,255,.15); color: var(--accent);
  padding: .1rem .45rem; border-radius: 999px; font-size: .65rem;
  text-transform: uppercase; letter-spacing: .04em;
}
.firms-health {
  padding: .15rem .55rem; border-radius: 999px; font-size: .7rem;
  border: 1px solid var(--border); cursor: help;
}
.firms-health-ok    { background: rgba(54,196,111,.12); color: var(--green); border-color: rgba(54,196,111,.35); }
.firms-health-warn  { background: rgba(245,166,35,.12); color: var(--warn);  border-color: rgba(245,166,35,.35); }
.firms-health-muted { background: var(--panel-2); color: var(--muted); }
.inflight {
  margin: .25rem 0 0; font-size: .75rem; color: var(--warn);
  font-variant-numeric: tabular-nums;
}

/* ---------- View toggle + admin view ---------- */
.view-toggle {
  display: inline-flex; border: 1px solid var(--border); border-radius: 999px; overflow: hidden;
}
.view-toggle button {
  background: transparent; color: var(--muted); border: 0; padding: .25rem .75rem;
  font: inherit; cursor: pointer; font-size: .75rem;
}
.view-toggle button.active { background: var(--accent); color: #0b1320; }
.view-toggle button:not(.active):hover { color: var(--text); }

.admin-view { display: flex; flex-direction: column; height: 100vh; overflow: auto; padding: 1rem; gap: 1rem; }
.admin-subbar {
  display: flex; justify-content: space-between; align-items: center;
  padding: .5rem 0; border-bottom: 1px solid var(--border);
}
.admin-subbar h2 { margin: 0; font-size: 1rem; font-weight: 600; }
.admin-subbar #admin-mode { color: var(--accent); font-weight: 600; }
.admin-panel { background: var(--panel); border: 1px solid var(--border); border-radius: 6px; padding: .75rem 1rem; }
.admin-panel h3 { margin: 0 0 .6rem; font-size: .9rem; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; }
.admin-inline-form { display: flex; gap: .5rem; margin-bottom: .6rem; }
.admin-inline-form input {
  flex: 1; background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
  border-radius: 4px; padding: .35rem .5rem; font: inherit;
}
.admin-eod-output {
  margin: .6rem 0 0; padding: .5rem; background: var(--panel-2); border: 1px solid var(--border);
  border-radius: 4px; font: 11px/1.4 ui-monospace, Menlo, Consolas, monospace;
  white-space: pre-wrap; word-break: break-word; max-height: 240px; overflow: auto;
}
.danger-btn {
  border: 0; padding: .35rem .75rem; border-radius: 4px; cursor: pointer; font: inherit; font-size: .75rem;
  text-transform: uppercase; letter-spacing: .04em; font-weight: 600;
}
.danger-btn.engage { background: rgba(239,83,80,.18); color: var(--red); }
.danger-btn.engage:hover { background: rgba(239,83,80,.32); }
.danger-btn.revive { background: rgba(54,196,111,.18); color: var(--green); }
.danger-btn.revive:hover { background: rgba(54,196,111,.32); }
.killed-tag {
  display: inline-block; padding: .05rem .4rem; border-radius: 3px;
  background: rgba(239,83,80,.18); color: var(--red);
  font-size: .65rem; text-transform: uppercase; letter-spacing: .04em;
  margin-right: .35rem;
}
/* Firms admin: highlight rows whose FIXP session is not Established or
 * is mid-reconnect so the worst row is visible at a glance (#342). */
tr.firm-row-bad > td { background: rgba(239,83,80,.10); }
.muted { color: var(--muted); text-align: center; padding: .8rem; }

/* ---------- Blotter filter + row UX (section 3 of #30) ---------- */
.blotter-filter {
  display: flex; gap: .4rem; margin-bottom: .4rem;
}
.blotter-filter input, .blotter-filter select {
  background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
  border-radius: 4px; padding: .25rem .45rem; font: inherit; font-size: .8rem;
}
.blotter-filter input { flex: 1; }
/* #342: "Working only" toggle sits next to the status select. The
 * checkbox inherits browser styling but the label text picks up the
 * muted palette so the toggle reads as a filter, not as a status pill. */
.blotter-filter-toggle {
  display: inline-flex; align-items: center; gap: .3rem;
  color: var(--muted); font-size: .78rem; white-space: nowrap;
  user-select: none; cursor: pointer;
}
.blotter-filter-toggle input { flex: none; margin: 0; }
.blotter-pagination {
  display: flex; align-items: center; justify-content: flex-end;
  gap: .5rem; margin-top: .4rem; font-size: .8rem;
}
.blotter-pagination button {
  background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
  border-radius: 4px; padding: .25rem .55rem; font: inherit; font-size: .8rem;
  cursor: pointer;
}
.blotter-pagination button:disabled {
  opacity: .4; cursor: default;
}
.blotter-pagination button:not(:disabled):hover {
  background: rgba(255,255,255,.05);
}
.blotter-page-info { color: var(--muted); font-variant-numeric: tabular-nums; }

/* #342: Positions click-to-sort headers. The column whose
 * aria-sort is set gets an inline indicator using ::after so the
 * indicator never disturbs the column's text alignment. */
.positions-sort-hint {
  font-size: .65rem; font-weight: 400; text-transform: none;
  letter-spacing: 0; color: var(--muted); opacity: .55;
}
.panel.positions th.sortable {
  cursor: pointer; user-select: none;
}
.panel.positions th.sortable:hover { color: var(--text); }
.panel.positions th.sortable:focus-visible {
  outline: 1px solid var(--accent, #4aa3ff); outline-offset: 1px;
}
.panel.positions th.sortable[aria-sort="ascending"]::after  { content: " ▲"; opacity: .7; }
.panel.positions th.sortable[aria-sort="descending"]::after { content: " ▼"; opacity: .7; }

/* FE-OPT-3 (#499). Positions grouping toggle + grouped rows */
.btn-toggle {
  opacity: 0.6;
  transition: opacity 0.15s, background 0.15s;
}
.btn-toggle:hover { opacity: 0.9; }
.btn-toggle.active, .btn-toggle[aria-pressed="true"] {
  opacity: 1;
  background: var(--accent, #4aa3ff);
  color: #fff;
}
.pos-group-header > td {
  background: var(--panel-2, #1e1e1e);
  border-top: 1px solid var(--border, #333);
}
.pos-indent > td:first-child {
  padding-left: 1.5rem;
}

/* FE-OPT-3 (#499). Expiry strip for option positions */
.expiry-strip {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  border-top: 1px solid var(--border, #333);
  font-size: 0.75rem;
  overflow-x: auto;
}
.expiry-strip-label {
  color: var(--muted);
  white-space: nowrap;
}
.expiry-strip-items {
  display: flex;
  gap: 0.35rem;
  flex-wrap: wrap;
}
.expiry-chip {
  background: var(--panel-2, #1e1e1e);
  border: 1px solid var(--border, #333);
  border-radius: 4px;
  padding: 0.2rem 0.5rem;
  font-size: 0.7rem;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s, border-color 0.15s;
}
.expiry-chip:hover {
  background: var(--panel-3, #2a2a2a);
  border-color: var(--accent, #4aa3ff);
}
.expiry-chip.active {
  background: var(--accent, #4aa3ff);
  color: #fff;
  border-color: var(--accent, #4aa3ff);
}
.expiry-count {
  opacity: 0.7;
}

/* #342: Executions log symbol filter sits inside the panel <h2>. */
.exec-filter-symbol {
  font: inherit; font-size: .72rem; font-weight: 400; text-transform: none;
  letter-spacing: 0; color: var(--text);
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 4px;
  padding: .15rem .4rem; width: 9rem;
}
.exec-filter-symbol::placeholder { color: var(--muted); opacity: .8; }
@keyframes row-fresh-fade {
  0%   { background: rgba(79,158,255,.32); }
  100% { background: transparent; }
}
tr.row-fresh > td { animation: row-fresh-fade 2s ease-out forwards; }
tr.row-selected > td {
  outline: 1px solid var(--accent); outline-offset: -1px;
}
tr[data-clordid] { cursor: pointer; }
tr[data-clordid]:hover > td { background: rgba(255,255,255,.025); }
.feedback.warn { color: var(--warn); }

/* #342: Transient WS error toast. Lives between the topbar and the
 * panel grid, takes no space when hidden, and surfaces frame-level
 * errors (unknown_channel, bad subscribe args, server-side close
 * reasons) so the trader doesn't have to open the devtools console
 * to notice that a subscription quietly failed. */
.ws-error-toast {
  margin: 0 1rem; padding: .35rem .7rem;
  background: rgba(239,83,80,.14); color: var(--red);
  border: 1px solid rgba(239,83,80,.35); border-radius: 4px;
  font-size: .8rem; font-variant-numeric: tabular-nums;
}

/* #421: Transient success/info toast for order submits. Same slot as
 * .ws-error-toast (lives between the topbar and the panel grid, takes
 * no space when hidden) but green-tinted by default. Kind modifiers
 * (.warn / .error) let the same surface carry a fat-finger warning or
 * a submit failure when we want them above the fold rather than buried
 * under the ticket form. */
.order-toast {
  margin: .35rem 1rem 0; padding: .35rem .7rem;
  background: rgba(38,166,154,.14); color: var(--green, #26a69a);
  border: 1px solid rgba(38,166,154,.35); border-radius: 4px;
  font-size: .8rem; font-variant-numeric: tabular-nums;
}
.order-toast.warn {
  background: rgba(255,167,38,.14); color: var(--yellow, #ffa726);
  border-color: rgba(255,167,38,.35);
}
.order-toast.error {
  background: rgba(239,83,80,.14); color: var(--red);
  border-color: rgba(239,83,80,.35);
}

/* ---------- Session-expiry modal + helpers (section 4 of #30) ---------- */
.muted-cell { color: var(--muted); }
.muted-line { color: var(--muted); margin: 0 0 .4rem; font-size: .8rem; }
/* Order ticket lot/tick hint (T4). Driven by rulesFor(symbol) so the
   trader sees the lot size before submit-time validation rejects the
   order. Subdued so it doesn't compete with the form labels. */
.field-hint {
  margin: -.2rem 0 .2rem; font-size: .7rem; color: var(--muted);
  letter-spacing: .03em;
}
.login-card label.remember { flex-direction: row; align-items: center; gap: .5rem; font-size: .8rem; color: var(--muted); }
.login-card label.remember input { width: auto; }
.modal-backdrop {
  position: fixed; inset: 0; background: rgba(8,11,16,.65);
  display: grid; place-items: center; z-index: 1000;
}
.modal-card {
  background: var(--panel); border: 1px solid var(--border); border-radius: 8px;
  padding: 1.25rem 1.5rem; width: min(360px, 90vw);
  display: flex; flex-direction: column; gap: .8rem;
}
.modal-card h2 { margin: 0; font-size: 1rem; }
.modal-card label { display: grid; gap: .25rem; font-size: .8rem; color: var(--muted); }
.modal-card input {
  background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
  border-radius: 4px; padding: .45rem .55rem; font: inherit;
}
.modal-actions { display: flex; justify-content: space-between; align-items: center; gap: .5rem; }
.modal-actions button[type="submit"] {
  background: var(--accent); color: #0b1320; border: 0; padding: .45rem .9rem;
  border-radius: 4px; font-weight: 600; cursor: pointer; font: inherit;
}
.modal-actions button[type="submit"]:hover { filter: brightness(1.1); }

/*
 * Modify-modal helper line.
 *
 * The qty input on the modify modal represents the **new remaining**
 * (leaves) quantity the trader wants the order to keep working — this
 * is more intuitive after a partial fill than "new total". The wire
 * (FIX OrderCancelReplaceRequest.OrderQty / our equivalent) is always
 * the new total = cum + remaining, computed at submit time. The hint
 * below the qty input shows the trader exactly what total the wire
 * will carry so there is no ambiguity between UX and protocol.
 */
.modify-hint { margin: -.4rem 0 0; color: var(--muted); font-size: .75rem; }
.modify-hint.error { color: var(--red); }

/* ---------- Bot credentials view (sub-issue #169) ---------- */
.bot-credentials-view {
  display: flex; flex-direction: column; height: 100vh; overflow: auto;
  padding: 1rem; gap: 1rem;
}
.bot-credentials-view .admin-actions { display: flex; gap: .75rem; }
.bot-credentials-secret-card { width: min(440px, 92vw); }
.bot-credentials-secret-card input[readonly] {
  font: 13px/1.4 ui-monospace, Menlo, Consolas, monospace;
  user-select: all;
}
.bot-credentials-secret-warning {
  margin: 0; padding: .5rem .6rem; border-radius: 4px;
  background: rgba(245,166,35,.12); border: 1px solid var(--warn);
  color: var(--warn); font-size: .8rem;
}
.bot-credentials-secret-card .modal-actions button[type="button"] {
  background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
  border-radius: 4px; padding: .4rem .8rem; font: inherit; font-size: .8rem; cursor: pointer;
}
.bot-credentials-secret-card .modal-actions button[type="button"]:hover {
  background: rgba(255,255,255,.05);
}

/* ---------- Order detail modal (#245) ---------- */
.order-detail-backdrop { align-items: start; padding-top: 4vh; }
.order-detail-card {
  width: min(880px, 94vw);
  max-height: 90vh;
  padding: 1rem 1.25rem 1.25rem;
  gap: .75rem;
}
.order-detail-header { display: flex; flex-direction: column; gap: .5rem; }
.order-detail-title-row {
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
}
.order-detail-title-row h2 { font-size: 1rem; margin: 0; }
.order-detail-close {
  background: transparent; border: 0; color: var(--muted); font-size: 1.4rem;
  line-height: 1; padding: .1rem .4rem; cursor: pointer; border-radius: 4px;
}
.order-detail-close:hover { color: var(--text); background: rgba(255,255,255,.05); }
.order-detail-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.order-detail-body {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: .5rem .85rem; font-size: .8rem;
}
.order-detail-body .field { display: flex; flex-direction: column; gap: .15rem; }
.order-detail-body .field .label {
  color: var(--muted); font-size: .65rem; letter-spacing: .04em;
  text-transform: uppercase;
}
.order-detail-body .field .value {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .85rem; color: var(--text);
}
.order-detail-body .field.field-wide { grid-column: 1 / -1; }
.modal-progress {
  position: relative;
  height: 6px; border-radius: 3px; background: var(--panel-2);
  overflow: hidden; margin-top: .25rem;
}
.modal-progress > .modal-progress-fill {
  position: absolute; top: 0; left: 0; bottom: 0;
  background: var(--accent);
}
.order-detail-exec-scroll { max-height: 50vh; overflow: auto; }
.order-detail-table {
  width: 100%; border-collapse: collapse;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .75rem;
}
.order-detail-table th, .order-detail-table td {
  padding: .3rem .5rem; border-bottom: 1px solid #20283a; text-align: left;
}
.order-detail-table th {
  position: sticky; top: 0; background: var(--panel);
  color: var(--muted); font-weight: 600; font-size: .7rem;
  text-transform: uppercase; letter-spacing: .04em;
}
.order-detail-table td.num, .order-detail-table th.num {
  text-align: right; font-variant-numeric: tabular-nums;
}
.order-detail-table tr.empty td { color: var(--muted); text-align: center; padding: .8rem; }
#blotter-body tr[data-clordid] { cursor: pointer; }
#blotter-body tr[data-clordid] td.row-stale-actions { cursor: default; }

/* ---------- FE-OPT-2 (#498) Option chain picker ---------- */
.chain-picker-modal {
  max-width: 90vw;
  max-height: 90vh;
}
.chain-picker-content {
  display: flex;
  flex-direction: column;
  height: 100%;
}
.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem;
  border-bottom: 1px solid var(--border-color, #e5e7eb);
}
.modal-header h2 { margin: 0; }
.modal-close {
  background: none;
  border: none;
  font-size: 1.5rem;
  cursor: pointer;
  color: var(--text);
  padding: 0;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
}
.modal-close:hover { color: var(--accent); }
.chain-picker-controls {
  display: flex;
  gap: 1rem;
  align-items: flex-end;
  padding: 1rem;
  border-bottom: 1px solid var(--border-color, #e5e7eb);
}
.chain-picker-controls label {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-size: .8rem;
  color: var(--muted);
}
.chain-picker-controls input {
  width: 120px;
  padding: 0.5rem;
  border: 1px solid var(--border-color, #e5e7eb);
  border-radius: 4px;
  font-size: .8rem;
}
.chain-picker-grid {
  flex: 1;
  overflow: auto;
  padding: 1rem;
}
.chain-placeholder {
  color: #6b7280;
  text-align: center;
  padding: 2rem;
  margin: 0;
}
.chain-table {
  border-collapse: collapse;
  width: 100%;
  font-size: 0.875rem;
}
.chain-table th,
.chain-table td {
  border: 1px solid var(--border-color, #e5e7eb);
  padding: 0.5rem;
  text-align: center;
}
.chain-table th {
  background: var(--header-bg, #f3f4f6);
  position: sticky;
  top: 0;
}
.chain-table .strike-col {
  font-weight: 600;
  background: var(--header-bg, #f3f4f6);
  position: sticky;
  left: 0;
}
.chain-cell {
  cursor: pointer;
}
.chain-cell:hover {
  background: var(--hover-bg, #e0f2fe);
}
.chain-cell-call { color: #16a34a; }
.chain-cell-put { color: #dc2626; }
.modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  padding: 1rem;
  border-top: 1px solid var(--border-color, #e5e7eb);
}
.btn {
  padding: 0.5rem 1rem;
  border: 1px solid var(--border-color, #e5e7eb);
  border-radius: 4px;
  cursor: pointer;
  font-size: .8rem;
  background: var(--panel);
  color: var(--text);
}
.btn:hover {
  filter: brightness(1.1);
}
.btn-primary {
  background: var(--accent);
  color: white;
  border-color: var(--accent);
}
.btn-secondary {
  background: var(--panel-2);
  border-color: var(--border-color, #e5e7eb);
}
.btn-icon {
  padding: 0.5rem;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Mobile: full screen */
@media (max-width: 768px) {
  .chain-picker-modal {
    max-width: 100vw;
    max-height: 100vh;
    width: 100vw;
    height: 100vh;
    margin: 0;
    border-radius: 0;
  }
}

/* ---------- Phase badges + auction panel (Q1.6 / #258) ---------- */

/* Compact pill rendered next to the symbol cell in the market-data
   table. Color tokens reuse the global --green / --warn / --red /
   --gray surface so the palette stays consistent with status-cell-*. */
.phase-badge {
  display: inline-block;
  margin-left: .35rem;
  padding: .05rem .35rem;
  font-size: .65rem; font-weight: 600;
  letter-spacing: .03em; text-transform: uppercase;
  border-radius: 999px; border: 1px solid currentColor;
  vertical-align: middle;
  font-variant-numeric: tabular-nums;
}
.phase-badge.PRE-OPEN { color: var(--warn);   background: rgba(245,166,35,.12); }
.phase-badge.OPEN     { color: var(--green);  background: rgba(54,196,111,.12); }
.phase-badge.CLOSING  { color: #f08c2e;       background: rgba(240,140,46,.14); }
.phase-badge.CLOSED   { color: var(--gray);   background: rgba(107,114,128,.16); }
.phase-badge.RESERVED { color: var(--red);    background: rgba(239,83,80,.14); }

/* Auction panel — collapsible, wedged into the trader grid. Default
   collapsed visual is just the toggle line; expanded state surfaces a
   2-column metric grid + a recent-prints list. */
.auction-panel { padding: .55rem .75rem; }
.auction-panel.collapsed { padding-bottom: .45rem; }
.auction-toggle {
  display: inline-flex; align-items: center; gap: .35rem;
  background: transparent; border: 0; color: var(--text);
  font: inherit; font-weight: 600; cursor: pointer; padding: 0;
}
.auction-toggle:hover { color: var(--accent); }
.auction-caret { display: inline-block; width: .9em; color: var(--muted); }
.auction-body { margin-top: .5rem; }
.auction-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: .35rem .8rem;
  margin-bottom: .55rem;
}
.auction-cell { display: flex; flex-direction: column; gap: .1rem; }
.auction-label {
  font-size: .68rem; color: var(--muted); text-transform: uppercase;
  letter-spacing: .04em;
}
.auction-value {
  font-size: .95rem; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.auction-arrow { display: inline-block; margin-right: .15rem; }
.auction-arrow.up   { color: var(--green); }
.auction-arrow.down { color: var(--red); }
.auction-value.imb-buy  { color: var(--green); }
.auction-value.imb-sell { color: var(--red); }
.auction-prints-title {
  margin: .35rem 0 .25rem 0; font-size: .72rem; font-weight: 600;
  color: var(--muted); text-transform: uppercase; letter-spacing: .04em;
}
.auction-prints {
  list-style: none; padding: 0; margin: 0;
  font-size: .8rem; max-height: 7rem; overflow-y: auto;
}
.auction-prints li { padding: .15rem 0; border-bottom: 1px solid var(--border); }
.auction-prints li:last-child { border-bottom: 0; }
.auction-print-kind { color: var(--muted); margin-right: .25rem; }
.auction-print-px,
.auction-print-qty { font-variant-numeric: tabular-nums; }

/* Ticket coupling: TIF hint colors (info vs. soft warning). */
.field-hint.hint-info { color: var(--accent); }
.field-hint.hint-warn { color: var(--warn); }

/* ── Q2.6 (#273). History / P&L / Statement view ─────────────────── */
.history-view { padding: 1rem 1.5rem; }
.pnl-totals {
  display: flex; gap: 2rem; align-items: baseline;
  margin: .25rem 0 .75rem 0;
}
.pnl-total { display: flex; flex-direction: column; }
.pnl-total-label {
  font-size: .68rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em;
}
.pnl-total-value {
  font-size: 1.25rem; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.pnl-pos { color: var(--green); }
.pnl-neg { color: var(--red); }
.pnl-zero { color: var(--muted); }
.pnl-live {
  font-size: .68rem; color: var(--green);
  text-transform: uppercase; letter-spacing: .08em;
  border: 1px solid var(--green); border-radius: 3px;
  padding: 0 .35rem; align-self: center;
}
.history-filters { flex-wrap: wrap; gap: .5rem; }
.history-more-row { margin: .4rem 0 .75rem 0; }
.statement-json-card {
  max-width: 80vw; max-height: 80vh; display: flex; flex-direction: column;
}
.statement-json-body {
  flex: 1; overflow: auto; padding: .75rem;
  background: var(--surface-alt, #111); color: var(--fg, #eee);
  border: 1px solid var(--border); border-radius: 4px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .78rem; white-space: pre;
  max-height: 60vh;
}

/* ─── Q4.14 (#314). Compliance view ───────────────────────────── */
.compliance-view { padding: 1rem; display: flex; flex-direction: column; gap: 1rem; }
.compliance-view .admin-subbar { margin-bottom: .5rem; }
.compliance-feed-controls,
.compliance-cvm-controls {
  display: flex; gap: .5rem; align-items: center; margin-bottom: .6rem; flex-wrap: wrap;
}
.compliance-audit-form {
  display: flex; gap: .5rem; flex-wrap: wrap; align-items: flex-end; margin-bottom: .6rem;
}
.compliance-audit-form label { display: flex; flex-direction: column; font-size: .75rem; color: var(--muted); }
.compliance-audit-form input,
.compliance-audit-form select {
  font-family: inherit; padding: .25rem .4rem; min-width: 9rem;
}
.compliance-feed table tbody tr td { font-size: .8rem; }

/* ─── #320: TOTP QR badge in Security panel ───────────────────────── */
.qr-badge {
  display: inline-block;
  background: #fff;
  padding: .5rem;
  border-radius: 4px;
  line-height: 0;
  margin: .25rem 0 .75rem;
}
.qr-badge[hidden] { display: none; }
.qr-badge svg {
  display: block;
  width: 192px;
  height: 192px;
  shape-rendering: crispEdges;
}

/* ── Fase 2 (#398). Algos tab. ─────────────────────────────────── */
.algos-view .admin-subbar-actions { display: flex; gap: 1rem; align-items: center; }
.algos-view .inline-field { display: inline-flex; gap: .35rem; align-items: center; font-size: .85rem; }
.algos-grid {
  display: grid;
  grid-template-columns: 1.4fr 1.2fr 1fr;
  gap: .75rem;
  padding: .5rem;
  align-items: start;
}
@media (max-width: 1200px) { .algos-grid { grid-template-columns: 1fr; } }
.algos-list-panel .table-scroll { max-height: 60vh; overflow: auto; }
.algos-list-table tbody tr { cursor: pointer; }
.algos-list-table tbody tr.selected { background: var(--row-selected, rgba(255,255,255,.08)); }
.algos-list-table tbody tr:hover { background: var(--row-hover, rgba(255,255,255,.04)); }
.algos-type-tabs { display: flex; gap: .25rem; margin-bottom: .5rem; flex-wrap: wrap; }
.algos-type-tab {
  padding: .25rem .6rem;
  background: transparent;
  border: 1px solid var(--border, #444);
  color: inherit;
  cursor: pointer;
  border-radius: 4px;
  font-size: .85rem;
}
.algos-type-tab.active { background: var(--accent, #357); color: white; border-color: var(--accent, #357); }
.algos-boleta-form fieldset { border: 1px solid var(--border, #444); padding: .5rem .75rem; margin-bottom: .5rem; border-radius: 4px; }
.algos-boleta-form legend { padding: 0 .35rem; font-size: .8rem; color: var(--muted, #999); }
.algos-boleta-form label {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: .5rem;
  margin-bottom: .35rem;
  font-size: .85rem;
}
.algos-boleta-form input, .algos-boleta-form select { width: 100%; box-sizing: border-box; }
.algos-boleta-footer { display: flex; gap: 1rem; align-items: center; }
.algos-boleta-status { margin: 0; font-size: .85rem; }
.algos-boleta-status.error { color: var(--danger, #d66); }
.algos-boleta-status.success { color: var(--accent, #6c6); }
.algos-detail-grid { display: grid; grid-template-columns: max-content 1fr; gap: .25rem .75rem; font-size: .85rem; margin: 0 0 .75rem; }
.algos-detail-grid dt { color: var(--muted, #999); }
.algos-detail-grid dd { margin: 0; word-break: break-word; }
.algos-params-pre { background: rgba(0,0,0,.25); padding: .5rem; border-radius: 4px; font-size: .75rem; overflow: auto; max-height: 14rem; }
.algos-modify-form { display: grid; gap: .35rem; border-top: 1px solid var(--border, #444); padding-top: .5rem; margin-top: .5rem; }
.algos-modify-form h4 { margin: 0; }

/* Fase 3 (#399). Settings sub-tabs — mirror the algos-type-tabs visual
   so the user sees the same primary/sub-tab idiom across the app. */
.settings-subtabs {
  display: flex;
  gap: .25rem;
  margin: .5rem 0 .75rem;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--border, #444);
  padding-bottom: .5rem;
}
.settings-subtab {
  padding: .35rem .75rem;
  background: transparent;
  border: 1px solid var(--border, #444);
  color: inherit;
  cursor: pointer;
  border-radius: 4px;
  font-size: .9rem;
}
.settings-subtab.active {
  background: var(--accent, #357);
  color: white;
  border-color: var(--accent, #357);
}
.settings-subpanel { margin-top: .5rem; }
.settings-subpanel .admin-subbar { margin-bottom: .5rem; }

/* ─────────────────────────────────────────────────────────────────
   Fase 4 (#400). Trader-view reflow: sub-tabs + lower-band tabs +
   compact Quick ticket. Built on the same primary/sub-tab idiom as
   `.settings-subtabs` / `.algos-type-tabs` so the visual vocabulary
   stays consistent across the app.
   ───────────────────────────────────────────────────────────────── */

/* Sub-tab navigation (Markets / Watchlist / Auctions). */
.trader-subtabs {
  display: flex; gap: .25rem; flex-wrap: wrap;
  border-bottom: 1px solid var(--border, #444);
  padding-bottom: .35rem;
}
.trader-subtab {
  padding: .35rem .75rem;
  background: transparent;
  border: 1px solid var(--border, #444);
  color: inherit; cursor: pointer;
  border-radius: 4px; font-size: .9rem;
}
.trader-subtab.active {
  background: var(--accent, #357); color: white;
  border-color: var(--accent, #357);
}

/* Active sub-panel — fills the remaining trader-layout space. Each
   sub-tab uses its own internal grid; auctions is full-width. */
.trader-subpanel {
  flex: 1; min-height: 0;
  display: grid; gap: 8px;
}
.markets-layout {
  /* Chart wide + DOB + Tape stacked on the right. */
  grid-template-columns: minmax(0, 1.4fr) minmax(240px, .9fr) minmax(220px, .9fr);
  grid-template-rows: minmax(0, 1fr);
}
.watchlist-layout {
  /* Heatmap+MD table wide on the left, Positions on the right. */
  grid-template-columns: minmax(0, 1.6fr) minmax(240px, .9fr);
  grid-template-rows: minmax(0, 1fr);
}
.auctions-layout {
  grid-template-columns: minmax(0, 1fr);
}

/* Lower-band tabbed panel (Working orders / Executions). */
.trader-bottom-band {
  min-height: 180px; max-height: 30vh;
  display: flex; flex-direction: column;
}
.trader-bottom-tabs {
  display: flex; gap: .25rem; flex-wrap: wrap;
  border-bottom: 1px solid var(--border, #444);
  padding-bottom: .35rem; margin-bottom: .5rem;
}
.trader-bottom-tab {
  padding: .3rem .65rem;
  background: transparent;
  border: 1px solid var(--border, #444);
  color: inherit; cursor: pointer;
  border-radius: 4px; font-size: .85rem;
}
.trader-bottom-tab.active {
  background: var(--accent, #357); color: white;
  border-color: var(--accent, #357);
}
.trader-bottom-subpanel {
  flex: 1; min-height: 0;
  display: flex; flex-direction: column;
}
.trader-bottom-subpanel[hidden] { display: none; }
/* h2-equivalent strip inside the lower-band sub-panels (the old h2
   tags moved into the tab list, so each sub-panel now uses a flat
   header strip for its filter/cancel-all controls). */
.blotter-h2, .executions-h2 {
  display: flex; justify-content: space-between; align-items: center;
  margin: 0 0 .5rem 0; font-size: .85rem; font-weight: 600;
  color: var(--muted); text-transform: uppercase; letter-spacing: .06em;
}

/* Quick ticket (compact horizontal strip). */
.ticket.ticket-compact {
  /* The compact ticket is a single-row strip, so the per-form vertical
     scroll the old layout needed is no longer necessary; let height be
     content-driven instead. */
  overflow-y: visible;
  padding: .6rem .8rem;
}
.ticket-form-compact {
  display: flex; flex-direction: column; gap: .35rem;
}
.ticket-row-quick {
  display: flex; flex-wrap: wrap; gap: .5rem; align-items: flex-end;
}
.ticket-field {
  display: flex; flex-direction: column; min-width: 0;
}
.ticket-field > span {
  font-size: .7rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em;
  margin-bottom: .1rem;
}
.ticket-field > input, .ticket-field > select {
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 4px;
  padding: .35rem .4rem; font: inherit; font-size: .85rem;
  min-width: 0;
}
.ticket-field-symbol > input { min-width: 7.5rem; }
.ticket-submit {
  padding: .5rem 1rem; font: inherit; font-size: .85rem;
  background: var(--accent, #357); color: white;
  border: 1px solid var(--accent, #357); border-radius: 4px;
  cursor: pointer; font-weight: 600;
}
.ticket-submit:hover { filter: brightness(1.1); }
.ticket-notional {
  font-size: .875rem;
  color: var(--gray);
  margin-left: .5rem;
  white-space: nowrap;
  align-self: flex-end;
  padding-bottom: .35rem;
}
.ticket-advanced-toggle {
  padding: .35rem .65rem; font: inherit; font-size: .8rem;
  background: transparent; color: inherit;
  border: 1px solid var(--border); border-radius: 4px;
  cursor: pointer;
}
.ticket-advanced-toggle.active {
  background: var(--panel-2);
  border-color: var(--accent, #357);
}
.ticket-advanced-toggle .ticket-advanced-caret { margin-right: .25rem; }
.ticket-advanced {
  display: flex; flex-wrap: wrap; gap: .5rem;
  padding-top: .35rem; border-top: 1px dashed var(--border);
}
.ticket-advanced[hidden] { display: none; }
.ticket-advanced .ticket-field { min-width: 8rem; }

/* Auction panel inside the Auctions sub-tab fills its container —
   the legacy `.collapsed` modifier (default state when the panel
   lived alongside the others) was dropped in markup, but keep the
   rule for any test fixture that still sets the class. */
.auctions-layout .auction-panel { flex: 1; }

/* ─────────────────────────────────────────────────────────────────
   Fase 5 (#401). Polish: density toggle, focus-visible, responsive.
   ───────────────────────────────────────────────────────────────── */

/* UI density.  Comfortable (default) is unchanged; compact pulls every
   rem-based metric down ~10%.  We do this by lowering the root
   font-size and tightening a handful of obviously roomy paddings so
   the trader can fit more rows on a 1366×768 laptop. */
:root[data-density="compact"] { font-size: 14px; }
:root[data-density="compact"] .panel { padding: .35rem .5rem; }
:root[data-density="compact"] table th,
:root[data-density="compact"] table td { padding: .15rem .35rem; }
:root[data-density="compact"] .ticket-row-quick { gap: .35rem; }
:root[data-density="compact"] .ticket-advanced  { gap: .35rem; }
:root[data-density="compact"] .topbar           { padding: .35rem .5rem; }

/* Preferences panel: density toggle. */
.preferences-panel .preference-row { padding: .5rem 0; }
.preferences-panel .preference-row + .preference-row {
  border-top: 1px dashed var(--border);
  margin-top: .5rem;
}
.preferences-panel .preference-label {
  display: block; font-weight: 600; margin-bottom: .25rem;
}
.preferences-panel .field-hint {
  margin: .25rem 0 0; font-size: .8rem; color: var(--muted, #888);
}
.density-toggle { display: inline-flex; gap: .25rem; }
.density-option {
  padding: .35rem .75rem; font: inherit; font-size: .85rem;
  background: transparent; color: inherit;
  border: 1px solid var(--border); border-radius: 4px;
  cursor: pointer;
}
.density-option.active {
  background: var(--accent, #357); color: white;
  border-color: var(--accent, #357);
}
.keyboard-shortcuts {
  margin: .25rem 0 0; padding-left: 1.25rem; font-size: .85rem;
  line-height: 1.6;
}
.keyboard-shortcuts kbd {
  display: inline-block; padding: 0 .3rem;
  background: var(--panel-2, #222); border: 1px solid var(--border);
  border-radius: 3px; font-size: .8em; font-family: ui-monospace, monospace;
}

/* Focus-visible. Surface a clearly visible focus ring on keyboard
   navigation; mouse focus stays unstyled. */
:focus-visible {
  outline: 2px solid var(--accent, #357);
  outline-offset: 2px;
}
:focus:not(:focus-visible) { outline: none; }

/* Responsive ≤1280px.  Multi-column trader sub-tabs collapse to a
   single column so they remain usable on a 1366×768 laptop. */
@media (max-width: 1280px) {
  .markets-layout {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto auto;
  }
  .watchlist-layout { grid-template-columns: 1fr; }
  .ticket-row-quick { flex-wrap: wrap; }

  /* `.trader-subpanel`'s default `flex: 1; min-height: 0` (desktop) is a
     shrink-to-fit strategy that pairs with `.markets-layout`'s definite
     `minmax(0, 1fr)` row so the panel can flex-shrink to whatever space
     is available. At this breakpoint `.markets-layout`/`.watchlist-layout`
     switch to content-sized `auto` row(s) instead — those two strategies
     conflict: `min-height: 0` still lets the *flex item* itself be
     squeezed below its content's real height by whatever's left over
     after `.ticket`/`.trader-subtabs`/`.trader-bottom-band` claim their
     share of the viewport, but the `auto` grid rows inside don't shrink
     to match — they keep their full content height and simply paint
     past the squeezed box's bottom edge (no `overflow` is set on
     `.trader-subpanel`), visually overlapping `.trader-bottom-band`
     (Working orders / Executions) below. `min-height: auto` restores
     flexbox's normal "automatic minimum size" (don't shrink below
     content), so the panel takes its real ~content height and the page
     grows/scrolls instead — consistent with the "no overflow trap, page
     scrolls" approach documented above for `.trader-view`. */
  .trader-subpanel { min-height: auto; }

  /* `.trader-bottom-band`'s desktop sizing (`min-height: 180px; max-height:
     30vh`) is a *floor and ceiling* pair, not a target — with no
     `flex-grow` of its own (unlike `.trader-subpanel`, the only
     flex-grow item in `.trader-layout`'s column), the band's actual
     height is driven by its content's natural size, which — thanks to
     the `flex: 1; min-height: 0` chain inside it
     (`.trader-bottom-subpanel` → `.table-scroll`) — collapses right
     back down to the 180px floor rather than growing toward the 30vh
     ceiling. That's fine on desktop, where the *rest* of the viewport
     (Chart/DOB/Tape) needs the room instead. On mobile, now that
     `.trader-subpanel` takes its full natural height and the page
     scrolls, there's no such competition — so bumping the *floor*
     itself (not just the ceiling) gives Working orders/Executions a
     genuinely usable area instead of one barely tall enough for a
     single row. */
  .trader-bottom-band { min-height: 45vh; max-height: 60vh; }

  /* Same underlying flexbox spec quirk as `.trader-subpanel`/`.dob`/
     `.dob-grid` above, now hitting `.ticket` itself: it has its own
     `overflow-y: auto` (line ~233), so its "automatic minimum size" is
     forced to 0 rather than being based on content — on desktop this
     was masked because `.ticket-row-quick` fits on one line and there
     was always enough leftover flex space in `.trader-layout`'s column
     to naturally fit ~95px, but once `.trader-subpanel`/
     `.trader-bottom-band`'s min-heights grew to their real (large)
     content sizes just above, `.ticket`'s "leftover" share of the flex
     column shrank toward 0, and — being flex-shrinkable with no min-
     height floor of its own — it collapsed to a sliver just tall
     enough to clip its own first line, spilling the rest of the (now
     multi-line, `flex-wrap: wrap` — see above) quick-ticket row on top
     of `.trader-subtabs` below. Unlike `.trader-subpanel` above,
     `min-height: auto` is a no-op here (the automatic-minimum-size
     override applies specifically because *this* element itself has
     non-visible overflow) — `min-height: max-content` sidesteps that
     by requesting the content's real size directly instead of relying
     on the (overflow-suppressed) automatic minimum. */
  .ticket { min-height: max-content; }
}

/* ---------- Mobile navigation drawer (#408) ----------
   The primary tablist + hamburger trigger swap places at the 768px
   breakpoint. Drawer + backdrop live as body-level siblings of the
   topbar so they overlay any view; both are display:none-via-[hidden]
   above 768px (the trigger is also display:none above the breakpoint)
   so desktop is byte-identical to before. */
.mobile-nav-trigger {
  display: none;  /* shown <768px by media query below */
  background: transparent; border: 1px solid var(--border); color: var(--text);
  border-radius: 4px; padding: .2rem .55rem; font: inherit; font-size: 1rem;
  cursor: pointer; line-height: 1;
}
.mobile-nav-trigger:hover, .mobile-nav-trigger:focus-visible {
  border-color: var(--accent); color: var(--accent);
}
.mobile-nav-backdrop {
  position: fixed; inset: 0; background: rgba(0, 0, 0, .55);
  z-index: 99;
}
.mobile-nav-drawer {
  position: fixed; top: 0; left: 0; bottom: 0;
  width: min(280px, 80vw);
  background: var(--panel); border-right: 1px solid var(--border);
  padding: .75rem .5rem; z-index: 100;
  display: flex; flex-direction: column; gap: .25rem;
  box-shadow: 4px 0 18px rgba(0, 0, 0, .35);
}
.mobile-nav-list {
  display: flex; flex-direction: column; gap: .15rem;
}
.mobile-drawer-item {
  background: transparent; border: 0; color: var(--text);
  font: inherit; font-size: .95rem; text-align: left;
  padding: .65rem .75rem; border-radius: 4px; cursor: pointer;
}
.mobile-drawer-item:hover, .mobile-drawer-item:focus-visible {
  background: var(--panel-2); outline: none;
}
.mobile-drawer-item.active {
  background: var(--accent); color: #0b1320; font-weight: 600;
}
@media (max-width: 767px) {
  .mobile-nav-trigger { display: inline-flex; align-items: center; }
  /* Hide the inline tablist on phones — the drawer owns primary nav. */
  .view-toggle { display: none !important; }

  /* #408 follow-up: on phones the topbar packs hamburger + brand +
     symbol select + gateway/ws/balance pills + logout into one
     `justify-content: space-between` row with no wrap allowed, so on
     narrow viewports (~360-400px) everything overlaps instead of
     shrinking. Let both rows wrap and shrink the brand text so the
     status cluster gets its own line(s) instead of overlapping. */
  .topbar { flex-wrap: wrap; row-gap: .35rem; }
  .status { flex-wrap: wrap; row-gap: .35rem; }
  .brand { font-size: .95rem; }
}
