/*
 * ClaudeTrader v2 — Dashboard stylesheet.
 *
 * Dark theme, V1-parity design pass 2026-05-11.
 * D291 (2026-05-16): pagination controls, regime-flag scroll + tooltips,
 *   system-health divider, dash-header class.
 * dashboard-design-agent owns this file (lane-def 2026-05-11).
 *
 * Design targets:
 *   - Background  #0e1117 (very dark near-black, matches V1)
 *   - Cards       #161b22 (dark charcoal panel)
 *   - Card border #30363d (subtle dark separator)
 *   - Text        #e6edf3 (near-white primary)
 *   - Muted text  #9aa0a6 (muted gray)
 *   - Success     #3fb950 (green — test env / gains)
 *   - Danger      #f85149 (red — losses / stale)
 *   - Warning     #ff9800 (orange — prod env)
 *   - Accent      #58a6ff (link-blue for interactive elements)
 *
 * CSP: style-src 'self' — this file is self-hosted; no inline style
 * attributes, no external stylesheets. Section partials use CSS classes
 * from this file instead of inline style="...".
 *
 * Banner: env-test (green) / env-preprod (yellow, AB#1114) / env-prod (orange)
 * per §2.6 D137.
 * Banner is load-bearing safety signal — do NOT weaken or remove.
 */

/* ==========================================================================
   Reset & base
   ========================================================================== */

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

html, body {
    height: 100%;
}

/*
 * AB#1092 -- the open-position drill's scroll targets land BELOW the sticky top stack.
 *
 * #env-banner (38px) and .site-nav (44px + 1px border) are BOTH `position:
 * sticky; top: 0`. Sticky siblings do not stack: once stuck they overlap, the
 * banner (z 9999) on top of the nav (z 999), so the stack's bottom edge is the
 * taller of the two (the nav, 45px), not the sum. Element.scrollIntoView(
 * {block:"start"}) puts its target at the viewport top, i.e. under that stack,
 * hiding e.g. the trade drill's "Back to trades" button. scrollIntoView honours
 * the TARGET's scroll-margin, so the offset is carried by the targets below and
 * by nothing else.
 *
 * It is deliberately NOT `scroll-padding-top` on html/body: that would offset
 * EVERY scrollIntoView on EVERY page, including backtests.js's click-a-window-
 * row scroll to #bt-trades-container, and the Backtests page must behave
 * exactly as it did (operator 2026-10-31). The targets, and the JS that scrolls
 * to each:
 *   #si-trade-drill-view     strategy_instance.js siScrollToEl(drill view)
 *   #dash-trade-drill-view   dashboard.js _scrollToEl(drill view)
 *   .db-pos-lot-drill        the clicked lot row, J7 scroll back (both pages)
 *   #si-open-positions-card  strategy_instance.js fallback if the row was re-rendered
 *   #db-positions-container  dashboard.js fallback if the row was re-rendered
 * 8px is breathing room. If the banner or nav grows, grow --sticky-top-stack
 * (a test derives the requirement from those rules and fails if it is short).
 * A new scroll target on one of these pages must be ADDED to this list.
 */
:root {
    --sticky-top-stack: 45px;
}

#si-trade-drill-view,
#dash-trade-drill-view,
.db-pos-lot-drill,
#si-open-positions-card,
#db-positions-container {
    scroll-margin-top: calc(var(--sticky-top-stack) + 8px);
}

body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
                 "Helvetica Neue", Arial, sans-serif;
    font-size: 14px;
    line-height: 1.5;
    color: #e6edf3;
    background: #0e1117;
    margin: 0;
    padding: 0;
}

/* ==========================================================================
   Bootstrap overrides — dark-mode resets
   ========================================================================== */

/* Tables */
.table {
    color: #e6edf3;
    --bs-table-bg: transparent;
    --bs-table-striped-bg: rgba(255,255,255,0.03);
    --bs-table-hover-bg: rgba(255,255,255,0.06);
    --bs-table-border-color: #30363d;
}

.table thead th {
    color: #9aa0a6;
    border-bottom-color: #30363d;
    font-weight: 600;
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.table tbody td {
    border-color: #30363d;
    padding-top: 0.3rem;
    padding-bottom: 0.3rem;
    vertical-align: middle;
}

/* D356 Stage 3 (2026-05-28): smooth background-colour transition on
   table rows that update via SSE push.  Gives a subtle flash when a
   row's data changes (e.g. open position price updates) without being
   visually distracting.  Keep transition short — operator wants
   seamless, not flashy. */
.table tbody tr {
    transition: background-color 0.3s ease;
}

.table-borderless td,
.table-borderless th {
    border: none;
}

/* Badges — override Bootstrap defaults for dark background */
.badge.bg-light {
    background-color: #30363d !important;
    color: #e6edf3 !important;
    border: 1px solid #444c56;
}

.badge.bg-secondary {
    background-color: #444c56 !important;
}

/* Card component (Bootstrap) — dark variant */
.card {
    background-color: #161b22;
    border-color: #30363d;
    color: #e6edf3;
}

.card-header {
    background-color: #1c2128;
    border-bottom-color: #30363d;
}

/* Bootstrap text utilities */
.text-muted {
    color: #9aa0a6 !important;
}

.text-secondary {
    color: #9aa0a6 !important;
}

.text-dark {
    color: #e6edf3 !important;
}

/* Bootstrap bg-light used in sub-metric cells */
.bg-light {
    background-color: #1c2128 !important;
}

/* Form controls (not used in dashboard today; forward-compat) */
.form-control {
    background-color: #1c2128;
    border-color: #30363d;
    color: #e6edf3;
}

/* List group dark */
.list-group-item {
    background-color: transparent;
    border-color: #30363d;
    color: #e6edf3;
}

/* Alert (no-data state) */
.alert-secondary {
    background-color: #1c2128;
    border-color: #30363d;
    color: #9aa0a6;
}

/* code element */
code {
    color: #58a6ff;
    background: transparent;
}

/* Border utilities */
.border-bottom {
    border-bottom-color: #30363d !important;
}

.border-top {
    border-top-color: #30363d !important;
}

.border {
    border-color: #30363d !important;
}

.border-warning {
    border-color: #d29922 !important;
}

/* bg-warning bg-opacity-10 for halted bot cells */
.bg-warning.bg-opacity-10 {
    background-color: rgba(210, 153, 34, 0.1) !important;
}

/* ==========================================================================
   Layout — page wrap
   ========================================================================== */

.page-wrap {
    /* Full-viewport-width: V1 scales to viewport.
     * 1800px cap prevents stretched-flat look on ultra-wide monitors.
     * margin-auto centers on narrower viewports. */
    max-width: 1800px;
    margin: 0 auto;
    padding: 0 24px 40px;
}

/* ==========================================================================
   Environment banner (#env-banner)
   ========================================================================== */

/*
 * Load-bearing safety signal per §2.6 D137.
 * Green for test / yellow for pre-prod / orange for prod. Sticky, full-width, non-dismissable.
 * Colours are non-negotiable (operator-pinned 2026-05-11).
 *
 * Note: banner HTML uses class="env-test", "env-preprod" or "env-prod" on
 * #env-banner.
 * This works with CSP style-src 'self' (no inline <style> blocks).
 */
#env-banner {
    position: sticky;
    top: 0;
    z-index: 9999;
    text-align: center;
    padding: 7px 0;
    font-weight: 700;
    font-size: 1rem;
    width: 100%;
    user-select: none;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

#env-banner.env-test {
    background: #28a745;
    color: #fff;
}

#env-banner.env-prod {
    background: #ff9800;
    color: #fff;
}

/*
 * Pre-prod (AB#1114): the shared-TEST-VM pre-prod dashboard. Yellow #ffeb3b with
 * dark #212529 text (12.6:1; white on this yellow is 1.2:1). Deliberately not
 * amber #ffc107, so it cannot be read as the PROD orange above.
 */
#env-banner.env-preprod {
    background: #ffeb3b;
    color: #212529;
}

/* ==========================================================================
   Header — legacy selector kept for backward-compat; .dash-header is the
   canonical class used by dashboard.html (D291 rename).
   ========================================================================== */

header, .dash-header-legacy {
    padding: 16px 0 12px;
    border-bottom: 1px solid #30363d;
    margin-bottom: 20px;
}

header h1 {
    margin: 0 0 2px;
    font-size: 1.25rem;
    font-weight: 600;
    color: #e6edf3;
}

header p {
    margin: 0;
    color: #9aa0a6;
    font-size: 0.8rem;
}

/* ==========================================================================
   Instance section headers
   ========================================================================== */

.instance-section > h2,
#cross-instance > h2 {
    font-size: 0.75rem;
    font-weight: 700;
    color: #9aa0a6;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    padding: 6px 10px;
    background: #1c2128;
    border-left: 3px solid #30363d;
    border-radius: 0 4px 4px 0;
    margin-bottom: 12px;
}

/* Instance sections for prod instances: orange accent */
.instance-section[data-class="prod"] > h2 {
    border-left-color: #ff9800;
    color: #ff9800;
}

/* Instance sections for test instances: green accent */
.instance-section[data-class="test"] > h2 {
    border-left-color: #3fb950;
    color: #3fb950;
}

/* Cross-instance system health: muted */
#cross-instance > h2 {
    border-left-color: #444c56;
    color: #9aa0a6;
}

/* ==========================================================================
   Panel cards
   ========================================================================== */

.panel {
    background: #161b22;
    border: 1px solid #30363d;
    border-radius: 6px;
    padding: 12px 14px;
    overflow: hidden;
}

/* Deprecated .panel h3 kept for backward-compat; card-header used instead */
.panel h3 {
    margin: 0 0 8px;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #9aa0a6;
    border-bottom: 1px solid #30363d;
    padding-bottom: 6px;
}

.card-header span.fw-semibold {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    color: #9aa0a6;
}

/* Panel header timestamp */
.ts-label {
    font-size: 0.68rem;
    color: #6e7681;
}

/* "No snapshot." — dimmed italic */
.panel .missing,
.missing {
    color: #6e7681;
    font-style: italic;
    font-size: 0.8rem;
    margin: 4px 0 0;
}

/* ==========================================================================
   Metric tiles (account_summary, drawdown sub-cells)
   ========================================================================== */

/* Dark metric tile — replaces Bootstrap .bg-light.rounded.p-2 */
.metric-tile {
    background: #1c2128;
    border: 1px solid #30363d;
    border-radius: 5px;
    padding: 8px 10px;
    text-align: center;
}

.metric-tile .metric-label {
    font-size: 0.68rem;
    color: #9aa0a6;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-bottom: 2px;
}

.metric-tile .metric-value {
    font-size: 1.05rem;
    font-weight: 700;
    color: #e6edf3;
    line-height: 1.2;
}

.metric-tile .metric-sub {
    font-size: 0.68rem;
    color: #6e7681;
    margin-top: 1px;
}

/* ==========================================================================
   Colour semantics
   ========================================================================== */

.text-gain,
.text-success,
.pnl-pos { color: #3fb950 !important; }

.text-loss,
.text-danger,
.pnl-neg  { color: #f85149 !important; }

/* Prod env orange */
.text-prod    { color: #ff9800 !important; }

/* Pending / placeholder  */
.text-pending { color: #6e7681; font-style: italic; font-size: 0.8rem; }

/* ==========================================================================
   Stat summary row (top-of-instance 6-up strip)
   ========================================================================== */

.stat-strip {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 16px;
}

.stat-strip .stat-cell {
    flex: 1 1 120px;
    background: #1c2128;
    border: 1px solid #30363d;
    border-radius: 5px;
    padding: 8px 12px;
}

.stat-strip .stat-cell .stat-label {
    font-size: 0.65rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #9aa0a6;
}

.stat-strip .stat-cell .stat-val {
    font-size: 1.15rem;
    font-weight: 700;
    color: #e6edf3;
    margin: 2px 0 0;
}

.stat-strip .stat-cell .stat-sub {
    font-size: 0.65rem;
    color: #6e7681;
    margin-top: 1px;
}

/* ==========================================================================
   Strategy statistics table (full row-per-strategy format)
   ========================================================================== */

.strategy-stats-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.82rem;
}

.strategy-stats-table thead th {
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #9aa0a6;
    border-bottom: 2px solid #30363d;
    padding: 4px 8px;
}

.strategy-stats-table tbody td {
    padding: 5px 8px;
    border-bottom: 1px solid #21262d;
    color: #e6edf3;
}

.strategy-stats-table tbody tr:last-child td {
    border-bottom: none;
}

.strategy-stats-table .strategy-name {
    font-weight: 600;
    color: #e6edf3;
    white-space: nowrap;
}

/* ==========================================================================
   Bot status grid
   ========================================================================== */

.bot-cell {
    background: #1c2128;
    border: 1px solid #30363d;
    border-radius: 5px;
    padding: 8px 10px;
}

.bot-cell.halted {
    border-color: #d29922;
    background: rgba(210, 153, 34, 0.08);
}

/*
 * .bot-cell.disabled-setting — 2026-09-21 (b562e7fea repoint, D990/D999/
 * AB#567): bot_runtime_settings.enabled = FALSE, a deliberate operator
 * settings choice, NOT a risk halt. Deliberately MUTED grey, never the
 * amber .halted treatment above — amber is reserved for an alarming /
 * unexpected state, and this one is neither. .halted itself is retained
 * for the trading-halt-banner CSS family (sections/trading_halt_banner.html,
 * a separate mechanism); it is no longer applied by bot_status.html or
 * system_health_standalone.html, which have no halt producer to read.
 */
.bot-cell.disabled-setting {
    border-color: #484f58;
    background: rgba(110, 118, 129, 0.08);
}

.bot-cell.disabled-setting .bot-name {
    color: #9aa0a6;
}

.bot-cell .bot-name {
    font-size: 0.8rem;
    font-weight: 600;
    color: #e6edf3;
    word-break: break-word;
}

.bot-cell .bot-meta {
    font-size: 0.72rem;
    color: #9aa0a6;
    margin-top: 3px;
}

.bot-cell .bot-hb {
    font-size: 0.68rem;
    color: #6e7681;
    margin-top: 2px;
}

/*
 * A.1 (2026-06-10) — per-bot dependency colour-coding.
 *
 * Three states keyed to worst-tier dependency health:
 *   bot-dep-green  — all sources healthy (subtle green left-border)
 *   bot-dep-yellow — degrading dependency impaired; bot still operates (amber border)
 *   bot-dep-red    — critical dependency dead; bot will NOT produce trades (red border)
 *
 * Border treatment: left-border accent (3px) provides instant at-a-glance scan
 * without overwhelming the halted (amber full-border) state. The halted border
 * wins via specificity (.bot-cell.halted has full border-color).
 *
 * NOTE: .bot-cell.halted takes visual precedence — a halted bot is already
 * clearly distinguished; the dep-colour left-border provides additional signal
 * only when NOT halted.
 */
.bot-cell.bot-dep-green {
    border-left: 3px solid #3fb950;
}

.bot-cell.bot-dep-yellow {
    border-left: 3px solid #d29922;
    background: rgba(210, 153, 34, 0.05);
}

.bot-cell.bot-dep-red {
    border-left: 3px solid #f85149;
    background: rgba(248, 81, 73, 0.07);
}

/* Reason text injected by JS beneath the bot meta row */
.bot-cell .bot-dep-reason {
    font-size: 0.65rem;
    color: #9aa0a6;
    margin-top: 3px;
    line-height: 1.35;
    word-break: break-word;
    font-style: italic;
}

.bot-cell.bot-dep-red .bot-dep-reason {
    color: #f85149;
    font-style: normal;
    font-weight: 600;
}

.bot-cell.bot-dep-yellow .bot-dep-reason {
    color: #d29922;
}

/*
 * Legend row in the bot-status card header (compact dot + label).
 * Shows green/yellow/red meaning to the operator in-card.
 */
.bot-dep-legend-label {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: 0.62rem;
    color: #6e7681;
    white-space: nowrap;
    margin-right: 6px;
}

.bot-dep-dot {
    display: inline-block;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    margin-left: 5px;
    flex-shrink: 0;
}

.bot-dep-dot-green  { background: #3fb950; }
.bot-dep-dot-yellow { background: #d29922; }
.bot-dep-dot-red    { background: #f85149; }

/* ==========================================================================
   Service health table
   ========================================================================== */

.svc-name {
    font-size: 0.78rem;
    font-weight: 600;
    word-break: break-word;
    max-width: 16rem;
}

/* ==========================================================================
   Pipeline Health widget (D362 fan-out #3, 2026-06-03)
   Severity: WARNING only (yellow/amber). Never critical/red.
   ========================================================================== */

/* Green checkmark for all-clear state */
.pipeline-health-ok-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: rgba(63, 185, 80, 0.15);
    border: 1px solid #3fb950;
    color: #3fb950;
    font-size: 0.85rem;
    font-weight: 700;
    flex-shrink: 0;
}

.pipeline-health-ok-text {
    font-size: 0.88rem;
    color: #3fb950;
}

/* Warning badge — amber, never red */
.pipeline-health-warn-badge {
    background-color: rgba(210, 153, 34, 0.20) !important;
    color: #d29922 !important;
    border: 1px solid #d29922;
    font-size: 0.78rem;
    font-weight: 700;
    padding: 3px 8px;
    border-radius: 4px;
}

/* Event table */
.pipeline-health-table thead th {
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #9aa0a6;
    border-bottom: 1px solid #30363d;
    padding: 4px 8px 4px 0;
    white-space: nowrap;
}

.pipeline-health-table tbody td {
    padding: 5px 8px 5px 0;
    border-bottom: 1px solid #21262d;
    vertical-align: top;
}

.pipeline-health-table tbody tr:last-child td {
    border-bottom: none;
}

/* Event rows: subtle amber left-border to reinforce WARNING tier */
.pipeline-health-event-row {
    border-left: 2px solid #d29922;
}

/* event_type chip — compact monospace pill */
.pipeline-health-event-type-chip {
    display: inline-block;
    font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
    font-size: 0.72rem;
    color: #d29922;
    background: rgba(210, 153, 34, 0.08);
    border: 1px solid rgba(210, 153, 34, 0.25);
    border-radius: 3px;
    padding: 1px 5px;
    white-space: nowrap;
}

/* action_taken + suggested_next_step cells — allow wrap at word boundary */
.pipeline-health-action,
.pipeline-health-next-step {
    color: #e6edf3;
    word-break: break-word;
    max-width: 18rem;
    font-size: 0.80rem;
}

.pipeline-health-next-step {
    color: #9aa0a6;
}

/* ==========================================================================
   Charts
   ========================================================================== */

/* Wrapper to give canvas a defined height while allowing responsiveness */
.chart-wrap {
    position: relative;
    width: 100%;
}

.chart-wrap canvas {
    display: block;
    width: 100% !important;
}

/* Placeholder card for charts pending data backbone (D153/D154) */
.chart-placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    background: #1c2128;
    border: 1px dashed #30363d;
    border-radius: 5px;
    padding: 24px 16px;
    text-align: center;
    color: #6e7681;
    font-size: 0.8rem;
}

.chart-placeholder .ph-label {
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #9aa0a6;
    margin-bottom: 6px;
}

/* Donut chart canvas — fixed square so donut renders correctly */
.donut-wrap {
    position: relative;
    width: 100%;
    max-width: 200px;
    margin: 0 auto;
}

/* ==========================================================================
   Alpaca-direct stat strip (D291 2026-05-18)
   ========================================================================== */

/*
 * Top-of-page live stat strip. Six tiles fed by XHR to /api/alpaca/account
 * + /api/alpaca/positions (dashboard.js initAlpacaWidgets). Dark themed,
 * consistent with stat-strip cells below but wider label/value sizing.
 *
 * .alpaca-stat-strip wraps the .stat-strip flex row + the timestamp/refresh
 * meta row below it.
 */
.alpaca-stat-strip {
    background: #0e1117;
    border: 1px solid #30363d;
    border-radius: 6px;
    padding: 12px 14px 8px;
}

/* Meta row below the stat tiles: timestamp (left) + refresh button (right) */
.alpaca-strip-meta {
    border-top: 1px solid #21262d;
    padding-top: 5px;
    margin-top: 6px;
}

/* Refresh button — small, muted, icon-only */
.alpaca-refresh-btn {
    background: transparent;
    border: 1px solid #30363d;
    color: #9aa0a6;
    font-size: 0.85rem;
    padding: 1px 7px;
    border-radius: 4px;
    cursor: pointer;
    line-height: 1.4;
}

.alpaca-refresh-btn:hover {
    border-color: #58a6ff;
    color: #58a6ff;
}

/* Section divider label used in system_health_standalone */
.section-divider-label {
    font-size: 0.75rem;
    font-weight: 700;
    color: #9aa0a6;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    padding: 6px 10px;
    background: #1c2128;
    border-left: 3px solid #444c56;
    border-radius: 0 4px 4px 0;
    margin-bottom: 12px;
}

/* ==========================================================================
   Section dividers
   ========================================================================== */

.section-label {
    font-size: 0.65rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: #6e7681;
    margin-bottom: 4px;
    margin-top: 10px;
}

/* ==========================================================================
   Footer
   ========================================================================== */

footer {
    margin-top: 40px;
    padding-top: 12px;
    border-top: 1px solid #30363d;
    font-size: 0.72rem;
    color: #6e7681;
    text-align: center;
}

/* ==========================================================================
   Dashboard header
   ========================================================================== */

/* D291: renamed from plain `header` selector to .dash-header to avoid
   conflicting with any Bootstrap component that uses <header> internally. */
.dash-header {
    padding: 16px 0 12px;
    border-bottom: 1px solid #30363d;
    margin-bottom: 20px;
}

.dash-header h1 {
    margin: 0 0 2px;
    font-size: 1.25rem;
    font-weight: 600;
    color: #e6edf3;
}

.dash-header p {
    margin: 0;
    color: #9aa0a6;
    font-size: 0.8rem;
}

/* ==========================================================================
   System-health section divider (D291)
   ========================================================================== */

/*
 * Full-width divider band that visually separates the trading sections
 * (per-instance, top) from the system-health / infrastructure panels
 * (cross-instance, bottom). The band is deliberately muted — it draws
 * the eye without competing with the trading content colour signals.
 */
.system-health-divider {
    margin: 24px -24px 20px;   /* bleed to page-wrap padding edges */
    padding: 10px 24px;
    background: #1c2128;
    border-top: 1px solid #30363d;
    border-bottom: 1px solid #30363d;
    display: flex;
    align-items: center;
    gap: 10px;
}

.system-health-divider::before,
.system-health-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: #30363d;
}

.system-health-divider-label {
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.10em;
    color: #6e7681;
    white-space: nowrap;
}

.system-health-section {
    /* Slight background tint to reinforce the secondary nature of
       this section vs the primary trading panels above. */
    background: rgba(28, 33, 40, 0.35);
    border-radius: 6px;
    padding: 16px 12px 8px;
    margin-left: -12px;
    margin-right: -12px;
}

/* ==========================================================================
   Pagination controls (D291)
   ========================================================================== */

.pager-controls,
.bt-trades-pager {
    border-top: 1px solid #30363d;
    padding-top: 8px;
    margin-top: 4px;
}

.pager-controls .btn-outline-secondary,
.bt-trades-pager .btn-outline-secondary {
    color: #9aa0a6;
    border-color: #30363d;
    font-size: 0.75rem;
    padding: 2px 10px;
}

.pager-controls .btn-outline-secondary:hover:not(:disabled),
.bt-trades-pager .btn-outline-secondary:hover:not(:disabled) {
    background: #1c2128;
    color: #e6edf3;
    border-color: #58a6ff;
}

.pager-controls .btn-outline-secondary:disabled,
.bt-trades-pager .btn-outline-secondary:disabled {
    opacity: 0.35;
    cursor: not-allowed;
}

.pager-status {
    font-size: 0.72rem;
    color: #6e7681;
}

/* Rows hidden by pagination — dashboard.js toggles this class */
.pager-hidden {
    display: none !important;
}

/* ==========================================================================
   AB#286 — trades-tab numbered/jump pager (First/Prev/numbered/Next/Last +
   jump-to-page). #bt-trades-pager's contents are fully rebuilt by
   backtests.js::renderTradesPagerControls on every render (numbered pages
   vary per run) — these rules style that generated markup. Shared by
   backtests.html's run-detail AND strategy_instance.html's reused
   CTBacktests.renderTrades (one pager style for both).
   ========================================================================== */

#bt-trades-pager,
.bt-trades-pager {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
}

.bt-trades-pager-nums {
    display: flex;
    align-items: center;
    gap: 2px;
}

.bt-trades-pager-num {
    min-width: 1.75rem;
}

.bt-trades-pager-current {
    color: #e6edf3;
    border-color: #58a6ff;
    background: #1c2128;
}

.bt-trades-pager-ellipsis {
    padding: 0 2px;
}

.bt-trades-pager-jump {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-left: auto;
}

.bt-trades-pager-jump-input {
    width: 4.5rem;
}

/* ==========================================================================
   Regime-flag analysis widget (D291)
   ========================================================================== */

/*
 * .regime-flag-scroll — own overflow-y:auto container so the flag table
 * has its own scroll-bar rather than growing the page height unbounded.
 * Max-height sized to show ~6 flag rows (each ~32px) before scrolling.
 */
.regime-flag-scroll {
    max-height: 220px;
    overflow-y: auto;
    overflow-x: auto;
    border: 1px solid #30363d;
    border-radius: 4px;
    background: #0e1117;
    /* Custom scrollbar — webkit only; degrades gracefully elsewhere */
    scrollbar-width: thin;
    scrollbar-color: #30363d #0e1117;
}

.regime-flag-scroll::-webkit-scrollbar {
    width: 6px;
    height: 6px;
}

.regime-flag-scroll::-webkit-scrollbar-track {
    background: #0e1117;
}

.regime-flag-scroll::-webkit-scrollbar-thumb {
    background: #30363d;
    border-radius: 3px;
}

.regime-flag-scroll::-webkit-scrollbar-thumb:hover {
    background: #444c56;
}

/* Sticky header inside the scroll container */
.regime-flag-table thead th {
    position: sticky;
    top: 0;
    background: #161b22;
    z-index: 1;
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #9aa0a6;
    border-bottom: 1px solid #30363d;
    padding: 5px 6px;
    white-space: nowrap;
}

.regime-flag-table tbody td {
    font-size: 0.78rem;
    padding: 4px 6px;
    border-bottom: 1px solid #21262d;
    vertical-align: middle;
}

.regime-flag-table tbody tr:last-child td {
    border-bottom: none;
}

/* Flag name cell — cursor indicates tooltip is available */
.regime-flag-name {
    font-weight: 600;
    color: #e6edf3;
    cursor: help;
    border-bottom: 1px dashed #444c56;
    padding-bottom: 1px;
}

/* Inline help icon next to each flag name */
.regime-flag-help-inline,
.regime-flag-help-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: #30363d;
    color: #9aa0a6;
    font-size: 0.6rem;
    font-weight: 700;
    cursor: help;
    margin-left: 4px;
    vertical-align: middle;
    line-height: 1;
    user-select: none;
    border: 1px solid #444c56;
}

.regime-flag-help-icon {
    /* Standalone icon in the section-label row — slightly larger */
    width: 16px;
    height: 16px;
    font-size: 0.65rem;
}

/* Sub-label showing strategy name under each flag row */
.regime-flag-strategy {
    font-size: 0.72rem;
    color: #9aa0a6;
}

/* Legend below the flag table */
.regime-flag-legend {
    font-size: 0.7rem;
    color: #6e7681;
    line-height: 1.4;
    padding-top: 4px;
}

/* ==========================================================================
   Alpaca-direct positions panel (D291-WIDGETS 2026-05-18)
   ========================================================================== */

/*
 * .alpaca-pos-live wraps the live Alpaca positions table shown when no
 * reporting-service snapshot is available yet (reporting-stale F3 state).
 * Provides a subtle indicator that the data is broker-direct, not snapshot.
 */
.alpaca-pos-live {
    /* No structural difference from snapshot view — same dark card.
       The badge sub-label ("Alpaca live") and ts-label distinguish it. */
}

.alpaca-pos-loading td {
    padding: 12px 0;
    color: #6e7681;
    font-style: italic;
    font-size: 0.8rem;
}

/* Timestamp on open_positions header (live variant) */
.alpaca-pos-ts {
    font-size: 0.68rem;
    color: #3fb950;   /* green — live / fresh */
    font-style: normal;
}

/* ==========================================================================
   V1-parity chart cards + KPI stat cards (D291-third-pass 2026-05-18)
   ========================================================================== */

/*
 * .chart-card — V1-parity dark card for Chart.js canvases and stats tables.
 * Used in the new consolidated chart section of dashboard.html.
 * Mirrors V1's .chart-card exactly (background, border, radius, padding).
 *
 * CRITICAL: display:flex + flex-direction:column lets height-variant classes
 * (.chart-card-h280 etc.) constrain the card; canvas { flex:1; min-height:0 }
 * then fills the remaining space without overflowing / growing the parent.
 * Without this cascade the Chart.js responsive resize loop has nothing to
 * clamp against — parent grows → resize event → canvas grows → repeat.
 */
.chart-card {
    background: #161b22;
    border: 1px solid #30363d;
    border-radius: 12px;
    padding: 1.25rem;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

/* Height variant classes — replaces inline style="height:..." attributes
   which are blocked by CSP style-src 'self'.                              */
.chart-card-h280 { height: 280px; }
.chart-card-h250 { height: 250px; }
.chart-card-h260 { height: 260px; }
.chart-card-h240 { height: 240px; }

/* Canvas fills remaining vertical space below .chart-title without
   growing the card. min-height:0 is required in flex context to allow
   the canvas to shrink below its natural content height.                  */
.chart-card canvas {
    display: block;
    width: 100% !important;
    max-width: 100%;
    flex: 1;
    min-height: 0;
}

/* The inner flex row for side-by-side donuts in the Capital Distribution
   card. Fills remaining space below the chart-title.                      */
.chart-card-inner-flex {
    flex: 1;
    min-height: 0;
    display: flex;
    gap: 8px;
}

.chart-card-inner-flex .flex-fill {
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.chart-card-inner-flex .flex-fill canvas {
    flex: 1;
    min-height: 0;
}

/*
 * Capital Distribution near-zero-base note (2026-07-16).
 *
 * Shown by dashboard.js showCapitalNote() in place of a donut whose total base
 * is zero or trivially small (< CAPITAL_PCT_MIN_BASE_USD). A percentage off a
 * near-zero base misrepresents dust as a distribution, so the honest render is
 * absolute dollars as text. Muted grey per the non-attention colour semantics —
 * "no capital deployed" is a normal state, not an alert.
 *
 * .capital-hidden is the toggle both the note and the canvas share (the JS
 * flips exactly one of them on). Kept as a dashboard.css class rather than an
 * inline style attribute — CSP style-src 'self' rejects inline styles.
 */
.capital-note {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    min-height: 0;
    padding: 8px 6px;
    color: #9aa0a6;
}

.capital-note-head {
    font-size: 0.85rem;
    font-weight: 600;
    color: #c9d1d9;
}

.capital-note-sub {
    font-size: 0.78rem;
    color: #9aa0a6;
    word-break: break-word;
}

/*
 * .capital-note-alert (2026-09-02) — modifier applied by showCapitalNote()'s
 * `variant` param for a capital-donut absence state that is an ATTENTION
 * condition rather than a normal "no data" one: deployed capital exceeding
 * total account equity (over-deployed / on margin). Red, matching the
 * house alert/loss semantic (#e74c3c), so this reads as unmistakably
 * different from the muted grey "no capital deployed" states above.
 */
.capital-note-alert {
    border: 1px solid #e74c3c;
    border-radius: 6px;
    background: rgba(231, 76, 60, 0.08);
}

.capital-note-alert .capital-note-head {
    color: #e74c3c;
}

/*
 * .capital-caveat (2026-09-02) — persistent footnote shown ALONGSIDE a
 * normally-drawn capital_locked_donut ring (never a replacement for it,
 * unlike .capital-note above) when one or more slices are not fully priced
 * (deployed_price_basis: "unavailable"). Amber/caution, not the alert red —
 * a stale price is a caveat on an otherwise-normal render, not itself an
 * attention condition.
 */
.capital-caveat {
    font-size: 0.72rem;
    color: #d29922;
    text-align: center;
    padding: 2px 6px 4px;
    word-break: break-word;
}

.capital-hidden {
    display: none !important;
}

/*
 * .chart-title — section label above each chart (uppercase, muted, small).
 * Mirrors V1's .chart-title exactly.
 */
.chart-title {
    font-size: 0.85rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: #8b949e;
    margin-bottom: 0.75rem;
}

/*
 * .stat-card — V1-parity KPI tile for the 5-up top row.
 * Background, border, radius, padding all match V1 .stat-card.
 */
.stat-card {
    background: #161b22;
    border: 1px solid #30363d;
    border-radius: 12px;
    padding: 1.2rem 1.5rem;
    height: 100%;
}

.stat-card .stat-label {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: #8b949e;
    margin-bottom: 0.3rem;
}

.stat-card .stat-val {
    font-size: 1.8rem;
    font-weight: 700;
    line-height: 1;
    color: #e6edf3;
}

.stat-card .stat-sub {
    font-size: 0.8rem;
    color: #8b949e;
    margin-top: 0.25rem;
}

/* Guard badge in header */
#guard-badge.badge-trading {
    background: #238636;
    color: #fff;
}

#guard-badge.badge-paused {
    background: #da3633;
    color: #fff;
}

/* Refresh button in header */
.refresh-btn {
    font-size: 0.78rem;
}

/* No-data state for chart containers */
.no-data {
    color: #8b949e;
    font-style: italic;
    text-align: center;
    padding: 2rem;
    margin: 0;
}

/* Chart-level empty-state injected by dashboard.js _setChartEmptyState().
   Uses flex so it fills the remaining vertical space inside .chart-card
   and centres the message both horizontally and vertically.
   The canvas is hidden (display:none) when this element is visible. */
.chart-empty-state {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.82rem;
    padding: 1rem;
}

/* A panel whose /api/data-sourced content did NOT load (dashboard.js
   _paintApiPanelFailure). Layered ON TOP of .no-data / .chart-empty-state, so
   it keeps their layout and only changes the look: a failed load must not
   read like the muted-italic "No open positions." genuine empty state, which
   is an affirmative claim about the book. Declared after .no-data so it wins
   at equal specificity; the td form out-ranks Bootstrap's `.table > * > *`
   cell colour, which otherwise leaves the table-cell message white. Colour is
   the alert red already used for DEGRADED / stale; the text itself states what
   was observed and what it is NOT. */
.api-panel-failed,
td.no-data.api-panel-failed {
    color: #e74c3c;
    font-style: normal;
}

/* "unavailable" in a KPI tile is a STATE, not a figure: at the figure's 1.8rem it
   overflowed a phone-width tile (and ran into its neighbour in the vs-S&P tile),
   and a big bold word reads as a result. Smaller, still the tile's headline. */
.stat-card .stat-val .kpi-unavailable {
    display: inline-block;
    font-size: 1.05rem;
    font-weight: 600;
    line-height: 1.2;
    max-width: 100%;
}

/* The header's status line now carries sentences ("... the server reported a
   failed read for 2 panels"). As a plain non-wrapping flex row it was squeezed
   into a one-word-per-line column on a phone. Let both rows wrap and give the
   status text its own basis. */
.dash-header > .d-flex,
.dash-header > .d-flex > .d-flex {
    flex-wrap: wrap;
    row-gap: 0.5rem;
}
.dash-header .ts-label {
    flex: 1 1 14rem;
    min-width: 0;
}

/* The REASON a KPI tile has no figure (dashboard.js renderKpis kpiCaption): a
   small muted line under the value, wrapping freely. It is the visible form of
   what used to live only in a title= tooltip, which a phone cannot hover. The
   value elements are large and may be uppercase / tracked, so the caption
   resets all of that. */
.kpi-reason {
    display: block;
    font-size: 0.7rem;
    font-weight: 400;
    letter-spacing: normal;
    text-transform: none;
    line-height: 1.25;
    color: #9aa0a6;
    margin-top: 0.2rem;
    white-space: normal;
}

/* A panel whose rows DREW but whose figures are partly missing because the
   server reported one of the reads behind them failed (dashboard.js
   _setDegradedNote). Sits above the panel, outside the container the renderers
   rewrite. Alert red left rule, muted body: it qualifies the rows beneath it
   rather than replacing them. */
.api-panel-degraded {
    border-left: 3px solid #e74c3c;
    color: #c9d1d9;
    font-size: 0.85rem;
    padding: 0.4rem 0.75rem;
    margin-bottom: 0.5rem;
}

/* ==========================================================================
   Regime-flag details/summary expandable (replaces inline style attrs)
   ========================================================================== */

/* <details> block for the regime-flag explainer */
.regime-details {
    font-size: 0.82rem;
}

/* <summary> toggle cursor + colour */
.regime-details-summary {
    cursor: pointer;
    color: #bcc4cc;
}

/* Body text inside the expanded details */
.regime-details-body {
    line-height: 1.45;
    color: #9aa4ad;
}

/* ==========================================================================
   Site navigation bar (Phase-B-3a-3 2026-05-18)
   ========================================================================== */

/*
 * .site-nav — full-width top bar above .page-wrap.
 * Sticky so it scrolls with the banner (banner is sticky too; nav is below it).
 * Dark background matching card panels; tab highlights via .site-nav-tab-active.
 * No inline style= attributes — all colours here in CSS classes.
 *
 * CSP style-src 'self' compliant (this file is self-hosted).
 */
.site-nav {
    width: 100%;
    background: #161b22;
    border-bottom: 1px solid #30363d;
    position: sticky;
    top: 0;                 /* stacks below #env-banner which is also sticky top:0 */
    z-index: 999;           /* below #env-banner (z-index:9999) */
}

/*
 * When #env-banner is visible (#env-banner is sticky top:0 at z:9999),
 * .site-nav stacks naturally below it because both are sticky and the
 * banner renders first in the DOM flow. No JS offset needed.
 */

.site-nav-inner {
    max-width: 1800px;
    margin: 0 auto;
    padding: 0 24px;
    display: flex;
    align-items: center;
    gap: 0;
    height: 44px;
}

/* Brand mark — left side */
.site-nav-brand {
    font-size: 0.9rem;
    font-weight: 700;
    color: #e6edf3;
    letter-spacing: 0.02em;
    margin-right: 24px;
    white-space: nowrap;
    flex-shrink: 0;
}

/* Tab list — fills remaining space */
.site-nav-tabs {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    align-items: stretch;
    flex: 1;
    gap: 0;
}

.site-nav-tabs li {
    display: flex;
    align-items: stretch;
}

/* Individual tab link */
.site-nav-tab {
    display: flex;
    align-items: center;
    padding: 0 16px;
    height: 44px;
    font-size: 0.85rem;
    font-weight: 500;
    color: #9aa0a6;
    text-decoration: none;
    border-bottom: 2px solid transparent;
    transition: color 0.15s ease, border-color 0.15s ease;
    white-space: nowrap;
}

.site-nav-tab:hover {
    color: #e6edf3;
    border-bottom-color: #444c56;
    text-decoration: none;
}

/* Active tab — bright text + accent underline */
.site-nav-tab-active,
.site-nav-tab-active:hover {
    color: #58a6ff;
    border-bottom-color: #58a6ff;
    font-weight: 600;
}

/* Logout form — right-aligned */
.site-nav-logout {
    margin-left: auto;
    padding: 0;
    flex-shrink: 0;
}

.site-nav-logout-btn {
    background: transparent;
    border: 1px solid #30363d;
    color: #9aa0a6;
    font-size: 0.8rem;
    padding: 4px 12px;
    border-radius: 4px;
    cursor: pointer;
    line-height: 1.4;
    font-family: inherit;
}

.site-nav-logout-btn:hover {
    border-color: #f85149;
    color: #f85149;
}

/* ==========================================================================
   Login page (Phase-B-3a-3 2026-05-18)
   ========================================================================== */

/*
 * .login-wrap — full-viewport centred container for the login card.
 * Dark background; card floats in centre.
 */
.login-wrap {
    min-height: calc(100vh - 80px);   /* account for banner height */
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 40px 16px;
}

.login-card {
    background: #161b22;
    border: 1px solid #30363d;
    border-radius: 12px;
    padding: 36px 40px 32px;
    width: 100%;
    max-width: 400px;
}

/* Brand mark inside login card */
.login-brand {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 20px;
}

.login-brand-icon {
    font-size: 1.4rem;
    line-height: 1;
}

.login-brand-name {
    font-size: 1rem;
    font-weight: 700;
    color: #e6edf3;
    letter-spacing: 0.01em;
}

.login-heading {
    font-size: 1.2rem;
    font-weight: 600;
    color: #e6edf3;
    margin: 0 0 20px;
}

/* Notice banners (expired / logged_out / error) */
.login-notice {
    border-radius: 6px;
    padding: 10px 14px;
    font-size: 0.85rem;
    margin-bottom: 16px;
    border: 1px solid transparent;
}

.login-notice-error {
    background: rgba(248, 81, 73, 0.12);
    border-color: rgba(248, 81, 73, 0.35);
    color: #f85149;
}

.login-notice-warn {
    background: rgba(255, 152, 0, 0.10);
    border-color: rgba(255, 152, 0, 0.30);
    color: #ff9800;
}

.login-notice-info {
    background: rgba(88, 166, 255, 0.10);
    border-color: rgba(88, 166, 255, 0.25);
    color: #58a6ff;
}

/* Form field layout */
.login-form {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.login-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.login-label {
    font-size: 0.82rem;
    font-weight: 600;
    color: #9aa0a6;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.login-input {
    background: #0e1117;
    border: 1px solid #30363d;
    border-radius: 6px;
    color: #e6edf3;
    font-size: 0.95rem;
    font-family: inherit;
    padding: 9px 12px;
    outline: none;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.login-input:focus {
    border-color: #58a6ff;
    box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.20);
}

.login-input::placeholder {
    color: #6e7681;
}

.login-submit-btn {
    background: #238636;
    border: 1px solid #2ea043;
    color: #fff;
    font-size: 0.95rem;
    font-weight: 600;
    font-family: inherit;
    padding: 10px 0;
    border-radius: 6px;
    cursor: pointer;
    margin-top: 4px;
    transition: background 0.15s ease, border-color 0.15s ease;
}

.login-submit-btn:hover {
    background: #2ea043;
    border-color: #3fb950;
}

.login-submit-btn:active {
    background: #1a7f37;
}

.login-footer-note {
    font-size: 0.75rem;
    color: #6e7681;
    text-align: center;
    margin: 20px 0 0;
}

/* ==========================================================================
   Settings page (Phase-B-3a-3 2026-05-18)
   ========================================================================== */

/*
 * Settings page wrapper and heading.
 */
.settings-wrap {
    max-width: 1000px;
    margin: 0 auto;
    padding: 24px 0 40px;
}

.settings-heading {
    font-size: 1.1rem;
    font-weight: 700;
    color: #e6edf3;
    margin-bottom: 20px;
    letter-spacing: 0.01em;
}

/*
 * LIFECYCLE BADGE (AB#14, operator 2026-07-20) — the registry lifecycle_state of one
 * strategy instance, on the settings table.
 *
 * Colour is the SIGNAL, not decoration: a paused instance (universe E, paused after it
 * engaged a fail-closed collateral fence) must be distinguishable at a glance from an
 * active sibling that shares its strategy code and its allocation value. The text is
 * always present regardless of colour — colour alone is never the only carrier.
 */
.lifecycle-badge {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 10px;
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    border: 1px solid transparent;
}

.lifecycle-badge-active {
    background: #12261a;
    color: #3fb950;
    border-color: #238636;
}

.lifecycle-badge-paused {
    background: #2b2412;
    color: #d29922;
    border-color: #9e6a03;
}

.lifecycle-badge-retired {
    background: #21262d;
    color: #8b949e;
    border-color: #30363d;
}

.lifecycle-badge-unknown {
    background: #2b1214;
    color: #f85149;
    border-color: #da3633;
}

.settings-load-error-detail {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.8rem;
    margin-top: 8px;
    word-break: break-word;
}

.settings-load-error-hint {
    font-size: 0.8rem;
    margin-top: 8px;
    opacity: 0.85;
}

/*
 * Running total banner (div wrapping the total % display).
 * JS adds .alloc-banner-valid / .alloc-banner-invalid to drive colour.
 */
.alloc-total-banner {
    background: #161b22;
    border: 1px solid #30363d;
    border-radius: 6px;
    padding: 10px 16px;
    font-size: 0.9rem;
    font-weight: 600;
    color: #e6edf3;
}

.alloc-total-check {
    margin-left: 8px;
    font-size: 0.82rem;
    opacity: 0.85;
}

.alloc-total-ok {
    color: #3fb950;
    font-weight: 700;
}

/* State classes toggled by settings.js on #alloc-total-banner */
.alloc-banner-valid {
    border-color: #3fb950;
    color: #3fb950;
}

.alloc-banner-invalid {
    border-color: #e74c3c;
    color: #e74c3c;
}

/* Hides the OK check span when total is not 100% */
.alloc-check-hidden {
    display: none;
}

/*
 * Empty bots no-data message.
 */
.settings-no-data {
    background: #161b22;
    border: 1px solid #30363d;
    border-radius: 6px;
    padding: 20px;
    color: #9aa0a6;
    font-size: 0.88rem;
}

/*
 * Flash message (server-rendered after save POST redirect).
 */
.settings-flash {
    border-radius: 6px;
    padding: 11px 16px;
    font-size: 0.88rem;
    margin-bottom: 16px;
    border: 1px solid transparent;
}

.settings-flash-success {
    background: rgba(63, 185, 80, 0.12);
    border-color: rgba(63, 185, 80, 0.35);
    color: #3fb950;
}

.settings-flash-error {
    background: rgba(248, 81, 73, 0.12);
    border-color: rgba(248, 81, 73, 0.35);
    color: #f85149;
}

.settings-flash-info {
    background: rgba(88, 166, 255, 0.10);
    border-color: rgba(88, 166, 255, 0.25);
    color: #58a6ff;
}

/*
 * Allocation total banner — sticky just below the site-nav.
 * Green when total == 100; red otherwise.
 * z-index 998 (below site-nav:999, below env-banner:9999).
 */
.alloc-banner {
    position: sticky;
    top: 44px;              /* height of .site-nav */
    z-index: 998;
    padding: 9px 16px;
    font-size: 0.9rem;
    font-weight: 700;
    text-align: center;
    border-radius: 0;
    border-bottom: 2px solid transparent;
    letter-spacing: 0.01em;
    margin-left: -24px;     /* bleed to page-wrap edges */
    margin-right: -24px;
    margin-bottom: 20px;
}

.alloc-banner-valid {
    background: #196430;
    border-bottom-color: #3fb950;
    color: #3fb950;
}

.alloc-banner-invalid {
    background: #3d0f0c;
    border-bottom-color: #f85149;
    color: #f85149;
}

.alloc-banner-ok {
    margin-left: 6px;
    font-size: 1rem;
}

.alloc-banner-check {
    margin-left: 6px;
    font-size: 0.82rem;
    opacity: 0.85;
}

/*
 * Settings section blocks.
 */
.settings-section {
    background: #161b22;
    border: 1px solid #30363d;
    border-radius: 8px;
    padding: 20px 20px 16px;
    margin-bottom: 20px;
}

.settings-section-heading {
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: #9aa0a6;
    margin: 0 0 6px;
    border-bottom: 1px solid #30363d;
    padding-bottom: 8px;
}

.settings-section-desc {
    font-size: 0.82rem;
    color: #9aa0a6;
    margin: 0 0 14px;
    line-height: 1.5;
}

/*
 * Bot allocation table.
 */
.settings-bot-table {
    color: #e6edf3;
    font-size: 0.85rem;
    margin-bottom: 0;
}

.settings-bot-table thead th {
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #9aa0a6;
    border-bottom: 2px solid #30363d;
    padding: 6px 8px;
    background: transparent;
}

.settings-bot-table tbody td {
    padding: 7px 8px;
    border-bottom: 1px solid #21262d;
    vertical-align: middle;
}

.settings-bot-table tbody tr:last-child td {
    border-bottom: none;
}

.bot-table-name {
    font-weight: 600;
    color: #e6edf3;
    white-space: nowrap;
}

.bot-table-tenor,
.bot-table-regime {
    font-size: 0.78rem;
    color: #9aa0a6;
    white-space: nowrap;
}

/* Disabled bot row — visually de-emphasised */
.bot-row-disabled .bot-table-name {
    color: #6e7681;
    text-decoration: line-through;
    text-decoration-color: #444c56;
}

.bot-row-disabled .bot-table-tenor,
.bot-row-disabled .bot-table-regime {
    opacity: 0.5;
}

/* Enable/disable checkbox toggle */
.bot-toggle-label {
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.bot-enabled-check {
    width: 16px;
    height: 16px;
    cursor: pointer;
    accent-color: #3fb950;
}

/* Allocation number input */
.bot-alloc-input {
    background: #0e1117;
    border: 1px solid #30363d;
    border-radius: 4px;
    color: #e6edf3;
    font-size: 0.88rem;
    font-family: inherit;
    padding: 4px 8px;
    text-align: right;
    width: 80px;
    outline: none;
    transition: border-color 0.15s ease;
}

.bot-alloc-input:focus {
    border-color: #58a6ff;
}

.bot-alloc-input:disabled {
    opacity: 0.4;
    cursor: not-allowed;
    background: #161b22;
}

/* Collateral cap column header */
.bot-table-collateral-header {
    white-space: nowrap;
}

.bot-table-th-note {
    display: block;
    font-size: 0.65rem;
    font-weight: 400;
    color: #6c757d;
    text-transform: none;
    letter-spacing: 0;
}

/* Collateral cap cell */
.bot-table-collateral-cell {
    white-space: nowrap;
}

/* Per-contract collateral cap number input (options bots only) */
.bot-collateral-input {
    background: #0e1117;
    border: 1px solid #30363d;
    border-radius: 4px;
    color: #e6edf3;
    font-size: 0.88rem;
    padding: 4px 6px;
    width: 100px;
    outline: none;
    transition: border-color 0.15s ease;
}

.bot-collateral-input:focus {
    border-color: #58a6ff;
}

/* N/A marker for shares-only strategies */
.bot-collateral-na {
    color: #6c757d;
    font-size: 0.85rem;
}

/*
 * PA allocation control toggle section.
 */
.settings-section-pa {
    border-left: 3px solid #58a6ff;
}

.pa-toggle-row {
    margin-bottom: 8px;
}

.pa-toggle-label {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    cursor: pointer;
}

.pa-toggle-check {
    margin-top: 2px;
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    accent-color: #58a6ff;
    cursor: pointer;
}

.pa-toggle-text {
    font-size: 0.88rem;
    color: #e6edf3;
    line-height: 1.5;
}

.pa-toggle-caption {
    font-size: 0.78rem;
    color: #58a6ff;
    font-style: italic;
    margin: 0 0 0 26px;
    opacity: 0.9;
}

.pa-toggle-caption-hidden {
    display: none;
}

/*
 * Phase 2 placeholder section (sentinel toggle).
 */
.settings-section-phase2 {
    border-left: 3px solid #30363d;
    opacity: 0.6;
}

/*
 * Save button row — sticks to bottom of viewport on tall forms.
 */
.settings-save-row {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px 0 24px;
    border-top: 1px solid #30363d;
    margin-top: 4px;
}

.settings-save-btn {
    background: #238636;
    border: 1px solid #2ea043;
    color: #fff;
    font-size: 0.92rem;
    font-weight: 600;
    font-family: inherit;
    padding: 9px 28px;
    border-radius: 6px;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
    flex-shrink: 0;
}

.settings-save-btn:hover:not(:disabled) {
    background: #2ea043;
    border-color: #3fb950;
}

.settings-save-btn:disabled {
    background: #21262d;
    border-color: #30363d;
    color: #6e7681;
    cursor: not-allowed;
}

.settings-save-hint {
    font-size: 0.8rem;
    color: #f85149;
    font-style: italic;
}

/* ==========================================================================
   Watchlist UI (Dnn-MANUAL-TICKER-WATCHLIST Phase 1 — 2026-05-20)
   ========================================================================== */

/*
 * .watchlist-section — outer container for the watchlist management UI
 * on the Settings page.  Mirrors .settings-section visual language but
 * with a blue accent border to distinguish from bot / PA sections.
 */
.watchlist-section {
    background: #161b22;
    border: 1px solid #30363d;
    border-left: 3px solid #388bfd;
    border-radius: 8px;
    padding: 20px 20px 16px;
    margin-bottom: 20px;
}

/*
 * .watchlist-add-panel — the add-ticker form card.
 */
.watchlist-add-panel {
    background: #0e1117;
    border: 1px solid #30363d;
    border-radius: 6px;
    padding: 16px;
    margin-bottom: 16px;
}

.watchlist-form-row {
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
    align-items: flex-start;
    margin-bottom: 10px;
}

.watchlist-form-group {
    display: flex;
    flex-direction: column;
    gap: 4px;
    flex: 1 1 160px;
    min-width: 140px;
}

.watchlist-form-group-wide {
    flex: 2 1 240px;
}

.watchlist-form-label {
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #9aa0a6;
}

/* Shared input / select styles */
.watchlist-input,
.watchlist-select {
    background: #161b22;
    border: 1px solid #30363d;
    border-radius: 5px;
    color: #e6edf3;
    font-size: 0.88rem;
    font-family: inherit;
    padding: 6px 10px;
    outline: none;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
    width: 100%;
}

.watchlist-input:focus,
.watchlist-select:focus {
    border-color: #388bfd;
    box-shadow: 0 0 0 2px rgba(56, 139, 253, 0.18);
}

.watchlist-input::placeholder {
    color: #6e7681;
}

/* Ticker field — slightly wider to accommodate AAPL-style symbols */
.watchlist-ticker-input {
    font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
    font-size: 0.92rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

/* Autocomplete dropdown */
.watchlist-autocomplete-wrap {
    position: relative;
}

.watchlist-autocomplete-list {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: #1c2128;
    border: 1px solid #388bfd;
    border-top: none;
    border-radius: 0 0 5px 5px;
    z-index: 200;
    max-height: 200px;
    overflow-y: auto;
    margin: 0;
    padding: 0;
    list-style: none;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.45);
    display: none;
}

.watchlist-autocomplete-list.open {
    display: block;
}

.watchlist-ac-item {
    padding: 7px 10px;
    cursor: pointer;
    border-bottom: 1px solid #21262d;
    font-size: 0.84rem;
    display: flex;
    align-items: baseline;
    gap: 8px;
}

.watchlist-ac-item:last-child {
    border-bottom: none;
}

.watchlist-ac-item:hover,
.watchlist-ac-item.active {
    background: #21262d;
}

.watchlist-ac-ticker {
    font-family: "SFMono-Regular", Consolas, monospace;
    font-weight: 700;
    color: #e6edf3;
    font-size: 0.88rem;
    min-width: 52px;
}

.watchlist-ac-name {
    color: #9aa0a6;
    font-size: 0.78rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex: 1;
}

.watchlist-ac-type {
    font-size: 0.7rem;
    color: #6e7681;
    flex-shrink: 0;
}

/* Add button */
.watchlist-add-btn {
    background: #238636;
    border: 1px solid #2ea043;
    color: #fff;
    font-size: 0.88rem;
    font-weight: 600;
    font-family: inherit;
    padding: 6px 18px;
    border-radius: 5px;
    cursor: pointer;
    white-space: nowrap;
    align-self: flex-end;
    transition: background 0.15s ease, border-color 0.15s ease;
    flex-shrink: 0;
}

.watchlist-add-btn:hover:not(:disabled) {
    background: #2ea043;
    border-color: #3fb950;
}

.watchlist-add-btn:disabled {
    background: #21262d;
    border-color: #30363d;
    color: #6e7681;
    cursor: not-allowed;
}

/* Form-level error/status feedback */
.watchlist-form-error {
    font-size: 0.8rem;
    color: #f85149;
    margin-top: 4px;
    min-height: 1.2em;
}

.watchlist-form-ok {
    font-size: 0.8rem;
    color: #3fb950;
    margin-top: 4px;
    min-height: 1.2em;
}

/*
 * .watchlist-strategy-tabs — horizontal tab row to switch between strategies.
 * Compact pill tabs.
 */
.watchlist-strategy-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-bottom: 12px;
    padding-bottom: 10px;
    border-bottom: 1px solid #30363d;
}

.watchlist-tab-btn {
    background: transparent;
    border: 1px solid #30363d;
    color: #9aa0a6;
    font-size: 0.75rem;
    font-family: inherit;
    font-weight: 600;
    padding: 4px 12px;
    border-radius: 12px;
    cursor: pointer;
    transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
    white-space: nowrap;
}

.watchlist-tab-btn:hover {
    border-color: #58a6ff;
    color: #58a6ff;
    background: rgba(88, 166, 255, 0.07);
}

.watchlist-tab-btn.wl-tab-active {
    background: #1c3a6b;
    border-color: #388bfd;
    color: #58a6ff;
}

/*
 * .watchlist-pane — per-strategy list pane (hidden unless active).
 */
.watchlist-pane {
    display: none;
}

.watchlist-pane.wl-pane-active {
    display: block;
}

/*
 * Mode sub-headers inside a pane (Universe Add / Scanner Override).
 */
.watchlist-mode-header {
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: #6e7681;
    margin: 12px 0 6px;
    padding-bottom: 4px;
    border-bottom: 1px solid #21262d;
}

/*
 * Per-entry row in the watchlist table.
 */
.watchlist-table {
    width: 100%;
    font-size: 0.82rem;
    border-collapse: collapse;
    margin-bottom: 0;
}

.watchlist-table thead th {
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #9aa0a6;
    border-bottom: 1px solid #30363d;
    padding: 4px 8px;
    white-space: nowrap;
}

.watchlist-table tbody td {
    padding: 5px 8px;
    border-bottom: 1px solid #1a1f28;
    vertical-align: middle;
    color: #e6edf3;
}

.watchlist-table tbody tr:last-child td {
    border-bottom: none;
}

.watchlist-table tbody tr.wl-row-disabled td {
    color: #6e7681;
    opacity: 0.65;
    text-decoration: line-through;
    text-decoration-color: #444c56;
}

/* Ticker cell — monospace */
.wl-ticker {
    font-family: "SFMono-Regular", Consolas, monospace;
    font-weight: 700;
    font-size: 0.88rem;
    color: #e6edf3;
    white-space: nowrap;
}

/* Company name — smaller, muted */
.wl-company {
    font-size: 0.78rem;
    color: #9aa0a6;
}

/* Added-by text */
.wl-added-by {
    font-size: 0.78rem;
    color: #9aa0a6;
}

/* Revise note group — wider flex item */
.wl-revise-note-group {
    flex: 2 1 200px;
}

/* Source note — italicised */
.wl-note {
    font-size: 0.75rem;
    color: #6e7681;
    font-style: italic;
    max-width: 200px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Date cell */
.wl-date {
    font-size: 0.72rem;
    color: #6e7681;
    white-space: nowrap;
}

/* Status badge */
.wl-status-badge {
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 2px 7px;
    border-radius: 10px;
    white-space: nowrap;
}

.wl-status-active {
    background: rgba(63, 185, 80, 0.15);
    border: 1px solid rgba(63, 185, 80, 0.35);
    color: #3fb950;
}

.wl-status-disabled {
    background: rgba(110, 118, 129, 0.12);
    border: 1px solid rgba(110, 118, 129, 0.30);
    color: #6e7681;
}

/* Action button cell */
.wl-actions {
    white-space: nowrap;
    text-align: right;
}

.wl-action-btn {
    background: transparent;
    border: 1px solid #30363d;
    color: #9aa0a6;
    font-size: 0.72rem;
    font-family: inherit;
    font-weight: 600;
    padding: 2px 9px;
    border-radius: 4px;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
    margin-left: 4px;
}

.wl-action-btn:hover {
    border-color: #58a6ff;
    color: #58a6ff;
    background: rgba(88, 166, 255, 0.07);
}

.wl-action-disable-btn:hover {
    border-color: #f85149;
    color: #f85149;
    background: rgba(248, 81, 73, 0.07);
}

/* Empty state */
.wl-empty-state {
    color: #6e7681;
    font-size: 0.8rem;
    font-style: italic;
    padding: 8px 0;
}

/* Inline revise form (expands below the target row) */
.wl-revise-form-row {
    background: #0e1117;
    border-top: 1px solid #21262d;
}

.wl-revise-form-row td {
    padding: 10px 8px !important;
}

.wl-revise-inner {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    align-items: flex-end;
}

.wl-revise-inner .watchlist-form-group {
    flex: 1 1 140px;
}

.wl-revise-save-btn {
    background: #238636;
    border: 1px solid #2ea043;
    color: #fff;
    font-size: 0.82rem;
    font-weight: 600;
    font-family: inherit;
    padding: 5px 14px;
    border-radius: 5px;
    cursor: pointer;
    flex-shrink: 0;
    align-self: flex-end;
    transition: background 0.15s ease, border-color 0.15s ease;
}

.wl-revise-save-btn:hover:not(:disabled) {
    background: #2ea043;
    border-color: #3fb950;
}

.wl-revise-cancel-btn {
    background: transparent;
    border: 1px solid #30363d;
    color: #9aa0a6;
    font-size: 0.82rem;
    font-family: inherit;
    padding: 5px 12px;
    border-radius: 5px;
    cursor: pointer;
    flex-shrink: 0;
    align-self: flex-end;
}

.wl-revise-cancel-btn:hover {
    border-color: #6e7681;
    color: #e6edf3;
}

.wl-revise-error {
    font-size: 0.75rem;
    color: #f85149;
    margin-top: 4px;
}

/* Loading spinner text */
.wl-loading {
    color: #6e7681;
    font-size: 0.8rem;
    font-style: italic;
    padding: 8px 0;
}

/* Global-note badge shown when strategy=global */
.wl-global-badge {
    font-size: 0.65rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 2px 6px;
    border-radius: 8px;
    background: rgba(88, 166, 255, 0.12);
    border: 1px solid rgba(88, 166, 255, 0.25);
    color: #58a6ff;
    margin-left: 4px;
    vertical-align: middle;
}

/* ==========================================================================
   Responsive tweaks
   ========================================================================== */

@media (max-width: 768px) {
    .page-wrap {
        padding: 0 12px 32px;
    }

    .stat-strip .stat-cell {
        flex: 1 1 45%;
    }

    .system-health-divider {
        margin-left: -12px;
        margin-right: -12px;
        padding-left: 12px;
        padding-right: 12px;
    }

    .system-health-section {
        margin-left: -4px;
        margin-right: -4px;
        padding-left: 8px;
        padding-right: 8px;
    }

    .site-nav-inner {
        padding: 0 12px;
    }

    .site-nav-tab {
        padding: 0 10px;
        font-size: 0.8rem;
    }

    .site-nav-brand {
        margin-right: 12px;
        font-size: 0.82rem;
    }

    .login-card {
        padding: 28px 20px 24px;
    }

    .alloc-banner {
        margin-left: -12px;
        margin-right: -12px;
    }
}

/* ==========================================================================
   Data-quality alert strip (Dnn-DATA-QUALITY-EVENT-ROUTING 2026-05-19)
   ========================================================================== */

/*
 * .data-quality-banner — orange/yellow WARNING-level strip.
 *
 * Visually DISTINCT from the env safety banner (#env-banner):
 *   - env-banner:           full-width sticky; solid #ff9800 (prod) or
 *                           #28a745 (test) with white text; uppercase;
 *                           z-index 9999.
 *   - .data-quality-banner: sits inside .page-wrap (not sticky); amber
 *                           #ffb84d background with dark text (#1a1205);
 *                           collapsible per-event list.
 *
 * WARNING-level (not CRITICAL like sentinel red banner).
 * Sits above the dash-header, below the nav bar.
 */

.data-quality-banner {
    background: #ffb84d;
    color: #1a1205;
    border-radius: 6px;
    padding: 10px 14px;
    margin-bottom: 14px;
    border-left: 4px solid #e08f00;
    font-size: 0.88rem;
}

/*
 * A dispatch (2026-06-10): banner title is now a breakdown string that can be
 * longer ("9 unresolved DQ events: 6 wide_spread (XLI, ODC) + 2 options_data_stale").
 * Allow the header to wrap on narrow viewports; the expand button stays on the
 * same row by setting flex-shrink:0 on it.
 */
.data-quality-banner-header {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    flex-wrap: wrap;
}

.data-quality-banner-icon {
    font-size: 1.1rem;
    flex-shrink: 0;
    color: #7a4a00;
}

.data-quality-banner-title {
    flex: 1 1 auto;
    font-size: 0.88rem;
    font-weight: 700;
    color: #1a1205;
}

.data-quality-expand-btn {
    background: transparent;
    border: none;
    cursor: pointer;
    padding: 2px 6px;
    color: #7a4a00;
    font-size: 0.78rem;
    border-radius: 3px;
    transition: background 0.15s;
    flex-shrink: 0;
}

.data-quality-expand-btn:hover {
    background: rgba(0, 0, 0, 0.1);
}

.data-quality-chevron {
    display: inline-block;
    transition: transform 0.2s ease;
}

.data-quality-expand-btn[aria-expanded="true"] .data-quality-chevron {
    transform: rotate(180deg);
}

/* The collapsible event list (hidden by default; JS removes hidden attr) */
.data-quality-event-list {
    margin-top: 8px;
    border-top: 1px solid rgba(0, 0, 0, 0.15);
    padding-top: 8px;
}

/* Individual event row */
.data-quality-event-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    padding: 6px 0;
    border-bottom: 1px solid rgba(0, 0, 0, 0.1);
    font-size: 0.82rem;
    flex-wrap: wrap;
}

.data-quality-event-row:last-child {
    border-bottom: none;
    padding-bottom: 0;
}

.data-quality-event-type {
    font-weight: 600;
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    font-size: 0.78rem;
    color: #4a2800;
    word-break: break-word;
}

.data-quality-event-time {
    font-size: 0.73rem;
    color: #6b3d00;
    flex-shrink: 0;
    white-space: nowrap;
}

/* Per-event-type detail line (symbol, spread_bps, series, age, etc.).
   Rendered by DQ_EVENT_RENDERERS registry in dashboard.js.
   Sits between the event_type header and the timestamp. */
.data-quality-event-detail {
    font-size: 0.80rem;
    color: #3a1f00;
    margin-top: 2px;
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    word-break: break-word;
}

.data-quality-event-meta {
    flex: 1 1 auto;
    min-width: 0;
}

.data-quality-ack-btn {
    background: rgba(0, 0, 0, 0.12);
    border: 1px solid rgba(0, 0, 0, 0.20);
    color: #1a1205;
    font-size: 0.75rem;
    padding: 2px 10px;
    border-radius: 3px;
    cursor: pointer;
    white-space: nowrap;
    flex-shrink: 0;
    transition: background 0.15s;
}

.data-quality-ack-btn:hover {
    background: rgba(0, 0, 0, 0.22);
}

.data-quality-ack-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* ==========================================================================
   Trading-halt banner (operator directive 2026-08-19)
   ========================================================================== */

/*
 * .trading-halt-banner — CRITICAL-level banner, the louder sibling of
 * .data-quality-banner directly above this block.
 *
 * Deliberately heavier and redder than every other in-page signal:
 *   - #env-banner (base.html):     solid #ff9800 (prod) / #28a745 (test);
 *                                  1rem text; 7px vertical padding; sticky.
 *   - .data-quality-banner:        amber #ffb84d; 0.88rem text; 10px/14px
 *                                  padding; WARNING-level.
 *   - .trading-halt-banner:        solid deep red #b71c1c; 1.05rem title,
 *                                  700 weight, uppercase; 16px/20px padding;
 *                                  thick 8px border-left; box-shadow for
 *                                  visual weight. CRITICAL-level — heavier
 *                                  on every axis (size, weight, colour,
 *                                  padding) than the amber DQ strip.
 *
 * Deliberately NOT sticky (matches .data-quality-banner's placement
 * discipline, sits inside .page-wrap below the sticky #env-banner +
 * .site-nav) and NOT sticky is fine here because it is rendered FIRST in
 * the content block — it is always the top-most thing in the scrollable
 * content on load, non-dismissable, so it does not need to re-stick.
 *
 * No animation/transition/pulse anywhere in this block — operator asked
 * for "angry", explicitly not "distracting": weight, size and colour only,
 * nothing moving.
 *
 * Colour is never the only signal — the word CEASED / UNKNOWN in the title
 * carries the meaning for anyone who cannot distinguish red.
 */
/*
 * No `display` property is set here — deliberately. The `hidden` attribute
 * dashboard.js toggles relies on the browser's own UA-stylesheet rule
 * `[hidden] { display: none }`. An author-stylesheet rule of the SAME
 * specificity (0,1,0) placed on `.trading-halt-banner` — e.g. `display:
 * block` — would sit in a higher-priority CSS origin than the UA
 * stylesheet and permanently defeat `.hidden = true`, leaving the banner
 * visible even when JS believes it hid it (caught in RULE 19 visual
 * verification: the "running" state kept rendering the stale halted
 * markup because an earlier `display: block` here beat `[hidden]`).
 * .data-quality-banner above follows the same no-`display` discipline for
 * the same reason.
 */
.trading-halt-banner {
    background: #b71c1c;
    color: #ffffff;
    border-radius: 6px;
    border-left: 8px solid #6b0000;
    padding: 16px 20px;
    margin-bottom: 18px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
}

.trading-halt-banner-inner {
    display: flex;
    align-items: flex-start;
    gap: 14px;
}

.trading-halt-banner-icon {
    font-size: 1.9rem;
    line-height: 1;
    flex-shrink: 0;
}

.trading-halt-banner-body {
    flex: 1 1 auto;
    min-width: 0;
}

.trading-halt-banner-title {
    font-size: 1.15rem;
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #ffffff;
    margin-bottom: 4px;
}

.trading-halt-banner-detail {
    font-size: 0.92rem;
    font-weight: 500;
    color: #ffe5e5;
    word-break: break-word;
}

/*
 * .state-unknown — visually DISTINCT from the halted state (RULE 19
 * requirement: never render "unknown" so it could be mistaken for
 * "running", but also never render it identically to a confirmed halt).
 *
 * Deep charcoal-maroon (not vivid red) with a dashed amber border and an
 * amber title — reads as "attention, uncertain" rather than "confirmed
 * critical stop", while still being unmissable and un-dismissable.
 */
.trading-halt-banner.state-unknown {
    background: #3a1414;
    border-left-style: dashed;
    border-left-color: #d68a00;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
}

.trading-halt-banner.state-unknown .trading-halt-banner-title {
    color: #ffb84d;
}

.trading-halt-banner.state-unknown .trading-halt-banner-detail {
    color: #f0c98a;
}

/* ==========================================================================
   Dnn-DASHBOARD-V1-PARITY — Items 1-4+6 (2026-05-20)
   ========================================================================== */

/* Item 6: SPY inception caption under the SPY % in the KPI tile */
.kpi-spy-caption {
    font-size: 0.70rem;
    color: #6e7681;
    margin-top: 0.1rem;
    line-height: 1.1;
}

/* Item 2: Positions TOTAL footer row */
.positions-total-row td {
    border-top: 2px solid #444c56;
    padding-top: 0.45rem;
    padding-bottom: 0.3rem;
    font-size: 0.82rem;
}

/* ==========================================================================
   Capital allocation by strategy charts (D382 2026-06-08)
   ========================================================================== */

/*
 * .cap-alloc-donut-wrap -- fixed height for the allowance-share doughnut.
 * Needs a defined height so Chart.js responsive mode has a clamp point.
 */
.cap-alloc-donut-wrap {
    height: 220px;
}

/*
 * .cap-alloc-util-wrap -- fixed height for the utilisation horizontal bar.
 * Height scales with strategy count; min set to ensure readability.
 */
.cap-alloc-util-wrap {
    height: 160px;
}

/* ==========================================================================
   Locked-capital-by-trade table (D382 2026-06-08)
   ========================================================================== */

/*
 * .lct-section-label -- sub-section heading above each of the two tables
 * (Open Positions / Open Orders).  Matches .chart-title style but smaller
 * and without the large bottom-margin (tables follow immediately).
 */
.lct-section-label {
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: #8b949e;
    margin-bottom: 0.4rem;
    margin-top: 0.25rem;
    padding-bottom: 4px;
    border-bottom: 1px solid #21262d;
}

/*
 * .lct-table -- inherits the global .table overrides (dark text, hover,
 * border colours).  Additional tweaks for density + column widths.
 */
.lct-table thead th {
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #9aa0a6;
    border-bottom: 1px solid #30363d;
    padding: 4px 6px;
    white-space: nowrap;
}

.lct-table tbody td {
    font-size: 0.80rem;
    padding: 5px 6px;
    border-bottom: 1px solid #21262d;
    vertical-align: middle;
}

.lct-table tbody tr:last-child td {
    border-bottom: none;
}

/*
 * Highlight the Locked $ / Reserved $ column header with a subtle
 * bright tint so the operator's eye goes there first.
 */
.lct-th-locked {
    color: #e6edf3 !important;
}

/*
 * .lct-locked-val -- the locked/reserved dollar value cell.
 * Bright near-white + slightly larger to draw attention.
 */
.lct-locked-val {
    color: #e6edf3;
    font-size: 0.85rem;
}

/*
 * .lct-kpi-total -- the "Total Locked" KPI value in the summary strip.
 * Slightly larger than other KPI values — it is the headline figure.
 */
.lct-kpi-total {
    font-size: 1.15rem;
}

/*
 * .lct-symbol -- option ticker symbols are long (e.g. LULU260710P00105000).
 * Allow wrapping at word boundary rather than hard-breaking characters.
 */
.lct-symbol {
    word-break: break-word;
    max-width: 14rem;
    font-size: 0.80rem;
}

/*
 * Row-state colour semantics:
 *   .lct-row-breach  -- red left-border (strategy cap breached)
 *   .lct-row-warn    -- amber left-border (old position or stale order)
 *   .lct-row-ok      -- no extra decoration
 */
.lct-row-breach {
    border-left: 3px solid #f85149;
    background: rgba(248, 81, 73, 0.06) !important;
}

.lct-row-warn {
    border-left: 3px solid #d29922;
    background: rgba(210, 153, 34, 0.06) !important;
}

.lct-row-ok {
    border-left: 3px solid transparent;
}

/*
 * .lct-warn-text -- amber text for the "days open" / "hours pending"
 * cell when the threshold is exceeded.
 */
.lct-warn-text {
    color: #d29922 !important;
    font-weight: 600;
}

/*
 * .lct-strategy-pill -- compact pill chip showing strategy name.
 * Muted background; strategy name fits in a small label.
 */
.lct-strategy-pill {
    display: inline-block;
    font-size: 0.68rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: #9aa0a6;
    background: rgba(144, 160, 166, 0.10);
    border: 1px solid #30363d;
    border-radius: 3px;
    padding: 1px 5px;
    white-space: nowrap;
}

/*
 * .lct-order-status-chip -- compact monospace pill for order_status field
 * (e.g. "new", "partially_filled").
 */
.lct-order-status-chip {
    display: inline-block;
    font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
    font-size: 0.70rem;
    color: #58a6ff;
    background: rgba(88, 166, 255, 0.08);
    border: 1px solid rgba(88, 166, 255, 0.20);
    border-radius: 3px;
    padding: 1px 5px;
    white-space: nowrap;
}

/*
 * .lct-order-id-cell -- broker order ID cell; truncated display with
 * title tooltip for the full UUID.
 */
.lct-order-id-cell {
    font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
    font-size: 0.68rem;
    color: #6e7681;
    white-space: nowrap;
    max-width: 10rem;
    overflow: hidden;
    text-overflow: ellipsis;
}

/*
 * .lct-order-id-col -- column header for Order ID; keep compact.
 */
.lct-order-id-col {
    min-width: 8rem;
    max-width: 11rem;
}

/* ==========================================================================
   D395 — LOCKED $ column + OPEN ORDERS panel (2026-06-09)
   ========================================================================== */

/*
 * .locked-usd-cell -- LOCKED $ value cell in the Open Positions table.
 * Slightly larger and brighter so the operator's eye goes there first.
 * Inherits text-end + fw-semibold from the JS render.
 */
.locked-usd-cell {
    color: #e6edf3;
    font-size: 0.88rem;
}

/*
 * .locked-usd-total -- LOCKED $ total cell in the TOTAL footer row.
 * Brighter + bold to distinguish from muted totals.
 */
.locked-usd-total {
    color: #e6edf3;
    font-size: 0.88rem;
}

/*
 * .table-danger-row -- position row where locked_usd > 25% equity.
 * Red left-border + faint red background signals over-cap state.
 * Applied by renderPositionsTable() in dashboard.js.
 */
.table-danger-row {
    border-left: 3px solid #f85149;
    background: rgba(248, 81, 73, 0.07) !important;
}

/*
 * .open-order-stale -- pending order row where hours_pending > 4.
 * Amber left-border signals the order has been waiting too long.
 * Applied by renderOpenOrdersTable() in dashboard.js.
 */
.open-order-stale {
    border-left: 3px solid #d29922;
    background: rgba(210, 153, 34, 0.06) !important;
}

/*
 * .text-warning-muted -- amber text for hours_pending > 4 cell.
 * Distinct from Bootstrap .text-warning (which is too bright).
 */
.text-warning-muted {
    color: #d29922 !important;
    font-weight: 600;
}

/*
 * .reserved-usd-cell -- RESERVED $ value cell in the Open Orders table.
 * Matches .locked-usd-cell brightness for visual parity.
 */
.reserved-usd-cell {
    color: #e6edf3;
    font-size: 0.88rem;
}

/*
 * .reserved-usd-total -- RESERVED $ total cell in the TOTAL footer row.
 */
.reserved-usd-total {
    color: #e6edf3;
    font-size: 0.88rem;
}

/*
 * .order-id-cell -- broker order ID in the Open Orders table.
 * Monospace, truncated, muted — matches .lct-order-id-cell.
 */
.order-id-cell {
    font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
    font-size: 0.68rem;
    color: #6e7681;
    white-space: nowrap;
    max-width: 14rem;
    overflow: hidden;
    text-overflow: ellipsis;
}

/*
 * OPEN-ORDERS-STRATEGY-VERSION (2026-09-28, operator defect fix): STRATEGY column in the Open
 * Orders (Pending Fill) table — renders the resolved configuration
 * VERSION, not the bare strategy code, in the same vocabulary the Strategy
 * Versions panel already uses for these fields (compare .sv-version-label /
 * .sv-strategy-pill below). Rendered by _ooStrategyCellHtml() in
 * dashboard.js. Stacked vertically (no flex/grid on the <td> itself, same
 * "don't fight the surrounding Bootstrap <table>" convention
 * .sv-version-label's own comment states) so the two lines fit the narrow
 * column between SYMBOL and SIDE.
 *
 * .oo-strategy-td   -- the <td> itself; no width override, so it takes
 *                       only the space its content needs (Bootstrap table
 *                       layout), same as every other cell in this table.
 * .oo-strategy-cell -- wrapping block for the stacked lines.
 * .oo-version-label -- PRIMARY line: the resolved version_label (e.g.
 *                       "ORB_intraday_A v5"). Same weight as
 *                       .sv-version-label for cross-panel consistency.
 * .oo-instance-name -- SECONDARY line: display_name (e.g. "ORB Intraday
 *                       (A)"), muted, shown alongside the version when it
 *                       fits (per the dispatch: "show display_name
 *                       alongside it if the column has room").
 * .oo-strategy-code -- 2026-09-28 amendment: PRIMARY line for a row where
 *                       NEITHER version_label NOR display_name resolved but
 *                       a real strategy code did (covers a payload that
 *                       predates reporting-agent's field addition, and a
 *                       new-payload row whose instance genuinely never
 *                       resolved). Deliberately NOT the accent-blue
 *                       .oo-version-label treatment — neutral/muted, the
 *                       same "this is a raw identifier, not a resolved
 *                       name" register as .sv-sha-chip — so the bare code
 *                       never LOOKS like a resolved version.
 * .oo-version-unresolved -- explicit caveat line, shared by two states:
 *                       "version unresolved" (instance resolved, version
 *                       did not) and "version not available" (only the
 *                       bare code resolved) — muted/italic per this file's
 *                       established non-attention-text convention (matches
 *                       .sv-coverage-note below), never colour-coded as an
 *                       alert and never left blank.
 * .oo-unattributed  -- NO real strategy attribution at all (neither
 *                       display_name, version_label, NOR a real strategy
 *                       code — see _ooHasRealStrategyCode's docstring for
 *                       the backend sentinel values this excludes).
 *                       Muted/italic. Reads as true whenever it renders —
 *                       it is no longer used as a stand-in for "the new
 *                       payload fields haven't landed yet" (2026-09-28
 *                       amendment; that case now renders .oo-strategy-code
 *                       instead, since the order IS attributed).
 */
.oo-strategy-td {
    vertical-align: middle;
}

.oo-strategy-cell {
    display: block;
}

.oo-version-label {
    display: block;
    font-size: 0.72rem;
    font-weight: 600;
    color: #e6edf3;
}

.oo-strategy-code {
    display: block;
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Consolas, monospace;
    font-size: 0.68rem;
    font-weight: 600;
    color: #8b949e;
}

.oo-instance-name {
    display: block;
    font-size: 0.64rem;
    color: #8b949e;
    margin-top: 1px;
}

.oo-version-unresolved {
    display: block;
    font-size: 0.62rem;
    color: #6e7681;
    font-style: italic;
    margin-top: 1px;
}

.oo-unattributed {
    color: #6e7681;
    font-style: italic;
}

/* ==========================================================================
   D453 Item 6 — Dual timestamp columns (Market Time + Local Time)
   ========================================================================== */

/*
 * .dual-ts-market -- "Market Time" (ET) column.
 * .dual-ts-local  -- "Local Time" (browser TZ) column.
 * Both are compact and muted; market time is slightly brighter.
 */
.dual-ts-market {
    font-size: 0.75rem;
    color: #9aa0a6;
    white-space: nowrap;
}

.dual-ts-local {
    font-size: 0.72rem;
    color: #6e7681;
    white-space: nowrap;
}

/* ==========================================================================
   D452 — Strategy Versions widget
   ========================================================================== */

/*
 * Operator, 2026-09-02 -- the whole card is now a collapsed-by-default
 * panel; toggle-button mechanics (button + aria-expanded + JS-only
 * addEventListener, no inline style=/handler) match the codebase's
 * existing idiom (.data-quality-expand-btn / strategy_instance.html's
 * .si-ticker-decision-toggle-btn), re-themed here as dark-on-dark (this
 * card sits on the .chart-card #161b22 background, not the amber
 * data-quality banner) -- muted #8b949e at rest -> lighter #c9d1d9 on
 * hover, same pair every other muted control in this file uses. The whole
 * title bar is the button (not just the triangle) so "click the header" —
 * the operator's own words — is literally true, with a large click target,
 * and the "Strategy Versions" title text is a plain child of the button so
 * it is NEVER hidden by the collapse — only #sv-panel-body collapses.
 *
 * Operator follow-up, same day, on the GLYPH specifically: "a little
 * triangle" — explicitly NOT the chevron/rotate-180 convention the two
 * precedents above use. .sv-panel-triangle draws a disclosure triangle as
 * a single Unicode text glyph (no icon library, no external asset):
 * &#9654; (BLACK RIGHT-POINTING TRIANGLE) authored pointing right at rest
 * (collapsed) and rotated 90deg by CSS to point down when
 * aria-expanded="true" -- the classic disclosure-triangle direction the
 * operator specified, achieved with one glyph + one transform, no JS
 * glyph-swap.
 */
.sv-panel-toggle-btn {
    background: transparent;
    border: none;
    cursor: pointer;
    padding: 0;
    margin: 0;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font: inherit;
    text-transform: inherit;
    letter-spacing: inherit;
    color: #8b949e;
}

.sv-panel-toggle-btn:hover {
    color: #c9d1d9;
}

.sv-panel-triangle {
    display: inline-block;
    font-size: 0.7rem;
    transition: transform 0.2s ease;
}

.sv-panel-toggle-btn[aria-expanded="true"] .sv-panel-triangle {
    transform: rotate(90deg);
}

/*
 * Sort-column headers: pointer cursor + no text selection.
 * Note: th.sv-sort-col already has this applied inline by JS on clone,
 * but the CSS class is the canonical source (CSP: no inline style=).
 */
.sv-sort-col {
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
}

.sv-sort-col:hover {
    background: rgba(255, 255, 255, 0.04);
}

/*
 * .sv-arrow -- sort direction indicator glyph (▲ ▼ ·).
 * Inactive: muted gray. Active: accent blue.
 */
.sv-arrow {
    font-size: 0.62rem;
    color: #6e7681;
    margin-left: 3px;
    transition: color 0.15s;
}

.sv-arrow.sv-arrow-active {
    color: #58a6ff;
}

/*
 * .ss-hdr-date -- the served session date shown in/under the Strategy
 * Instance Statistics panel's "Today's P&L" / "Yesterday's P&L" column
 * headers (operator, 2026-09-16). Parent <th> is uppercase/letter-spaced
 * per `.table thead th`; this overrides both so the date reads as a plain
 * date, not shouted, and sits on its own line under the label rather than
 * crowding the sort arrow inline.
 */
.ss-hdr-date {
    display: block;
    font-size: 0.62rem;
    font-weight: 400;
    text-transform: none;
    letter-spacing: normal;
    color: #6e7681;
}

/*
 * .sv-live-badge -- green "LIVE" badge on open-era rows.
 * Intentionally compact; sits next to the strategy pill.
 */
.sv-live-badge {
    display: inline-block;
    font-size: 0.60rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    padding: 1px 5px;
    border-radius: 3px;
    background: rgba(63, 185, 80, 0.20);
    color: #3fb950;
    border: 1px solid rgba(63, 185, 80, 0.35);
    vertical-align: middle;
}

/*
 * .sv-strategy-pill -- strategy name chip in the versions table.
 * Matches the pill style used in other tables.
 */
.sv-strategy-pill {
    display: inline-block;
    font-size: 0.68rem;
    font-weight: 600;
    padding: 1px 6px;
    border-radius: 4px;
    background: rgba(88, 166, 255, 0.12);
    color: #58a6ff;
    border: 1px solid rgba(88, 166, 255, 0.22);
    margin-right: 3px;
    vertical-align: middle;
}

/*
 * .sv-era-retired -- retired era rows (live=false).
 * Dimmed to 55% opacity so active eras visually dominate.
 */
.sv-era-retired {
    opacity: 0.55;
}

/*
 * Version-cell enrichment (operator ruling, 2026-09-22, relayed by Build
 * Agent Front End): "a version is the configuration, not the build" -- the
 * Version <td> now stacks the label pill, a "since <first activation>"
 * line, the series' distinct code SHAs as compact chips, and (only for a
 * series with more than one activation) a per-activation timeline. Kept
 * as a plain vertical stack (no flex/grid on the <td> itself, which would
 * fight the surrounding Bootstrap <table>) -- each piece is its own block
 * element, density-first per this panel's existing compact styling.
 */
.sv-version-label {
    font-weight: 600;
}

.sv-version-since {
    font-size: 0.62rem;
    color: #6e7681;
}

.sv-sha-row {
    display: flex;
    flex-wrap: wrap;
    gap: 3px;
    margin-top: 2px;
}

/*
 * .sv-sha-chip -- one distinct code SHA (short 8-char form) the series ran
 * under. Deliberately a NEUTRAL gray, not the blue .sv-strategy-pill accent
 * or the green .sv-live-badge accent -- a SHA is an attribute of the
 * series, not an identity or a status. Full SHA is on the title= tooltip
 * (CSP-safe: a plain attribute, not inline style= or an inline handler).
 */
.sv-sha-chip {
    display: inline-block;
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Consolas, monospace;
    font-size: 0.6rem;
    padding: 0 4px;
    border-radius: 3px;
    background: rgba(255, 255, 255, 0.06);
    color: #9aa0a6;
    border: 1px solid rgba(255, 255, 255, 0.10);
}

/*
 * .sv-activation-timeline -- one line per activations[] entry, shown only
 * when a series carries more than one (i.e. a same-hash/same-profile
 * redeploy occurred within the series -- see dashboard.js's own comment at
 * the call site for why the single-activation case does not repeat this).
 */
.sv-activation-timeline {
    margin-top: 2px;
    font-size: 0.6rem;
    color: #6e7681;
}

.sv-activation-item {
    display: block;
}

/*
 * CHANGE 1 (2026-09-23 config-series dispatch, relayed by Build Agent Front
 * End): per-series expand toggle -- "the individual profile versions inside
 * the series must remain visible, expandable from the series row." Same
 * disclosure-triangle idiom as .sv-panel-toggle-btn/.sv-panel-triangle above
 * (a smaller, per-row instance of the same mechanism, not a new one) --
 * &#9654; pointing right at rest, rotated 90deg by CSS when
 * aria-expanded="true" on the button.
 */
.sv-version-head {
    display: flex;
    align-items: center;
    gap: 4px;
}

.sv-row-expand-btn {
    background: transparent;
    border: none;
    cursor: pointer;
    padding: 0;
    margin: 0;
    display: inline-flex;
    align-items: center;
    color: #8b949e;
}

.sv-row-expand-btn:hover {
    color: #c9d1d9;
}

.sv-row-triangle {
    display: inline-block;
    font-size: 0.6rem;
    transition: transform 0.2s ease;
}

.sv-row-expand-btn[aria-expanded="true"] .sv-row-triangle {
    transform: rotate(90deg);
}

/*
 * .sv-versions-detail-row / .sv-versions-subtable -- the expanded
 * versions[] list beneath a series row. Collapsed by default (`hidden`
 * attribute set in the row markup, same convention as #sv-panel-body).
 * The nested table is density-matched to the outer strategy-versions-table
 * but visually subordinate: smaller text, no card background of its own
 * (sits inside the outer <td>), and a left border to read as "belongs to
 * the row above" rather than a second independent table.
 */
.sv-versions-subtable {
    margin: 4px 0 2px 18px;
    width: calc(100% - 18px);
    border-left: 2px solid rgba(255, 255, 255, 0.08);
}

.sv-versions-subtable thead th {
    font-size: 0.6rem;
    color: #6e7681;
    padding: 2px 8px;
}

.sv-versions-subtable td {
    font-size: 0.72rem;
    padding: 3px 8px;
}

/*
 * .sv-sha-chip-created -- CHANGE 2 (2026-09-23 config-series dispatch): the
 * one code_shas[] chip whose created_on is true, i.e. "the version the row
 * was created on" (the dispatch's own words). Same neutral SHA-chip shape
 * as .sv-sha-chip but with the accent-blue treatment .sv-strategy-pill
 * uses, so it reads as "this one is distinguished" without introducing a
 * third chip colour into the panel.
 */
.sv-sha-chip-created {
    background: rgba(88, 166, 255, 0.14);
    color: #58a6ff;
    border-color: rgba(88, 166, 255, 0.30);
}

/*
 * .sv-coverage-note -- CHANGE 2: code_shas_coverage.complete === false, i.e.
 * the SHA chip row above is a PARTIAL list (the source doesn't reach back
 * to the series' own start). Muted/non-attention text per the panel's
 * established convention -- an informational caveat, not an alert.
 */
.sv-coverage-note {
    font-size: 0.6rem;
    color: #6e7681;
    font-style: italic;
    margin-top: 2px;
}

/*
 * AB#632 (FS-SS-15) -- Strategy Instance Statistics panel: the retired-hidden
 * count + "show retired" control row, and the per-row retired marking it
 * governs. Deliberately a SEPARATE class from .lifecycle-badge (settings
 * table) / .sv-era-retired (Strategy Versions) -- this panel's own contract,
 * so it carries no coupling to the AB#399/AB#400 settings-page confusion of
 * lifecycle_state with the bot_runtime_settings.enabled toggle (FS-SS-16).
 * The colour is the same muted-gray "non-attention" convention used
 * throughout (#8b949e text / #30363d border) -- retired is a fact, not an
 * alert, so it is never rendered in the red/orange alert palette.
 */
.ss-retired-control-row {
    padding: 0 2px 6px;
    min-height: 1.4rem;
}

.ss-retired-control-row .form-check-input {
    cursor: pointer;
}

.ss-retired-control-row .form-check-label {
    cursor: pointer;
    color: #9aa0a6;
}

/* Applied to a <tr> in #strat-table-body whose row is lifecycle_state ===
   "retired" and IS shown (FS-SS-13: a retired row already carrying activity
   is NEVER hidden, only marked). Dimmed like .sv-era-retired for the same
   reason -- distinguishable at a glance, never illegible. */
.ss-row-retired {
    opacity: 0.6;
}

/* Text marker appended after display_name in the retired row's name cell --
   ⛔ NOTHING else is appended to display_name (FS-SS-4; the withdrawn
   disambiguator, D1024, is not to be reintroduced here in any form). This
   badge sits AFTER the name, in its own element, never concatenated into
   the name string itself. */
.ss-retired-badge {
    display: inline-block;
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    padding: 1px 5px;
    margin-left: 6px;
    border-radius: 3px;
    background: #21262d;
    color: #8b949e;
    border: 1px solid #30363d;
    vertical-align: middle;
}

/*
 * .strategy-versions-table -- full-width, no column width caps.
 */
.strategy-versions-table {
    min-width: 900px;
}

/* ==========================================================================
   D453 Item 2 — Day P&L KPI tile — rolling 5d / 30d sub-boxes
   ========================================================================== */

/*
 * .kpi-daypnl-card -- the Day P&L KPI tile wrapper.
 * Uses standard .stat-card padding (no override needed with new structure).
 */
.kpi-daypnl-card {
    /* inherits .stat-card padding */
}

/*
 * .kpi-daypnl-main -- full tile content wrapper.
 * Stacks label -> value-row -> sub vertically (block layout).
 * Previously .kpi-daypnl-inner held the flex row; that role now belongs to
 * .kpi-daypnl-value-row so xpath=.. of #kpi-daypl contains the rolling boxes.
 */
.kpi-daypnl-main {
    /* block — children stack vertically */
}

/*
 * .kpi-daypnl-value-row -- flex row: Day P&L value (left) + rolling boxes (right).
 * This is the direct parent of #kpi-daypl so the smoke-test xpath=.. check
 * finds both the value and the 5d/30d rolling sub-boxes in the same element.
 */
.kpi-daypnl-value-row {
    display: flex;
    flex-wrap: wrap;       /* a phone-width tile: the rolling boxes drop below the value instead of crushing it */
    align-items: center;
    gap: 0;
    margin-top: 4px;
    margin-bottom: 2px;
}

/*
 * .kpi-rolling-pnl -- right column: two stacked sub-boxes.
 * 5d above, 30d below. Separated from main value by a subtle border.
 */
.kpi-rolling-pnl {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding-left: 12px;
    border-left: 1px solid rgba(255, 255, 255, 0.08);
    min-width: 80px;
    justify-content: center;
    margin-left: auto;
}

/*
 * .kpi-rolling-box -- individual 5d / 30d sub-box.
 */
.kpi-rolling-box {
    background: rgba(255, 255, 255, 0.04);
    border-radius: 4px;
    padding: 3px 7px;
    text-align: right;
}

/*
 * .kpi-rolling-label -- small label above the rolling P&L value ("5-Day").
 */
.kpi-rolling-label {
    font-size: 0.60rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #6e7681;
    line-height: 1.2;
}

/*
 * .kpi-rolling-val -- rolling P&L value ("$1,234.56").
 * Colour applied by JS via text-gain / text-loss class.
 */
.kpi-rolling-val {
    font-size: 0.80rem;
    font-weight: 700;
    line-height: 1.3;
}

/* ==========================================================================
   D453 Item 4 — System Health page contrast + Bot Status card
   ========================================================================== */

/*
 * .svc-events-list -- replaces Bootstrap .list-group (which has a white
 * background in Bootstrap 5 without explicit dark variant). Pure dark,
 * no background override needed.
 */
.svc-events-list {
    margin-top: 0;
}

/*
 * .svc-event-row -- single event row: no Bootstrap .list-group-item so no
 * inherited light background. Just a thin bottom border for separation.
 */
.svc-event-row {
    padding: 5px 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}

.svc-event-row:last-child {
    border-bottom: none;
}

/*
 * .svc-event-type -- event type text (bold, primary text colour).
 */
.svc-event-type {
    font-size: 0.82rem;
    font-weight: 600;
    color: #e6edf3;
}

/*
 * .svc-event-summary -- payload_summary sub-text below event type.
 */
.svc-event-summary {
    font-size: 0.72rem;
    color: #6e7681;
    margin-top: 1px;
}

/*
 * .svc-name-wrap -- service name cell in the system health table.
 * word-break: break-word (not break-all) so long service names wrap
 * at word/hyphen boundaries rather than mid-character.
 */
.svc-name-wrap {
    word-break: break-word;
    max-width: 18rem;
}

/*
 * .health-bot-status-card -- Bot Status card wrapper on /health page.
 * Inherits .panel .card dark styling; no additional overrides needed.
 */
.health-bot-status-card {
    border-color: rgba(255, 255, 255, 0.08);
}

/* ==========================================================================
   DB Open Positions panel (reconciler drift view) — 2026-06-18
   ========================================================================== */

/*
 * .db-positions-panel-wrap — outer wrapper for the DB positions card,
 * stacked directly beneath the broker-live open positions card.
 * Adds a top margin and a left accent border to visually distinguish it
 * from the broker panel above.
 */
.db-positions-panel-wrap {
    margin-top: 16px;
    border-left: 3px solid rgba(248, 177, 51, 0.55);  /* amber — DB/reconciler accent */
    padding-left: 0;
}

/*
 * .db-positions-card-header-label — "DB (reconciler view)" sub-label in
 * the card header.  Styled slightly smaller and amber so it reads as
 * "secondary source" alongside the broker panel.
 */
.db-positions-card-header-label {
    color: #f8b133;
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/*
 * .db-pos-badge-ok — drift badge when the book's signed net qty equals the
 * broker's for EVERY symbol (AB#929). The row COUNTS legitimately differ
 * (book is per instance+symbol, broker is per symbol).  Green.
 */
.db-pos-badge-ok {
    background-color: rgba(63, 185, 80, 0.25);
    color: #3fb950;
    border: 1px solid rgba(63, 185, 80, 0.45);
    font-size: 0.78rem;
    font-weight: 600;
}

/*
 * .db-pos-badge-drift — drift badge when the book's SIGNED NET qty for any
 * symbol differs from the broker's (AB#929: direction inversion, quantity
 * mismatch, book-only, broker-only). RED, not amber: a $3M direction
 * inversion read as amber-warning is under-called; red is the alert colour.
 */
.db-pos-badge-drift {
    background-color: rgba(220, 53, 69, 0.22);
    color: #ff6b6b;
    border: 1px solid rgba(220, 53, 69, 0.6);
    font-size: 0.78rem;
    font-weight: 600;
    max-width: 100%;
    white-space: normal;
    word-break: break-word;
}

/*
 * .db-pos-badge-unknown — drift badge when broker count is unknown
 * (broker panel not yet loaded).
 */
.db-pos-badge-unknown {
    background-color: rgba(110, 118, 129, 0.18);
    color: #9aa0a6;
    border: 1px solid rgba(110, 118, 129, 0.35);
    font-size: 0.78rem;
}

/*
 * AB#929 -- #db-pos-recon-banner, the book-vs-broker reconciliation banner in the
 * Local Book panel. Operator: "super clear red banner ... list out what's not
 * matching". A genuine mismatch is RARE and serious, so it is loud: solid red,
 * white text, a heavy border, large heading. It persists while the condition does.
 *   .recon-disagree     red, the book and broker do not reconcile
 *   .recon-unavailable  grey, the book could not be compared (NOT green, NOT red)
 * Agreement renders no banner at all (the element stays .d-none).
 */
.recon-banner {
    border-radius: 6px;
    padding: 14px 18px;
    margin-bottom: 14px;
}
.recon-banner .recon-title {
    font-size: 1.15rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.recon-banner .recon-note {
    font-size: 0.85rem;
    margin-top: 8px;
}
.recon-banner.recon-disagree {
    background-color: #b02a37;
    color: #ffffff;
    border: 3px solid #ff6b6b;
}
.recon-banner.recon-unavailable {
    background-color: #1c2128;
    color: #c9d1d9;
    border: 2px dashed #6c757d;
}
.recon-banner .recon-list {
    list-style: none;
    margin: 12px 0 0 0;
    padding: 0;
}
.recon-banner .recon-sym {
    background-color: rgba(0, 0, 0, 0.28);
    border-left: 6px solid #ffffff;
    border-radius: 4px;
    padding: 10px 14px;
    margin-bottom: 10px;
    font-size: 0.95rem;
}
.recon-banner .recon-sym-name {
    font-size: 1.3rem;
    font-weight: 800;
    margin-right: 10px;
}
.recon-banner .recon-sym-kind {
    font-weight: 700;
    letter-spacing: 0.05em;
    background-color: #ffffff;
    color: #b02a37;
    border-radius: 3px;
    padding: 2px 8px;
}
.recon-banner .recon-line {
    margin-top: 5px;
}
.recon-banner .recon-lbl {
    display: inline-block;
    min-width: 8.5rem;
    opacity: 0.85;
}
.recon-banner .recon-fig {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}
.recon-banner .recon-inst {
    opacity: 0.9;
    word-break: break-word;
}
.recon-banner .recon-words {
    font-weight: 700;
}

/*
 * .db-pos-drift-extra — row present in the DB book but NOT in the broker's
 * live position list.  Amber left-border highlight draws the eye quickly.
 */
.db-pos-drift-extra {
    border-left: 3px solid #f8b133 !important;
    background-color: rgba(248, 177, 51, 0.06) !important;
}

/*
 * .db-pos-lot-row -- one expanded LOT of an open position (2026-10-06,
 * operator: lot rows use the SAME column grid as their parent row). A lot row
 * is an ordinary <tr> of .db-pos-table with the same eleven cells as the
 * position row above it, so every figure sits under the parent header of its
 * own column. Quieter than the position row (muted tint, thin blue rule on the
 * first cell) so the two read as parent and children. Blue, never orange --
 * #ff9800 is reserved for the live-env banner.
 *
 * .db-pos-lot-drill -- the lot carries what the drill-in needs, so the whole
 * row is clickable (same pointer + hover affordance as .trade-drill-row).
 * .db-pos-lot-entry-label -- the small "entry" caption in front of the lot's
 * entry price, which has no parent header of its own to sit under.
 * CSP: cursor/hover live here, never as inline style= in JS.
 */
.db-pos-lot-row {
    --bs-table-bg: #12171f;
}

.db-pos-lot-row > td:first-child {
    box-shadow: inset 3px 0 0 #3b5f8f;
}

.db-pos-lot-drill {
    cursor: pointer;
}

.db-pos-lot-drill:hover > td {
    background-color: rgba(88, 166, 255, 0.08);
}

.db-pos-lot-drill:focus-visible {
    outline: 1px solid #58a6ff;
    outline-offset: -1px;
}

.db-pos-lot-entry-label {
    color: #9aa0a6;
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

/* ==========================================================================
 * Entry-price scope (dashboard-design-agent, 2026-10-06, found on PROD)
 *
 * The broker panel's Entry is Alpaca's ACCOUNT-LEVEL blended average; the DB
 * panel's lot "entry" is ONE lot's own fill price. They are not comparable,
 * so the page says so:
 * .pos-scope-note       -- the always-visible one-paragraph explanation above
 *                          the broker table.
 * (No "N instances" badge exists here, by operator decision 2026-10-13: the
 *  Strategy column and the one-row-per-instance DB panel already say it.)
 * (No average-entry-price line exists here, by ruling: frontend-surface-spec
 *  FS-I-3 -- averaging lots invents a price at which nothing was bought.)
 * ========================================================================== */
.pos-scope-note {
    margin: 0 0 0.6rem;
    color: #9aa0a6;
    font-size: 0.8rem;
    line-height: 1.35;
}

.pos-scope-note strong {
    color: #c9d1d9;
    font-weight: 600;
}

/* ==========================================================================
 * Backtests dashboard tab (Chunk B, dashboard-design-agent, 2026-07-03)
 *
 * Read-only master-detail UI over the T&V backtest results store
 * (backtest_run_summary / backtest_run_trades / backtest_run_windows).
 * Reuses .chart-card / .chart-title / .table.table-dark / .pager-controls /
 * .no-data / .text-gain / .text-loss / .text-muted from elsewhere in this
 * file — this block only adds backtests-tab-specific layout + badges.
 * ========================================================================== */

/* CSP-clean visibility toggle — replaces inline style="display:none"
   (blocked by style-src 'self'). backtests.js toggles this class instead
   of writing element.style.display directly. */
.bt-hidden {
    display: none !important;
}

.backtests-wrap {
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.backtests-subhead {
    color: #8b949e;
    font-size: 0.85rem;
    max-width: 900px;
    margin-bottom: 0;
}

.backtests-em-dash {
    color: #9aa0a6;
    font-weight: 700;
}

.backtests-list-card {
    overflow-x: auto;
}

.backtests-detail-wrap {
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.bt-back-btn {
    align-self: flex-start;
    width: auto;
    padding: 0.4rem 1rem;
}

/* #45 Chunk 2: live Recent-Trades rows that carry a data-trade-id +
   data-instance-id are clickable — a click drills into the SHARED per-trade
   drill-in (window.CTDrill) via the live route. Pointer cursor + subtle hover
   give the row a clear clickable affordance (the whole row is the target).
   CSP: cursor/hover live here in the class, never as inline style= in JS. */
.trade-drill-row {
    cursor: pointer;
}

.trade-drill-row:hover {
    background-color: rgba(88, 166, 255, 0.08);
}

/* Run-list + windows/trades tables: dense, information-forward per the
   V1-parity density standard (0.85rem body, tight padding). */
.bt-table {
    width: 100%;
    font-size: 0.85rem;
}

.bt-table th {
    color: #8b949e;
    text-transform: uppercase;
    font-size: 0.72rem;
    letter-spacing: 0.05em;
    font-weight: 600;
    white-space: nowrap;
}

.bt-table td,
.bt-table th {
    padding: 0.45rem 0.6rem;
}

/* Clickable run-list row — whole row navigates to detail. */
.bt-run-row {
    cursor: pointer;
}

.bt-run-row:hover {
    background-color: rgba(88, 166, 255, 0.08);
}

/* Sortable run-list column headers (W3(a)). Same visual convention as the
   strategy-versions .sv-sort-col pattern (pointer cursor, no text-select,
   ▲/▼/· arrow that goes accent-blue when active) but bt-scoped because the
   bt- run table headers are JS-built, not Jinja-rendered, so they can't share
   the sv-arr-<col> id scheme. CSP: cursor/user-select live here in the class,
   never as an inline style= in JS. honest_limits header keeps the plain
   .bt-table th styling (no .bt-sort-col) so it has no click affordance. */
.bt-sort-col {
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
}

.bt-sort-col:hover {
    background: rgba(255, 255, 255, 0.04);
}

.bt-sort-arrow {
    font-size: 0.62rem;
    color: #6e7681;
    margin-left: 4px;
    transition: color 0.15s;
}

.bt-sort-arrow.bt-sort-arrow-active {
    color: #58a6ff;
}

/* run_id / config_variant_label can be long (e.g. the price_rs walk-forward
   timestamped id) — wrap at word boundaries, never character-wrap.
   min-width keeps the column from collapsing to unreadable width. */
.bt-run-id-cell {
    max-width: 20rem;
    min-width: 12rem;
    white-space: normal;
    word-break: break-word;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.78rem;
    color: #e6edf3;
}

.bt-config-variant-cell {
    max-width: 14rem;
    white-space: normal;
    word-break: break-word;
    color: #c9d1d9;
}

/* D543 — runs-list view header (title + strategy selector row). */
.bt-list-header {
    margin-bottom: 0.6rem;
}

.bt-strategy-select-label {
    color: #8b949e;
    margin-bottom: 0;
}

.bt-strategy-select {
    min-width: 12rem;
    max-width: 16rem;
    background-color: #0d1117;
    color: #e6edf3;
    border-color: #30363d;
}

/* D543 runs-list spine — Window range cell. Kept on one line (no wrap) so the
   date range reads left-to-right; a null side renders an em-dash inline. */
.bt-window-cell {
    white-space: nowrap;
    color: #c9d1d9;
    font-size: 0.8rem;
}

/* D543 — per-strategy config block. The config columns are visually delineated
   from the spine by a thin vertical divider column so the eye separates "what
   was run" (config) from "how it did" (spine). ONE table, one row per run,
   UNIFORM row height (the divider is a narrow, borderless spacer that carries
   only the left rule). */
.bt-config-divider {
    width: 1px;
    min-width: 1px;
    max-width: 1px;
    padding: 0 !important;
    border-left: 1px solid #30363d;
    background: transparent;
}

/* Config column header (categorical, sortable) — same accent as the spine
   sort headers but tinted so the config group reads as a distinct band. */
.bt-config-col {
    background: rgba(88, 166, 255, 0.04);
}

.bt-config-col-cell {
    background: rgba(88, 166, 255, 0.03);
}

/* D543 2(b) — config VALUE cell: WIDTH-MANAGED. Long text (the exit string
   especially) truncates with an ellipsis and a full-value title tooltip; it
   NEVER wraps, so a long value produces the SAME row height as a short one.
   The span is an inline-block with a hard max-width + overflow:hidden so the
   ellipsis engages; the surrounding <td> does not grow the row. */
.bt-config-value {
    display: inline-block;
    max-width: 11rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    vertical-align: bottom;
    color: #c9d1d9;
    font-size: 0.8rem;
}

/* ROC-alongside-dollar pattern: the $ figure and its ROC% render as a single
   stacked cell — never as separate unlinked columns a reader has to
   cross-reference (spec item 3 / invariant B6). */
.bt-money-roc-cell {
    display: flex;
    flex-direction: column;
    line-height: 1.3;
}

.bt-money-roc-cell .bt-money-line {
    font-weight: 600;
}

.bt-money-roc-cell .bt-roc-line {
    font-size: 0.76rem;
    color: #8b949e;
}

/* honest_limits — compact badge in the list, full text via title attr
   (spec item 4 / invariant B7: never omitted, never buried). */
.bt-honest-limits-badge {
    display: inline-block;
    background-color: rgba(248, 177, 51, 0.16);
    color: #f8b133;
    border: 1px solid rgba(248, 177, 51, 0.4);
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: 600;
    padding: 0.1rem 0.55rem;
    cursor: help;
}

.bt-honest-limits-none {
    color: #6e7681;
}

/* gate_status badges — verdict-class colour coding.  Grade / rejected
   states use the same amber/red vocabulary as the rest of the dashboard
   (never a bare "0" or blank — gate_status is first-class per invariant B7). */
.bt-gate-badge {
    display: inline-block;
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: 700;
    padding: 0.1rem 0.6rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.bt-gate-badge-pass {
    background-color: rgba(63, 185, 80, 0.18);
    color: #3fb950;
    border: 1px solid rgba(63, 185, 80, 0.4);
}

.bt-gate-badge-fail {
    background-color: rgba(248, 81, 73, 0.18);
    color: #f85149;
    border: 1px solid rgba(248, 81, 73, 0.4);
}

.bt-gate-badge-grade {
    background-color: rgba(110, 118, 129, 0.18);
    color: #9aa0a6;
    border: 1px solid rgba(110, 118, 129, 0.35);
}

/* ==========================================================================
 * D576 — operator-facing PERFORMANCE SCORE badges + FAVORITE star. Colour
 * bands per D576: GREEN >= 8.0, YELLOW 6.0-7.9, ORANGE 4.0-5.9, RED < 4.0.
 * A NULL (unscored) badge is a MUTED em-dash (.bt-score-na) — NEVER a red 0.0.
 * The band-class -> colour mapping here IS the load-bearing render contract the
 * jsdom gate asserts (getComputedStyle colour per boundary value).
 * ========================================================================== */
.bt-score-cluster {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    flex-wrap: wrap;
}

.bt-score-badge {
    display: inline-block;
    border-radius: 999px;
    font-size: 0.7rem;
    font-weight: 700;
    padding: 0.08rem 0.5rem;
    letter-spacing: 0.02em;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* GREEN >= 8.0 */
.bt-score-green {
    background-color: rgba(63, 185, 80, 0.18);
    color: #3fb950;
    border: 1px solid rgba(63, 185, 80, 0.4);
}

/* YELLOW 6.0 - 7.9 */
.bt-score-yellow {
    background-color: rgba(248, 177, 51, 0.18);
    color: #f8b133;
    border: 1px solid rgba(248, 177, 51, 0.4);
}

/* ORANGE 4.0 - 5.9 */
.bt-score-orange {
    background-color: rgba(255, 152, 0, 0.18);
    color: #ff9800;
    border: 1px solid rgba(255, 152, 0, 0.4);
}

/* RED < 4.0 (incl. a REAL 0.0 — the worst score, distinct from "not scored") */
.bt-score-red {
    background-color: rgba(248, 81, 73, 0.18);
    color: #f85149;
    border: 1px solid rgba(248, 81, 73, 0.4);
}

/* NULL / unscored -> muted em-dash badge (NO band colour, NEVER a red 0.0). */
.bt-score-na {
    background-color: rgba(110, 118, 129, 0.14);
    color: #8b949e;
    border: 1px solid rgba(110, 118, 129, 0.30);
}

/* Run-DETAIL score row (a dedicated row OUTSIDE .bt-figure-row so the four
 * headline figures keep their uniform font-size). */
.bt-score-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0.5rem 0 0.25rem;
    flex-wrap: wrap;
}

.bt-score-row-label {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #8b949e;
}

/* Leading FAVORITE-star table column (list) + the star glyph itself.
 * Width unchanged at 1.6rem for both the header (.bt-star-col) and body
 * cell (.bt-star-cell): the hollow star added below shares the filled
 * star's font-size/line-height, and the filled star already fit this
 * width, so there is nothing new to clip. */
.bt-star-col {
    width: 1.6rem;
    text-align: center;
    /* Column marker only -- no click handler lives on the header cell, so
     * it must never read as clickable even though the body cell below
     * does. */
    cursor: default;
}

.bt-star-cell {
    text-align: center;
    width: 1.6rem;
    /* The click handler lives on this cell (backtests.js), so the whole
     * column reads as interactive, not just the glyph inside it. */
    cursor: pointer;
}

.bt-fav-star {
    color: #f8b133;
    font-size: 0.95rem;
    line-height: 1;
}

/* Un-favorited state: hollow star (&#9734;), emitted by backtests.js as
 * .bt-fav-star-empty for both the un-favorited body cell and the header
 * column marker. Same font-size/line-height as .bt-fav-star -- row height
 * must stay identical across the NULL / false / true states, and matching
 * the metrics is what guarantees that. Colour is #9aa0a6, this sheet's
 * established muted-text token (see the file-header note at the top of
 * this file, "Muted text #9aa0a6", and its ~90 other uses throughout) --
 * legible against the table-dark surface (#161b22 / #0d1117, #30363d
 * borders) while staying clearly subordinate to the gold #f8b133 filled
 * star. */
.bt-fav-star-empty {
    color: #9aa0a6;
    font-size: 0.95rem;
    line-height: 1;
}

/* Hovering the star cell brightens a hollow star toward the favorite gold,
 * so hovering the row makes the column read as clickable. Scoped to
 * .bt-star-cell (the body td) only -- the filled star needs no hover cue
 * (it is already the brightest glyph in the column, so it is left
 * unchanged on hover) and the header's copy of .bt-fav-star-empty is
 * excluded on purpose (see below): it is a marker, not a button. */
.bt-star-cell:hover .bt-fav-star-empty {
    color: #f8b133;
}

/* The header glyph is a column marker, not a button -- it has no click
 * handler and must not invite one. Force the muted colour regardless of
 * row/column hover so it never brightens like the body cell's hollow
 * star. */
.bt-star-col .bt-fav-star-empty {
    color: #9aa0a6 !important;
}

/* Run-LIST performance-scores column — keep the cluster compact, non-wrapping
 * per badge, left-aligned. */
.bt-scores-col {
    text-align: left;
    white-space: nowrap;
}

.bt-scores-cell {
    text-align: left;
    white-space: nowrap;
}

/* Grade-run explicit empty-trades state (spec item 5 / invariant B1) —
   distinct from the generic .no-data alert so a grade run reads as
   "graded, not merely empty". */
.bt-grade-notice {
    background-color: rgba(110, 118, 129, 0.10);
    border: 1px solid rgba(110, 118, 129, 0.30);
    border-radius: 8px;
    padding: 0.75rem 1rem;
    color: #9aa0a6;
    font-size: 0.85rem;
    font-style: italic;
}

/* Summary card key/value grid (Run Summary detail view). */
.bt-summary-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 0.9rem 1.5rem;
}

.bt-summary-item-label {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #8b949e;
    margin-bottom: 0.15rem;
}

.bt-summary-item-value {
    font-size: 0.95rem;
    color: #e6edf3;
    font-weight: 600;
    word-break: break-word;
}

.bt-summary-honest-limits-block {
    grid-column: 1 / -1;
}

.bt-summary-honest-limits-list {
    margin: 0.25rem 0 0;
    padding-left: 1.1rem;
    color: #f8b133;
    font-size: 0.82rem;
}

@media (max-width: 992px) {
    .bt-summary-grid {
        grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    }
}

/* ==========================================================================
 * D537 Run Summary redesign — HEADLINE (strategy name + verdict pill + setup
 * line + uniform figure row, no hero), "vs just holding the basket" line, and
 * collapsed Details/Caveats <details>. Dark-theme consistent with the rest of
 * the detail cards; reuses .bt-summary-grid / .bt-summary-item-label /
 * .bt-summary-item-value inside <details> so the Details block matches the
 * pre-existing key/value grid styling exactly.
 * ========================================================================== */

.bt-headline-top {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 1rem;
    flex-wrap: wrap;
}

.bt-headline-strategy {
    margin: 0;
    font-size: 1.5rem;
    font-weight: 650;
    letter-spacing: -0.01em;
    line-height: 1.15;
    color: #e6edf3;
}

.bt-headline-sub {
    display: block;
    font-size: 0.78rem;
    font-weight: 500;
    color: #8b949e;
    font-family: var(--bs-font-monospace, monospace);
    margin-top: 0.3rem;
}

/* Verdict pill — colour-coded by verdict, defaulting to a neutral "grade"
   treatment for null (never blank, never a fabricated PASS/FAIL). */
.bt-verdict-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    border-radius: 999px;
    padding: 0.3rem 0.8rem;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    white-space: nowrap;
}

.bt-verdict-pass {
    background-color: rgba(63, 185, 80, 0.16);
    color: #3fb950;
    border: 1px solid rgba(63, 185, 80, 0.4);
}

.bt-verdict-fail {
    background-color: rgba(248, 81, 73, 0.16);
    color: #f85149;
    border: 1px solid rgba(248, 81, 73, 0.4);
}

.bt-verdict-grade {
    background-color: rgba(110, 118, 129, 0.16);
    color: #9aa0a6;
    border: 1px solid rgba(110, 118, 129, 0.35);
}

/* ONE plain-English setup line — universe / exit in numeric MA names / floor /
   window date range. Always visible (not inside Details). */
.bt-setup-line {
    margin: 0.9rem 0 0;
    padding-top: 0.9rem;
    border-top: 1px solid #21262d;
    font-size: 0.85rem;
    color: #9aa0a6;
    line-height: 1.5;
}

.bt-setup-line b {
    color: #e6edf3;
    font-weight: 600;
}

/* UNIFORM FIGURE ROW (load-bearing, D537): Return on $100k / Net profit /
   Worst dip / Trades ALL share .bt-figure-value — the SAME font-size/weight —
   there is deliberately NO larger/coloured "hero" figure. The return is not
   to be celebrated (operator-explicit). */
.bt-figure-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.9rem 2.4rem;
    margin-top: 1.4rem;
}

.bt-figure-label {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #8b949e;
    margin-bottom: 0.3rem;
}

.bt-figure-value {
    font-family: var(--bs-font-monospace, monospace);
    font-size: 1.25rem;
    font-weight: 600;
    color: #e6edf3;
    font-variant-numeric: tabular-nums;
}

/* "vs just holding the basket" line. */
.bt-versus-line {
    margin-top: 1.1rem;
    background-color: #0d1117;
    border: 1px solid #21262d;
    border-radius: 8px;
    padding: 0.7rem 1rem;
    display: flex;
    gap: 0.5rem 1.4rem;
    flex-wrap: wrap;
    align-items: baseline;
    font-size: 0.85rem;
}

.bt-versus-label {
    color: #8b949e;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-size: 0.68rem;
}

.bt-versus-mine {
    font-family: var(--bs-font-monospace, monospace);
    color: #e6edf3;
    font-variant-numeric: tabular-nums;
}

.bt-versus-theirs {
    font-family: var(--bs-font-monospace, monospace);
    color: #9aa0a6;
    font-variant-numeric: tabular-nums;
}

/* SPY (buy-and-hold) comparator segment — the THIRD figure on both the total-
   return versus line and the annualized line, alongside strategy (.bt-versus-mine)
   and basket (.bt-versus-theirs). Coloured amber to MATCH its chart line
   (STRATEGY_VS_BASKET_CHART.spyColor #d29922), the same way the basket's grey text
   matches the basket's grey chart line — so the operator visually associates the
   SPY figure with the SPY equity line on the chart above. */
.bt-versus-spy {
    font-family: var(--bs-font-monospace, monospace);
    color: #d29922;
    font-variant-numeric: tabular-nums;
}

.bt-versus-sep {
    color: #484f58;
}

/* Annualized (%/yr) comparison line — the per-YEAR rate, stacked directly under
   the total-over-window versus line and reusing .bt-versus-line. Tighten the top
   gap (vs the default 1.1rem) so the two read as ONE paired block: total return
   over the window, then the equivalent per-year CAGR. Strategy (.bt-versus-mine)
   sits next to basket (.bt-versus-theirs) for a direct comparison. */
.bt-annualized-line {
    margin-top: 0.5rem;
}

/* Details / Caveats collapsed <details> blocks. Both closed by default (the
   browser default for <details> without an "open" attribute) — Caveats is
   explicitly OFF by default per D537. */
.bt-summary-details,
.bt-caveats-details {
    margin-top: 1rem;
    padding-top: 0.9rem;
    border-top: 1px solid #21262d;
}

.bt-summary-details summary,
.bt-caveats-details summary {
    cursor: pointer;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #9aa0a6;
    font-weight: 700;
    list-style: revert;
}

.bt-summary-details summary:hover,
.bt-caveats-details summary:hover {
    color: #e6edf3;
}

.bt-summary-details .bt-summary-grid {
    margin-top: 1rem;
}

.bt-caveats-count {
    color: #6e7681;
    font-weight: 500;
    letter-spacing: 0;
    text-transform: none;
}

.bt-caveats-list {
    margin-top: 0.8rem;
}

/* ROC "buried in Details" warning flag — the explicit "not your capital"
   caution required alongside the demoted ROC figure (D537). */
.bt-warnflag {
    color: #d4a03a;
    font-size: 0.68rem;
    font-weight: 600;
    text-transform: none;
    letter-spacing: 0;
    margin-left: 0.35rem;
}

/* 50-DMA floor ON/OFF colour coding (Run Config exit rows). */
.bt-floor-on {
    color: #3fb950;
    font-weight: 700;
}

.bt-floor-off {
    color: #f85149;
    font-weight: 700;
}

/* ==========================================================================
 * Backtests B-series (window-first-class detail) — B1 window-filter STATE
 * (the filter control + per-window subtotal panel were removed under AB#299;
 * a run now has ONE window, so the panel was redundant — the shared
 * _tradesWindowFilter plumbing + B2 windows-row drill that sets it are JS-only,
 * no dedicated CSS); B2 pass/fail badges + clickable window rows; B3 sub-basket
 * diverging bar. Reuses .chart-card / .bt-table / .text-gain / .text-loss /
 * .text-muted; this block only adds the B-series-specific layout + badges +
 * chart card sizing.
 * ========================================================================== */

/* Trades-tab ticker filter row — same visual convention as the window filter
   row (label + compact select), scoped to the trades table. CSP: layout lives
   here in the class, never an inline style= in JS. */
.bt-trade-symbol-filter-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
    margin-bottom: 0.75rem;
}

.bt-trade-symbol-filter-label {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #8b949e;
    margin: 0;
}

.bt-trade-symbol-filter-select {
    max-width: 16rem;
    width: auto;
    background-color: #0d1117;
    color: #e6edf3;
    border: 1px solid #30363d;
}

/* Trades-tab per-trade Regime badge — SAME regime palette as the shading bands
   (bull=green / chop=orange / bear=red / normal=muted). A higher-opacity fill
   than the translucent chart bands so the small badge reads clearly against the
   dark row. An unknown/other market_type falls back to the muted grey. */
.bt-regime-badge {
    display: inline-block;
    border-radius: 999px;
    font-size: 0.68rem;
    font-weight: 700;
    padding: 0.06rem 0.5rem;
    text-transform: capitalize;
    line-height: 1.35;
}

.bt-regime-bull {
    background: rgba(63, 185, 80, 0.22);
    color: #56d364;
}

.bt-regime-chop {
    background: rgba(255, 152, 0, 0.22);
    color: #f0a020;
}

.bt-regime-bear {
    background: rgba(248, 81, 73, 0.22);
    color: #f85149;
}

.bt-regime-normal,
.bt-regime-unknown {
    background: rgba(139, 148, 158, 0.16);
    color: #8b949e;
}

.bt-trade-regime-cell {
    white-space: nowrap;
}

/* Trades-tab clickable row + its expandable detail row. The open row gets a
   subtle highlight; the detail row spans the full table width and lays its
   fields out in a responsive definition grid. CSP: all styling as classes;
   JS only toggles .bt-hidden. */
.bt-trade-row {
    cursor: pointer;
}

.bt-trade-row:hover {
    background: rgba(255, 255, 255, 0.03);
}

.bt-trade-row-open {
    background: rgba(88, 166, 255, 0.08);
}

.bt-trade-detail-cell {
    background: rgba(255, 255, 255, 0.02);
    padding: 0.75rem 1rem;
}

.bt-trade-detail-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
    gap: 0.4rem 1.25rem;
}

.bt-trade-detail-item {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
}

.bt-trade-detail-label {
    font-size: 0.66rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #8b949e;
}

.bt-trade-detail-value {
    font-size: 0.85rem;
    color: #c9d1d9;
    word-break: break-word;
}

.bt-trade-detail-actions {
    margin-top: 0.75rem;
}

/* Run-config panel dispatch (2026-08-11) — honest-absence note for the
 * trade-detail drill button. Operator
 * clicked a trade, it expanded, and there was no "open trade detail" button
 * AND no explanation -- indistinguishable from the button failing to load.
 * Measured disposition: correct to omit the button when the trade has no
 * linked order (trades_orders_id_map has 0 rows for that trade, so
 * drillTradeId resolves null) -- a fallback that guesses at an order_id
 * would risk opening the WRONG trade (AB#587, order_id and trades.id are
 * independent BIGSERIALs). This class is CSS ONLY: the markup that
 * conditionally emits it is frontend-js-agent's (backtests.js
 * tradeDetailHtml, ~line 8511-8526) -- not edited here; see this dispatch's
 * surface-back for the exact hand-back. Matches the existing honest-absence
 * convention (.no-data / .bt-run-config-omitted-list li): muted + italic,
 * never a red/error treatment -- this is an expected, correct state, not a
 * fault. */
.bt-trade-detail-no-drill {
    color: #9aa0a6;
    font-size: 0.78rem;
    font-style: italic;
    margin-top: 0.75rem;
}

/* B1 — trades table window cell: subtle accent so the mapped window reads as
 * a first-class column, not muted like the date columns. */
.bt-trade-window-cell {
    color: #c9d1d9;
    white-space: nowrap;
}

/* B2 — window pass/fail-eval badge. NULL renders as a muted em-dash badge
 * (bt-pf-badge-na), VISUALLY DISTINCT from the red fail badge — a not-
 * evaluable window must never read as a failure. */
.bt-pf-badge {
    display: inline-block;
    border-radius: 999px;
    font-size: 0.7rem;
    font-weight: 700;
    padding: 0.08rem 0.55rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.bt-pf-badge-pass {
    background-color: rgba(63, 185, 80, 0.18);
    color: #3fb950;
    border: 1px solid rgba(63, 185, 80, 0.4);
}

.bt-pf-badge-fail {
    background-color: rgba(248, 81, 73, 0.18);
    color: #f85149;
    border: 1px solid rgba(248, 81, 73, 0.4);
}

.bt-pf-badge-na {
    background-color: transparent;
    color: #6e7681;
    border: 1px dashed rgba(110, 118, 129, 0.5);
}

/* B2 — clickable windows-table row (whole row drills into that window's
 * trades via the B1 filter). Same hover affordance as the run-list row. */
.bt-window-row {
    cursor: pointer;
}

.bt-window-row:hover {
    background-color: rgba(88, 166, 255, 0.08);
}

/* B3 — sub-basket diverging-bar chart card. Fixed height so the canvas has a
 * stable box to render into (Chart.js maintainAspectRatio:false). */
.backtests-sub-basket-card {
    min-height: 260px;
}

.bt-sub-basket-canvas-wrap {
    position: relative;
    height: 240px;
}

/* ==========================================================================
 * Backtests B1 (task #45 D513) — per-trade drill-in view. Reuses .chart-card /
 * .chart-title / .bt-summary-grid / .bt-summary-item / .bt-back-btn /
 * .text-gain / .text-loss / .text-muted / .no-data from elsewhere in this
 * file; this block only adds the drill-in-specific price-chart box + the
 * "why the bot decided" grouped key/value panel.
 * ========================================================================== */

/* Marked-up price chart card. Fixed-height canvas wrap so Chart.js has a
 * stable box (maintainAspectRatio:false), same pattern as the sub-basket bar.
 * Taller than the sub-basket bar — a price line + markers wants more room. */
.backtests-price-chart-card {
    min-height: 620px;
}

.bt-price-chart-wrap {
    position: relative;
    height: 600px;
}

/* AB#471 (D961/D872, RULE 63) — the Marked-up Price Series degrade note
 * (#bt-price-chart-note) must read DIFFERENTLY for an EXPECTED absence
 * (no symbol / no window / Polygon key or fetch error / zero fetched bars /
 * zero RTH bars / empty display window — a normal, low-attention condition,
 * still styled via the base .no-data gray-italic treatment above) versus a
 * BROKEN producer (the server's `_bt_price_series_for_trade` guard-3
 * catch-all — an unexpected exception, operator-actionable). The producer
 * contract (v2/app/dashboard.py) words the broken case with ONE fixed
 * sentinel string: "price series construction failed unexpectedly — see
 * server logs" — that text IS the discriminator; this class is the visual
 * one. Applied by trade_drill.js (frontend-js-agent lane) to
 * #bt-price-chart-note ALONGSIDE its existing .no-data.small classes,
 * exactly when price_series_note equals that sentinel — never for any
 * expected-absence note, which keeps the plain .no-data look. Mirrors the
 * existing .si-error red-left-border treatment used for the same kind of
 * degraded/operator-actionable state elsewhere in this file (see .si-error
 * below); higher selector specificity (id + two classes) than the bare
 * .no-data rule above guarantees the override regardless of source order. */
#bt-price-chart-note.bt-note-broken {
    background: #2d1418;
    border-left: 4px solid #f85149;
    color: #ffb4ab;
    font-style: normal;
    text-align: left;
    padding: 14px 18px;
    border-radius: 6px;
    margin: 0;
}

/* AB#252 — day-trade (minute-resolution) candlestick chart container.
 * trade_drill.js creates this div at render-time as a sibling of the
 * (hidden) Chart.js canvas inside .bt-price-chart-wrap, and mounts the
 * vendored TradingView Lightweight Charts instance into it — Lightweight
 * Charts manages its own internal canvas and cannot draw into a
 * pre-existing <canvas> element. Fills the SAME fixed-height wrap the
 * daily Chart.js line chart uses, so the card's layout does not shift
 * between a daily-resolution and a minute-resolution drill-in. */
.bt-candle-chart-container {
    width: 100%;
    height: 100%;
    /* Anchors .bt-candle-legend (position:absolute) below — the legend box
     * is a CHILD of this container so it is torn down for free alongside it
     * (trade_drill.js's teardownCandleChart removes this whole container). */
    position: relative;
}

/* Operator follow-up to AB#257 (2026-07-24, verbatim: "can the text label be
 * moved to a legend"). Collects the labels that used to render inline on the
 * candlestick plot itself (Lightweight Charts series `title` / the ORH-ORL
 * axis "last value" badge) into ONE compact box instead — bias/regime SMA,
 * exit SMA, ORH, ORL, pullback zone (see renderCandleLegend in
 * trade_drill.js, which builds the rows from _candleLegendItems). Top-left
 * placement (per the dispatch: "doesn't collide with the price axis on the
 * right"). pointer-events:none so the box never steals the chart's own mouse
 * handling (drag/zoom/crosshair) underneath it — same convention as
 * .bt-svb-positions-tooltip below. Dark surface + border (not transparent
 * text floating on the candles) matching this file's other dark-card chrome
 * (#0d1117-ish background, #30363d border — see .bt-svb-positions-tooltip /
 * .bt-regime-legend for the same palette). */
.bt-candle-legend {
    position: absolute;
    top: 8px;
    left: 8px;
    z-index: 5;
    pointer-events: none;
    background: rgba(13, 17, 23, 0.85);
    border: 1px solid #30363d;
    border-radius: 6px;
    padding: 6px 10px;
    font-size: 0.72rem;
    line-height: 1.5;
    color: #c9d1d9;
    max-width: 65%;
}

.bt-candle-legend-item {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    white-space: nowrap;
}

.bt-candle-legend-swatch {
    display: inline-block;
    width: 0.85rem;
    height: 0.6rem;
    border-radius: 2px;
    flex: 0 0 auto;
}

/* Vertical time-boundary line (operator, 2026-07-24, verbatim: "a vertical
 * line at market open and another vertical line for the marking of the
 * entry window"). A DOM-overlay div — Lightweight Charts 4.2.3 has no
 * full-height vertical-line primitive (see addOrbVerticalLine in
 * trade_drill.js for the confirmed-by-reading-the-vendored-source finding).
 * `left` is set via CSSOM (element.style.left, not an inline style=""
 * HTML attribute) by trade_drill.js on every reposition — same CSP-safe
 * runtime-position convention as .bt-svb-positions-tooltip below. Dashed
 * (border-left-style) so it reads as a TIME marker, never confused with a
 * solid price-level line (createPriceLine levels / ORH-ORL both use
 * lineStyle:2 dashed too, but on the price axis, not full pane height — the
 * two conventions don't collide visually). pointer-events:none so it never
 * steals the chart's own drag/zoom/crosshair handling underneath it. Colour
 * is set per-instance via border-left-color (element.style, JS-driven,
 * same CSSOM pattern) since it varies by boundary (ORB_CHART.entryWindowColor
 * today; a future market-open line would carry its own). */
.bt-candle-vline {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 0;
    border-left: 1px dashed;
    pointer-events: none;
    z-index: 3;
}

.bt-candle-vline-label {
    position: absolute;
    /* Vertically centred on the line (NOT top-anchored, NOT bottom-anchored)
     * — 2026-07-24 rendering check: a top-anchored label collided with
     * .bt-candle-legend's top-left box (read as faded text under its
     * semi-opaque background), and a bottom-anchored label risked colliding
     * with Lightweight Charts' own time-axis tick labels (also bottom of
     * the pane, drawn by the chart itself inside the SAME full-height
     * container this overlay spans). Mid-pane is clear of both. */
    top: 50%;
    transform: translateY(-50%);
    left: 4px;
    white-space: nowrap;
    font-size: 0.68rem;
    background: rgba(13, 17, 23, 0.85);
    padding: 1px 4px;
    border-radius: 3px;
}

/* AB#257 15m-view follow-up (operator, 2026-07-25, verbatim: "a control to
 * switch to 15-minute bars"). trade_drill.js's renderCandleResolutionToggle
 * mounts this as a CHILD of .bt-candle-chart-container, top-RIGHT (mirrors
 * .bt-candle-legend's top-LEFT placement above so neither box collides with
 * the other or with the right-hand price axis). Same dark-card chrome as
 * .bt-candle-legend / .bt-svb-positions-tooltip (this file's established
 * palette for chart-overlay chrome: #0d1117-ish translucent background,
 * #30363d border) so the control reads as part of the same visual family,
 * not a foreign widget. UNLIKE the legend, this box must accept pointer
 * events (it holds actual buttons), so no pointer-events:none here. */
.bt-candle-res-toggle {
    position: absolute;
    top: 8px;
    right: 8px;
    z-index: 5;
    background: rgba(13, 17, 23, 0.85);
    border: 1px solid #30363d;
    border-radius: 6px;
    padding: 4px 6px;
    font-size: 0.72rem;
}

.bt-candle-res-row {
    display: flex;
    gap: 4px;
}

/* Plain buttons re-skinned to match the dark chart chrome rather than the
 * browser's default control styling — small, low-profile, never competing
 * visually with the candlestick data itself. */
.bt-candle-res-btn {
    background: transparent;
    border: 1px solid #30363d;
    border-radius: 4px;
    color: #9aa0a6;
    font-size: 0.7rem;
    line-height: 1;
    padding: 3px 7px;
    cursor: pointer;
}

.bt-candle-res-btn:hover {
    border-color: #58a6ff;
    color: #c9d1d9;
}

/* The currently-drawn resolution — accent-blue, matching this file's price-
 * line accent colour (PRICE_CHART.lineColor in trade_drill.js) so "active"
 * reads as the same accent used elsewhere on this chart, not a new colour
 * vocabulary. */
.bt-candle-res-btn.active {
    background: rgba(88, 166, 255, 0.18);
    border-color: #58a6ff;
    color: #c9d1d9;
}

/* "15m view (run trades <N>m)" — the LOAD-BEARING caption (operator's own
 * explicit ask) that keeps an aggregated view from ever being mistaken for
 * the bot's actual trading resolution. Muted + small, under the buttons,
 * but never hidden while the aggregated view is active. */
.bt-candle-res-caption {
    color: #9aa0a6;
    font-size: 0.65rem;
    margin-top: 3px;
    white-space: nowrap;
}

/* 50-DMA / 150-DMA overlay caption (D514). Rendered by backtests.js's
 * renderPriceChart into #bt-dma-note ALONGSIDE the drawn chart when
 * price_series.dma_note is set (e.g. the intraday case: bars + this note, no
 * DMA lines). Muted, small, sits under the canvas. Distinct from the
 * .no-data degrade note (which stands IN PLACE OF the chart). */
.bt-dma-note {
    color: #9aa0a6;
    margin-top: 0.5rem;
    font-style: italic;
}

/* --------------------------------------------------------------------------
 * C6 run-config visibility panel (#bt-run-config-container). Renders the
 * strategy config actually run: ENTRY / EXIT criteria + universe/window blocks
 * (each a titled sub-section reusing .bt-summary-grid / .bt-summary-item), plus
 * a "not captured for this run" block listing run_config.omitted[]. Dark-theme
 * consistent with the rest of the detail cards. */
.bt-run-config-block {
    margin-bottom: 1.1rem;
}

.bt-run-config-block:last-child {
    margin-bottom: 0;
}

.bt-run-config-block-title {
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #e6edf3;
    font-weight: 700;
    padding-bottom: 0.3rem;
    margin-bottom: 0.6rem;
    border-bottom: 1px solid #21262d;
}

/* sc-display-fix-2: plain-language config sentences. Each line is a full-width
 * human sentence (label + value + units) rather than a squeezed key/value grid
 * cell — the config panel must read as prose, not code-letters + bare numbers. */
.bt-config-lines {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.bt-config-line {
    font-size: 0.9rem;
    line-height: 1.4;
    color: #e6edf3;
    padding: 0.1rem 0;
    word-break: break-word;
}

/* The raw stored token shown in parentheses after a human sentence (e.g. the
 * exit_mode code) — muted + monospace so the operator can still see the stored
 * value without it competing with the plain-language sentence. */
.bt-config-raw {
    color: #9aa0a6;
    font-family: var(--bs-font-monospace, monospace);
    font-size: 0.82rem;
}

/* The "not captured for this run" list — muted so it reads as an honest
 * absence note, not a data field. Never $0/0: these are fields the stored
 * config did not resolve, surfaced explicitly rather than silently dropped. */
.bt-run-config-omitted-list {
    list-style: none;
    padding-left: 0;
    margin: 0;
}

.bt-run-config-omitted-list li {
    color: #9aa0a6;
    font-size: 0.85rem;
    padding: 0.15rem 0;
    font-style: italic;
}

/* --------------------------------------------------------------------------
 * AB#285 — ORB run-config panel: HEADLINE band (larger/boxed, the run's
 * identity at a glance) + spec-Section10 block grouping with bold/dim
 * on-off styling. ORB-only (#bt-run-config-container renders this markup
 * only for a strategy === "orb_intraday" run — every other strategy keeps
 * the pre-existing .bt-run-config-block / .bt-config-line markup above,
 * untouched). Dark-theme consistent with the rest of the detail cards. */
.bt-orb-headline {
    background-color: rgba(88, 166, 255, 0.08);
    border: 1px solid rgba(88, 166, 255, 0.35);
    border-radius: 8px;
    padding: 0.9rem 1.1rem 1.05rem;
    margin-bottom: 1.3rem;
}

.bt-orb-headline-title {
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #58a6ff;
    font-weight: 700;
    margin-bottom: 0.7rem;
}

.bt-orb-headline-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 0.9rem 1.4rem;
}

.bt-orb-headline-label {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #8b949e;
    margin-bottom: 0.2rem;
}

.bt-orb-headline-value {
    font-size: 1.15rem;
    font-weight: 700;
    color: #e6edf3;
    word-break: break-word;
}

/* A caveat note appended to a headline value (e.g. "(stop basis: atr)" when
 * the headline's pct-of-OR-range stop is inert under a different stop
 * basis) — muted + smaller so it reads as a qualifier, not part of the
 * headline figure itself. */
.bt-orb-headline-note {
    font-size: 0.68rem;
    font-weight: 500;
    color: #9aa0a6;
    display: block;
}

/* Operator relay 2026-09-14: "the description under each value is set far
 * too small to read... Set it at the same size as the value's label."
 * .bt-param-desc (AB#825's stable hook, always paired with
 * .bt-orb-headline-note on the same span -- see orbParamDescHtml() /
 * paramDescHtml()) is the class that marks specifically a per-parameter
 * description, distinct from the three bare-.bt-orb-headline-note caveats
 * renderOrbHeadline appends to a headline VALUE ("(buffer off)" / "(stop
 * basis: ...)" / "(target off)") -- those are qualifiers on a value, not
 * per-parameter descriptions, and are deliberately left at the smaller
 * 0.68rem caveat size, unaffected by this rule. 0.85rem matches
 * .bt-orb-row-label's font-size exactly (the "value's label" the operator
 * means) -- read from that rule, not assumed. This ALSO enlarges the
 * headline band's own per-knob description (renderOrbHeadline uses the
 * same orbParamDescHtml/.bt-param-desc) and the ETF twin panel's
 * per-parameter row description (etf_run_config_rows.js::paramDescHtml
 * reuses the identical class) -- both are the same kind of per-parameter
 * description the operator's complaint describes, so matching them is the
 * consistent reading, not scope creep. */
.bt-param-desc {
    font-size: 0.85rem;
}

/* Per-block wrapper reuses .bt-run-config-block's spacing; the title reuses
 * .bt-run-config-block-title's type scale, with an ORB-specific grey-out
 * modifier applied when EVERY row in that block resolved inactive (a block
 * that is entirely off for this run, e.g. the bias gate). */
.bt-orb-block-title.bt-orb-block-title-off {
    color: #6e7681;
    border-bottom-color: #21262d;
}

/* AB#289 (re-fix v3, closes the 2nd Live-Verified bounce) — replace the
 * FIXED-constant column width with one DERIVED FROM THE BLOCK'S OWN LABEL
 * CONTENT. No fixed number is left anywhere in this rule set for a future
 * edit to widen.
 *
 * History, so the next reader does not repeat either mistake:
 *   Attempt 1 (ddef4545): capped the PARENT `.bt-orb-rows` to a max-width
 *     and left `.bt-orb-row { justify-content:space-between }` in place.
 *     space-between pins the value to the ROW's own right edge, and the row
 *     stretches to fill whatever width its container is given -- gap =
 *     rowWidth - labelWidth - valueWidth, which SCALES WITH CONTAINER
 *     WIDTH. Bounced: gap ~414px median at a 538px row.
 *   Attempt 2: fixed the mechanism (row no longer grows to fill its
 *     container: `justify-content:flex-start` + non-growing children) but
 *     pinned `.bt-orb-row-label` to a FIXED `min-width:16rem` magic
 *     constant, chosen without reference to the real label population it
 *     had to hold (labels run 64-226px+; the widest real label,
 *     "Legacy intraday trend gate (single-slope) enabled", is 317px --
 *     40% past the constant). UNGUARDED: nothing stopped the constant being
 *     widened -- raising it to 60rem still passed every test in that round,
 *     because the round's own test measured the CSS `gap` PROPERTY reading
 *     itself back (12px by construction, the flex box-to-box gap), never
 *     the actual label-TEXT-to-value distance. Bounced again: a real
 *     907px gap was reachable with the suite green.
 *
 * THIS FIX: `.bt-orb-rows` is a CSS Grid (`grid-template-columns:
 * max-content max-content`) and `.bt-orb-row` is `display:contents`, which
 * promotes its two children (label, value) to be direct items of that
 * grid. The GRID -- not the row -- owns the two-column layout, and the
 * browser derives the label column's width from the actual rendered
 * content of THAT BLOCK's OWN rows: each `.bt-orb-rows` is its own
 * independent grid container, so one block's long labels never widen a
 * sibling block's column, and the column width IS the content -- there is
 * no fixed constant left in this file for a future edit to widen (the
 * 907px hazard attempt 2 had is no longer expressible: nothing here can be
 * set to 60rem, because nothing here is a length at all). The residual
 * label-to-value gap for any one row is `column-gap` (0.75rem, ~12px)
 * PLUS however much that row's OWN label is shorter than the widest label
 * in its block -- i.e. the minimum cost of keeping every value in a block
 * aligned to one column, not an arbitrary padding number. Width-
 * independence is structural (nothing here reads the CONTAINER's width,
 * same argument as attempt 2) and now additionally free of the fixed-
 * min-width hazard attempt 2 introduced. `.bt-orb-block-title` and the
 * headline band above are untouched -- confined to `.bt-orb-rows` and its
 * two child classes, verified by a real-Chromium control-group render
 * comparing this CSS against the pre-AB#289 CSS (test file cites the base
 * commit SHA). No `max-width` cap on `.bt-orb-rows` remains, and none is
 * needed: verified real-label-population column widths stay well inside a
 * readable line length (see the render test's measured figures). */
/* dc54f1e8 (frontend-js-agent) made renderOrbBlocks (backtests.js) and the
 * shared renderEtfBlocks (etf_run_config_rows.js) emit label/value/desc as
 * THREE sibling cells per row, each carrying an explicit inline
 * `style="grid-column:1|2|3"` — that commit's own note flagged the two
 * follow-ups landed below as out of its lane (dashboard.css). Before this
 * fix, `.bt-orb-rows` only DECLARED two tracks (`max-content max-content`);
 * the 3rd column that made the description line up rendered only because a
 * CSS Grid auto-generates an implicit track sized to its content's
 * max-content when an explicitly-placed item lands past the declared
 * template. That is why the AB#289/`:has(.bt-orb-block)` fix below
 * (grid-template-columns) is the FIRST of the two follow-ups: it declares
 * the 3rd (description) track explicitly instead of relying on the
 * implicit-track fallback. `1fr` (not `max-content`) is deliberate for the
 * SAME reason AB#289's value column used `1fr` inside the container-level
 * grid (comment further below): a `max-content` description track would
 * size the whole row to its own widest description sentence — unbounded,
 * since prose has no natural max-content ceiling the way a numeric/short
 * value does — instead of wrapping within the row's available width. `1fr`
 * both bounds the column (long descriptions WRAP inside it via
 * `.bt-orb-row-desc`'s own rule below, never widening the layout) and keeps
 * the label/value columns' widths exactly as AB#289 established (still
 * derived only from real label/value content). This rule also governs the
 * ETF drill-in criteria-echo panel (#bt-criteria-echo-container), which is
 * NOT matched by the `#bt-run-config-container:has(.bt-orb-block)` grid
 * further below (different container id) and so depends on THIS
 * declaration for its own 3-column alignment. The plain ORB drill-in
 * renderer (trade_drill.js's own renderOrbBlocks) still emits only 2 cells
 * per row (no description support there — a separate, pre-existing payload
 * gap, not touched by this fix); those rows simply auto-place into columns
 * 1-2 and leave column 3 an unused (empty) track, which is harmless. */
.bt-orb-rows {
    display: grid;
    grid-template-columns: max-content max-content 1fr;
    column-gap: 0.75rem;
    row-gap: 0.35rem;
    align-items: baseline;
}

.bt-orb-row {
    display: contents;
}

.bt-orb-row-label {
    color: #9aa0a6;
    font-size: 0.85rem;
    line-height: 1.4;
}

.bt-orb-row-value {
    text-align: left;
    word-break: break-word;
    font-size: 0.85rem;
    line-height: 1.4;
}

/* The description cell (dc54f1e8) is ALWAYS emitted by the renderer, even
 * when the row has no stored description, so the 3rd grid column keeps its
 * position (AB#825: alignment must survive an absent description — do NOT
 * add a rule that collapses/hides an empty `.bt-orb-row-desc`; an empty
 * grid cell with zero content simply occupies its track at whatever width
 * the row's own description text (if any) requires, which is exactly the
 * behaviour we want). `min-width: 0` overrides the CSS Grid default
 * (`min-width: auto`, which refuses to shrink a grid item below its
 * content's un-wrapped intrinsic width) — without it, one long description
 * sentence would blow out the `1fr` track and misalign every other row's
 * column 3, exactly the ragged layout the operator asked to have fixed.
 * With it, long text WRAPS inside the column instead (word-break: break-word
 * matches `.bt-orb-row-value`'s own choice on this same panel; nothing here
 * truncates a description, so no knob's description is ever silently
 * shortened). */
.bt-orb-row-desc {
    min-width: 0;
    word-break: break-word;
    line-height: 1.4;
}

/* Active: bold, full-contrast — the knob genuinely governs this run.
 * Inactive: dim/grey on BOTH label and value — a filter/gate that is off,
 * or a value inert because its own governing flag is off. Styling only:
 * row order never changes between runs (AB#285 part 3).
 *
 * AB#825 follow-up (2nd of the two dc54f1e8 flagged as out of its lane):
 * .bt-orb-row-desc itself carries no text — the description sentence lives
 * in a NESTED `.bt-param-desc.bt-orb-headline-note` span
 * (orbParamDescHtml()/paramDescHtml() in backtests.js /
 * etf_run_config_rows.js), which sets its OWN explicit `color: #9aa0a6`
 * (.bt-orb-headline-note, further up this file). A color set on an
 * ancestor never overrides a child's own explicitly-set color, so without
 * the extra `.bt-orb-headline-note` selectors below targeting that nested
 * span directly, an inactive row's description stayed at
 * .bt-orb-headline-note's fixed #9aa0a6 — actually BRIGHTER than the row's
 * dimmed #6e7681 label/value — which is the exact defect the operator's
 * screenshot showed ("Falling-knife slope floor", greyed, with its
 * description not greyed). The description is folded into the SAME
 * active/inactive rule as label/value (full symmetry: it now literally
 * inherits the row's on/off treatment, not merely a hand-picked approximation
 * of it) rather than given its own bespoke color pairing — a partial fix
 * that dimmed only the inactive case would leave the active case in a third,
 * unrelated shade, which is its own version of "does not follow the row's
 * state". Font-weight is intentionally NOT forced onto the description (it
 * stays normal-weight prose in both states) — bolding full sentences was
 * never the reported defect (a COLOR mismatch) and would make every active
 * row's description compete visually with the label/value it is annotating. */
.bt-orb-row-active .bt-orb-row-label,
.bt-orb-row-active .bt-orb-row-value,
.bt-orb-row-active .bt-orb-row-desc,
.bt-orb-row-active .bt-orb-row-desc .bt-orb-headline-note {
    color: #e6edf3;
}

.bt-orb-row-active .bt-orb-row-label,
.bt-orb-row-active .bt-orb-row-value {
    font-weight: 700;
}

.bt-orb-row-inactive .bt-orb-row-label,
.bt-orb-row-inactive .bt-orb-row-value,
.bt-orb-row-inactive .bt-orb-row-desc,
.bt-orb-row-inactive .bt-orb-row-desc .bt-orb-headline-note {
    color: #6e7681;
}

.bt-orb-row-inactive .bt-orb-row-label,
.bt-orb-row-inactive .bt-orb-row-value {
    font-weight: 400;
}

@media (max-width: 992px) {
    .bt-orb-headline-grid {
        grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    }
}

/* Run-config panel dispatch (2026-08-11) — align the label/value columns ACROSS every
 * section, not just within one (operator: "resolved parameters has nice,
 * neat columns where all the values line up under each other. In run
 * config, the columns are separate for each section... can they be the
 * same for each section?"). Scoped to #bt-run-config-container ONLY, via
 * :has(.bt-orb-block) so the pre-existing generic .bt-run-config-block /
 * .bt-config-line renderers used by every OTHER strategy (and the
 * #bt-criteria-echo-container / #bt-why-container drill-in panels, which
 * intentionally keep AB#289's per-panel independent column per D542's own
 * "own card" framing) are completely untouched -- this rule can only ever
 * match the ORB/ETF run-config LIST panel, the one surface the operator's
 * screenshot was of.
 *
 * NOT a revert of AB#289: the ORIGINAL `.bt-orb-rows { display: grid;
 * grid-template-columns: max-content max-content; ...}` rule two rules
 * above is left byte-for-byte untouched -- AB#289's actual fix (no fixed
 * magic-number column width anywhere, column width DERIVED from real label
 * content, so the label-to-value gap can never scale with container width)
 * is preserved and, if anything, extended: instead of each block deriving
 * its OWN max-content column from only its OWN rows, this rule promotes
 * every block's rows (via chained `display: contents`, the same promotion
 * mechanism AB#289 already uses for .bt-orb-row -> its label/value
 * children, just one level higher) up to be direct items of ONE grid
 * rooted at the container, so the max-content column-sizing algorithm now
 * runs over EVERY row in EVERY section at once -- still 100% content-
 * derived, still zero fixed constants, same "no future edit can silently
 * widen this" guarantee AB#289 established.
 *
 * ⚠️ KNOWN CROSS-LANE IMPACT (surfaced in the dispatch closing this out):
 * AB#289's real-Chromium regression gate
 * (tests/integration/test_dashboard_backtests_ab289_label_text_gap_render.py,
 * frontend-js-agent-owned, NOT edited by this change) canary-checks that
 * `.bt-orb-rows` itself computes `display: grid` with a real 2-track
 * grid-template-columns. Under this rule, for the two surfaces that render
 * into #bt-run-config-container (orb_list, etf_list) ONLY,
 * `.bt-orb-rows` computes `display: contents` instead (it is promoted, not
 * a grid itself -- the outer container is now the grid). That canary's OWN
 * docstring anticipates exactly this: "the mechanism has since been
 * re-implemented to something other than CSS Grid, in which case this
 * canary... need[s] updating to match the new mechanism, not just this
 * test's cap" -- see this dispatch's surface-back for the real-Chromium
 * measured label-to-value gaps proving the SUBSTANCE AB#289 protects (no
 * container-width-scaling blowup, gap stays small and content-derived) is
 * unaffected; only the literal display-value assertion needs the owning
 * lane's update. The two drill-in surfaces (orb_drillin/etf_drillin, into
 * #bt-criteria-echo-container) are NOT matched by this :has() scope and are
 * verified unaffected -- `.bt-orb-rows` there still computes display:grid
 * exactly as AB#289 shipped it. */
/* grid-template-columns is `max-content 1fr`, NOT `max-content max-content`:
 * measured in a real Chromium render (see this dispatch's surface-back) --
 * `max-content max-content` sizes the WHOLE grid to the sum of its two
 * content-derived tracks (a few hundred px), which then squeezes the
 * RUN IDENTITY headline band (grid-column:1/-1, an auto-fill sub-grid whose
 * own intrinsic max-content contribution is small -- it depends on being
 * GIVEN width, not on wanting it) down to that same narrow width --
 * measured 1362.8px -> 408.8px, a real visual regression, not merely a
 * test-assertion technicality. The value column as `1fr` instead keeps
 * column 1 (the label column AB289's fix derives from real content --
 * unchanged) content-derived while column 2 absorbs all remaining
 * available inline space, so the GRID's total width is once again the
 * container's full available width (matching every other panel on this
 * page) -- exactly restoring the RUN IDENTITY band's pre-existing width.
 * The label-to-value TEXT gap AB289 protects is measured as
 * `valueRect.left - labelTextRect.right`, which depends only on column 1's
 * width (max-content) and the column-gap -- column 2's own width does not
 * enter that measurement, so this does not reopen AB289's gap.
 *
 * AB#825 follow-up (1st of the two dc54f1e8 flagged as out of its lane) —
 * a THIRD track for the description column, landed alongside the matching
 * `.bt-orb-rows` fix further up this file (see that rule's own comment for
 * the full mechanism). The reasoning two paragraphs up for why the LAST
 * track must be `1fr` (else the RUN IDENTITY headline band gets squeezed to
 * the grid's own narrow content width) still applies verbatim -- it is now
 * the description column that is the last track and carries the `1fr`,
 * which is also the correct choice for the description SPECIFICALLY
 * (bounds prose to the row's available width so it wraps instead of
 * blowing out the layout; see `.bt-orb-row-desc`'s own min-width:0 rule).
 * The value column moves from `1fr` to `max-content`: it is no longer the
 * last track, and sizing it to its own real content (exactly like the
 * label column, and exactly what AB289 already does for `.bt-orb-rows` in
 * its non-container-grid form) keeps every section's value column reading
 * as a single aligned strip rather than each value stretching to fill
 * space now claimed by the description. */
#bt-run-config-container:has(.bt-orb-block) {
    display: grid;
    grid-template-columns: max-content max-content 1fr;
    column-gap: 0.75rem;
    row-gap: 0.35rem;
    align-items: baseline;
}

#bt-run-config-container:has(.bt-orb-block) > .bt-orb-headline {
    grid-column: 1 / -1;
}

#bt-run-config-container:has(.bt-orb-block) .bt-run-config-block.bt-orb-block {
    display: contents;
}

#bt-run-config-container:has(.bt-orb-block) .bt-run-config-block-title.bt-orb-block-title {
    grid-column: 1 / -1;
    /* .bt-run-config-block's own margin-bottom:1.1rem (the pre-existing
     * inter-section spacing) no longer applies once .bt-orb-block is
     * display:contents -- margin-top here on the FOLLOWING section's own
     * title is the substitute, so every section but the first still gets
     * visible separation from the section above it. */
    margin-top: 0.75rem;
}

/* The headline band (when present) is always emitted first, immediately
 * followed by the first block -- target it with the adjacent-sibling
 * combinator (NOT :first-child: .bt-orb-headline itself is the container's
 * actual first child, so a block is never :first-child when a headline
 * renders) to zero out the redundant top margin right after the headline's
 * own margin-bottom. When no headline renders (defensive: every current
 * caller emits one), .bt-run-config-container > .bt-run-config-block:first-child
 * covers that case too so the very first title never gets a stray gap. */
#bt-run-config-container:has(.bt-orb-block) .bt-orb-headline + .bt-run-config-block.bt-orb-block .bt-run-config-block-title.bt-orb-block-title,
#bt-run-config-container:has(.bt-orb-block) > .bt-run-config-block.bt-orb-block:first-child .bt-run-config-block-title.bt-orb-block-title {
    margin-top: 0;
}

#bt-run-config-container:has(.bt-orb-block) .bt-orb-rows {
    display: contents;
}

/* D542 — run-level criteria echo panel (#bt-criteria-echo-container /
   #dash-criteria-echo-container). Reuses .bt-run-config-block /
   .bt-config-lines / .bt-config-line (same visual language as the Run Config
   panel) so the RULES echo reads consistently with Run Config, while staying
   a visually DISTINCT card (own .chart-title "Run Criteria (Rules)") from the
   "Why the Bot Decided" per-fill CONTEXT card. */
.bt-criteria-version {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #58a6ff;
    font-weight: 700;
    margin-bottom: 0.9rem;
}

/* --------------------------------------------------------------------------
 * DMA line colours (5 / 15 / 50 / 150). The DMA overlay line colours are the
 * canonical single source of truth here as CSS custom properties, mirrored by
 * the PRICE_CHART.dma5Color / dma15Color / dma50Color / dma150Color constants
 * in trade_drill.js (the vendored Chart.js takes colours as JS values on the
 * dataset config, not via CSS classes, so the JS constants are the values
 * actually painted — these variables document the intended palette and keep it
 * greppable in one place). dma_5 + dma_15 are the NEW fast MAs; dma_50 + dma_150
 * shipped D514. Each is distinct from the accent-blue close line + green/amber
 * entry/exit markers AND from each other so all four averages read clearly:
 * 5-DMA teal, 15-DMA coral, 50-DMA amber-gold, 150-DMA magenta-purple. All
 * dashed to distinguish overlays from the solid price line. */
:root {
    --bt-dma5-color: #2dd4bf;
    --bt-dma15-color: #f2777a;
    --bt-dma50-color: #e3b341;
    --bt-dma150-color: #bc8cff;
}

/* AB#257 — ORB day-trade overlay colours (bias/regime + exit SMAs, ORH/ORL,
 * pullback band, clock markers). Same convention as the DMA block above: the
 * vendored Lightweight Charts library takes colours as JS values on the
 * series config (ORB_CHART in trade_drill.js is the value actually painted),
 * not via CSS classes — these variables document the intended palette and
 * keep it greppable in one place. dma_5/dma_15 are REMOVED from the
 * candlestick (day-trade) path as of AB#257 (daily averages, flat and
 * meaningless intraday, never consulted by ORB) — these four replace them:
 * bias/regime SMA light-blue, exit SMA bright gold (solid + bold — the SC's
 * highest-value line, 100% of exits fired here), ORH light-blue-grey, ORL
 * light-orange, pullback-band fill a translucent violet, entry-window
 * markers neutral grey, hard-flat marker red (the risk-relevant clock
 * boundary). dma_50/dma_150 are UNCHANGED on this path (not in AB#257 scope). */
:root {
    --orb-sma-bias-regime-color: #79c0ff;
    --orb-sma-exit-color: #ffd60a;
    --orb-orh-color: #a5d6ff;
    --orb-orl-color: #ffab70;
    --orb-pullback-fill-top: rgba(163, 113, 247, 0.20);
    --orb-pullback-fill-bottom: rgba(163, 113, 247, 0.06);
    --orb-entry-window-color: #8b949e;
    --orb-hard-flat-color: #f85149;
}

/* multifill-frontend: per-fill-reason marker colours on the drill-in price
 * chart. Each of a position's fills renders as its OWN distinct marked event;
 * these five colours (mirrored by the FILL_STYLE map in trade_drill.js, which
 * are the values Chart.js actually paints — the vendored Chart.js takes colours
 * as JS values on the dataset, not via CSS classes) keep the intended palette
 * greppable in one place, exactly like the DMA colours above. The two EXIT
 * colours are the most divergent pair (amber vs red) so a dual-exit trade's two
 * sell dots (exit_first_partial + exit_second_close) can never be mistaken for
 * one — they also sit at different prices (fast-MA break vs 50-DMA backstop) and
 * carry different point shapes (square vs diamond). Buys (entry green, reweight
 * add teal) read differently from sells. */
:root {
    --bt-fill-entry-color: #3fb950;             /* opening buy */
    --bt-fill-reweight-add-color: #2dd4bf;      /* top-up buy */
    --bt-fill-reweight-trim-color: #f2777a;     /* partial sell (not an exit) */
    --bt-fill-exit-first-color: #f0b429;        /* tier-1 fast-MA break */
    --bt-fill-exit-second-color: #f85149;       /* tier-2 50-DMA backstop */
}

/* "Why the bot decided" panel. Entry + exit each get a labelled block; within
 * a block, each nested decision_context group is a titled sub-section of
 * key/value rows. */
.bt-why-block {
    margin-bottom: 1.25rem;
}

.bt-why-block:last-child {
    margin-bottom: 0;
}

.bt-why-block-title {
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #e6edf3;
    font-weight: 700;
    padding-bottom: 0.35rem;
    margin-bottom: 0.75rem;
    border-bottom: 1px solid #21262d;
}

/* A decision_context group (decision_quote / filter_trace / computed_values /
 * bus_state_snapshot / General extension group). Compact card so the panel
 * reads as distinct grouped sections, not one flat wall of keys. */
.bt-why-group {
    background-color: rgba(255, 255, 255, 0.02);
    border: 1px solid #21262d;
    border-radius: 8px;
    padding: 0.6rem 0.8rem;
    margin-bottom: 0.75rem;
}

.bt-why-group:last-child {
    margin-bottom: 0;
}

.bt-why-group-title {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #58a6ff;
    font-weight: 600;
    margin-bottom: 0.5rem;
}

/* Key/value rows within a group — a responsive grid so pairs pack tightly on
 * wide viewports and stack on narrow ones. */
.bt-why-kv-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 0.35rem 1.25rem;
}

.bt-why-kv-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 0.75rem;
    padding: 0.15rem 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.03);
}

.bt-why-kv-label {
    font-size: 0.76rem;
    color: #8b949e;
    white-space: nowrap;
}

/* AB#274 — info-icon tooltip on a relabelled COMPUTED VALUES row (ORB
 * trade-detail entry/exit panels today; the markup is generic so any future
 * tabled key on any strategy gets the same treatment for free). Native
 * `title=` attribute carries the tooltip text — CSP-safe (no inline
 * <script>, no custom JS tooltip widget) and matches the codebase's existing
 * title=-hover pattern. Dark-theme-readable: muted at rest, brightens to the
 * same accent blue as .bt-why-group-title on hover/focus so it reads as
 * "interactive" without competing with the label text next to it. */
.bt-why-info-icon {
    display: inline-block;
    margin-left: 0.3rem;
    color: #6e7681;
    cursor: help;
    font-size: 0.78rem;
    line-height: 1;
    vertical-align: middle;
}

.bt-why-info-icon:hover,
.bt-why-info-icon:focus {
    color: #58a6ff;
    outline: none;
}

.bt-why-kv-value {
    font-size: 0.82rem;
    color: #e6edf3;
    font-weight: 600;
    text-align: right;
    word-break: break-word;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* Explicit "no decision context recorded" state — reuses the muted grade-notice
 * vocabulary so an absence reads as a deliberate, first-class state (never a
 * blank panel, never fabricated zero values). */
.bt-why-no-context {
    margin: 0;
}

/* ----------------------------------------------------------------------
 * PER-FILL why-panel (perfill-whypanel). When the trade-detail payload
 * carries a `fills` array, the why-panel renders ONE block PER fill in seq
 * order so the panel count MATCHES the chart-marker count (operator: "make
 * the panel match the graph"). Each block header carries the fill's reason +
 * side + qty + price + date + per-slice P&L; the entry fill's block hosts the
 * full entry_decision_context, the final-exit fill's block the full
 * exit_decision_context, and every other fill an honest "no separate trace"
 * note. Reuses .bt-why-block / .bt-why-group / .bt-why-kv-* below so the rich
 * context rendering is byte-identical to the two-block panel.
 * -------------------------------------------------------------------- */

/* The fill block's header line — the seq-ordered fill summary that titles each
 * per-fill block. A flex row: reason label on the left, the fill facts
 * (side/qty/price/date/P&L) on the right, wrapping on narrow viewports. */
.bt-why-fill-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.3rem 0.9rem;
    padding-bottom: 0.4rem;
    margin-bottom: 0.75rem;
    border-bottom: 1px solid #21262d;
}

/* Left side of the header — the human-readable reason + a seq badge. */
.bt-why-fill-reason {
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #e6edf3;
    font-weight: 700;
}

.bt-why-fill-seq {
    color: #8b949e;
    font-weight: 600;
    margin-left: 0.4rem;
}

/* Right side of the header — the fill facts, monospace so numbers align. */
.bt-why-fill-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 0.2rem 0.9rem;
    font-size: 0.76rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    color: #8b949e;
}

.bt-why-fill-fact-value {
    color: #e6edf3;
    font-weight: 600;
}

/* Per-fill P&L colour semantics (gain/loss/muted) reuse the shared
 * text-gain / text-loss / text-muted classes already applied by the JS. */

/* Honest "no separate decision trace recorded for this fill" note — a reweight
 * or partial exit carries no recorded decision context (the writer records one
 * entry + one exit context per round-trip only). Muted, first-class absence —
 * NEVER a fabricated trace. */
.bt-why-fill-no-trace {
    font-size: 0.78rem;
    color: #8b949e;
    font-style: italic;
    margin: 0.25rem 0 0;
}

@media (max-width: 992px) {
    .bt-why-kv-grid {
        grid-template-columns: 1fr;
    }

    .bt-why-fill-head {
        flex-direction: column;
    }
}

/* ======================================================================
 * Per-Strategy view host — CHUNK F0 (dashboard-design-agent)
 *
 * The source-agnostic Per-Strategy "day in the life" host (per_strategy.js,
 * window.CTPerStrategy). Dark-theme, V1-parity — reuses the same palette as
 * .chart-card (#161b22 panel / #30363d border / #8b949e muted / #58a6ff accent).
 * ONE stylesheet drives BOTH sources (live now, backtest later) — no per-source
 * styling fork. CSP: all styling here as classes; JS toggles .bt-hidden /
 * body.ps-host-open only, never inline style=.
 * ==================================================================== */

/* When the host is open, hide the sibling dashboard rows so the per-strategy
 * view stands alone (the host row itself, .ps-host-row, stays visible). Class is
 * toggled on <body> by the injected ctx.onShow/onBack — CSP-safe (no inline
 * style). The header + nav (outside .page-wrap's .row list) stay put. */
body.ps-host-open .page-wrap > .row:not(.ps-host-row) {
    display: none !important;
}

.per-strategy-view {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.per-strategy-view-bar {
    gap: 12px;
    flex-wrap: wrap;
}

/* Breadcrumb — the L1 > L2 > L3 > L4 nav trail (source label leads). */
.ps-breadcrumb {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    font-size: 0.85rem;
}

.ps-crumb {
    color: #8b949e;
}

.ps-crumb-link {
    color: #58a6ff;
    text-decoration: none;
    cursor: pointer;
}

.ps-crumb-link:hover {
    text-decoration: underline;
}

.ps-crumb-current {
    color: #e6edf3;
    font-weight: 600;
}

.ps-crumb-sep {
    color: #484f58;
}

/* Source tag — leads the breadcrumb so the operator always sees WHICH data
 * source this host renders (live | backtest). Colour-coded but not a safety
 * signal (that is the env banner). */
.ps-crumb-source {
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-size: 0.72rem;
    font-weight: 700;
    padding: 1px 8px;
    border-radius: 999px;
    border: 1px solid transparent;
}

.ps-crumb-live {
    color: #3fb950;
    border-color: rgba(63, 185, 80, 0.4);
    background: rgba(63, 185, 80, 0.1);
}

.ps-crumb-backtest {
    color: #d29922;
    border-color: rgba(210, 153, 34, 0.4);
    background: rgba(210, 153, 34, 0.1);
}

/* Level panes + level titles. */
.ps-level {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.ps-level-title {
    font-size: 1rem;
    font-weight: 600;
    color: #e6edf3;
}

.ps-l1-title {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
}

.ps-title-sub {
    color: #8b949e;
    font-size: 0.8rem;
    font-weight: 400;
}

/* L1 chart row — cumulative + daily P&L side by side (stacks on narrow). */
.ps-charts-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
}

.ps-chart-card {
    background: #161b22;
    border: 1px solid #30363d;
    border-radius: 12px;
    padding: 1rem 1.25rem;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.ps-chart-title {
    font-size: 0.78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #8b949e;
}

.ps-chart-wrap {
    position: relative;
    min-height: 140px;
}

.ps-chart-wrap canvas {
    display: block;
    width: 100% !important;
    max-width: 100%;
}

/* Versions panel + day-view panels reuse the dark card look. */
.ps-versions-panel,
.ps-day-panel,
.ps-day-config {
    background: #161b22;
    border: 1px solid #30363d;
    border-radius: 12px;
    padding: 1rem 1.25rem;
}

/* Day view — the four panels (scanner picks / rejected / entered / trades). */
.ps-day-panels {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
}

.ps-placeholder {
    padding: 1rem;
}

/* NULL-not-empty "not captured for this run" state (spec §Phasing) — an
 * explicit absence, styled distinctly from a plain placeholder so it reads as a
 * deliberate empty, never a broken panel. */
.ps-not-captured {
    color: #6e7681;
    border: 1px dashed #30363d;
    border-radius: 8px;
}

/* F3 — L3 day view. The day-view panels span the full grid width where the four
 * panels stack; the day-config annotation strip sits above them full-width. */
.ps-day-config {
    grid-column: 1 / -1;
    margin-bottom: 4px;
    font-size: 0.85rem;
    color: #c9d1d9;
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
}

.ps-day-config-label {
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-size: 0.72rem;
    color: #8b949e;
}

.ps-day-config-version {
    font-weight: 600;
    color: #d2a8ff;
}

.ps-day-config-period {
    color: #8b949e;
}

.ps-day-config-note {
    color: #79c0ff;
    font-style: italic;
}

/* L3 title — strategy + version + day, small-caps version/day chips. */
.ps-l3-title {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
}

.ps-l3-version {
    color: #d2a8ff;
    font-size: 0.85rem;
    font-weight: 600;
}

.ps-l3-day {
    color: #8b949e;
    font-size: 0.85rem;
    font-weight: 400;
}

.ps-day-panel {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.ps-day-panel-body {
    min-height: 40px;
}

/* Day-view tables (scanner picks / rejected / entered / trades) — compact, dark,
 * dense. Reuses Bootstrap table-dark; these tighten spacing to the V1 density. */
.ps-day-table {
    margin-bottom: 0;
    font-size: 0.82rem;
}

.ps-day-table th {
    font-weight: 600;
    color: #8b949e;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    font-size: 0.7rem;
}

/* Rejected-pick gate reason chip (the why-not-entered) + entered tag. */
.ps-skip-reason {
    color: #f0883e;
    font-weight: 500;
}

.ps-entered-tag {
    color: #3fb950;
    font-weight: 500;
}

/* A clickable pick/trade row in a day panel (links through to L4 trade detail). */
.ps-pick-drill-row,
.ps-day-table .ps-trade-drill-row {
    cursor: pointer;
}

.ps-pick-drill-row:hover,
.ps-day-table .ps-trade-drill-row:hover {
    background-color: rgba(88, 166, 255, 0.08);
}

/* Clickable strategy rows (Strategy Statistics + Strategy Versions tables) route
 * into the host on click. Pointer + subtle hover give a clear affordance (same
 * idiom as .trade-drill-row). CSP: cursor/hover in the class, never inline. */
.ps-strategy-row {
    cursor: pointer;
}

.ps-strategy-row:hover {
    background-color: rgba(88, 166, 255, 0.08);
}

/* F1 — L1 P&L-chart empty state (NULL-not-zero: an explicit "no realized P&L yet"
 * message shown in place of a phantom-zero line/bar, never a flat 0 curve). */
.ps-chart-empty {
    color: #8b949e;
    font-size: 0.85rem;
    margin: 0;
}

/* F1 — the strategy-versions panel list (version eras + active period + net). */
.ps-versions-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.ps-version-row {
    display: grid;
    grid-template-columns: minmax(6rem, auto) 1fr auto;
    align-items: baseline;
    gap: 12px;
    padding: 6px 8px;
    border: 1px solid #21262d;
    border-radius: 8px;
    background: #0d1117;
    font-size: 0.85rem;
}

.ps-version-label {
    font-weight: 600;
    color: #e6edf3;
}

.ps-version-live {
    display: inline-block;
    margin-left: 6px;
    padding: 0 6px;
    font-size: 0.68rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #0d1117;
    background: #3fb950;
    border-radius: 6px;
    vertical-align: middle;
}

.ps-version-period {
    color: #8b949e;
    font-variant-numeric: tabular-nums;
}

.ps-version-net {
    text-align: right;
    font-variant-numeric: tabular-nums;
    color: #8b949e;
}

.ps-version-note {
    grid-column: 1 / -1;
    color: #6e7681;
    font-size: 0.78rem;
}

/* ---------------------------------------------------------------------------
 * F2 — L2 per-version view: clickable version rows, the per-version trades panel,
 * the reconciliation badge, and the client-side date selector (shared by the L2
 * trades table AND the main-dashboard recent_trades table). Dark, V1-parity; all
 * styling as classes (CSP: no inline style attrs, no inline <style>).
 * ------------------------------------------------------------------------- */

/* A version row descends to L2 on click — pointer + subtle hover affordance,
 * mirroring .ps-strategy-row / .trade-drill-row. */
.ps-version-clickable {
    cursor: pointer;
}

.ps-version-clickable:hover {
    background-color: rgba(88, 166, 255, 0.08);
    border-color: #388bfd;
}

/* L2 title row — the version label + the reconciliation badge inline. */
.ps-l2-title {
    display: flex;
    align-items: baseline;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 12px;
}

/* Reconciliation badge. NULL-not-zero: the null state is a distinct MUTED badge
 * ("reconciles: —"), never a red "no"/0/False. */
.ps-l2-reconciles {
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 1px 8px;
    border-radius: 6px;
}

.ps-recon-ok {
    color: #0d1117;
    background: #3fb950;
}

.ps-recon-bad {
    color: #0d1117;
    background: #f85149;
}

.ps-recon-null {
    color: #8b949e;
    background: #21262d;
    border: 1px solid #30363d;
}

/* The per-version trades panel (dark card, like the versions panel). */
.ps-trades-panel {
    background: #161b22;
    border: 1px solid #30363d;
    border-radius: 12px;
    padding: 1rem 1.25rem;
    margin-top: 16px;
}

/* Trades panel header: title on the left, date selector on the right. Reused by
 * the main-dashboard recent_trades table (#trades-filter-head). */
.ps-trades-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 10px;
}

/* Recent Trades window note: a non-default sort with no date orders only the
 * newest closed trades (plus every open lot). Shown ONLY when the server says the
 * ordering is truncated, in the same amber the equity-curve grain note uses for a
 * caveat -- the operator must see he is sorting a window, not everything. */
.trades-window-note {
    font-size: 0.78rem;
    line-height: 1.35;
    color: #e3b341;
    margin: 0 0 8px;
    word-break: break-word;
}

/* Recent Trades: an OPEN LOT is not a completed trade. Distinct row tint +
 * left rule + badge so a reader sees at a glance which rows are still-open
 * positions (dated by when they OPENED) and which are completed trades. Blue
 * is used, not orange: orange #ff9800 is reserved for the live-env banner. */
.trade-row-open {
    --bs-table-bg: #10233a;
}
.trade-row-open > td:first-child {
    box-shadow: inset 3px 0 0 #58a6ff;
}
.trade-open-badge {
    display: inline-block;
    padding: 1px 8px;
    border: 1px solid #58a6ff;
    border-radius: 10px;
    color: #58a6ff;
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    white-space: nowrap;
}
.trade-open-ts-label {
    color: #58a6ff;
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* A day cut by the page boundary says so (page_days from the server). */
.trades-partial-day-note {
    font-size: 0.78rem;
    line-height: 1.35;
    color: #e3b341;
    margin: 0 0 8px;
    word-break: break-word;
}

/* The date selector control — a native date input + a Clear button. */
.ps-date-filter {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 0.78rem;
    color: #8b949e;
    margin: 0;
}

.ps-date-input {
    background: #0d1117;
    color: #e6edf3;
    border: 1px solid #30363d;
    border-radius: 6px;
    padding: 3px 8px;
    font-size: 0.82rem;
    color-scheme: dark;
}

.ps-date-input:focus {
    outline: none;
    border-color: #388bfd;
}

.ps-date-clear {
    background: #21262d;
    color: #c9d1d9;
    border: 1px solid #30363d;
    border-radius: 6px;
    padding: 3px 10px;
    font-size: 0.78rem;
    cursor: pointer;
}

.ps-date-clear:hover {
    background: #30363d;
    color: #e6edf3;
}

/* L2 trades table — the recent_trades look, scoped to the version. */
.ps-trades-table {
    margin-bottom: 0;
    font-size: 0.85rem;
}

.ps-trade-drill-row {
    cursor: pointer;
}

.ps-trade-drill-row:hover {
    background-color: rgba(88, 166, 255, 0.08);
}

/* Client-side date-filter hide (distinct from the pager's .pager-hidden so the two
 * mechanisms compose — a row is visible only when NEITHER class is set). */
.ps-filtered-out {
    display: none !important;
}

@media (max-width: 992px) {
    .ps-charts-row,
    .ps-day-panels {
        grid-template-columns: 1fr;
    }
}

/* ==========================================================================
 * SC dashboard bundle ITEMS 5-7 (D553 / D554 / D555 / D556).
 * Reuses .chart-card / .chart-title / .bt-table / .bt-sort-col /
 * .bt-sort-arrow / .text-gain / .text-loss / .text-muted / .no-data from
 * elsewhere in this file; this block only adds the item-specific surfaces.
 * Dark-theme consistent with the rest of the Backtests detail cards.
 * ========================================================================== */

/* ITEM 5 — per-run Ticker Universe table. Reuses .bt-table entirely; these
 * rules only theme the theme column + the never-traded (NULL) rows. */
.bt-universe-theme {
    color: #9aa0a6;
    font-size: 0.85rem;
}

/* Never-traded universe members (traded=false) render as NULL rows — dimmed so
 * they read as universe COVERAGE context, visually subordinate to the traded
 * rows, without being hidden. .text-muted (already applied inline) sets the
 * colour; this lowers the weight a touch further. */
.bt-universe-untraded td {
    font-style: italic;
    opacity: 0.85;
}

/* ITEM 6 STRATEGY VS BASKET — strategy equity card (the "Strategy" line is the
 * mtm equity curve). Fixed-height canvas wrap so Chart.js has a stable box
 * (maintainAspectRatio:false), same pattern as the sub-basket bar. */
.backtests-strategy-vs-basket-card {
    min-height: 300px;
}

.bt-strategy-vs-basket-canvas-wrap {
    position: relative;
    height: 280px;
}

/* REGIME SHADING legend (operator/SC request) — a compact swatch row above the
 * Strategy-vs-Basket chart so a screenshot self-explains the full-height
 * vertical background bands (bull=green / chop=orange / bear=red; normal draws
 * NO band). Shown by backtests.js::setRegimeSeries only when a run has at least
 * one coloured band; .bt-hidden otherwise. Swatch fills use a HIGHER opacity
 * than the actual on-canvas bands (a tiny swatch needs more saturation to read
 * than a large area), but the SAME hue. CSP-clean: colours are classes, never
 * inline style attrs. */
.bt-regime-legend {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem 0.85rem;
    margin: 0.1rem 0 0.45rem;
    font-size: 0.72rem;
    color: #8b949e;
}

.bt-regime-legend-title {
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 600;
    color: #c9d1d9;
}

.bt-regime-legend-item {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
}

.bt-regime-swatch {
    display: inline-block;
    width: 0.85rem;
    height: 0.6rem;
    border-radius: 2px;
    flex: 0 0 auto;
}

.bt-regime-swatch-bull {
    background: rgba(63, 185, 80, 0.5);
}

.bt-regime-swatch-chop {
    background: rgba(255, 152, 0, 0.55);
}

.bt-regime-swatch-bear {
    background: rgba(248, 81, 73, 0.5);
}

/* `normal` = NO band on the chart (unshaded) — the swatch is an OUTLINE only,
 * no fill, so the legend states the absence honestly rather than implying a
 * grey band exists. */
.bt-regime-swatch-normal {
    background: transparent;
    border: 1px dashed #6c757d;
}

/* AB#9 (D640) bull_run_end_risk OVERLAY swatch — purple, matching
 * BULL_RUN_END_RISK_COLOR in backtests.js (rgba(163, 113, 247, 0.7) on
 * canvas; a touch more opaque here for legend legibility, same hue, same
 * convention as the three band swatches above). This is an OVERLAY ribbon
 * drawn OVER the bands, never a fifth band colour — it is deliberately NOT
 * grouped with the REGIME_BAND_COLOR swatches in backtests.js. */
.bt-regime-swatch-bull-run-end-risk {
    background: rgba(163, 113, 247, 0.75);
}

/* AB#149 recovery_window OVERLAY swatch — a 1:1 legend mirror of the
 * bull_run_end_risk swatch above, differing ONLY in colour: BRIGHT GREEN,
 * matching RECOVERY_WINDOW_COLOR in backtests.js (rgba(57, 255, 20, 0.55) on
 * canvas — same value here for legend/canvas parity). This is an OVERLAY
 * ribbon (a SECOND thin strip below the purple one), never a sixth band
 * colour.
 *
 * AB#360 (OPERATOR REVISION, dated 2026-08-01): alpha lowered 0.75 -> 0.55
 * to match the canvas ribbon's tone-down, landed through the same
 * parameterised path in backtests.js. Hue is UNCHANGED — only the alpha
 * moved. The line below is RETIRED, not corrected: it accurately recorded
 * the operator's July preference at the time it was written; the
 * preference itself has since changed:
 *
 * USED TO SAY: "Deliberately a louder, more saturated green than
 * .bt-regime-swatch-bull (the muted band fill), so the two stay visually
 * distinguishable in the legend as they do on-canvas."
 *
 * STILL TRUE (kept, load-bearing): the two overlays and the bull band must
 * stay visually DISTINCT from each other. That does not depend on
 * loudness — hue alone (green vs purple vs the bull band's own shade)
 * already satisfies it. See backtests.js's own note on RECOVERY_WINDOW_COLOR
 * for the house-green collision this does NOT resolve (REGIME_BAND_COLOR.bull
 * rgba(63,185,80,...) and gainColor #3fb950 elsewhere on the same chart) —
 * reported there, not resolved here; not this dispatch's call. */
.bt-regime-swatch-recovery-window {
    background: rgba(57, 255, 20, 0.55);
}

/* POSITIONS-HELD hover tooltip (operator/SC request) — a Chart.js EXTERNAL
 * (custom HTML) tooltip listing the book held on the hovered date. Absolutely
 * positioned inside the canvas wrap (position:relative above); left/top set via
 * CSSOM in backtests.js (NOT an inline style= attr — CSP-safe). pointer-events
 * none so it never steals the hover it depends on. */
.bt-svb-positions-tooltip {
    position: absolute;
    z-index: 20;
    pointer-events: none;
    max-width: 460px;
    background: #10151d;
    border: 1px solid #30363d;
    border-radius: 6px;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45);
    padding: 8px 10px;
    color: #e6edf3;
    font-size: 0.72rem;
    line-height: 1.35;
}

.bt-svb-tt-header {
    font-weight: 600;
    color: #c9d1d9;
    margin-bottom: 6px;
    white-space: nowrap;
}

.bt-svb-tt-table {
    border-collapse: collapse;
    width: 100%;
}

.bt-svb-tt-table th {
    font-weight: 500;
    color: #8b949e;
    text-align: right;
    padding: 1px 6px;
    border-bottom: 1px solid #21262d;
    white-space: nowrap;
}

.bt-svb-tt-table td {
    text-align: right;
    padding: 1px 6px;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* Left-aligned columns (ticker + entry date). */
.bt-svb-tt-table th.bt-svb-tt-l,
.bt-svb-tt-table td.bt-svb-tt-l {
    text-align: left;
}

.bt-svb-tt-ticker {
    font-weight: 600;
    color: #58a6ff;
}

.bt-svb-tt-pos { color: #3fb950; }
.bt-svb-tt-neg { color: #f85149; }

.bt-svb-tt-more {
    margin-top: 4px;
    color: #8b949e;
    font-style: italic;
}

/* % DEPLOYED STRIP (D573) — a short filled-area strip time-aligned directly
 * below the Strategy vs Basket equity chart (mirrors the live dashboard's
 * drawdown strip). Shorter than the equity chart so it reads as a subordinate
 * strip; its y-axis left gutter is pinned in JS (afterFit) to match the equity
 * chart's, so the plot areas line up. */
.backtests-deployment-strip-card {
    min-height: 160px;
    margin-top: -8px;
}

.bt-deployment-strip-canvas-wrap {
    position: relative;
    height: 120px;
}

/* D578 — PER-ETF BASKET OVERLAY: a multi-line chart, one buy-and-hold equity
 * line per universe member, with a clickable legend. Taller than the equity
 * chart to give the legend + many lines room. Same fixed-height wrap pattern. */
.backtests-basket-overlay-card {
    min-height: 320px;
}

.bt-basket-overlay-canvas-wrap {
    position: relative;
    height: 300px;
}

/* Per-ETF overlay controls row — "Hide all" / "Show all" buttons + the
 * legend-hover description tip. Sits between the .chart-title and the canvas;
 * hidden (bt-hidden) until a chart is drawn. Buttons mirror the pager
 * .btn-outline-secondary look for a consistent dense, muted control. */
.bt-basket-overlay-controls {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 2px 0 6px;
    min-height: 1.4rem;
}

.bt-overlay-btn {
    color: #9aa0a6;
    background: transparent;
    border: 1px solid #30363d;
    border-radius: 4px;
    font-size: 0.72rem;
    padding: 2px 10px;
    cursor: pointer;
}

.bt-overlay-btn:hover {
    background: #1c2128;
    color: #e6edf3;
    border-color: #58a6ff;
}

/* Legend-hover description tip — muted, small, single line; wraps at word
 * boundaries if a description is long. Empty when nothing is hovered. */
.bt-basket-overlay-hover {
    color: #8b949e;
    font-size: 0.75rem;
    margin-left: 4px;
    word-break: break-word;
}

/* ITEM 6 P&L BY ENTRY DATE — realized + unrealized P&L by entry date (diverging stacked
 * bar). Fixed-height canvas wrap. */
.backtests-entry-date-pnl-card {
    min-height: 300px;
}

.bt-entry-date-pnl-canvas-wrap {
    position: relative;
    height: 280px;
}

/* DAILY P&L (D557) — strategy per-day P&L as diverging bars (day-over-day
 * deltas of mtm_equity_curve; positive up / negative down). Fixed-height canvas
 * wrap, same pattern as the strategy-vs-basket + entry-date cards. */
.backtests-daily-pnl-card {
    min-height: 300px;
}

.bt-daily-pnl-canvas-wrap {
    position: relative;
    height: 280px;
}

/* AB#301 — Daily|Weekly|Monthly granularity toggle for the Daily P&L card.
 * Inserted dynamically by backtests.js's _wireDailyPnlGranularityToggle
 * right after .chart-title, above the note/canvas — a thin inline-row
 * wrapper (NOT position:absolute — unlike .bt-candle-res-toggle below, which
 * overlays a chart canvas, this control sits in the card's own flow next to
 * its title). The BUTTONS reuse .bt-candle-res-btn / .bt-candle-res-btn.active
 * VERBATIM (same dark chrome + accent-blue active state as the "5m | 15m"
 * candle-resolution toggle) so this reads as the app's one shared
 * segmented-toggle vocabulary — only this layout wrapper is new. */
.bt-daily-pnl-gran-toggle {
    margin: -0.35rem 0 0.6rem;
}

.bt-daily-pnl-gran-row {
    display: flex;
    gap: 4px;
}

/* ==========================================================================
 * AB#279 — CAPITAL DEPLOYED (ORB / non-basket runs only): daily PEAK
 * concurrent capital deployed as % of equity, log-scale y-axis (real runs
 * span 1% .. 26,000%+), a dashed 4x-margin (400%) reference line, and a
 * turnover % companion line. Same fixed-height canvas-wrap pattern as the
 * Strategy vs Basket / Daily P&L cards above.
 * ========================================================================== */
.backtests-capital-deployed-card {
    min-height: 320px;
}

.bt-capital-deployed-canvas-wrap {
    position: relative;
    height: 280px;
}

/* Headline stat row above the chart — "Peak utilization: N% (Mx equity)"
 * plus, ONLY when exceeds_4x_margin is true, a loud red flag badge. This is
 * the honest-warning surface the panel exists to deliver, so the exceeded
 * state must read as an alarm at a glance, not a muted footnote. */
.bt-capdep-headline {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.5rem 0.75rem;
    margin: 0.1rem 0 0.6rem;
    font-size: 0.85rem;
    color: #9aa0a6;
}

.bt-capdep-peak-value {
    font-family: var(--bs-font-monospace, monospace);
    font-weight: 700;
    font-size: 1rem;
    font-variant-numeric: tabular-nums;
    color: #e6edf3;
}

/* Loud red when the run's own peak breaches the 4x margin ceiling — the
 * SAME red used by .bt-gate-badge-fail / .text-loss elsewhere on this
 * dashboard, so "danger" reads consistently across the app. */
.bt-capdep-peak-value-danger {
    color: #f85149;
}

.bt-capdep-peak-mult {
    font-family: var(--bs-font-monospace, monospace);
    font-variant-numeric: tabular-nums;
    color: #8b949e;
}

.bt-capdep-badge {
    display: inline-block;
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: 700;
    padding: 0.15rem 0.65rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.bt-capdep-badge-danger {
    background-color: rgba(248, 81, 73, 0.18);
    color: #f85149;
    border: 1px solid rgba(248, 81, 73, 0.5);
}

/* ITEM 7 — shares-outstanding (slope signal) chart card, below the price
 * chart in the drill-in. Fixed-height canvas wrap. */
.backtests-shares-outstanding-card {
    min-height: 260px;
}

.bt-shares-outstanding-canvas-wrap {
    position: relative;
    height: 240px;
}

/* ==========================================================================
   STRATEGY-INSTANCE DETAIL PAGE (D610 / D611 §15 step 5)
   templates/strategy_instance.html + static/strategy_instance.js
   ==========================================================================

   THE THREE-STATE HONESTY CONTRACT, IN CSS.

   metric_availability[metric].state in {available, no_trades, unavailable}.
   These three MUST be distinguishable AT A GLANCE, not only on close reading:

     .si-state-available   -> bright, normal-weight value. A real number.
     .si-state-no-trades   -> muted SLATE, dashed border, italic text.
                              "made nothing" - the metric has no subject.
     .si-state-unavailable -> AMBER, solid left rule, hatched background.
                              "cannot be measured" - a statement about our
                              instrumentation, NOT about the strategy.

   The two null states are deliberately given DIFFERENT HUES (slate vs amber),
   DIFFERENT BORDER TREATMENTS (dashed vs solid+hatch) and DIFFERENT TEXT - three
   independent signals. Any ONE of them collapsing still leaves the other two, so
   an operator can never read "not measurable" as "earned zero". Amber is chosen
   for unavailable (not red) because it is an INSTRUMENTATION GAP, not a loss and
   not an error.

   Colours follow this file's existing dark palette (#0e1117 / #161b22 panels,
   #e6edf3 text) and the operator semantic convention: green gains, red losses,
   amber attention, muted grey non-attention. */

/* AB#645 (2026-08-15, dashboard-design-agent, operator visual-consistency
   ruling): #si-back-link used to be styled by .si-breadcrumb-link (small grey
   text). It now reuses .settings-save-btn + .bt-back-btn (see
   strategy_instance.html for the full rationale) so every back control in the
   product is the SAME green button. .si-breadcrumb-link is retired dead CSS
   -- no template references the class any more (grep-verified). */
.si-breadcrumb {
    margin: 4px 0 12px;
}

/* ONE VISIBLE BACK CONTROL PER SCREEN STATE (operator ruling, same dispatch).
   #si-trade-drill-view (strategy_instance.html) is a CHILD of #si-detail, and
   strategy_instance.js's onShow/onBack toggle only #si-detail's own children
   -- this breadcrumb sits OUTSIDE #si-detail (a sibling under #si-root), so
   without this rule it would stay visible alongside the drill-in's own
   #si-back-to-trades-btn green button, i.e. two back controls on screen at
   once (the exact AB#631 grey-link/green-button pairing the operator flagged
   as confusing). Pure CSS, keyed off the SAME .bt-hidden class
   strategy_instance.js already toggles on #si-trade-drill-view -- no JS
   change, frontend-js-agent's onShow/onBack logic is untouched. Requires
   :has() (Chromium 105+ / Safari 15.4+ / Firefox 121+); this dashboard is
   operator-only on a controlled modern-Chromium fleet. */
#si-root:has(#si-trade-drill-view:not(.bt-hidden)) .si-breadcrumb {
    display: none;
}

.si-loading {
    color: #9aa0a6;
    padding: 24px 0;
    font-size: 0.9rem;
}

.si-error {
    background: #2d1418;
    border-left: 4px solid #f85149;
    color: #ffb4ab;
    padding: 14px 18px;
    border-radius: 6px;
    margin: 12px 0;
    font-size: 0.9rem;
}

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

.si-header {
    margin-bottom: 16px;
}

.si-header-top {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}

.si-title {
    font-size: 1.4rem;
    font-weight: 600;
    color: #e6edf3;
    margin: 0;
}

.si-lifecycle {
    font-size: 0.7rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    padding: 3px 10px;
    border-radius: 10px;
    background: #21262d;
    color: #9aa0a6;
    border: 1px solid #30363d;
}

.si-lifecycle-active {
    background: #12261a;
    color: #3fb950;
    border-color: #238636;
}

.si-lifecycle-paused,
.si-lifecycle-retired {
    background: #2b2000;
    color: #f0b429;
    border-color: #9e6a03;
}

.si-header-sub {
    display: flex;
    gap: 18px;
    flex-wrap: wrap;
    margin-top: 6px;
}

.si-meta {
    font-size: 0.8rem;
    color: #9aa0a6;
}

.si-meta code {
    color: #c9d1d9;
    background: #161b22;
    padding: 1px 5px;
    border-radius: 3px;
    /* Long ids wrap at word boundaries, never mid-character (the banned
       break-all + narrow max-width pattern character-wraps service names). */
    word-break: break-word;
}

/* ---- Cards ------------------------------------------------------------- */

.si-card {
    background: #161b22;
    border: 1px solid #30363d;
    border-radius: 6px;
    padding: 14px 16px;
    margin-bottom: 16px;
}

.si-card-title {
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: #9aa0a6;
    margin: 0 0 12px;
}

.si-chart-wrap {
    position: relative;
    min-height: 220px;
}

.si-chart-note {
    font-size: 0.78rem;
    color: #9aa0a6;
    margin: 8px 0 0;
}

/* ---- Stat grid --------------------------------------------------------- */

.si-stat-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
}

.si-stat {
    flex: 1 1 170px;
    min-width: 150px;
    background: #0e1117;
    border: 1px solid #30363d;
    border-radius: 5px;
    padding: 10px 12px;
}

.si-stat-label {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #9aa0a6;
    margin-bottom: 4px;
}

/* Every figure shares ONE font-size - no hero number. The return is not to be
   celebrated over the drawdown (the backtest headline holds the same rule). */
.si-stat-value {
    font-size: 1.05rem;
    font-weight: 600;
    color: #e6edf3;
}

.si-stat-why {
    font-size: 0.68rem;
    margin-top: 4px;
    font-style: italic;
}

/* --- STATE 1: available - a real, measured number. ---------------------- */
.si-stat.si-state-available .si-stat-value {
    color: #e6edf3;
}

/* --- STATE 2: no_trades - muted SLATE, DASHED border, ITALIC text. ------
   "It made nothing." Distinct from unavailable by hue AND border style. */
.si-stat.si-state-no-trades {
    border: 1px dashed #484f58;
    background: #12161c;
}

.si-stat.si-state-no-trades .si-stat-value,
.si-state-no-trades .si-state-text {
    color: #7d8590;
    font-style: italic;
    font-weight: 500;
    font-size: 0.9rem;
}

.si-stat.si-state-no-trades .si-stat-why {
    color: #6e7681;
}

/* --- STATE 3: unavailable - AMBER, SOLID left rule, HATCHED background. --
   "We cannot measure this." NOT zero. NOT no-trades. The hatch is the third
   independent signal (beyond hue + border) that this box is not a value. */
.si-stat.si-state-unavailable {
    border: 1px solid #9e6a03;
    border-left: 4px solid #f0b429;
    background-color: #1c1710;
    background-image: repeating-linear-gradient(
        45deg,
        rgba(240, 180, 41, 0.06),
        rgba(240, 180, 41, 0.06) 6px,
        transparent 6px,
        transparent 12px
    );
}

.si-stat.si-state-unavailable .si-stat-value,
.si-state-unavailable .si-state-text {
    color: #f0b429;
    font-weight: 500;
    font-size: 0.9rem;
}

.si-stat.si-state-unavailable .si-stat-why {
    color: #b8860b;
}

/* ---- Legend ------------------------------------------------------------
   Not decoration: it is what makes the two null states READABLE as different
   claims rather than two greyed-out boxes. */

.si-legend {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top: 14px;
    padding-top: 12px;
    border-top: 1px solid #30363d;
}

.si-legend-item {
    font-size: 0.74rem;
    color: #8b949e;
    display: flex;
    align-items: baseline;
    gap: 8px;
}

.si-legend-swatch {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: 2px;
    flex: 0 0 auto;
}

.si-legend-item.si-state-no-trades .si-legend-swatch {
    border: 1px dashed #7d8590;
    background: #12161c;
}

.si-legend-item.si-state-unavailable .si-legend-swatch {
    border: 1px solid #f0b429;
    background: #1c1710;
}

/* ---- Panel-level unavailable card --------------------------------------
   Used where a whole PANEL cannot be computed (Strategy-vs-Basket, the
   deployed-capital time series). Deliberately NOT an empty chart: an empty
   chart reads as "this strategy tracked the basket at zero". */

.si-unavailable-card {
    border: 1px solid #9e6a03;
    border-left: 4px solid #f0b429;
    background-color: #1c1710;
    background-image: repeating-linear-gradient(
        45deg,
        rgba(240, 180, 41, 0.05),
        rgba(240, 180, 41, 0.05) 6px,
        transparent 6px,
        transparent 12px
    );
    border-radius: 5px;
    padding: 14px 16px;
    margin-top: 12px;
}

.si-unavailable-title {
    color: #f0b429;
    font-weight: 600;
    font-size: 0.9rem;
    margin-bottom: 6px;
}

.si-unavailable-body {
    color: #c9d1d9;
    font-size: 0.82rem;
    margin: 0 0 6px;
}

.si-unavailable-detail {
    color: #8b949e;
    font-size: 0.76rem;
    margin: 0 0 6px;
}

.si-unavailable-reason {
    color: #6e7681;
    font-size: 0.72rem;
    margin: 0;
}

.si-unavailable-reason code {
    color: #b8860b;
    word-break: break-word;
}

/* ---- Config state / self-identification / params ----------------------- */

.si-config-state {
    padding: 12px 14px;
    border-radius: 5px;
    margin-bottom: 12px;
    font-size: 0.82rem;
    color: #c9d1d9;
}

.si-config-state.si-state-unavailable {
    border: 1px solid #9e6a03;
    border-left: 4px solid #f0b429;
    background: #1c1710;
}

.si-config-state.si-state-unavailable strong {
    color: #f0b429;
}

.si-self-id,
.si-params-resolved {
    margin-top: 16px;
}

.si-self-id-heading,
.si-params-heading {
    font-size: 0.78rem;
    color: #c9d1d9;
    margin-bottom: 8px;
}

.si-params-count {
    color: #9aa0a6;
    font-weight: 400;
    font-size: 0.72rem;
}

.si-self-id-table,
.si-params-table {
    font-size: 0.8rem;
}

/* Long fingerprints/ids wrap at word boundaries. The pre-Bundle-D 8rem
   max-width + break-all pattern is BANNED (it character-wraps). */
.si-self-id-key,
.si-param-key {
    color: #9aa0a6;
    font-weight: 500;
    width: 22rem;
    max-width: 22rem;
    word-break: break-word;
}

.si-self-id-val,
.si-param-val {
    color: #e6edf3;
    word-break: break-word;
    width: 10rem;
    max-width: 10rem;
}

/* ---- "What it does" column ---------------------------------------------
   The knob's ACTION, and the two ways a true sentence still misleads. These are
   styled as THREE visually distinct things on purpose: an operator scanning this
   table must be able to see "this knob does nothing right now" without reading a
   paragraph. Collapsing them into one grey block would satisfy the letter of the
   standing rule and lose its intent.

   .si-param-action  -> the plain-English trading action. Primary text.
   .si-param-inert   -> AMBER, matching the .si-state-unavailable hue already used
                        for "cannot be measured". Same claim shape: this is a
                        statement about REACHABILITY, not about the strategy. An
                        operator tuning an inert knob is wasting their time.
   .si-param-caveat  -> RED-toned: a load-bearing correction ("HAS NO EFFECT", or
                        a knob that degrades silently). Never decorative. */
.si-param-desc {
    color: #c9d1d9;
    word-break: break-word;
    line-height: 1.4;
}

.si-param-action {
    display: block;
}

.si-param-inert,
.si-param-caveat {
    display: block;
    margin-top: 0.25rem;
    font-size: 0.72rem;
    padding-left: 0.4rem;
}

.si-param-inert {
    color: #f0b429;
    border-left: 2px solid #9e6a03;
}

.si-param-caveat {
    color: #ff9492;
    border-left: 2px solid #a13c37;
}

.si-self-id-val code,
.si-param-val code {
    word-break: break-word;
}

/* ---- Settings row link ------------------------------------------------- */

.bot-table-name-link {
    color: #58a6ff;
    text-decoration: none;
}

.bot-table-name-link:hover {
    color: #79c0ff;
    text-decoration: underline;
}

/* ==========================================================================
   LIVE STRATEGY INSTANCES ROSTER (dashboard.html Row 0b)
   The per-instance entry point on the TRADING view, filtered to entries-on.

   The /strategy-instance/<id> pages were only ever linked from /settings — a
   config page — so an operator on the Dashboard had no per-instance surface.
   This table is that surface. Link colour matches .bot-table-name-link (the
   settings page's equivalent link) so the same affordance reads the same way
   in both places.

   The MUTED-SLATE treatment (.si-roster-off) is deliberately not amber and not
   red. It now carries the "no instance has entries on" empty state rather than
   per-row "Off" text, which the entries-on filter removes from this panel — but
   the reasoning is unchanged and is why the colour is: entries being off is an
   operator SETTING, not a fault, and must not compete for attention with real
   alerts. This mirrors the .si-state-no-trades reasoning — "made nothing" vs
   "cannot be measured" — and keeps amber reserved for genuine instrumentation
   gaps. The per-row "Off" styling is retained because the renderer is not
   coupled to the filter: loosen the filter and the rows style correctly again.
   ========================================================================== */

/* NO max-height / inner scroll. The whole point of this table is that the
   operator can SEE his instances; an inner scrollbar hid 6 of 19 below a fold
   inside a fold, which is the same "reachable in principle, invisible in
   practice" failure this table exists to fix. The Strategy Versions table below
   renders its full length for the same reason. 19 rows is not a long list. */
.si-roster-scroll {
    overflow-x: auto;
}

.si-roster-table {
    font-size: 0.85rem;
    margin-bottom: 0;
}

.si-roster-table th,
.si-roster-table td {
    padding: 6px 10px;
    vertical-align: middle;
}

/* Long strategy_instance_id / display_name values wrap at WORD boundaries.
   break-all would character-wrap "alpaca-claudev2-test:etf_trend_rotation"
   into unreadable confetti. */
.si-roster-table td,
.si-roster-table th {
    word-break: break-word;
    overflow-wrap: anywhere;
}

.si-roster-link {
    color: #58a6ff;
    text-decoration: none;
    font-weight: 500;
}

.si-roster-link:hover,
.si-roster-link:focus {
    color: #79c0ff;
    text-decoration: underline;
}

.si-roster-row:hover {
    background-color: #1b2129;
}

.si-roster-lifecycle {
    color: #9aa0a6;
    text-transform: uppercase;
    font-size: 0.75rem;
    letter-spacing: 0.04em;
}

/* Disabled entries — a deliberate operator setting, stated plainly. */
.si-roster-off {
    color: #7d8590;
    font-style: italic;
}

/* No overlay row => every value in this row is a documented DEFAULT rather
   than an operator-chosen value. A left rule marks the row without shouting;
   the title attribute carries the full statement. */
.si-roster-defaulted td:first-child {
    border-left: 3px solid #484f58;
}

/* Roster-level unavailable text. The .si-state-unavailable colour rules
   elsewhere in this file bind via .si-stat or a .si-state-text child; roster
   cells have neither, so the amber is bound explicitly here. Without this the
   fail-loud message would render in default body colour and read as ordinary
   copy — the signal would be lost exactly when it matters. */
.si-roster-table .si-state-unavailable,
.si-roster-table td.si-state-unavailable {
    color: #f0b429;
    font-weight: 500;
}

/* The entries-on filter's disclosure line: "Showing 1 of 19 — 18 hidden ...".
   Muted, because a filter working as asked is not an alert; but PRESENT, because
   a panel that shows 1 of 19 without saying so has hidden 18 instances from the
   operator. Sits directly under the table so the count is read with the rows it
   describes, never as a page footnote. */
.si-roster-note {
    margin-top: 10px;
    padding-top: 8px;
    border-top: 1px solid #21262d;
    color: #7d8590;
    font-size: 0.78rem;
    line-height: 1.5;
}

/* Empty when the roster has not loaded (or failed): no border, no dead gap. */
.si-roster-note:empty {
    display: none;
}

.si-roster-note-link {
    color: #58a6ff;
    text-decoration: none;
}

.si-roster-note-link:hover,
.si-roster-note-link:focus {
    color: #79c0ff;
    text-decoration: underline;
}

/* ==========================================================================
   RECONCILIATION ALERT (AB#348)
   #si-reconciliation (templates/strategy_instance.html) is rendered entirely
   by static/strategy_instance.js against payload key `reconciliation`.

   FOUR STATES, NONE MAY LOOK ALIKE — data-recon-state on #si-reconciliation
   drives everything below via attribute selector (no separate class to keep
   in sync with the JS-set attribute):

     clean           -> quiet, muted GREEN one-liner. The only "silent" state.
     divergence      -> ALERT. Red, heaviest weight on the page. Deliberately
                        NOT a .si-card / .panel — this section sits FIRST in
                        #si-detail, outside the holdings/performance card grid,
                        so it structurally cannot be mistaken for a position
                        row (design rule 2: rendering broker truth as a
                        holding would make this dashboard a second source of
                        position truth and MASK the defect).
     partial_unknown -> AMBER, hatched (reuses the .si-state-unavailable
                        "instrumentation gap, not a value" visual grammar
                        from this page's stat cells) — some symbols could not
                        be compared this pass. Must not collapse into clean.
     unknown         -> BLUE-slate, dashed. Comparator liveness is itself
                        unknown (no pass telemetry, stale, or aborted). Must
                        carry a last-ran-at timestamp when one exists — never
                        blank, never "just now" for a stale one — and must
                        read as neither an error (not red) nor reassurance
                        (not green). This is the state v2-test legitimately
                        renders today (AB#321 merged, not deployed).

   Four independent hues (green / red / amber / blue-slate) mirror the
   established convention on this page (STRATEGY-INSTANCE DETAIL PAGE block
   above): hue + border-style + a real text change, never colour alone. */

.si-recon {
    border-radius: 6px;
    padding: 14px 18px;
    margin-bottom: 20px;
    font-size: 0.86rem;
    line-height: 1.5;
}

.si-recon-body:empty {
    display: none;
}

.si-recon-badge {
    display: inline-block;
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    padding: 2px 9px;
    border-radius: 10px;
    margin-right: 8px;
    vertical-align: middle;
}

.si-recon-headline {
    font-size: 0.92rem;
    font-weight: 600;
    vertical-align: middle;
}

.si-recon-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top: 12px;
    padding-top: 10px;
    border-top: 1px solid rgba(255, 255, 255, 0.08);
}

.si-recon-item {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
    font-size: 0.82rem;
}

.si-recon-symbol {
    font-weight: 700;
    font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
}

.si-recon-qty {
    font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
    color: #c9d1d9;
}

/* divergence_type — 6-value DB enum (broker-has-position-book-doesnt,
   book-has-position-broker-doesnt, qty-mismatch, price-mismatch,
   close-state-mismatch, unattributed). One shared chip shape; colour
   inherits from the parent state (see per-state rules below) so a single
   class works across divergence AND partial_unknown lists. */
.si-recon-type-chip {
    display: inline-block;
    font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
    font-size: 0.68rem;
    padding: 1px 6px;
    border-radius: 3px;
    white-space: nowrap;
}

.si-recon-note {
    font-size: 0.76rem;
    color: #9aa0a6;
    margin-top: 10px;
    line-height: 1.45;
}

.si-recon-timestamp {
    font-size: 0.74rem;
    color: #8b949e;
    margin-top: 6px;
    display: block;
}

/* --- STATE: clean — the only quiet one. Muted green, thin left rule. ---- */
#si-reconciliation[data-recon-state="clean"] {
    background: #12261a;
    border: 1px solid #1c3a26;
    border-left: 3px solid #238636;
    color: #7ee2a8;
}

#si-reconciliation[data-recon-state="clean"] .si-recon-badge {
    background: rgba(63, 185, 80, 0.15);
    color: #3fb950;
    border: 1px solid #238636;
}

#si-reconciliation[data-recon-state="clean"] .si-recon-headline {
    color: #9aa0a6;
    font-weight: 500;
}

/* --- STATE: divergence — the ALERT. Heaviest weight on the page. -------- */
#si-reconciliation[data-recon-state="divergence"] {
    background: #2d1418;
    border: 1px solid #f85149;
    border-left: 6px solid #f85149;
    box-shadow: 0 0 0 1px rgba(248, 81, 73, 0.15);
}

#si-reconciliation[data-recon-state="divergence"] .si-recon-badge {
    background: #f85149;
    color: #fff;
}

#si-reconciliation[data-recon-state="divergence"] .si-recon-headline {
    color: #ffb4ab;
}

#si-reconciliation[data-recon-state="divergence"] .si-recon-list {
    border-top-color: rgba(248, 81, 73, 0.25);
}

#si-reconciliation[data-recon-state="divergence"] .si-recon-symbol {
    color: #ffb4ab;
}

#si-reconciliation[data-recon-state="divergence"] .si-recon-type-chip {
    color: #f85149;
    background: rgba(248, 81, 73, 0.1);
    border: 1px solid rgba(248, 81, 73, 0.35);
}

/* --- STATE: partial_unknown — AMBER hatch (reuses .si-state-unavailable's
   "instrumentation gap, not a value" grammar). Must not read as clean. ---- */
#si-reconciliation[data-recon-state="partial_unknown"] {
    background-color: #1c1710;
    background-image: repeating-linear-gradient(
        45deg,
        rgba(240, 180, 41, 0.06),
        rgba(240, 180, 41, 0.06) 6px,
        transparent 6px,
        transparent 12px
    );
    border: 1px solid #9e6a03;
    border-left: 6px solid #f0b429;
}

#si-reconciliation[data-recon-state="partial_unknown"] .si-recon-badge {
    background: rgba(240, 180, 41, 0.18);
    color: #f0b429;
    border: 1px solid #9e6a03;
}

#si-reconciliation[data-recon-state="partial_unknown"] .si-recon-headline {
    color: #f0b429;
}

#si-reconciliation[data-recon-state="partial_unknown"] .si-recon-list {
    border-top-color: rgba(240, 180, 41, 0.25);
}

#si-reconciliation[data-recon-state="partial_unknown"] .si-recon-symbol {
    color: #f0b429;
}

#si-reconciliation[data-recon-state="partial_unknown"] .si-recon-type-chip {
    color: #f0b429;
    background: rgba(240, 180, 41, 0.1);
    border: 1px solid rgba(240, 180, 41, 0.35);
}

/* --- STATE: unknown — comparator liveness unknown. Blue-slate, dashed.
   Neither an error (not red) nor reassurance (not green). This is the
   state v2-test legitimately renders today (AB#321 merged, not deployed:
   reconciliation_pass_result is not flowing there). ---------------------- */
#si-reconciliation[data-recon-state="unknown"] {
    background: #0d1520;
    border: 1px dashed #30363d;
    border-left: 4px solid #58a6ff;
}

#si-reconciliation[data-recon-state="unknown"] .si-recon-badge {
    background: rgba(88, 166, 255, 0.15);
    color: #58a6ff;
    border: 1px solid rgba(88, 166, 255, 0.4);
}

#si-reconciliation[data-recon-state="unknown"] .si-recon-headline {
    color: #c9d1d9;
}

#si-reconciliation[data-recon-state="unknown"] .si-recon-timestamp {
    color: #58a6ff;
}

/* ---- Stat-tile reconciliation caveat (AB#348) --------------------------
   NOT a fifth metric_availability state — a COUPLED condition layered
   (combo class, e.g. "si-state-available si-stat-caveat" or
   "si-state-no-trades si-stat-caveat") on top of an EXISTING stat cell
   when summary.has_recorded_trades is false AND reconciliation.state is
   "divergence" or "partial_unknown". Confirmed scope with frontend-js-agent
   2026-07-31: applies to all four STAT_SPECS cells from the operator's own
   bug report — trade_count (TRADES), net_pnl_usd, realized_pnl_usd,
   win_rate — never the equity-derived cells (return/drawdown/sharpe/
   calmar), which are already `unavailable` and need no further layering.

   The underlying fact does NOT change and is NOT replaced: trade_count is
   still the real measured count and the P&L/win-rate cells still honestly
   have no recorded subject. What changes is that neither renders BARE next
   to the alert above — a plain "0" or plain "No trades recorded" beside a
   divergence alert reads as a measured fact contradicting broker truth.

   .si-stat-caveat is CLASS-ONLY — self-sufficient with no required data
   attribute (frontend-js-agent applies it as a plain modifier class, no new
   data-state value). The optional [data-caveat-state="divergence"|
   "partial_unknown"] attribute, if a caller sets it, refines the colour to
   mirror the SAME hue #si-reconciliation uses for that state (so the tile
   reads as CONNECTED to the alert above, not a second contradicting fact
   beside it) — see the class-only default below for the case where no
   caller sets it. The corner flag (::before) is a third independent signal
   (beyond hue + border/background), matching this page's
   hue+border+marker convention, so the caveat is visible even to a reader
   who cannot resolve colour. */

.si-stat.si-stat-caveat {
    position: relative;
    overflow: visible;
    /* Class-only default: red-toned "flagged" treatment (matches the
       divergence alert's hue) so this is never invisible/uncoloured when
       no data-caveat-state attribute is present. */
    border: 1px solid #f85149 !important;
    background-color: #1c1114;
    color: #f85149; /* feeds ::before corner marker via currentColor */
}

.si-stat.si-stat-caveat::before {
    content: "";
    position: absolute;
    top: -1px;
    right: -1px;
    width: 0;
    height: 0;
    border-style: solid;
    border-width: 0 15px 15px 0;
    border-color: transparent currentColor transparent transparent;
    border-radius: 0 5px 0 0;
}

.si-stat.si-stat-caveat .si-stat-value,
.si-stat.si-stat-caveat .si-stat-why {
    color: #ffb4ab;
    font-style: italic;
    font-weight: 500;
    font-size: 0.9rem;
}

.si-stat.si-stat-caveat .si-stat-why {
    font-size: 0.68rem;
    font-weight: 600;
}

/* Optional refinement: caller-provided data-caveat-state="partial_unknown"
   softens the default red to the same amber #si-reconciliation uses for
   that state — divergence keeps the class-only red default above (no
   override rule needed; it already matches). */
.si-stat.si-stat-caveat[data-caveat-state="partial_unknown"] {
    border-color: #9e6a03 !important;
    background-color: #1c1710;
    color: #f0b429;
}

.si-stat.si-stat-caveat[data-caveat-state="partial_unknown"] .si-stat-value,
.si-stat.si-stat-caveat[data-caveat-state="partial_unknown"] .si-stat-why {
    color: #f0b429;
}

/* ==========================================================================
   D828 — per-member universe flag panel: the bull-run-end-risk ENTER column
   and the HOLD THROUGH map column.

   Shared by BOTH surfaces that render the panel (the backtest run page and the
   strategy-instance settings detail page) because both go through the ONE
   renderer, backtests.js::renderUniverseFlags.

   THE POINT OF THIS BLOCK is visible DISTINCTION, not decoration. Both new
   fields ship a permissive DEFAULT (enter_in_bull_run_end_risk DEFAULT TRUE,
   hold_through NULL = hold-through-all), so on a typical universe almost every
   cell is the default and the handful of RESTRICTED members are the entire
   operator signal. If a restricted member and a default member read alike at a
   glance, the feature is invisible exactly where it matters.
   ========================================================================== */

/* A flag reading that must NOT be scanned past: the "NO" on
   enter_in_bull_run_end_risk (may not be BOUGHT on a marker session) and the
   force-exit badge on a hold map that omits the overlay. Orange, not red: this
   is high-attention configuration, not a failure. */
.bt-flag-warn {
    display: inline-block;
    color: #ff9800;
    background: rgba(255, 152, 0, 0.14);
    border: 1px solid rgba(255, 152, 0, 0.45);
    border-radius: 3px;
    padding: 0 5px;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    white-space: nowrap;
}

/* hold_through NULL -> the DEFAULT rendering. Muted (it carries no news) but
   PRESENT and named: a blank cell would make hold-through-all indistinguishable
   from "not recorded", which are different claims.

   AB#793: .bt-flag-default (enter_in_bull_run_end_risk, "Yes — default taken")
   expresses the SAME fact — this value is the column default, not a recorded
   choice — so it shares this one rule rather than carrying a second copy that
   could drift. */
.bt-hold-default,
.bt-flag-default {
    color: #7d8590;
    font-size: 0.78rem;
    font-style: italic;
    white-space: nowrap;
}

/* A restricted hold map that STILL holds through the overlay — real
   configuration, but not the force-exit case. Neutral blue: distinct from both
   the muted default and the orange warn. */
.bt-hold-restricted {
    display: inline-block;
    color: #58a6ff;
    border: 1px solid rgba(88, 166, 255, 0.4);
    border-radius: 3px;
    padding: 0 5px;
    font-size: 0.72rem;
    font-weight: 600;
    white-space: nowrap;
}

/* The map body itself, under whichever badge was emitted. One line per entry
   regime so a multi-regime map stays readable in a dense table. */
.bt-hold-map {
    display: block;
    margin-top: 2px;
}

.bt-hold-entry {
    display: block;
    color: #9aa0a6;
    font-size: 0.72rem;
    line-height: 1.35;
    /* Regime names are words — wrap at word boundaries, never mid-token. */
    word-break: break-word;
}

/* The hold-through column carries two stacked lines; give it room to breathe
   without letting it wrap to a character-per-line in the dense flag grid. */
.bt-universe-flags-table td:has(.bt-hold-map),
.bt-universe-flags-table td:has(.bt-hold-default) {
    min-width: 11rem;
}

/* ==========================================================================
   AB#357 — STRATEGY-INSTANCE PAGE: OPEN POSITIONS, DEPLOYED-CAPITAL-OVER-
   TIME + DAILY P&L CHART CAVEATS, BASKET INCOMPARABILITY AFFORDANCE
   templates/strategy_instance.html — markup/style only (dashboard-design-
   agent lane, D875); render logic is frontend-js-agent's.

   ONE VISUAL VOCABULARY, REUSED, NOT REINVENTED PER PANEL:
     GREEN  (#3fb950 / #238636)          -> a measured, real fact (may be 0).
     BLUE-SLATE (#58a6ff), dashed        -> not yet available; no telemetry.
     AMBER  (#f0b429 / #9e6a03), hatched -> structurally cannot be computed
            (instrumentation gap, never a value). Reuses .si-unavailable-
            card / .si-state-unavailable verbatim — no new amber class here.
     RED    (#f85149), heaviest weight   -> a DIVERGENCE: book and broker
            disagree. Mirrors .si-recon[data-recon-state="divergence"] and
            .si-stat-caveat exactly — this block adds the CHART-scoped and
            TABLE-ROW-scoped forms of the SAME red, not a second vocabulary.
   ========================================================================== */

/* ---- Open positions (panel 1) ------------------------------------------- */

/* Blue-slate dashed "no data yet" state — a NEW modifier on the existing
   .si-config-state shell (the amber .si-state-unavailable modifier already
   exists; this adds the blue sibling so "pending" and "cannot compute" read
   as different claims, matching .si-recon's unknown-vs-partial_unknown
   split elsewhere on this page). */
.si-config-state.si-state-no-data {
    border: 1px dashed #30363d;
    border-left: 4px solid #58a6ff;
    background: #0d1520;
    color: #c9d1d9;
}

.si-config-state.si-state-no-data strong {
    color: #58a6ff;
}

/* Quiet green "measured, real" state (e.g. "No positions currently held" —
   a real fact, not a gap). Deliberately the LEAST visually loud of the
   three so a genuinely empty-but-measured book never competes for
   attention with the two states that DO need it. */
.si-config-state.si-state-measured {
    border: 1px solid #1c3a26;
    border-left: 4px solid #238636;
    background: #12261a;
    color: #9aa0a6;
}

.si-config-state.si-state-measured strong {
    color: #3fb950;
}

.si-positions-table {
    margin-bottom: 0;
}

.si-position-row td {
    vertical-align: middle;
}

/* Divergence row (AB#357 criteria 2/3) — a position the BROKER holds that
   the book does not, or disagrees with. NEVER the same shape as a booked
   row: red left rule + tinted background, independent of the badge text,
   so it reads as "not a holding" at a glance or on a scroll-past, not only
   on close reading of the Status cell. Same red as the RECONCILIATION
   shell's divergence state. */
.si-position-row--divergence {
    background: rgba(248, 81, 73, 0.08);
    border-left: 3px solid #f85149;
}

.si-position-row--divergence td {
    color: #ffb4ab;
    vertical-align: middle;
}

.si-position-divergence-badge {
    display: inline-block;
    font-size: 0.62rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    padding: 1px 7px;
    border-radius: 8px;
    background: #f85149;
    color: #fff;
}

.si-position-holding-badge {
    display: inline-block;
    font-size: 0.62rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    padding: 1px 7px;
    border-radius: 8px;
    background: rgba(63, 185, 80, 0.15);
    color: #3fb950;
    border: 1px solid #238636;
}

/* Criterion 5 — the panel's own booked-holding count vs the page's
   open_position_count tile. Silent disagreement between two numbers on one
   page is the defect this item exists to fix; this class makes a STATED
   disagreement visible rather than letting it pass unremarked. */
.si-positions-consistency-mismatch {
    color: #f85149;
    font-weight: 600;
}

.si-legend-item-holding .si-legend-swatch {
    border: 1px solid #238636;
    background: rgba(63, 185, 80, 0.15);
}

.si-legend-item-divergence .si-legend-swatch {
    border: 1px solid #f85149;
    background: rgba(248, 81, 73, 0.15);
}

/* ---- Deployed-capital-over-time + Daily P&L chart shells (panels 3, 4) -- */

.si-chart-subtitle {
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #8b949e;
    margin: 14px 0 8px;
    padding-top: 12px;
    border-top: 1px solid #30363d;
}

.si-chart-block {
    position: relative;
}

/* Shared shell for the chart-level flag banner — the unavailable card AND
   the divergence caveat render into this, one at a time, never both.
   :empty collapse mirrors .si-recon-body:empty so an untouched shell (JS
   not yet wired, or state == measured) takes up no space. */
.si-chart-flag-body:empty {
    display: none;
}

/* Same fixed-height canvas-wrap discipline as the backtest surface's
   Daily P&L card (.bt-daily-pnl-canvas-wrap) — same chart type, same axis treatment, same
   units (AB#357 criterion 8: grammar reuse asserted concretely, not "looks
   similar"). */
.si-daily-pnl-canvas-wrap {
    position: relative;
    height: 280px;
}

/* The basket-only section (Per-ETF overlay + P&L by entry date) holds cards
   strategy_instance.js REMOVES when the strategy's backtest page does not carry
   them. A section with no element left in it takes up no space, so a removed
   pair never leaves an empty card behind. */
#si-parity-extra:not(:has(> *)) {
    display: none;
}

/* DIVERGENCE CAVEAT — the chart-scoped sibling of .si-stat-caveat. Applied
   (class-only, no data attribute required) to #si-deployed-capital-chart-
   block / #si-daily-pnl-chart-block when the underlying curve is real
   (mtm_equity_curve.state == "available") BUT this instance's own
   `reconciliation.state` is divergence/partial_unknown — i.e. the curve is
   book-derived and the book is KNOWN to disagree with the broker. This IS
   the live condition on both ETF instances today (2026-08-01: AB#13's
   writer has rows, every row reads equity==cash==capital_base with
   held_market_value==0, because the `trades` book is empty per AB#305) — a
   flat/derived line off that data is not a measurement and must never
   render as an ordinary series. */
.si-chart-block.si-chart-caveat {
    border: 1px solid #f85149;
    border-radius: 6px;
    padding: 10px 12px 12px;
    background-color: #1c1114;
}

.si-chart-flag-body .si-chart-caveat-banner {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    font-size: 0.8rem;
    color: #ffb4ab;
    background: rgba(248, 81, 73, 0.1);
    border: 1px solid rgba(248, 81, 73, 0.35);
    border-radius: 5px;
    padding: 8px 12px;
    margin-bottom: 10px;
}

.si-chart-caveat-badge {
    display: inline-block;
    flex: 0 0 auto;
    font-size: 0.66rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    padding: 2px 9px;
    border-radius: 10px;
    background: #f85149;
    color: #fff;
}

/* Third independent signal (beyond the banner text + corner marker): the
   canvas itself sits on a hatched red-tinted surface so even a genuinely
   flat/derived line is visibly "flagged", never clean data. Same hatch
   technique as .si-state-unavailable, red instead of amber. */
.si-chart-block.si-chart-caveat .si-chart-wrap,
.si-chart-block.si-chart-caveat .si-daily-pnl-canvas-wrap {
    background-color: #1c1114;
    background-image: repeating-linear-gradient(
        45deg,
        rgba(248, 81, 73, 0.06),
        rgba(248, 81, 73, 0.06) 6px,
        transparent 6px,
        transparent 12px
    );
    border-radius: 4px;
}

/* Corner flag marker — same third-independent-signal convention as
   .si-stat-caveat::before, sized up slightly for a whole chart card rather
   than a small stat tile. */
.si-chart-block.si-chart-caveat::before {
    content: "";
    position: absolute;
    top: -1px;
    right: -1px;
    width: 0;
    height: 0;
    border-style: solid;
    border-width: 0 18px 18px 0;
    border-color: transparent #f85149 transparent transparent;
    border-radius: 0 6px 0 0;
}

/* ---- Strategy vs Basket incomparability affordance (criterion 6a) ------
   D887 R3: the live basket is priced through market.get_bars, which always
   passes adjusted=True; a backtest basket is also ADJUSTED. Whether the two
   are comparable is an OPEN question pending an architect ruling, and
   because both share a basis a corporate action can shift an origin close,
   so re-running an old session may not reproduce the original figure
   exactly. AB#249 owns computing/asserting this caveat;
   this class + #si-basket-caveat-note (strategy_instance.html) are the
   AFFORDANCE this lane provides so the caveat can be attached to the
   figure itself, never a tooltip nobody opens. */
.si-basket-caveat {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.68rem;
    font-weight: 600;
    color: #f0b429;
    background: rgba(240, 180, 41, 0.12);
    border: 1px solid rgba(240, 180, 41, 0.4);
    border-radius: 4px;
    padding: 1px 7px;
    margin-left: 6px;
    white-space: nowrap;
}

/* ==========================================================================
   TICKERS CONSIDERED panel (operator request 2026-08-18) —
   strategy_instance.html, directly below the trades panel. Markup/style
   only; frontend-js-agent populates #si-ticker-decisions-body and
   #si-ticker-decisions-session-body. See strategy_instance.html's own
   comment above the section for the full element + row-shape contract.

   Deliberately reuses this page's OWN established vocabulary rather than
   inventing a new one:
     - panel state banner   -> .si-config-state + .si-state-measured /
                                .si-state-unavailable (Open Positions panel,
                                above, already defines the base + both
                                modifiers — only the table-specific bits are
                                new here)
     - outcome badges       -> same visual WEIGHT as .si-position-holding-
                                badge (green filled pill) and .bt-pf-badge-na
                                (muted dashed pill) elsewhere in this file —
                                no new colour introduced
     - row expand affordance -> same chevron-rotate convention as
                                .data-quality-expand-btn / .data-quality-
                                chevron; same open-row highlight weight as
                                backtests.js's own .bt-trade-row-open
   ========================================================================== */

.si-ticker-decisions-table {
    margin-bottom: 0;
}

.si-ticker-decision-toggle-col {
    width: 1.75rem;
}

/* Expand/collapse control — transparent button + rotating chevron glyph,
   same mechanics as .data-quality-expand-btn/.data-quality-chevron so this
   panel's affordance reads identically to the one the operator already
   knows from the data-quality banner. */
.si-ticker-decision-toggle-btn {
    background: transparent;
    border: none;
    cursor: pointer;
    padding: 2px 4px;
    color: #8b949e;
    font-size: 0.72rem;
    line-height: 1;
    border-radius: 3px;
}

.si-ticker-decision-toggle-btn:hover {
    background: rgba(255, 255, 255, 0.06);
    color: #c9d1d9;
}

.si-ticker-decision-chevron {
    display: inline-block;
    transition: transform 0.2s ease;
}

.si-ticker-decision-toggle-btn[aria-expanded="true"] .si-ticker-decision-chevron {
    transform: rotate(90deg);
}

/* AB#875-followup chart-through button (strategy_instance.js:1573) — sits in
   the SAME .si-td-symbol cell as the borderless icon-only toggle above, so it
   must read as visually SEPARATE from it at a glance (never a second chevron-
   style icon). Given a visible border to make it read as its own small
   button/pill — the toggle button above is deliberately border:none — with
   the SAME muted/hover contrast pair .si-ticker-decision-toggle-btn already
   uses (#8b949e at rest -> #c9d1d9 + rgba(255,255,255,0.06) wash on hover) so
   the two controls share a family without being mistakable for one another.
   Sits on the dark .chart-card/table background (#161b22 lineage), so this is
   light-text-on-dark, matching every other muted control in this file — never
   the UA default light button that produced the original grey-on-grey
   complaint (.si-etf-dr-nav-btn's docstring above). */
.si-ticker-decision-chart-btn {
    margin-left: 8px;
    background: transparent;
    border: 1px solid #30363d;
    border-radius: 4px;
    color: #8b949e;
    font-size: 0.68rem;
    line-height: 1;
    padding: 2px 8px;
    cursor: pointer;
    vertical-align: middle;
}

.si-ticker-decision-chart-btn:hover {
    background: rgba(255, 255, 255, 0.06);
    border-color: #58a6ff;
    color: #c9d1d9;
}

/* Summary row — clickable, whole-row hover + open-state highlight. Same
   weight as .bt-trade-row / .bt-trade-row-open (this page's own trades
   panel, immediately above), restated under this panel's si- namespace so
   a future change to the reused trades renderer's classes can never
   silently reshape this one. */
.si-ticker-decision-row {
    cursor: pointer;
}

.si-ticker-decision-row:hover {
    background: rgba(255, 255, 255, 0.03);
}

.si-ticker-decision-row.si-ticker-decision-row-open {
    background: rgba(88, 166, 255, 0.08);
}

.si-ticker-decision-row td {
    vertical-align: middle;
}

/* Detail row — full-width cell, chronological event list. Mirrors
   .bt-trade-detail-cell/.bt-trade-detail-grid's definition-grid layout. */
.si-ticker-decision-detail-cell {
    background: rgba(255, 255, 255, 0.02);
    padding: 0.75rem 1rem;
}

.si-ticker-decision-detail-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.si-ticker-decision-event-item {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 10px;
    font-size: 0.8rem;
    padding: 4px 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}

.si-ticker-decision-event-item:last-child {
    border-bottom: none;
}

.si-ticker-decision-event-time {
    color: #8b949e;
    font-size: 0.72rem;
    white-space: nowrap;
    min-width: 9rem;
}

.si-ticker-decision-event-decision {
    color: #c9d1d9;
    font-weight: 600;
    text-transform: uppercase;
    font-size: 0.72rem;
    letter-spacing: 0.03em;
}

.si-ticker-decision-event-reason {
    color: #c9d1d9;
    word-break: break-word;
}

/* Outcome badges — two states, visually distinct at a glance. "entered"
   reuses the SAME green-filled-pill weight as .si-position-holding-badge;
   "no_entry" reuses the SAME muted-dashed-pill weight as .bt-pf-badge-na
   (neutral, NOT an error — "considered, never opened" is an ordinary
   outcome). No new colour system. */
.si-ticker-outcome-badge {
    display: inline-block;
    font-size: 0.62rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    padding: 1px 7px;
    border-radius: 8px;
}

.si-ticker-outcome-badge-entered {
    background: rgba(63, 185, 80, 0.15);
    color: #3fb950;
    border: 1px solid #238636;
}

.si-ticker-outcome-badge-no-entry {
    background: transparent;
    color: #6e7681;
    border: 1px dashed rgba(110, 118, 129, 0.5);
}

.si-legend-item-entered .si-legend-swatch {
    border: 1px solid #238636;
    background: rgba(63, 185, 80, 0.15);
}

.si-legend-item-no-entry .si-legend-swatch {
    border: 1px dashed #6e7681;
    background: transparent;
}

/* Session-level events — criterion 1: visually SEPARATE from the ticker
   rows so a portfolio-level fact (no symbol) can never read as belonging
   to one ticker. Blue accent — the SAME hue already used by
   .si-config-state.si-state-no-data elsewhere on this page — deliberately
   distinct from both outcome-badge colours (green/muted) above. */
.si-ticker-decisions-session {
    margin-top: 4px;
    padding: 10px 14px;
    border: 1px solid #1c3350;
    border-left: 4px solid #58a6ff;
    border-radius: 5px;
    background: #0d1520;
}

.si-ticker-decisions-session-note {
    font-size: 0.76rem;
    color: #8b949e;
    margin: 0 0 8px;
}

.si-ticker-decision-session-event-item {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 10px;
    font-size: 0.8rem;
    padding: 4px 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}

.si-ticker-decision-session-event-item:last-child {
    border-bottom: none;
}

/* ORB symbol-only drill "Why the Bot Decided" content (strategy_instance.js
   _siOrbSymbolDrillWhyHtml, ~line 2096) — painted into #si-why-container,
   which is a .chart-card (#161b22 dark panel, see .chart-card near the top
   of this file), so all three rules below are light-text-on-dark, same
   direction as every other rule in this section.
   Outcome/Tradeable lines reuse the SAME muted-label / bright-value contrast
   .bt-why-kv-label (#8b949e) / .bt-why-kv-value (#e6edf3) already establish
   for this exact panel shape on the trade-drill side (frontend-js-agent's
   trade_drill.js), restated here under the si- namespace rather than
   depended on cross-file. Events-title reuses .bt-why-group-title's SAME
   small-caps blue-accent weight (#58a6ff) verbatim — no new palette. */
.si-td-symbol-drill-outcome,
.si-td-symbol-drill-tradeable {
    font-size: 0.85rem;
    color: #e6edf3;
    margin-bottom: 0.5rem;
}

.si-td-symbol-drill-outcome strong,
.si-td-symbol-drill-tradeable strong {
    color: #8b949e;
    font-weight: 600;
    margin-right: 0.35rem;
}

.si-td-symbol-drill-events-title {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #58a6ff;
    font-weight: 600;
    margin-top: 0.75rem;
    margin-bottom: 0.5rem;
}

/* ==========================================================================
   ETF DECISION RECORD panel (AB#789, child B of AB#730 / D1099) —
   strategy_instance.html, directly below "Tickers considered". Markup/style
   only; strategy_instance.js's renderEtfDecisionRecord family (already
   landed) populates #si-etf-decision-record-state and
   #si-etf-decision-record-body entirely, including the per-day
   <table class="si-etf-dr-symbols-table"> the second block below styles.

   INV-9 DISCIPLINE CARRIES INTO THIS CSS TOO: the renderer deliberately
   carries no hardcoded outcome / refusal-reason / exit-reason enum member
   list (spec §8 INV-9 — the vocabulary has moved 11 -> 14 -> 17 members in
   three days and will move again), so .si-etf-outcome-badge,
   .si-etf-refusal-reason-chip and .si-etf-exit-reason-chip below are styled
   ONE WAY EACH, generically, keyed on nothing but the class itself — never
   a per-value selector on data-outcome/data-refusal-reason/data-exit-reason.
   A per-value rule here would silently rebuild in CSS the exact lookup
   table INV-9 forbids in JS.

   Panel-state banner reuses the SAME .si-config-state +
   si-state-measured/si-state-unavailable base already defined above (Open
   Positions panel) — no new banner shell here, only the day-body layer.
   ========================================================================== */

/* Day navigator BUTTONS (operator-reported: "grey text on a grey
   background... even somebody that's not colourblind can't read them").
   etf_decision_record.js's renderNavBar emits .si-etf-dr-nav-btn (plus
   .si-etf-dr-nav-prev/.si-etf-dr-nav-next and a native `disabled` attribute
   at either end of the date range) but this file carried no rule for the
   class at all, so the buttons fell back to ambient/UA styling, which on
   this dark theme rendered as unreadable grey-on-grey. The panel commonly
   loads with only one day ("Day 1 of 1"), so BOTH buttons start disabled --
   that is the state the operator was actually looking at, so the disabled
   case is styled explicitly below rather than left to default again.
   Design language matches .bt-candle-res-btn (the existing small inline
   chart-control precedent) rather than inventing a new button style. */
.si-etf-dr-nav-btn {
    background: transparent;
    border: 1px solid #30363d;
    border-radius: 4px;
    color: #9aa0a6;
    font-size: 0.75rem;
    line-height: 1;
    padding: 4px 10px;
    cursor: pointer;
}

.si-etf-dr-nav-btn:hover:not(:disabled) {
    border-color: #58a6ff;
    color: #c9d1d9;
}

/* Disabled = unavailable, not invisible. Muted-gray #6c757d text (this
   file's standing "non-attention text" colour -- see e.g.
   .si-etf-dr-day-state.si-state-measured below) on the same transparent/
   dark-card background, well clear of grey-on-grey. Unavailability is
   conveyed by the dimmer border, the not-allowed cursor, and the loss of
   the hover accent above -- never by the text disappearing. opacity:1
   overrides any UA default dimming so nothing re-introduces the original
   defect. */
.si-etf-dr-nav-btn:disabled {
    background: transparent;
    border-color: #21262d;
    color: #6c757d;
    cursor: not-allowed;
    opacity: 1;
}

.si-etf-dr-body {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.si-etf-dr-day {
    border: 1px solid #30363d;
    border-radius: 5px;
    padding: 10px 12px;
    background: #0d1117;
}

/* "Did not run" (S3) — same amber weight as the panel's own
   .si-state-unavailable banner state, applied here to a day block instead
   of the whole-panel shell. */
.si-etf-dr-day-not-run {
    border-left: 4px solid #f0b429;
}

/* "Ran, nothing considered" (S2) — quiet green, same weight as
   .si-config-state.si-state-measured; deliberately the least visually loud
   of the three day states so a genuinely quiet-but-measured day never
   competes for attention with a populated or a did-not-run day. */
.si-etf-dr-day-quiet {
    border-left: 4px solid #238636;
}

/* Populated (S1) — neutral accent; the symbol table itself carries the
   visual weight, not the day border. */
.si-etf-dr-day-populated {
    border-left: 4px solid #58a6ff;
}

.si-etf-dr-day-date {
    font-size: 0.78rem;
    font-weight: 600;
    color: #c9d1d9;
    margin-bottom: 6px;
}

/* The day-state sentence combines si-state-text with si-state-measured /
   si-state-unavailable DIRECTLY on one element (not nested under a
   .si-config-state ancestor the way the panel banner is), so the existing
   descendant-selector rules for that pairing don't reach it — these two
   rules give the combined-class case its own colour, same hues as the
   .si-config-state modifiers above. */
.si-etf-dr-day-state.si-state-measured {
    color: #9aa0a6;
}

.si-etf-dr-day-state.si-state-unavailable {
    color: #f0b429;
}

.si-etf-dr-empty {
    padding: 8px 0;
}

.si-etf-dr-symbols-table {
    margin: 0;
    font-size: 0.8rem;
}

.si-etf-dr-symbols-table td {
    vertical-align: top;
    padding: 6px 8px;
}

.si-etf-dr-symbol {
    font-weight: 600;
    color: #c9d1d9;
    white-space: nowrap;
}

/* Outcome badge — ONE generic treatment for every outcome value (INV-9;
   see the block header above). Neutral slate, not green/red — this page
   already reserves green/red for booked P&L and holding state, and an
   ETF-day outcome is a classification, not a gain or loss. */
.si-etf-outcome-badge {
    display: inline-block;
    font-size: 0.62rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    padding: 1px 7px;
    border-radius: 8px;
    background: rgba(88, 166, 255, 0.12);
    color: #79c0ff;
    border: 1px solid rgba(88, 166, 255, 0.4);
}

.si-etf-refusal-reasons {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 4px;
}

.si-etf-refusal-reasons-empty {
    color: #8b949e;
    font-style: italic;
}

/* One chip per refusal reason, ALL rendered (INV-11 — never truncated).
   Generic per-chip treatment only (INV-9), same reasoning as the outcome
   badge above. */
.si-etf-refusal-reason-chip {
    display: inline-block;
    font-size: 0.68rem;
    padding: 1px 6px;
    border-radius: 4px;
    background: rgba(240, 180, 41, 0.12);
    color: #d29922;
    border: 1px solid rgba(240, 180, 41, 0.35);
    white-space: nowrap;
}

.si-etf-exit-reason-chip {
    display: inline-block;
    font-size: 0.68rem;
    padding: 1px 6px;
    border-radius: 4px;
    background: rgba(63, 185, 80, 0.12);
    color: #3fb950;
    border: 1px solid rgba(63, 185, 80, 0.35);
    white-space: nowrap;
}

/* not_eligible states B/C (spec §7/INV-13) — same amber "documented gap"
   weight as the day-level .si-state-unavailable text; C additionally gets
   its own note line, set apart in muted italic so the pending-classification
   sentence (INV-13 ⚠️ required content) reads as an annotation, not a claim
   about the row itself. */
.si-etf-not-eligible-state-b,
.si-etf-not-eligible-state-c {
    color: #d29922;
}

.si-etf-not-eligible-c-note {
    display: block;
    margin-top: 2px;
    font-style: italic;
    color: #8b949e;
}

/* ==========================================================================
   AB#812 -- ETF SESSION-LOG HEADER, ARM-LEVEL BLANK VOCABULARY, REPEATED-
   REASON COLLAPSE (spec Part II §12-19, INV-30..43). Markup/style only --
   frontend-js-agent's etf_decision_record.js (renderEtfSessionHeader /
   renderEtfArmDescriptions / renderEtfArmStatusBlock /
   renderEtfBlankVocabToken / renderEtfRefusalGroupRow, landed c5e6d917)
   already populates every class below; this file carried none of them
   until now -- unstyled = browser-default, the same defect the day-nav
   buttons shipped with once already (.si-etf-dr-nav-btn's docstring
   above).

   Background used for every contrast figure quoted in this block: #161b22
   (the card colour this section's shell below actually paints), not the
   page body or the #0d1117 day-block colour -- the header is its own card
   nested inside .si-etf-dr-day, so #0d1117's ratios do not apply to text
   painted on top of THIS shell.
   ========================================================================== */

/* ---- Header shell + state ------------------------------------------- */

.si-etf-session-header {
    background: #161b22;
    border: 1px solid #30363d;
    border-radius: 5px;
    padding: 10px 14px;
    margin-bottom: 10px;
    font-size: 0.8rem;
}

/* AVAILABLE is the base shell above, unmodified -- no extra rule needed. */

/* NO DATA -- "no session header recorded for this date". A quiet fact
   about an ABSENT header, not a failure to read one; muted left rule,
   same restrained weight .si-config-state.si-state-measured uses for "a
   real, quiet, measured absence" elsewhere on this page. Deliberately
   NOT the read-failed red below -- collapsing the two would report a
   missing-but-expected header the same as a page that could not be read
   at all. */
.si-etf-session-header-no-data {
    border-left: 4px solid #6c757d;
}

/* READ FAILED -- the page could not read the header at all ("Nothing is
   shown rather than a guess"). This IS a failure, so it gets this file's
   danger red, not the quiet grey above. */
.si-etf-session-header-read-failed {
    border-left: 4px solid #f85149;
    background: #1c1315;
}

/* Both states render their message as a bare `.si-state-text
   .si-state-measured` / `.si-state-text .si-state-unavailable` pair on
   ONE element (combined classes, not nested) -- the existing
   `.si-state-unavailable .si-state-text` descendant rule and the global
   per-box `.si-stat.si-state-unavailable` rule do not match a combined
   selector, exactly the gap `.si-etf-dr-day-state.si-state-measured` /
   `.si-state-unavailable` (above, in the panel block) already had to
   close for the day-level sentence. Same technique, scoped to this
   shell. */
.si-etf-session-header .si-state-text.si-state-measured {
    color: #9aa0a6;
}

.si-etf-session-header .si-state-text.si-state-unavailable {
    color: #f85149;
    font-weight: 500;
}

/* ---- Header summary row ---------------------------------------------- */

.si-etf-header-summary {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 4px;
    color: #c9d1d9;
    font-size: 0.8rem;
    line-height: 1.5;
}

.si-etf-header-field {
    display: inline-block;
    padding: 1px 8px 1px 7px;
    border-radius: 3px;
    border-left: 2px solid transparent;
}

/* Per-field accent colour -- a scan aid only (INV-30 governs the VALUE
   text inside each span, not this border), so each of the four fields is
   findable by colour without reading the label first. Reuses hues already
   assigned elsewhere on this page to the same kind of fact: blue for a
   classification (regime), green for "what's live", the money-amber this
   file uses nowhere else yet but which matches the existing amber "gap"
   family closely enough to read as "capital", muted grey for a plain
   count. */
.si-etf-header-field-regime {
    border-left-color: #58a6ff;
}

.si-etf-header-field-arms {
    border-left-color: #3fb950;
}

.si-etf-header-field-allocation {
    border-left-color: #d29922;
}

.si-etf-header-field-considered {
    border-left-color: #6c757d;
}

/* ---- Per-arm blocks ---------------------------------------------------- */

/* Two-column layout -- NOT a Bootstrap `.row` parent (this div wraps two
   plain <ul> elements the renderer owns, nothing Bootstrap-managed), so
   `display: grid` here does not fight the framework. Side-by-side rather
   than stacked is the deliberate density fix for the 2149px worst-case
   header: six static descriptions and up to six status lines no longer
   sit in one long vertical run. */
.si-etf-arm-block {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px 20px;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid #21262d;
}

@media (max-width: 900px) {
    .si-etf-arm-block {
        grid-template-columns: 1fr;
    }
}

/* Column captions -- pure CSS, no new markup: a label the operator reads
   once per panel, not once per arm, telling the two columns apart at a
   glance ("what it looks for" vs "what happened today"). */
.si-etf-arm-descriptions::before,
.si-etf-arm-status-block::before {
    display: block;
    font-size: 0.64rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #6c757d;
    margin-bottom: 3px;
}

.si-etf-arm-descriptions::before {
    content: "What each arm looks for";
}

.si-etf-arm-status-block::before {
    content: "What happened this session";
}

.si-etf-arm-descriptions,
.si-etf-arm-status-block {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.si-etf-arm-desc-item,
.si-etf-arm-status-item {
    font-size: 0.76rem;
    line-height: 1.35;
    padding: 3px 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.04);
    color: #c9d1d9;
}

.si-etf-arm-desc-item:last-child,
.si-etf-arm-status-item:last-child {
    border-bottom: none;
}

/* An arm live this session that this file's static description list does
   not know about (a future arm) -- flagged, never silently dropped or
   folded into the ordinary rows. Same amber "documented gap" family as
   NOT RECORDED below, since an undescribed live arm is exactly that kind
   of gap. */
.si-etf-arm-desc-unknown {
    border-left: 2px solid #f0b429;
    padding-left: 6px;
}

.si-etf-arm-status-none {
    color: #9aa0a6;
    font-style: italic;
    font-size: 0.78rem;
}

/* Live/not-live tags -- reuses the SAME entered/no-entry pill weight
   .si-ticker-outcome-badge-entered / -no-entry already establish on this
   page (green filled vs muted dashed), restated under this section's own
   namespace per that block's own no-cross-file-dependency convention. */
.si-etf-arm-live-tag {
    display: inline-block;
    font-size: 0.6rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 0px 6px;
    border-radius: 7px;
    margin-left: 4px;
    background: rgba(63, 185, 80, 0.15);
    color: #3fb950;
    border: 1px solid #238636;
}

.si-etf-arm-not-live-tag {
    background: transparent;
    color: #6e7681;
    border: 1px dashed rgba(110, 118, 129, 0.5);
}

/* ---- NOT-APPLICABLE arms + gate pseudo-arms (3745b551, AB#812 follow-up,
   landed mid-dispatch -- styled here rather than left for a third
   unstyled-class complaint on the same item) -------------------------- */

/* `.si-etf-arm-status-not-applicable-block` carries the base
   `.si-etf-arm-status-block` class too, so without an override it would
   inherit that block's "WHAT HAPPENED THIS SESSION" caption -- wrong
   claim for a list of arms that were never live. Same specificity as the
   base rule (one class each); this one wins on source order. */
.si-etf-arm-status-not-applicable-block::before {
    content: "Arms not live this session";
}

.si-etf-arm-status-not-applicable-item {
    color: #9aa0a6;
}

/* Gate pseudo-arms -- a violet accent this section has not used yet
   (already this file's own "config/version identity" hue, see
   .ps-l3-version / .ps-day-config-version), chosen deliberately DISTINCT
   from every arm-column colour above so a gate line cannot be mistaken
   for a 7th/8th entry arm at a glance, reinforcing the renderer's own
   "gate, not an arm" text. */
.si-etf-gate-block {
    grid-column: 1 / -1;
    margin-top: 6px;
    padding-top: 8px;
    border-top: 1px dashed #30363d;
}

.si-etf-gate-block-heading {
    font-size: 0.64rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #d2a8ff;
    margin-bottom: 4px;
}

.si-etf-gate-status-block {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.si-etf-gate-status-item {
    font-size: 0.76rem;
    line-height: 1.35;
    padding: 3px 0;
    color: #c9d1d9;
}

.si-etf-gate-tag {
    display: inline-block;
    font-size: 0.6rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 0px 6px;
    border-radius: 7px;
    margin: 0 4px;
    background: rgba(210, 168, 255, 0.14);
    color: #d2a8ff;
    border: 1px solid rgba(210, 168, 255, 0.4);
}

/* ---- THE BLANK VOCABULARY (spec §14) -----------------------------------
   Five states, five DISTINCT hues -- never distinguished by opacity alone
   (an earlier grey-on-grey pass was rejected on exactly that ground). Each
   below states, in the same breath, why that colour and why that weight;
   computed sRGB->WCAG contrast against this block's own #161b22 card
   background follows every rule (light text on a dark card throughout).

   NOT RECORDED is the ordinary case today (68/68 rows, D1128 §2) and
   NOTHING TO MEASURE is correct behaviour -- those two must read as
   opposite verdicts, not as two shades of the same warning, which is the
   one collapse this vocabulary exists to prevent (D1128, header ⭐⭐). */
.si-etf-blank-vocab {
    display: inline-block;
    font-size: 0.76rem;
    line-height: 1.3;
}

/* NOT RECORDED -- the arm ran and stored nothing this session. A GAP: the
   system is not working here. Amber filled chip, the SAME "documented
   gap" hue this file already uses for .si-etf-refusal-reason-chip and the
   not_eligible B/C states above -- real deficiency, moderate weight (not
   this file's alarm red), sized to be read on every row of a normal day
   without shouting on every row of a normal day.
   Contrast: #f0b429 on #161b22 = 9.28:1 (light-on-dark; WCAG AAA normal
   text, computed sRGB relative-luminance). */
.si-etf-blank-not-recorded {
    display: inline-block;
    padding: 1px 7px;
    border-radius: 4px;
    background: rgba(240, 180, 41, 0.14);
    border: 1px solid rgba(240, 180, 41, 0.4);
    color: #f0b429;
    font-weight: 600;
}

/* NOTHING TO MEASURE -- the gate is off; correct behaviour, not a
   deficiency. Deliberately the QUIETEST of the five: plain italic text,
   no chip, no border, this file's informational blue rather than either
   warning hue -- so a screen full of gates correctly switched off never
   reads as a screen full of gaps.
   Contrast: #58a6ff on #161b22 = 6.85:1 (light-on-dark; WCAG AA normal
   text). */
.si-etf-blank-nothing-to-measure {
    color: #58a6ff;
    font-style: italic;
    font-weight: 400;
}

/* NOT APPLICABLE -- the arm was not live this session; a neutral fact,
   not a verdict on the arm at all. Grey dashed chip, the SAME weight
   `.si-ticker-outcome-badge-no-entry` already gives "considered, never
   opened" elsewhere on this page -- an ordinary non-error outcome, reused
   rather than re-invented.
   Contrast: #8b949e on #161b22 = 5.63:1 (light-on-dark; WCAG AA normal
   text). */
.si-etf-blank-not-applicable {
    display: inline-block;
    padding: 1px 7px;
    border-radius: 8px;
    background: transparent;
    border: 1px dashed rgba(110, 118, 129, 0.5);
    color: #8b949e;
    font-weight: 400;
}

/* UNRESOLVABLE -- the measurement WAS written; its value is null. Fail-
   closed per spec: at least as serious as NOT RECORDED, so it takes this
   file's danger red rather than the amber above -- a solid-bordered
   filled chip, heavier than NOT RECORDED's, never mistakable for it at a
   glance.
   Contrast: #f85149 on #161b22 = 5.16:1 (light-on-dark; WCAG AA normal
   text). */
.si-etf-blank-unresolvable {
    display: inline-block;
    padding: 1px 7px;
    border-radius: 4px;
    background: rgba(248, 81, 73, 0.14);
    border: 1px solid rgba(248, 81, 73, 0.45);
    color: #f85149;
    font-weight: 600;
}

/* RECORDED -- the ordinary positive case: a measurement was written and
   has a value. Green filled chip, the SAME weight `.si-etf-exit-reason-
   chip` already uses -- the one state here that is neither a gap nor a
   deliberate absence, so it is the only one that gets this file's
   success green as a solid chip.
   Contrast: #3fb950 on #161b22 = 6.81:1 (light-on-dark; WCAG AA normal
   text). */
.si-etf-blank-recorded {
    display: inline-block;
    padding: 1px 7px;
    border-radius: 4px;
    background: rgba(63, 185, 80, 0.14);
    border: 1px solid rgba(63, 185, 80, 0.4);
    color: #3fb950;
    font-weight: 600;
}

/* ---- Repeated-refusal-reason collapse (spec §15.3, INV-42/43) ---------
   .bt-hidden is backtests.js's existing show/hide class (already styled
   at the top of this file, `display: none`); this block adds only the
   group row's own presentation, never a second hide/show mechanism.
   `.si-etf-dr-group-row` is also the class the condition-ladder's six
   fixed groups (Held / Recovery-eligible / Armed / Building / Established
   above / Out of reach, 32e4503b) render their own divider row with --
   this rule is shared by both callers, so the contrast fix below applies
   to whichever one is live on a given page. */

/* Operator, 2026-09-13: "there's not much visual separation between ARMED
   and BUILDING ... that lighter grey just needs to be a little bit
   lighter than it is." The row previously carried only a 2%-white
   overlay on the day block's own #0d1117 background -- effectively
   imperceptible. Matches `.si-etf-session-header`'s own treatment a few
   lines above (same nesting: a header shell one step lighter than the
   #0d1117 day block it sits inside) rather than inventing a new grey.
   Text size and weight are UNCHANGED -- contrast comes from the
   background step alone, plus a top rule using this file's standard
   `#30363d` divider colour (`.card-header` / `.table thead th` already
   use it for the same header/body separation elsewhere on this page). */
.si-etf-dr-group-row td {
    padding: 4px 8px;
    background: #161b22;
    border-top: 1px solid #30363d;
}

.si-etf-dr-group-toggle-btn {
    display: flex;
    align-items: baseline;
    gap: 6px;
    width: 100%;
    background: transparent;
    border: none;
    color: #9aa0a6;
    font-size: 0.78rem;
    text-align: left;
    padding: 4px 2px;
    cursor: pointer;
}

.si-etf-dr-group-toggle-btn:hover {
    color: #c9d1d9;
}

/* Chevron rotates open/closed off the SAME `aria-expanded` attribute the
   renderer already toggles -- same mechanics as `.si-ticker-decision-
   chevron` elsewhere in this file, restated under this section's own
   namespace. Text glyph only (the renderer emits &#9656;), no icon
   library. */
.si-etf-dr-group-chevron {
    display: inline-block;
    color: #6c757d;
    transition: transform 0.15s ease;
}

.si-etf-dr-group-toggle-btn[aria-expanded="true"] .si-etf-dr-group-chevron {
    transform: rotate(90deg);
    color: #58a6ff;
}

.si-etf-dr-group-summary {
    color: #c9d1d9;
}

.si-etf-dr-group-detail-row td {
    padding: 0;
    background: rgba(255, 255, 255, 0.015);
}

/* Nested table reuses `.si-etf-dr-symbols-table`'s own rules (margin,
   font-size, cell padding) verbatim -- this rule adds only the indent
   that marks it as a child of the collapsed row, not a second table
   style. */
.si-etf-dr-group-detail-table {
    margin-left: 14px;
    width: calc(100% - 14px);
}

/* ==========================================================================
   Equity curve RESOLUTION note (2026-09-29)

   #equityGrainNote is created by dashboard.js renderEquityChart() inside the
   Portfolio Equity Curve .chart-card, after the canvas. The card is a fixed
   height flex column and the canvas is flex:1 / min-height:0, so this block
   (flex:none) takes ITS height off the canvas and never grows the card. The
   canvas sits in .equity-chart-wrap (created by dashboard.js) so Chart.js
   measures exactly the space left rather than the whole card; it is
   kept compact (0.72rem, server note clamped to 2 lines with the full text in
   its title attribute, legend on one line) so the chart keeps most of the
   height. Severity comes from data-severity: "info" is muted grey, "warn"
   (hourly fetch failed) is amber on a tinted strip so the coarse graph is
   never mistaken for a working one.
   ========================================================================== */
.equity-chart-wrap {
    flex: 1 1 0;
    min-height: 0;
    min-width: 0;
    position: relative;
    overflow: hidden;
}
/* A failure / empty message injected by dashboard.js (_setChartEmptyState) lands
   INSIDE this wrapper once the equity chart has ever drawn (the canvas's parent
   is the wrapper, not the card). The wrapper is a plain block, so the message's
   own flex:1 / align-items:center had nothing to centre against and it sat at
   the top edge, unlike every other chart card. Filling the wrapper restores the
   centring. Only visible while the message is shown (it is display:none
   otherwise). */
.equity-chart-wrap > .chart-empty-state {
    position: absolute;
    inset: 0;
}
.equity-grain-note {
    flex: none;
    margin-top: 6px;
    font-size: 0.72rem;
    line-height: 1.3;
    color: #9aa0a6;
    min-width: 0;
}
.equity-grain-note[hidden] { display: none; }
.equity-grain-note .equity-grain-headline {
    font-weight: 600;
    color: #bcc4cc;
}
.equity-grain-note .equity-grain-axis {
    color: #9aa0a6;
    word-break: break-word;
}
.equity-grain-note .equity-grain-servernote {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    word-break: break-word;
}
.equity-grain-note .equity-grain-legend {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.equity-grain-note .equity-grain-seg + .equity-grain-seg::before {
    content: "\00b7";
    margin: 0 0.4em;
    color: #6c757d;
}
.equity-grain-note[data-severity="warn"] {
    color: #e3b341;
    background: rgba(227, 179, 65, 0.10);
    border-left: 3px solid #e3b341;
    border-radius: 4px;
    padding: 3px 8px;
}
.equity-grain-note[data-severity="warn"] .equity-grain-headline {
    color: #e3b341;
}

/* The resolution note costs the equity canvas ~50-70px of a 280px card, so
   when the note is visible the equity card AND its Drawdown neighbour (the
   next column of the same row) grow together to 360px: equal heights, the
   chart keeps >= ~170px. Progressive: a browser without :has() keeps both at
   280px, which is still equal and still correct, only tighter. */
.col-md-8:has(> .chart-card > #equityGrainNote:not([hidden])) > .chart-card-h280,
.col-md-8:has(> .chart-card > #equityGrainNote:not([hidden])) + .col-md-4 > .chart-card-h280 {
    height: 360px;
}

/* A symbol that hovers its "what it is" description (ticker_themes_hover.js).
   The dotted underline says there is something to hover for; the visible
   text is unchanged. */
.ticker-sym {
  cursor: help;
  text-decoration: underline dotted;
  text-underline-offset: 2px;
}

/* Backtests -- Decision log card (operator 2026-10-24): ORB, Sweep and
   Reclaim Reversal and Warrior intraday runs' own per-session log. */
.bt-session-log-nav:not(.bt-hidden) {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  margin-bottom: 0.5rem;
}
.bt-session-log-date {
  width: auto;
  max-width: 24rem;
}
.bt-sl-symbol {
  margin-bottom: 0.5rem;
}
.bt-sl-symbol-head {
  margin: 0.25rem 0;
}
.bt-sl-line {
  padding: 0.15rem 0;
}
details.bt-sl-symbol > summary {
  cursor: pointer;
}
.bt-sl-sym {
  display: inline-block;
  min-width: 4.5rem;
}
.bt-sl-traded {
  font-weight: 600;
}

/* ==========================================================================
   Unit-fault banner (AB#1018) -- templates/_unit_fault_banner.html
   ========================================================================== */

/*
 * One red alarm box at the top of every authenticated page's .page-wrap, one
 * <li> per faulted unit of this dashboard's own environment. Same rank as the
 * trading-halt banner (solid deep red, left rule, not sticky, non-dismissable,
 * nothing animated). The element is only rendered when there is a line to show,
 * so no display/hidden handling is needed here. Lines carry long unit names:
 * they wrap (word-break: break-word), never truncate, and the box has no fixed
 * width so it holds at phone width. The dashboard has a dark theme only (no
 * light theme exists in this stylesheet): white on #b71c1c is about 6.5:1.
 */
.unit-fault-banner {
    background: #b71c1c;
    color: #ffffff;
    border-radius: 6px;
    border-left: 8px solid #6b0000;
    padding: 12px 16px;
    margin-bottom: 18px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
}

.unit-fault-banner-lines {
    list-style: none;
    margin: 0;
    padding: 0;
}

.unit-fault-banner-line {
    font-size: 0.95rem;
    font-weight: 600;
    line-height: 1.35;
    color: #ffffff;
    word-break: break-word;
    overflow-wrap: anywhere;
}

.unit-fault-banner-line + .unit-fault-banner-line {
    margin-top: 6px;
    padding-top: 6px;
    border-top: 1px solid rgba(255, 255, 255, 0.28);
}

/* ==========================================================================
 * AB#1085 -- collapsible panels. A triangle beside the panel title hides or
 * shows the panel body.
 *
 * VISUAL LAYER ONLY. panel_collapse.js (loaded by backtests.html and
 * strategy_instance.html and by nothing else) adds the markers this block
 * keys on. EVERY selector below requires the class ct-panel-toggle or
 * ct-panel-glyph or a data-ct-panel* attribute so the block matches nothing
 * on a page that does not load the module. The ORB pages get the triangle
 * and nothing else.
 *
 * Markup the module produces:
 *   panel   [data-ct-panel="KEY"] [data-ct-panel-collapsed="true|false"]
 *     button.ct-panel-toggle [data-ct-panel-keep]   (first child)
 *     title   [data-ct-panel-title] [data-ct-panel-keep]
 *     title-row control [data-ct-panel-keep]   (Daily P&L Daily|Weekly|Monthly)
 *     every other direct child = the body
 *
 * TRIANGLE POSITION. A .chart-card is display:flex column so its children
 * are flex items and the triangle cannot share a line with the title by
 * display:inline. Instead the triangle is a fixed-height box exactly as tall
 * as the title first line with a NEGATIVE bottom margin of the same height
 * so the title is pulled up onto the same row and a left padding on the
 * title makes room. Nothing is moved and the card grows by no pixel. The
 * line height is a custom property per panel kind because the two kinds
 * differ: .chart-title is 0.85rem at line-height 1.5 and h2.si-card-title is
 * 0.75rem at the Bootstrap heading line-height of 1.2.
 * ========================================================================== */

/* The one rule that makes a collapse visible. */
[data-ct-panel-collapsed="true"] > :not([data-ct-panel-keep]) {
    display: none;
}

/* The same rule again with !important. Two real body children defeat the
 * plain rule above: #bt-run-config-container:has(.bt-orb-block) sets
 * display:grid at ID specificity (the ORB Run Config panel) and
 * #bt-trades-pager carries the Bootstrap d-flex utility which is
 * !important. Without this a closed Run Config would still show on ORB. */
[data-ct-panel-collapsed="true"] > :not([data-ct-panel-keep]) {
    display: none !important;
}

/* A collapsed panel is only its title row. Several cards carry a min-height
 * (the single-class rules near backtests-daily-pnl-card and friends) that
 * would leave an empty box under the title. */
[data-ct-panel][data-ct-panel-collapsed="true"] {
    min-height: 0;
}

/* A title that is the last visible thing in a collapsed panel drops its
 * bottom margin. A title followed by a kept control (the Daily P&L toggle)
 * keeps it so that control stays exactly where it sits today. */
[data-ct-panel-collapsed="true"] > [data-ct-panel-title]:not(:has(~ [data-ct-panel-keep])) {
    margin-bottom: 0;
}

/* Title line height per panel kind. */
.chart-card[data-ct-panel] {
    --ct-panel-title-line: 1.275rem;
}

section.si-card[data-ct-panel] {
    --ct-panel-title-line: 0.9rem;
}

/* Room for the triangle at the left of the title. The title keeps its own
 * font and colour and margin and text. */
[data-ct-panel] > [data-ct-panel-title] {
    padding-left: 1.5rem;
}

/* The triangle button: borderless and transparent on the dark card and
 * muted like the title. It is lifted above the title that overlaps it so
 * it receives the click. */
[data-ct-panel] > .ct-panel-toggle {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    flex: 0 0 auto;
    align-self: flex-start;
    box-sizing: border-box;
    width: 1.25rem;
    height: var(--ct-panel-title-line, 1.275rem);
    margin: 0 0 calc(-1 * var(--ct-panel-title-line, 1.275rem)) 0;
    padding: 0;
    border: 0;
    border-radius: 3px;
    background: transparent;
    color: #8b949e;
    font-size: 0.9rem;
    line-height: 1;
    cursor: pointer;
}

/* Larger hit area than the visible glyph without moving anything. */
[data-ct-panel] > .ct-panel-toggle::after {
    content: "";
    position: absolute;
    top: -0.25rem;
    right: -0.2rem;
    bottom: -0.25rem;
    left: -0.25rem;
}

[data-ct-panel] > .ct-panel-toggle:hover {
    color: #e6edf3;
}

/* Visible keyboard focus. */
[data-ct-panel] > .ct-panel-toggle:focus-visible {
    outline: 2px solid #58a6ff;
    outline-offset: 1px;
    color: #e6edf3;
}

.ct-panel-toggle > .ct-panel-glyph {
    display: block;
    font-size: 0.9rem;
    line-height: 1;
    pointer-events: none;
}

/* ==========================================================================
 * AB#1044 step 3 -- the ETF trade detail: inline header, the Buy / Partial sell
 * / Sell / Final sale timeline, and the fields below the chart. EVERY selector
 * here is keyed off an AB#1044 hook class (ctdrill-etf-header, ctdrill-hdr-,
 * ctdrill-timeline, ctdrill-tl-, ctdrill-other-) that only the ETF branch of
 * trade_drill.js emits, so the ORB card (.bt-summary-grid) and every other page
 * render exactly as before. Colour cues mirror the chart markers: Buy green,
 * Partial sell / Sell amber square, Final sale red star. CSP: classes only.
 * ========================================================================== */

/* Header: Symbol, Sector, Strategy, Strategy Instance, Direction, Status in a
 * wrapping inline flow (at most two lines at desktop width). */
.ctdrill-etf-header {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.3rem 0.6rem;
    font-size: 0.95rem;
    line-height: 1.4;
    min-width: 0;
}

.ctdrill-hdr-item {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0 0.35rem;
    min-width: 0;
}

.ctdrill-hdr-label {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #8b949e;
}

.ctdrill-hdr-value {
    color: #e6edf3;
    font-weight: 600;
    min-width: 0;
    overflow-wrap: anywhere;
}

.ctdrill-hdr-sep {
    color: #6e7681;
    user-select: none;
}

/* Timeline: one compact row per event, columns aligned down the page. The JS
 * joins each line's cells with ", " text nodes; font-size 0 on the line removes
 * those separators from view (the cells restore the size), so aligned columns
 * are not broken by a stray comma. */
.ctdrill-timeline {
    margin-top: 0.7rem;
    padding-top: 0.6rem;
    border-top: 1px solid #30363d;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.ctdrill-tl-line {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    /* 0.5rem, not 1rem: each hidden ", " text node is its own (zero-width) flex
     * item, so every visible gap is two of these. */
    column-gap: 0.5rem;
    row-gap: 0.1rem;
    font-size: 0;
    padding: 0.2rem 0 0.2rem 0.6rem;
    border-left: 3px solid #484f58;
}

.ctdrill-tl-cell {
    font-size: 0.88rem;
    line-height: 1.4;
    color: #e6edf3;
    min-width: 0;
    overflow-wrap: anywhere;
}

.ctdrill-tl-cell[data-cell="kind"] {
    flex: 0 0 7rem;
    font-weight: 700;
}

.ctdrill-tl-cell[data-cell="when"] {
    flex: 0 0 9.5rem;
    color: #9aa0a6;
    font-variant-numeric: tabular-nums;
}

.ctdrill-tl-cell[data-cell="qty_price"] {
    flex: 0 0 11rem;
    font-variant-numeric: tabular-nums;
}

.ctdrill-tl-cell[data-cell="reason"] {
    flex: 0 1 15rem;
}

.ctdrill-tl-cell[data-cell="tier"] {
    flex: 0 1 11rem;
    color: #9aa0a6;
}

.ctdrill-tl-cell[data-cell="open_after"] {
    flex: 0 1 auto;
    color: #9aa0a6;
}

/* Colour cue per event kind (matches the chart markers). */
.ctdrill-tl-line[data-kind="buy"] { border-left-color: #3fb950; }
.ctdrill-tl-line[data-kind="buy"] .ctdrill-tl-cell[data-cell="kind"] { color: #3fb950; }

.ctdrill-tl-line[data-kind="partial_sell"],
.ctdrill-tl-line[data-kind="sell"] { border-left-color: #f0b429; }
.ctdrill-tl-line[data-kind="partial_sell"] .ctdrill-tl-cell[data-cell="kind"],
.ctdrill-tl-line[data-kind="sell"] .ctdrill-tl-cell[data-cell="kind"] { color: #f0b429; }

.ctdrill-tl-line[data-kind="final_sale"] { border-left-color: #f85149; }
.ctdrill-tl-line[data-kind="final_sale"] .ctdrill-tl-cell[data-cell="kind"] { color: #f85149; }

/* Fields below the chart: the card has no title; keep the grid compact. */
.ctdrill-other-card {
    padding: 0.9rem 1.25rem;
}

.ctdrill-other-grid {
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 0.6rem 1.25rem;
}

/* Phone width: the timeline cells stop reserving columns and wrap to content;
 * the header and grid never force a horizontal scroll. */
@media (max-width: 576px) {
    .ctdrill-etf-header {
        font-size: 0.9rem;
    }

    .ctdrill-tl-cell[data-cell="kind"],
    .ctdrill-tl-cell[data-cell="when"],
    .ctdrill-tl-cell[data-cell="qty_price"],
    .ctdrill-tl-cell[data-cell="reason"],
    .ctdrill-tl-cell[data-cell="tier"] {
        flex: 0 1 auto;
    }

    .ctdrill-tl-cell[data-cell="kind"] {
        flex-basis: 100%;
    }

    .ctdrill-other-card {
        padding: 0.75rem 1rem;
    }

    .ctdrill-other-grid {
        grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
        gap: 0.5rem 0.9rem;
    }
}

/* ==========================================================================
 * AB#1075 step 2c -- the Perfect Storm trade drill's four timeframe chart
 * panels (spec v2/docs/ab1075-perfect-storm-timeframe-panels-spec.md, 5 and
 * INV-23).
 *
 * VISUAL LAYER ONLY. perfect_storm_tf_panels.js creates the container
 * (#bt-ps-tf-panels.ps-tf-panels) right after the Marked-up Price Series card
 * and only while a Perfect Storm trade is shown. EVERY selector below carries
 * one of the class tokens ps-tf-panels, ps-tf-panel or ps-tf-replaced, so the
 * block matches nothing on a page or a drill that does not have them (the ORB
 * and ETF drills never do: INV-20).
 *
 * Each panel is a .chart-card.ps-tf-panel whose direct children are
 * .chart-title, .ps-tf-settings, .ps-tf-notes, .ps-tf-chart and (G2, always
 * last) .ps-tf-marks. AB#1085's collapse rule hides everything but the title
 * row on a closed panel, so the settings line, the notes, the chart and the
 * mark list need no collapse rule here.
 *
 * CHART HEIGHT (gate ruling G2, amended by AB#1103). Each panel holds TWO
 * charts stacked inside .ps-tf-chart: a price chart in .ps-tf-chart-price above
 * a stochastic chart in .ps-tf-chart-stoch (spec
 * v2/docs/ab1103-perfect-storm-stochastic-own-chart-spec.md, 5). The charts use
 * autoSize, which follows their host, so each host needs a fixed non-zero
 * height or its chart is zero tall. .ps-tf-chart stays 520px (440px on a
 * phone) and the two hosts split it 0.70 / 0.30 and sum to it: 364px + 156px
 * on desktop, 308px + 132px on a phone. The width is the full card width,
 * except for the ROW CASE (AB#1091): the Perfect Storm drill puts timeframes 3
 * and 2 side by side in one div.ps-tf-row, so each of those two panels is (row
 * width - 20px) / 2 wide; the row is as tall as its taller panel and collapsing
 * one panel leaves the other at half width with nothing reflowing. At phone
 * width the row stacks (tf-3 above tf-2) and each panel is full width again.
 *
 * MARK LIST (gate ruling G2 (g)). .ps-tf-marks holds one p.ps-tf-mark per
 * marker placed on the chart. Each carries a leading EMPTY swatch span in its
 * marker's colour (from the line's data-ps-mark) and shape (from the swatch's
 * data-ps-shape), then the literal in .ps-tf-mark-text. The colours match the
 * chart markers: decisions #58a6ff, entry fill #3fb950, exit fill #f0b429,
 * scanner pass #bc8cff.
 * ========================================================================== */

/* The Marked-up Price Series card is replaced by the panels. */
.backtests-price-chart-card.ps-tf-replaced {
    display: none;
}

/* The container is a child of .backtests-detail-wrap (a column with a 20px
 * gap), so it only has to space its own children the same way. */
.ps-tf-panels {
    display: flex;
    flex-direction: column;
    gap: 20px;
    min-width: 0;
}

/* AB#1091: one row holding two panels side by side (timeframes 3 and 2). Only
 * perfect_storm_tf_panels.js creates .ps-tf-row, always inside .ps-tf-panels;
 * every selector keeps the .ps-tf-panels token (INV-23). flex-start so a
 * collapsed panel is not stretched to its neighbour's height. */
.ps-tf-panels .ps-tf-row {
    display: flex;
    flex-direction: row;
    gap: 20px;
    align-items: flex-start;
    min-width: 0;
}

.ps-tf-panels .ps-tf-row > .ps-tf-panel {
    flex: 1 1 0;
    min-width: 0;
}

/* A container-level line: "Position open at the run end - no exit." or the
 * route's own note when no panel can be drawn. */
.ps-tf-panels .ps-tf-panels-note {
    margin: 0;
    color: #8b949e;
    font-size: 0.85rem;
    line-height: 1.4;
    overflow-wrap: anywhere;
}

.ps-tf-panel {
    min-width: 0;
}

/* The timeframe's settings line: small and muted, close under the title. */
.ps-tf-panel .ps-tf-settings {
    margin: -0.25rem 0 0.5rem;
    color: #8b949e;
    font-size: 0.78rem;
    line-height: 1.3;
    overflow-wrap: anywhere;
}

/* The per-panel notes (a split warning, a mark whose bar is not drawn, a
 * chart that is not available). A panel with none shows no gap. */
.ps-tf-panel .ps-tf-notes {
    margin: 0 0 0.5rem;
    color: #8b949e;
    font-size: 0.8rem;
    line-height: 1.4;
    overflow-wrap: anywhere;
}

.ps-tf-panel .ps-tf-notes:empty {
    display: none;
}

.ps-tf-panel .ps-tf-note {
    margin: 0 0 0.3rem;
    color: #8b949e;
    font-size: 0.8rem;
    line-height: 1.4;
    overflow-wrap: anywhere;
}

.ps-tf-panel .ps-tf-note:last-child {
    margin-bottom: 0;
}

/* The chart box. Full card width, fixed height, positioned so the library can
 * lay its canvases out inside it. A panel with no chart carries bt-hidden on
 * this element. */
.ps-tf-panel .ps-tf-chart {
    position: relative;
    flex: 0 0 auto;
    box-sizing: border-box;
    width: 100%;
    min-width: 0;
    max-width: 100%;
    height: 520px;
    overflow: hidden;
}

/* AB#1103: the two chart hosts inside the chart box, price above stochastic.
 * Their heights sum to the chart box's (364 + 156 = 520). */
.ps-tf-panel .ps-tf-chart-price,
.ps-tf-panel .ps-tf-chart-stoch {
    position: relative;
    width: 100%;
    min-width: 0;
    overflow: hidden;
}

.ps-tf-panel .ps-tf-chart-price {
    height: 364px;
}

.ps-tf-panel .ps-tf-chart-stoch {
    height: 156px;
}

/* G2: the mark list, the panel's last child. Empty until a draw fills it, and
 * emptied again when a draw fails. */
.ps-tf-panel .ps-tf-marks {
    margin: 0.5rem 0 0;
    min-width: 0;
}

.ps-tf-panel .ps-tf-marks:empty {
    display: none;
}

/* One line per marker: swatch first, then the literal, which wraps. */
.ps-tf-panel .ps-tf-mark {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    margin: 0 0 0.3rem;
    color: #8b949e;
    font-size: 0.8rem;
    line-height: 1.4;
    min-width: 0;
    --ps-mark-colour: #8b949e;
}

.ps-tf-panel .ps-tf-mark:last-child {
    margin-bottom: 0;
}

.ps-tf-panel .ps-tf-mark[data-ps-mark="entry-decision"],
.ps-tf-panel .ps-tf-mark[data-ps-mark="exit-decision"] {
    --ps-mark-colour: #58a6ff;
}

.ps-tf-panel .ps-tf-mark[data-ps-mark="entry-fill"] {
    --ps-mark-colour: #3fb950;
}

.ps-tf-panel .ps-tf-mark[data-ps-mark="exit-fill"] {
    --ps-mark-colour: #f0b429;
}

.ps-tf-panel .ps-tf-mark[data-ps-mark="scanner-pass"] {
    --ps-mark-colour: #bc8cff;
}

/* The swatch is an empty span (its text stays empty), the size of a text
 * glyph. The default shape is a circle; the others are drawn with
 * border-radius or clip-path only. */
.ps-tf-panel .ps-tf-mark-swatch {
    display: inline-block;
    flex: 0 0 auto;
    box-sizing: border-box;
    width: 0.7rem;
    height: 0.7rem;
    background: var(--ps-mark-colour);
    border-radius: 50%;
}

.ps-tf-panel .ps-tf-mark-swatch[data-ps-shape="square"] {
    border-radius: 0;
}

.ps-tf-panel .ps-tf-mark-swatch[data-ps-shape="arrowUp"] {
    border-radius: 0;
    clip-path: polygon(50% 0, 100% 100%, 0 100%);
}

.ps-tf-panel .ps-tf-mark-swatch[data-ps-shape="arrowDown"] {
    border-radius: 0;
    clip-path: polygon(0 0, 100% 0, 50% 100%);
}

/* The literal wraps; the full decision strings are long. */
.ps-tf-panel .ps-tf-mark-text {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: anywhere;
}

/* Phone width: a shorter box, no horizontal scroll from a long note. */
@media (max-width: 576px) {
    .ps-tf-panel .ps-tf-chart {
        height: 440px;
    }

    /* AB#1103: 308 + 132 = 440. */
    .ps-tf-panel .ps-tf-chart-price {
        height: 308px;
    }

    .ps-tf-panel .ps-tf-chart-stoch {
        height: 132px;
    }

    .ps-tf-panels {
        gap: 16px;
    }

    .ps-tf-panels .ps-tf-row {
        flex-direction: column;
        align-items: stretch;
        gap: 16px;
    }

    .ps-tf-panels .ps-tf-row > .ps-tf-panel {
        flex: 0 0 auto;
    }
}
