/* style.css — OSRS-INVEST stylesheet
   Token-driven: every colour flows from the :root custom-property ramp
   so a future theme switcher can override the palette in one place.
   ───────────────────────────────────────────────────────────────────── */


/* ═══════════════════════════════════════════════════════════════════════
   §1  RESET + BASE STYLES
   ═══════════════════════════════════════════════════════════════════════ */

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

/* The browser's user-agent stylesheet hides [hidden] elements with
   `display: none`, but at the same specificity as any author rule that
   sets `display:` on the same element. Because author styles load AFTER
   UA styles in the cascade, an author rule like `.top-nav-link {
   display: inline-block }` wins and the [hidden] attribute silently
   stops working — the element stays visible despite the attribute. This
   bit us on the admin nav link: `el.hidden = true` was setting the
   attribute correctly but the link kept rendering for non-admins.
   Promoting `[hidden]` to !important here makes the attribute
   authoritative across the whole app. Anywhere else in the codebase can
   keep setting / unsetting the attribute and trust it to actually hide
   the element. */
[hidden] {
  display: none !important;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

:root {
  /* ── Theme architecture ── */
  /* The default palette below is the canonical theme. Future colour
     schemes can override these tokens to retheme the entire app. */

  /* Background tonal ramp — deep navy graphite */
  --bg:         oklch(0.16 0.022 250);
  --surface-1:  oklch(0.20 0.024 250);
  --surface-2:  oklch(0.24 0.026 250);
  --surface-3:  oklch(0.28 0.028 250);

  /* Borders / rules */
  --rule:        oklch(0.32 0.026 250 / 0.85);
  --rule-soft:   oklch(0.32 0.026 250 / 0.4);
  /* v0.9.7 — non-text UI components (form input borders, button outlines)
     require WCAG 2.1 SC 1.4.11 contrast of 3:1. The decorative --rule above
     gives 1.43-1.53:1 against bg/surface-1 (decorative-only is exempt). The
     --rule-strong stop at L=0.50 gives 3.02 vs surface-1 / 3.24 vs bg —
     passes the SC 1.4.11 floor. Use --rule-strong on inputs, selects,
     textareas, and ghost-button borders; keep --rule for decorative
     dividers between sections. */
  --rule-strong: oklch(0.50 0.030 250 / 0.95);

  /* Text ramp */
  --text:       oklch(0.94 0.005 250);
  --text-muted: oklch(0.66 0.008 250);
  --text-faint: oklch(0.50 0.008 250);

  /* Primary action accent — indigo / steel-blue */
  --accent:        oklch(0.62 0.16 255);
  --accent-soft:   oklch(0.62 0.16 255 / 0.18);
  --accent-deep:   oklch(0.46 0.18 255);
  /* v0.9.7 — primary buttons need a darker indigo than --accent so that
     the white-on-indigo body text passes WCAG 2.1 SC 1.4.3 (4.5:1).
     --accent at L=0.62 gives 3.08:1 (FAIL); --accent-deep at L=0.46
     gives 6.01:1 (PASS AA). Primary button DEFAULT background uses
     --accent-deep; HOVER goes to --accent-pressed below (L=0.40, 7.76:1).
     The --accent token stays for non-button surfaces (focus rings,
     swatches, the GE-input pair tint) where the contrast requirement
     doesn't apply or already passes. */
  --accent-pressed: oklch(0.40 0.16 255);

  /* Amber — "opportunity / high-side" highlight */
  --amber:        oklch(0.82 0.13 75);
  --amber-soft:   oklch(0.82 0.13 75 / 0.18);

  /* P/L conventional green/red */
  --green:      oklch(0.74 0.16 156);
  --green-soft: oklch(0.74 0.16 156 / 0.16);
  --red:        oklch(0.66 0.20 25);
  --red-soft:   oklch(0.66 0.20 25 / 0.14);

  /* Spacing scale — 4px base */
  --s-1: 4px; --s-2: 8px; --s-3: 12px; --s-4: 16px;
  --s-5: 24px; --s-6: 32px; --s-7: 48px;

  /* Type scale — fixed rem, 1.2 ratio */
  --t-micro: 11px; --t-small: 12px; --t-body: 14px;
  --t-h3: 18px; --t-h2: 24px; --t-h1: 30px;

  /* Radius scale */
  --r-1: 4px; --r-2: 8px; --r-3: 12px;

  /* Motion */
  --easeOut: cubic-bezier(0.16, 1, 0.3, 1);

  /* Monospace stack */
  --mono: ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Mono", "Roboto Mono", Consolas, monospace;

  /* ── Legacy aliases — consumed by existing JS via inline styles ──
     JS sets conic-gradient(var(--accent) ..., var(--poll-track) ...) directly.
     Chart JS sets stroke: var(--chart-high) etc. These must exist. */
  --border:              var(--rule);
  --surface:             var(--surface-1);
  --radius:              var(--r-2);
  --poll-track:          var(--surface-3);
  --price-up:            var(--green);
  --price-down:          var(--red);
  --chart-high:          var(--amber);
  --chart-low:           var(--accent);
  --chart-trade-start:   var(--text-muted);
  --chart-trade-filled:  var(--amber);
  --chart-trade-sold:    oklch(0.74 0.16 156);
  --demote:              var(--red);
  --muted:               var(--text-muted);
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", system-ui, sans-serif;
  font-size: var(--t-body);
  line-height: 1.45;
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
}

button, input, select, textarea {
  font: inherit;
}

a {
  color: var(--text);
  text-decoration: none;
}

.num, .mono {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}


/* ═══════════════════════════════════════════════════════════════════════
   §2  HEADER CHROME
   ═══════════════════════════════════════════════════════════════════════ */

/* Header layout: flex, left-clustered. The brand block (wordmark +
   version) anchors the left edge; the top-nav and the auth button sit
   immediately to its right. Empty space falls on the right. The version
   label sits directly under the wordmark in .brand-block's vertical
   stack. */
header {
  height: 60px;
  display: flex;
  align-items: center;
  gap: var(--s-5);
  padding: 0 var(--s-5);
  border-bottom: 1px solid var(--rule);
  background: var(--bg);
}

.brand-block {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  /* Tight stack so the wordmark + version read as one identity unit. */
  line-height: 1.05;
}

/* v0.9.7 — Brand wordmark is now a non-H1 element so each page has
   exactly one H1 (the page-head H1 in <main> is the page's actual topic).
   WCAG 2.1 SC 1.3.1 (Info and Relationships) and SC 2.4.6 (Headings and
   Labels) are best served by a single H1 per page; HTML5 technically
   permits multiple H1s, but most assistive tech and accessibility audits
   expect one.

   The selectors target both the <a class="brand-wordmark"> on
   authenticated views (links the wordmark to the home route, which is
   useful on its own) and the legacy <h1> on any view that hasn't been
   swept yet. Visual treatment is identical. */
.brand-block .brand-wordmark,
.brand-block h1 {
  margin: 0;
  font-size: var(--t-h3);
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--text);
  line-height: 1.05;
  text-decoration: none;
}

/* Legacy fallback — pages still shipping the unwrapped <h1>+<span#version>
   pair pre-round-3 markup get the old typography until their markup is
   swept. The flex parent will display them in source order, so the
   header still renders correctly; only the vertical stacking under the
   wordmark is missing. The four standard `<header>` views (profile,
   market, admin, admin-user) ship the wrapped `.brand-block` markup as
   of v0.9.3 round 3. v0.9.7 — also matches the new <a class="brand-wordmark">
   for any page hosting it directly in <header>. */
header > h1,
header > .brand-wordmark {
  margin: 0;
  font-size: var(--t-h3);
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--text);
  text-decoration: none;
}

#version,
.brand-block #version {
  font-size: var(--t-micro);
  color: var(--text-muted);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

/* ── Top navigation ──────────────────────────────────────────────── */

.top-nav {
  display: flex;
  gap: 2px;
  font-size: var(--t-body);
}

.top-nav-link {
  display: inline-block;
  padding: 8px 14px;
  border-radius: var(--r-1);
  color: var(--text-muted);
  font-weight: 500;
  text-decoration: none;
  transition: color 0.15s var(--easeOut), background 0.15s var(--easeOut);
}

.top-nav-link:hover {
  color: var(--text);
}

.top-nav-link.active {
  color: var(--text);
  background: var(--surface-2);
}

/* ── Sign Out / Sign In button ───────────────────────────────────── */

#logout-btn {
  background: transparent;
  color: var(--text-muted);
  border: 1px solid var(--rule);
  border-radius: var(--r-1);
  padding: 4px 12px;
  font-size: var(--t-small);
  cursor: pointer;
  transition: color 0.15s var(--easeOut), border-color 0.15s var(--easeOut);
}

#logout-btn:hover {
  color: var(--text);
  border-color: var(--text-faint);
}

#logout-btn.auth-btn-signin {
  /* v0.9.7 — primary-button background uses --accent-deep so white text
     passes WCAG AA (6.01:1). See :root token block for the full reasoning. */
  background: var(--accent-deep);
  color: var(--text);
  border-color: var(--accent-deep);
}

#logout-btn.auth-btn-signin:hover {
  background: var(--accent-pressed);
  border-color: var(--accent-pressed);
}


/* ═══════════════════════════════════════════════════════════════════════
   §3  MAIN LAYOUT
   ═══════════════════════════════════════════════════════════════════════ */

main {
  display: grid;
  grid-template-columns: 1fr 260px;
  grid-template-rows: auto auto;
  gap: var(--s-4);
  padding: 20px var(--s-5);
  max-width: 1500px;
  margin: 0 auto;
}

#scan-panel {
  background: var(--surface-1);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  padding: var(--s-4);
  grid-row: 1 / 3;
}

#slots {
  background: var(--surface-1);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  padding: var(--s-4);
}

#slots h2, #history h2 {
  font-size: var(--t-small);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  margin-bottom: var(--s-3);
}

#history {
  background: var(--surface-1);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  padding: var(--s-4);
}

.empty-note {
  color: var(--text-muted);
  font-style: italic;
  font-size: var(--t-small);
}


/* ═══════════════════════════════════════════════════════════════════════
   §4  CORE BUTTONS
   ═══════════════════════════════════════════════════════════════════════ */

.action-btn {
  margin-top: var(--s-1);
  background: var(--accent-soft);
  color: oklch(0.82 0.12 255);
  border: 1px solid var(--accent);
  border-radius: var(--r-1);
  padding: 5px 14px;
  font-size: var(--t-small);
  font-weight: 600;
  cursor: pointer;
  align-self: flex-start;
  transition: background 0.15s var(--easeOut), color 0.15s var(--easeOut);
}

.action-btn:hover {
  /* v0.9.7 — was background:var(--accent) which gives white-on-indigo at
     3.08:1 (FAIL WCAG AA). --accent-deep raises this to 6.01:1. */
  background: var(--accent-deep);
  color: var(--text);
}

.action-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.confirm-btn,
.cancel-btn {
  flex: 1;
  padding: 5px 10px;
  font-size: 0.76rem;
  font-weight: 700;
  border-radius: var(--r-1);
  cursor: pointer;
  border: none;
  transition: opacity 0.15s var(--easeOut);
}

.confirm-btn {
  /* v0.9.7 — primary-button background uses --accent-deep for WCAG AA. */
  background: var(--accent-deep);
  color: var(--text);
  border: 1px solid var(--accent-deep);
}

.cancel-btn {
  background: var(--surface-1);
  color: var(--text-muted);
  border: 1px solid var(--rule);
}

.confirm-btn:disabled,
.cancel-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.confirm-btn:not(:disabled):hover { opacity: 0.85; }
.cancel-btn:not(:disabled):hover  { opacity: 0.75; }


/* ═══════════════════════════════════════════════════════════════════════
   §5  CORE INPUTS
   ═══════════════════════════════════════════════════════════════════════ */

.slot-input,
.profile-input,
.scan-filter-input,
.readiness-search,
.pt-edit-input,
.pt-edit-textarea {
  background: var(--bg);
  color: var(--text);
  /* v0.9.7 — was --rule (1.43:1 vs surface-1, FAIL WCAG 2.1 SC 1.4.11
     non-text-contrast 3:1 floor for form inputs); --rule-strong gives
     3.02:1 vs surface-1 / 3.24:1 vs bg. */
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-1);
  padding: 6px 10px;
  font: inherit;
}

.slot-input:focus,
.profile-input:focus,
.scan-filter-input:focus,
.readiness-search:focus,
.pt-edit-input:focus,
.pt-edit-textarea:focus {
  border-color: var(--accent);
  outline: none;
}

.slot-input.input-error {
  border-color: var(--red);
}

/* Slot-input size overrides (preserved from v0.8) */
.slot-input {
  font-size: 0.82rem;
  padding: 5px 8px;
  width: 100%;
  transition: border-color 0.15s var(--easeOut);
}

/* Remove browser number input spinners */
.slot-input::-webkit-outer-spin-button,
.slot-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.slot-input[type=number]               { -moz-appearance: textfield; }

/* Profile input size override */
.profile-input {
  font-size: 0.9rem;
}

/* Filter input size override */
.scan-filter-input {
  font-size: 0.82rem;
  padding: 4px 7px;
  min-width: 92px;
}

/* Readiness search size override */
.readiness-search {
  flex: 1 1 220px;
  min-width: 0;
  font-size: 0.9rem;
  padding: 7px 10px;
}

/* Edit inputs */
.pt-edit-input,
.pt-edit-textarea {
  font-size: 0.85rem;
  text-transform: none;
  letter-spacing: 0;
}

.pt-edit-textarea {
  resize: vertical;
  min-height: 44px;
}


/* ═══════════════════════════════════════════════════════════════════════
   §6  TOGGLE (SHARED)
   ═══════════════════════════════════════════════════════════════════════ */

.toggle-track {
  position: relative;
  display: inline-block;
  width: 34px;
  height: 18px;
  background: var(--surface-3);
  border-radius: 9px;
  transition: background 0.2s var(--easeOut);
  flex-shrink: 0;
}

.toggle-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--text-muted);
  transition: transform 0.2s var(--easeOut), background 0.2s var(--easeOut);
}

.toggle-text {
  font-size: 0.75rem;
  color: var(--text-muted);
  white-space: nowrap;
  transition: color 0.2s var(--easeOut);
}

/* Auto-refresh toggle */
.auto-refresh-toggle {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  cursor: pointer;
  user-select: none;
}

.auto-refresh-toggle input[type="checkbox"] {
  display: none;
}

.auto-refresh-toggle input:checked + .toggle-track {
  background: var(--green-soft);
}

.auto-refresh-toggle input:checked + .toggle-track .toggle-thumb {
  transform: translateX(16px);
  background: var(--green);
}

.auto-refresh-toggle input:checked ~ .toggle-text {
  color: var(--green);
  font-weight: 700;
}


/* ═══════════════════════════════════════════════════════════════════════
   §7  BADGES / CHIPS (SHARED)
   ═══════════════════════════════════════════════════════════════════════ */

/* Shared pill geometry */
.scan-item-badge,
.slot-badge,
.badge-test,
.badge-edited,
.admin-role-badge,
.cfg-source-badge {
  display: inline-block;
  font-size: var(--t-micro);
  font-weight: 600;
  letter-spacing: 0.05em;
  padding: 2px 7px;
  border-radius: var(--r-1);
  text-transform: uppercase;
  border: 1px solid transparent;
}

/* Tier badges */
.badge-tier-very-high { background: var(--accent-soft); color: oklch(0.72 0.12 255); }
.badge-tier-high      { background: oklch(0.22 0.04 180 / 0.6); color: oklch(0.74 0.14 180); }
.badge-tier-medium    { background: var(--amber-soft); color: var(--amber); }
.badge-tier-mid       { background: var(--amber-soft); color: var(--amber); }
.badge-tier-low       { background: var(--red-soft); color: var(--red); }

/* Spike badges */
.badge-spike-normal         { background: var(--green-soft); color: var(--green); }
.badge-spike-elevated       { background: var(--amber-soft); color: var(--amber); }
.badge-spike-spike-warning  { background: var(--red-soft); color: var(--red); }

/* Status badges
   v0.9.8 — listed-status badge swapped from amber to indigo so the
   buy-phase ground reads as "in motion / interactive" (indigo, the
   action accent) and the sell-phase ground reads as "filled / earning"
   (green, the success accent). Keeps amber free for opportunity-only
   surfaces per DESIGN.md §2 the-two-accent-rule. The label text on these
   badges is also swapped: 'Listed' → 'BUYING', 'Inventory' → 'SELLING'
   in app.js's buildSlotCard. */
.badge-status-listed    { background: var(--accent-soft); color: oklch(0.82 0.12 255); }
.badge-status-inventory { background: var(--green-soft); color: var(--green); }

/* Test badge */
.badge-test {
  background: oklch(0.20 0.04 75 / 0.6);
  color: var(--amber);
  border-color: oklch(0.30 0.06 75);
  flex-shrink: 0;
}

/* Edited badge */
.badge-edited {
  background: var(--surface-2);
  color: var(--text-muted);
  border-color: var(--rule);
  flex-shrink: 0;
  margin-right: 6px;
}

/* Admin role badge */
.admin-role-badge {
  background: var(--accent-soft);
  color: oklch(0.72 0.12 255);
  border-color: oklch(0.30 0.06 255);
  margin-left: 6px;
  vertical-align: middle;
}

/* Config source badge */
.cfg-source-badge {
  background: var(--accent-soft);
  color: oklch(0.72 0.12 255);
  border-color: oklch(0.30 0.06 255);
  font-size: 0.70rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 1px 7px;
  border-radius: 3px;
  text-transform: uppercase;
}

.cfg-source-badge.restore {
  background: oklch(0.20 0.04 310 / 0.6);
  color: oklch(0.70 0.14 310);
}


/* ═══════════════════════════════════════════════════════════════════════
   §8  BANNERS
   Page-scope connection strip. The per-slot poll-error ribbon
   (.slot-poll-error in §22) is the single-slot scope.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Connection strip — page-scope error ─────────────────────────── */
.connection-banner {
  background: var(--amber-soft);
  border-bottom: 1px solid oklch(0.40 0.08 75);
  color: var(--amber);
  font-size: 0.80rem;
  font-weight: 600;
  text-align: center;
  padding: 8px var(--s-5);
  letter-spacing: 0.02em;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

/* Severity step-up after 5 min sustained outage. updateConnectionBanner()
   in app.js toggles .is-sustained via a 1s ticker started at first-entry
   into outage. The text body also swaps to "Connection lost for 5+ minutes"
   and the elapsed counter span is revealed. */
.connection-banner.is-sustained {
  background: var(--red-soft);
  border-bottom-color: oklch(0.45 0.13 25);
  color: oklch(0.78 0.18 25);
}

.connection-banner[hidden] { display: none; }

.connection-banner-icon {
  font-size: 1.0rem;
  line-height: 1;
}

.connection-banner-text {
  /* Inherits colour and weight from the parent banner. The amber → red
     swap lives on .connection-banner.is-sustained above. */
}

.connection-banner-elapsed {
  color: oklch(0.78 0.18 25 / 0.72);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
}
.connection-banner-elapsed[hidden] { display: none; }


/* ═══════════════════════════════════════════════════════════════════════
   §9  SCAN CONTROLS + TABLE
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Scan empty / error messages ─────────────────────────────────── */

.scan-status {
  color: var(--text-muted);
  font-style: italic;
  font-size: var(--t-small);
  margin-top: var(--s-3);
}

/* v0.9.6.3 — Scoring blurb below the scan table. Explains the composite
   score without a tooltip; italic + muted so it reads as meta-context,
   not another data row. */
.scan-scoring-blurb {
  color: var(--text-muted);
  font-style: italic;
  font-size: var(--t-small);
  margin-top: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px solid var(--rule-soft);
}

.scan-error {
  color: var(--red);
  font-style: normal;
}

/* ── Controls bar above the table ────────────────────────────────── */

.scan-controls {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex-wrap: wrap;
}

.scan-controls-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 10px;
}

.scan-control-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.78rem;
  color: var(--text-muted);
}

.scan-control-label select {
  background: var(--bg);
  color: var(--text);
  /* v0.9.7 — was --rule (FAIL WCAG SC 1.4.11); --rule-strong passes 3:1. */
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-1);
  padding: 4px 6px;
  font-size: 0.82rem;
  cursor: pointer;
}

#scan-btn {
  /* v0.9.7 — primary-button background uses --accent-deep for WCAG AA
     (was --accent at 3.08:1, now 6.01:1). Hover deepens to --accent-pressed. */
  background: var(--accent-deep);
  color: var(--text);
  border: none;
  border-radius: var(--r-1);
  padding: 10px 22px;
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s var(--easeOut);
}

#scan-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

#scan-btn:not(:disabled):hover {
  background: var(--accent-pressed);
}

.scan-columns-btn {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--rule);
  border-radius: var(--r-1);
  padding: 5px 12px;
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.15s var(--easeOut), color 0.15s var(--easeOut);
}

.scan-columns-btn:hover,
.scan-columns-btn.open {
  color: var(--amber);
  border-color: var(--amber);
}

/* Inline scan-status */
.scan-status-inline {
  font-size: 0.78rem;
  color: var(--text-muted);
  font-style: italic;
}

.scan-status-inline.scan-error {
  color: var(--red);
  font-style: normal;
  font-weight: 600;
}

/* ── Auto-refresh indicator ──────────────────────────────────────── */

.auto-refresh-indicator {
  font-size: 0.74rem;
  color: var(--text-muted);
  font-style: italic;
  white-space: nowrap;
}

.auto-refresh-indicator.auto-refresh-error {
  color: var(--red);
  font-style: normal;
  font-weight: 600;
}

/* ── Columns drawer — REMOVED v0.9.3 (round 4 cleanup) ─────────────
   The Columns drawer was dropped in v0.9.3. The Filters button still
   uses `.scan-columns-btn` for its chrome; that class stays. The
   drawer-specific selectors below were removed: .scan-columns-drawer,
   .scan-columns-hint, .scan-columns-list, .scan-columns-item,
   .scan-columns-header, .scan-columns-reset (the round-2 Reset button).
   See wiki/log.md for the v0.9.3 ship entry. */

/* ── Action message (place-order success/error) ──────────────────── */

.scan-action-message {
  margin-top: 10px;
  padding: 7px 10px;
  border-radius: var(--r-2);
  font-size: 0.82rem;
  border: 1px solid var(--rule);
}

.scan-action-success {
  background: var(--green-soft);
  color: var(--green);
  border-color: oklch(0.30 0.08 156);
}

.scan-action-error {
  background: var(--red-soft);
  color: var(--red);
  border-color: oklch(0.30 0.08 25);
}

/* ── Slot-availability hint ──────────────────────────────────────── */

.scan-slot-hint {
  margin-top: 10px;
  color: var(--amber);
  font-size: 0.82rem;
  font-weight: 600;
}

/* ── Table ────────────────────────────────────────────────────────── */

.scan-table-wrap {
  margin-top: 10px;
  /* v0.9.4 hotfix — pin the wrap to its grid cell width so overflow-x: auto
     actually engages when the table content is wider than the cell. Without
     this, the wrap sized itself to its content and pushed the grid cell
     past its column track on /market, hiding the rightmost columns
     (Recommendation, Fill Time, Place Order) past the visible area. */
  width: 100%;
  overflow-x: auto;
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
}

.scan-table {
  width: 100%;
  /* No min-width: the grid cell is wide enough on standard viewports
     that the table fits all 11 columns naturally without a horizontal
     scrollbar. */
  border-collapse: collapse;
  font-size: 0.82rem;
  background: var(--bg);
}

.scan-th {
  background: var(--surface-1);
  color: var(--text-faint);
  font-size: var(--t-micro);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  /* v0.9.4 hotfix #4 — horizontal padding tightened from var(--s-4) (16px)
     to var(--s-2) (8px) so columns sit closer together and all 11 fit
     inside the new 1488px /market grid cell without squeezing or
     scrolling. Vertical padding unchanged. */
  padding: var(--s-3) var(--s-2);
  border-bottom: 1px solid var(--rule);
  position: sticky;
  top: 0;
  z-index: 1;
  white-space: nowrap;
  user-select: none;
}

.scan-th-sortable {
  cursor: pointer;
  transition: color 0.15s var(--easeOut);
}

.scan-th-sortable:hover {
  color: var(--amber);
}

.scan-th-sortable.scan-th-desc::after { content: ' ▼'; color: var(--amber); }
.scan-th-sortable.scan-th-asc::after  { content: ' ▲'; color: var(--amber); }

/* ── Additional Sort dropdown (v0.9.8.1) ─────────────────────────────
   Lives inside the placeOrder column's <th>. Visually consistent with
   the .scan-control-label select (page-size dropdown) so the two read
   as siblings: same --bg ground, same --rule-strong border (WCAG AA),
   same --r-1 radius, same compact padding. The select strips the
   uppercase / micro-type treatment .scan-th applies to its children
   because dropdown content is a value pick, not a column label. */
.scan-additional-sort {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-1);
  padding: 4px 6px;
  font-family: inherit;
  font-size: var(--t-small);
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
  cursor: pointer;
  min-width: 150px;
}

.scan-additional-sort:focus {
  outline: none;
  border-color: var(--accent);
}

.scan-tr {
  border-bottom: 1px solid var(--rule);
  transition: background 0.1s var(--easeOut);
}

.scan-tr:hover {
  background: var(--surface-2);
}

.scan-tr:last-child {
  border-bottom: none;
}

.scan-td {
  /* v0.9.4 hotfix #4 — horizontal padding tightened from var(--s-4) (16px)
     to var(--s-2) (8px) so columns sit closer together and all 11 fit
     inside the new 1488px /market grid cell. Mirrors .scan-th above. */
  padding: var(--s-3) var(--s-2);
  vertical-align: middle;
  white-space: nowrap;
  color: var(--text);
}

.scan-align-left   { text-align: left; }
.scan-align-right  { text-align: right; }
.scan-align-center { text-align: center; }

/* ── GE-input pair (Qty + Rec. Buy) ─────────────────────────────── */

.ge-buy-pair {
  background: var(--amber-soft);
}

.ge-buy-pair-left {
  box-shadow:
    inset 0 1px 0 oklch(0.82 0.13 75 / 0.65),
    inset 0 -1px 0 oklch(0.82 0.13 75 / 0.65),
    inset 2px 0 0 oklch(0.82 0.13 75 / 0.65);
}

.ge-buy-pair-right {
  box-shadow:
    inset 0 1px 0 oklch(0.82 0.13 75 / 0.65),
    inset 0 -1px 0 oklch(0.82 0.13 75 / 0.65),
    inset -2px 0 0 oklch(0.82 0.13 75 / 0.65);
}

.scan-th.ge-buy-pair {
  background:
    linear-gradient(oklch(0.82 0.13 75 / 0.18), oklch(0.82 0.13 75 / 0.18)),
    var(--surface-1);
}

/* ── Item cell ───────────────────────────────────────────────────── */

.scan-item-cell {
  display: flex;
  align-items: center;
  gap: 8px;
}

.scan-item-icon {
  flex-shrink: 0;
}

.scan-item-stack {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.scan-item-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

/* scan-item-badge pill geometry is in §7 shared badges above */

.scan-badge-trading {
  background: oklch(0.20 0.06 300 / 0.6);
  color: oklch(0.72 0.12 300);
  border-color: oklch(0.30 0.06 300);
}

/* OPTIMIZED badge. Strict-coverage rule: paints only when the item has
   BOTH a live buy config AND a live sell config on item_configs
   (services/scanCache.js stamp via getLiveCoverageForItem). Green
   palette signals a positive trader cue (this item has been tuned on
   real trades) — distinct from the indigo Trading badge which signals
   operational state. Visible to all users (carve-out from the dev-mode
   gate that hides Trading). See public/app.js renderItemBadges. */
.scan-badge-optimized {
  background: var(--green-soft);
  color: var(--green);
  border-color: oklch(0.40 0.14 156);
}

/* The .badge-test rule in §7 styles the TEST chip on slot cards and
   completed-trade rows, which read from the auto-flagger. */

.scan-item-icon.item-image-icon {
  width: 24px;
  height: 24px;
}

.scan-item-icon.item-image-icon img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.scan-item-link {
  color: var(--accent);
  text-decoration: none;
  font-weight: 600;
}

.scan-item-link:hover {
  text-decoration: underline;
}

/* ── Profit cell ─────────────────────────────────────────────────── */

.scan-profit {
  color: var(--green);
  font-weight: 600;
}

/* ── Score pill ──────────────────────────────────────────────────── */

.scan-score-pill {
  display: inline-block;
  background: var(--surface-2);
  color: var(--accent);
  font-weight: 700;
  border-radius: var(--r-1);
  padding: 1px 9px;
  font-size: 0.78rem;
  min-width: 32px;
  text-align: center;
}

.scan-score-pill.scan-score-null {
  color: var(--text-muted);
  background: var(--surface-3);
}

/* v0.9.7.5 — high-score tint. Composite score ≥ 80 swaps the pill text
   from --accent to --green so the row's headline ranking number reads
   as "this one is a strong recommendation" at a glance. Background and
   weight stay identical to the base pill so the visual still reads as
   part of the same primitive (a tinted variant, not a new component). */
.scan-score-pill.scan-score-high {
  color: var(--green);
}

/* ── Tier / spike chip in optional columns ────────────────────────── */

.scan-tier-chip {
  display: inline-block;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 2px 8px;
  border-radius: 10px;
  text-transform: uppercase;
}

/* ── Flip Time cell ──────────────────────────────────────────────── */

.scan-flip-time {
  font-weight: 500;
}

/* ── Cool-down badge ─────────────────────────────────────────────── */

.scan-cooldown-badge {
  display: inline-block;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 2px 8px;
  border-radius: 10px;
  text-transform: uppercase;
  background: var(--amber-soft);
  color: var(--amber);
  cursor: help;
}

/* ── Fill note ───────────────────────────────────────────────────── */

.scan-fill-note {
  font-size: 0.66rem;
  color: var(--text-muted);
  font-style: italic;
  margin-top: 1px;
  cursor: help;
}

/* ── Filter bar ──────────────────────────────────────────────────── */

.scan-filters-bar {
  margin-top: 10px;
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  padding: 10px 12px;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px 14px;
}

.scan-filters-bar[hidden] {
  display: none;
}

.scan-filter {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.scan-filter > label:not(.scan-filter-checkbox) {
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
}

.scan-filter-search .scan-filter-input { min-width: 180px; }

.scan-filter-toggle {
  justify-content: flex-end;
}

.scan-filter-checkbox {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.78rem;
  color: var(--text);
  cursor: pointer;
  padding: 5px 0;
}

.scan-filter-checkbox input[type="checkbox"] {
  cursor: pointer;
}

.scan-filter-actions {
  margin-left: auto;
  justify-content: flex-end;
}

.scan-filter-reset {
  background: transparent;
  color: var(--text-muted);
  border: 1px solid var(--rule);
  border-radius: var(--r-1);
  padding: 5px 12px;
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  transition: color 0.15s var(--easeOut), border-color 0.15s var(--easeOut);
}

.scan-filter-reset:hover {
  color: var(--amber);
  border-color: var(--amber);
}

/* ── "No items match" banner row ─────────────────────────────────── */

.scan-empty-row {
  font-style: italic;
  color: var(--text-muted);
  text-align: center;
  padding: var(--s-4) var(--s-3);
}

/* ── Expand row ──────────────────────────────────────────────────── */

.scan-expand-tr {
  background: var(--bg);
}

.scan-expand-td {
  padding: 0;
  border-bottom: 1px solid var(--rule);
  background: var(--bg);
}

.scan-expand-panel {
  padding: var(--s-3) var(--s-4);
  display: flex;
  align-items: flex-start;
  gap: var(--s-5);
  flex-wrap: wrap;
  border-top: 1px dashed var(--rule);
}

.scan-expand-chart {
  flex: 0 0 240px;
  height: 48px;
  display: flex;
  align-items: center;
}

.scan-expand-right {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1 1 360px;
  min-width: 0;
}

.scan-expand-chips {
  display: flex;
  gap: var(--s-4);
  flex-wrap: wrap;
}

.scan-expand-chip {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 70px;
}

.scan-expand-chip-sell .scan-expand-chip-value,
.scan-expand-chip-total .scan-expand-chip-value {
  color: var(--text-muted);
}

.scan-expand-chip-label {
  font-size: 0.62rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.scan-expand-chip-value {
  font-size: 0.92rem;
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.scan-expand-note {
  font-size: 0.70rem;
  font-style: italic;
  color: var(--text-muted);
  line-height: 1.4;
  max-width: 60ch;
}

.scan-expand-loading {
  font-size: 0.78rem;
  color: var(--text-muted);
  font-style: italic;
}

/* ── Place Order button (table row variant) ──────────────────────── */

.scan-place-btn {
  background: var(--accent-soft);
  color: oklch(0.82 0.12 255);
  border: 1px solid var(--accent);
  border-radius: var(--r-1);
  padding: 5px 12px;
  font-size: 0.76rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s var(--easeOut), color 0.15s var(--easeOut);
}

.scan-place-btn:not(:disabled):hover {
  /* v0.9.7 — was --accent (3.08:1 white-on-indigo, FAIL WCAG AA);
     --accent-deep gives 6.01:1 (PASS AA). */
  background: var(--accent-deep);
  color: var(--text);
}

.scan-place-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.scan-place-btn.placed {
  background: var(--green-soft);
  color: var(--green);
  opacity: 1;
  cursor: default;
}

/* ── Anonymous "Sign in" surfaces ────────────────────────────────── */

.scan-place-wrap {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}

.scan-place-anon-error {
  color: var(--red);
  font-size: 0.7rem;
  font-weight: 600;
  line-height: 1.2;
  max-width: 110px;
}

.scan-place-anon-error:empty {
  display: none;
}

.scan-anon-controls-error {
  margin-top: 8px;
  padding: 6px 10px;
  color: var(--red);
  background: var(--red-soft);
  border: 1px solid oklch(0.30 0.08 25);
  border-radius: var(--r-2);
  font-size: 0.78rem;
  font-weight: 600;
  display: inline-block;
}


/* ═══════════════════════════════════════════════════════════════════════
   §10  SLOT CARDS (ACTIVE TRADES SIDEBAR)
   ═══════════════════════════════════════════════════════════════════════ */

.slot-card {
  background: var(--surface-1);
  border: 1px solid var(--rule);
  border-radius: var(--r-3);
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin-bottom: 8px;
}

.slot-card:last-child {
  margin-bottom: 0;
}

/* Phase-aware card treatment. Every card sits on the uniform
   --surface-1 ground; phase encoding rides on the border colour only.
   The indigo-toned border reads as "buy / listed" (waiting for fill);
   the green-toned border reads as "inventory / sold-listed" (waiting on
   the sell). Both at low chroma so they recede; just enough to scan the
   rail at a glance. The card's BUY / SELL verb at the top of the order
   row is the textual carrier; the border is the glance reinforcement. */
.slot-status-listed {
  border-color: oklch(0.55 0.13 255 / 0.55);
}

.slot-status-inventory {
  border-color: oklch(0.55 0.10 156 / 0.45);
}

.slot-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  margin-bottom: 2px;
}

.slot-name {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* .slot-badge pill geometry is in §7 shared badges above */
.slot-badge {
  flex-shrink: 0;
}

.slot-row {
  font-size: 0.80rem;
  color: var(--text);
  line-height: 1.5;
}

/* ── Active Trades header ────────────────────────────────────────── */

.slots-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.slots-header h2 {
  margin-bottom: 0;
  /* v0.9.3 round 3 follow-on — reverted the earlier `margin-top: var(--s-3)`
     fix. Box-edge alignment is now handled by sidebar.css dropping the
     whole `#persistent-sidebar` from `top: 60px` to `top: 80px`, which
     puts the rail's top edge level with main's content-start. The h2
     can sit at its natural position inside the rail; no extra margin
     needed. */
}

#slots .slots-header {
  margin-bottom: var(--s-3);
}

/* ── Slot sections (collapsible content areas within slot cards) ── */

.slot-section {
  /* Base class — sections share common spacing */
}

.slot-section-current {
  /* Current price section */
}

.slot-section-trend {
  /* Trend section */
}

.slot-section-predicted {
  /* Predicted section */
}

.slot-section-header {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 4px 0 2px;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 4px;
  width: 100%;
  text-align: left;
}

.slot-section-header-static {
  cursor: default;
}

.slot-section-label {
  /* Inherit from parent */
}

.slot-chevron {
  font-size: 0.65rem;
  color: var(--text-faint);
  transition: transform 0.15s var(--easeOut);
}

/* ── Slot order row ──────────────────────────────────────────────── */

.slot-order-row {
  /* Base for order-display rows within a slot */
}

/* ── Slot trend classes ──────────────────────────────────────────── */

.slot-trend-good { color: var(--green); }
.slot-trend-bad  { color: var(--red); }
.slot-trend-flat { color: var(--text); }

.slot-cell-label {
  font-size: 0.70rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.slot-cell-value {
  font-weight: 600;
}

/* ── Slot scenario tags ──────────────────────────────────────────── */

.slot-scenario-tag {
  display: inline-block;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 1px 6px;
  border-radius: var(--r-1);
  text-transform: uppercase;
  margin-left: 4px;
}

.slot-scenario-fast {
  background: var(--green-soft);
  color: var(--green);
}

.slot-scenario-max {
  background: var(--amber-soft);
  color: var(--amber);
}

/* ── Slot break-even badge ───────────────────────────────────────── */

.slot-be-badge {
  display: inline-block;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 1px 6px;
  border-radius: var(--r-1);
  text-transform: uppercase;
  background: var(--amber-soft);
  color: var(--amber);
  margin-left: 4px;
}

/* ── Slot below-min badge ────────────────────────────────────────── */

.slot-below-min-badge {
  display: inline-block;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 1px 6px;
  border-radius: var(--r-1);
  text-transform: uppercase;
  background: var(--red-soft);
  color: var(--red);
  margin-left: 4px;
}

/* ── Slot min price ──────────────────────────────────────────────── */

.slot-min-price {
  color: var(--text-muted);
  font-size: 0.76rem;
}

/* ── Slot sell-was (original recommendation) ─────────────────────── */

.slot-sell-was {
  color: var(--text-faint);
  font-size: 0.76rem;
  font-style: italic;
}

/* ── Slot SELLING price ──────────────────────────────────────────────
   The price on the SELLING row carries an amber tint + slow heartbeat
   pulse — the "this is the live operational number you're tracking" cue.
   Pulse cadence: 3.2s ease-in-out, full opacity ↔ ~55%. Slow enough to
   read as a heartbeat, not a flash. The price keeps the row's base
   font-size (no enlargement) — the tint + cadence carry the highlight
   without making the row visually heavier than its siblings. */
.slot-sell-price-active {
  color: var(--amber);
  font-weight: 700;
  animation: slotSellPulse 3.2s ease-in-out infinite;
}

@keyframes slotSellPulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}

/* Honour user motion preferences — pulse is decorative, the colour
   alone is enough signal for users who prefer reduced motion. */
@media (prefers-reduced-motion: reduce) {
  .slot-sell-price-active {
    animation: none;
  }
}

/* ── Slot partial-sell ───────────────────────────────────────────── */
/* .slot-partial-status carries the "X sold @ ~Y avg" running annotation
   under the order row when partial tranches have been recorded. */

.slot-partial-status {
  font-size: 0.76rem;
  color: var(--text-muted);
}

/* ── Slot refresh ────────────────────────────────────────────────── */
/* The Refresh-sell-estimate affordance is a small ↻ icon on the right
   edge of the SELLING row that opens a compact popover. Styles for the
   icon (.slot-refresh-icon) and the popover variants
   (.slot-popover-below-right + .slot-popover-arrow-up) live in the
   popover layer further down in this section. */

/* ── Slot warning badges ─────────────────────────────────────────── */

.slot-warning-badge {
  display: inline-block;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 2px 7px;
  border-radius: var(--r-1);
  text-transform: uppercase;
  cursor: pointer;
}

.slot-warning-advisory {
  background: var(--amber-soft);
  color: var(--amber);
  border: 1px solid oklch(0.35 0.06 75);
}

.slot-warning-warning {
  background: oklch(0.20 0.06 50 / 0.6);
  color: oklch(0.72 0.14 50);
  border: 1px solid oklch(0.30 0.08 50);
}

.slot-warning-critical {
  background: var(--red-soft);
  color: var(--red);
  border: 1px solid oklch(0.30 0.08 25);
}

.slot-warning-expand {
  margin-top: 4px;
  padding: 6px 8px;
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: var(--r-1);
  font-size: 0.74rem;
}

.slot-warning-reason {
  line-height: 1.4;
  padding: 2px 0;
}

.slot-warning-reason-advisory {
  color: var(--amber);
}

.slot-warning-reason-warning {
  color: oklch(0.72 0.14 50);
}

.slot-warning-reason-critical {
  color: var(--red);
}

/* ── Slot action layer ────────────────────────────────────────────────
   Three-button row at the bottom of every slot card. Each button (and
   the SELLING-row refresh icon on inventory cards) opens a popover
   anchored to itself; the slot card body never re-renders during user
   input. Universal ✓-then-✗ button order across every popover.

   Listed phase  : [Full Buy] [Partial Buy] [Cancel]   (red Cancel is
                   destructive at rest; popover anchors above each button)
   Inventory phase: [Full Sell] [Partial Sell] [Cancel] (third button is
                   disabled — Coming Soon)

   The popover surface separates from the slot card via tonal step
   (--surface-3) + a 1px --accent border; no drop shadow (flat by
   default).
   ─────────────────────────────────────────────────────────────── */

.slot-action-row {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 8px;
  position: relative;        /* anchor for action-button popovers */
}

.slot-btn {
  font: inherit;
  font-size: var(--t-micro);
  padding: 4px 10px;
  border-radius: var(--r-1);
  cursor: pointer;
  font-weight: 600;
  border: 1px solid transparent;
  line-height: 1.4;
  transition: opacity 0.15s var(--easeOut),
              color 0.15s var(--easeOut),
              border-color 0.15s var(--easeOut),
              background 0.15s var(--easeOut);
}

.slot-btn-primary {
  background: var(--accent-deep);
  color: var(--text);
  border-color: var(--accent-deep);
}
.slot-btn-primary:not(:disabled):hover {
  background: var(--accent-pressed);
  border-color: var(--accent-pressed);
}

.slot-btn-secondary {
  background: transparent;
  color: oklch(0.82 0.12 255);
  border-color: var(--accent);
}
.slot-btn-secondary:not(:disabled):hover {
  background: var(--accent-soft);
}

.slot-btn-cancel {
  background: transparent;
  color: var(--red);
  border-color: oklch(0.30 0.08 25);
}
.slot-btn-cancel:not(:disabled):hover {
  background: var(--red-soft);
}

.slot-btn:disabled {
  cursor: not-allowed;
  opacity: 0.45;
  border-style: dashed;
}

/* ── Slot popover (v0.9.8) ──────────────────────────────────────── */

.slot-popover {
  position: absolute;
  background: var(--surface-3);
  border: 1px solid var(--accent);
  border-radius: var(--r-2);
  padding: 8px 10px;
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 170px;
  box-sizing: border-box;
}

/* Action-row popovers float UPWARD from the action row, over the
   chart. Three horizontal anchor variants: left / mid / right. */
.slot-popover-above        { bottom: calc(100% + 8px); }
.slot-popover-anchor-left  { left: 0; }
.slot-popover-anchor-mid   { left: 50%; transform: translateX(-50%); }
.slot-popover-anchor-right { right: 0; }

/* Refresh-icon popover floats DOWN from the icon, with right edge
   aligned to the icon's right edge. Anchored to the SELLING row. */
.slot-popover-below-right {
  top: calc(100% + 8px);
  right: 0;
}

/* ── Popover arrow chevron ──────────────────────────────────────── */

.slot-popover-arrow {
  position: absolute;
  width: 10px;
  height: 10px;
  background: var(--surface-3);
  bottom: -6px;
  border-right: 1px solid var(--accent);
  border-bottom: 1px solid var(--accent);
  transform: rotate(45deg);
}
.slot-popover-arrow-left  { left: 18px; }
.slot-popover-arrow-mid   { left: 50%; margin-left: -5px; }
.slot-popover-arrow-right { right: 18px; }

/* Refresh-icon popover arrow — points UP at the icon. */
.slot-popover-arrow-up {
  bottom: auto;
  top: -6px;
  right: 8px;
  left: auto;
  border-right: none;
  border-bottom: none;
  border-left: 1px solid var(--accent);
  border-top: 1px solid var(--accent);
}

/* ── Popover content primitives ─────────────────────────────────── */

.slot-popover-text {
  font-size: var(--t-small);
  color: var(--text);
  font-weight: 500;
  line-height: 1.35;
}

.slot-popover-label {
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  font-weight: 600;
}

.slot-popover-form-row {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--t-small);
  color: var(--text);
  flex-wrap: wrap;
}

.slot-popover-input {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-1);
  padding: 3px 6px;
  font: inherit;
  font-size: var(--t-small);
  width: 80px;
  -moz-appearance: textfield;
  transition: border-color 0.15s var(--easeOut);
}
.slot-popover-input:focus {
  border-color: var(--accent);
  outline: none;
}
.slot-popover-input::-webkit-outer-spin-button,
.slot-popover-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* Validation pulse — JS adds .slot-input-pulse to the input when ✓ is
   clicked while value > target on Partial Buy. The keyframe pulses the
   border red over 400ms; JS removes the class on animationend. The
   reduced-motion fallback drops the animation but keeps the red border
   so the error signal still lands. */
.slot-popover-input.slot-input-pulse {
  animation: slotInputPulse 0.4s var(--easeOut);
}
@keyframes slotInputPulse {
  0%, 100% { border-color: var(--rule-strong); }
  50%      { border-color: var(--red); }
}
@media (prefers-reduced-motion: reduce) {
  .slot-popover-input.slot-input-pulse {
    animation: none;
    border-color: var(--red);
  }
}

.slot-popover-btns {
  display: flex;
  gap: 5px;
  justify-content: flex-end;
}

.slot-popover-btn {
  width: 28px;
  height: 24px;
  border-radius: var(--r-1);
  border: 1px solid transparent;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  line-height: 1;
  font-weight: 600;
  box-sizing: border-box;
  transition: opacity 0.15s var(--easeOut),
              background 0.15s var(--easeOut),
              border-color 0.15s var(--easeOut);
}
.slot-popover-btn-confirm {
  background: var(--accent-deep);
  color: var(--text);
  border-color: var(--accent-deep);
}
.slot-popover-btn-confirm:not(:disabled):hover {
  background: var(--accent-pressed);
  border-color: var(--accent-pressed);
}
.slot-popover-btn-cancel {
  background: transparent;
  color: var(--text-muted);
  border-color: var(--rule);
}
.slot-popover-btn-cancel:not(:disabled):hover {
  color: var(--text);
  border-color: var(--rule-strong);
}
.slot-popover-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.slot-popover-error {
  font-size: var(--t-micro);
  color: var(--red);
}

/* ── Refresh icon (v0.9.8) ──────────────────────────────────────── */

/* Small ↻ glyph button on the right edge of the SELLING row. Click
   opens a popover anchored downward + leftward from the icon
   (.slot-popover-below-right above). */
.slot-refresh-icon {
  background: transparent;
  border: 1px solid var(--rule);
  color: var(--text-muted);
  font-size: 13px;
  cursor: pointer;
  width: 22px;
  height: 22px;
  border-radius: var(--r-1);
  line-height: 1;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  /* margin-left: auto pushes the icon to the right edge of the SELLING
     row's flex layout, after all inline decorations. */
  margin-left: auto;
  transition: color 0.15s var(--easeOut), border-color 0.15s var(--easeOut);
}
.slot-refresh-icon:hover,
.slot-refresh-icon-active {
  color: oklch(0.82 0.12 255);
  border-color: var(--accent);
}

/* SELLING-row layout: flex so the refresh icon can push to the right
   edge via margin-left:auto, plus position:relative so the refresh
   popover anchors correctly to its right edge. The pre-v0.9.8 row was
   plain block flow; promoted to flex because the right-aligned ↻ icon
   is the new visible right-edge element. align-items: center keeps the
   icon vertically centred against the price text; gap:4px gives the
   inline tags (Fast / MaxProfit / Break Even / at a loss /
   "(was Xgp)") consistent breathing room. flex-wrap stays off: if the
   row gets too tight the icon would otherwise drop to its own line,
   which would clip into the Predicted section below it. Prefer the row
   clipping at narrow widths over the icon orphaning. */
.slot-listed-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: nowrap;
}

/* Poll error row */
.slot-poll-error {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.72rem;
  color: var(--amber);
  margin-top: 3px;
  padding-top: 3px;
  border-top: 1px dashed oklch(0.30 0.06 75);
}

.slot-poll-error::before {
  content: "⚠";
  display: inline-block;
  color: var(--amber);
  font-size: 0.85rem;
  line-height: 1;
}

/* ── Inline form (legacy) ─────────────────────────────────────────
   v0.9.8 — .slot-form, .slot-form-label, .slot-form-btns,
   .slot-form-error retired. Their replacements live in the v0.9.8
   popover layer above (.slot-popover-form-row, .slot-popover-label,
   .slot-popover-btns, .slot-popover-error). The Mark-Bought /
   Mark-Sold confirmation forms now render inside popovers anchored to
   the action-row buttons rather than swap-replacing the buttons. */

/* Slot cards flagged as test */
.slot-card.slot-is-test {
  border-style: dashed;
  opacity: 0.85;
}

/* rec-muted — legacy helper used in JS template literals */
.rec-muted {
  color: var(--text-muted);
  font-size: 0.78rem;
}


/* ═══════════════════════════════════════════════════════════════════════
   §11  COMPLETED TRADE ROWS
   ═══════════════════════════════════════════════════════════════════════ */

.trade-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  grid-template-rows: auto auto;
  gap: 2px 8px;
  padding: 9px 0;
  border-bottom: 1px solid var(--rule);
}

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

.trade-name {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
  grid-column: 1;
  grid-row: 1;
}

.trade-profit {
  font-size: 0.85rem;
  font-weight: 700;
  grid-column: 2;
  grid-row: 1;
  text-align: right;
}

.profit-positive { color: var(--green); }
.profit-negative { color: var(--red); }

.trade-details {
  font-size: 0.76rem;
  color: var(--text-muted);
  grid-column: 1;
  grid-row: 2;
}

.trade-date {
  font-size: 0.72rem;
  color: var(--text-muted);
  grid-column: 2;
  grid-row: 2;
  text-align: right;
}

/* Trade rows flagged as test */
.trade-row.trade-is-test {
  opacity: 0.80;
}

/* ── Trade row delete button (x) ─────────────────────────────────── */

.trade-delete-btn {
  grid-column: 3;
  grid-row: 1 / 3;
  align-self: center;
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-size: 1rem;
  line-height: 1;
  padding: 2px 4px;
  cursor: pointer;
  border-radius: 3px;
  transition: color 0.15s var(--easeOut), background 0.15s var(--easeOut);
}

.trade-delete-btn:hover {
  color: var(--red);
  background: var(--red-soft);
}

/* Confirm strip */
.trade-delete-confirm {
  grid-column: 1 / 4;
  grid-row: 3;
  display: flex;
  align-items: center;
  gap: 8px;
  padding-top: 6px;
}

.trade-delete-label {
  font-size: 0.75rem;
  color: var(--text-muted);
  flex: 1;
}

.trade-delete-yes,
.trade-delete-cancel {
  font-size: 0.72rem;
  font-weight: 700;
  padding: 3px 10px;
  border-radius: var(--r-1);
  cursor: pointer;
  border: none;
  transition: opacity 0.15s var(--easeOut);
}

.trade-delete-yes {
  background: oklch(0.24 0.08 25);
  color: var(--red);
}

.trade-delete-cancel {
  background: var(--surface-1);
  color: var(--text-muted);
  border: 1px solid var(--rule);
}

.trade-delete-yes:not(:disabled):hover    { opacity: 0.8; }
.trade-delete-cancel:not(:disabled):hover { opacity: 0.75; }

.trade-delete-yes:disabled,
.trade-delete-cancel:disabled { opacity: 0.4; cursor: not-allowed; }

.trade-delete-error {
  font-size: 0.72rem;
  color: var(--red);
}


/* ═══════════════════════════════════════════════════════════════════════
   §12  TEST MODE + TOGGLES
   ═══════════════════════════════════════════════════════════════════════ */

/* Toggle shared primitives are in §6 above. This section holds only the
   scoping rules that were not already captured. */


/* ═══════════════════════════════════════════════════════════════════════
   §13  AUTH TIER — /login + /signup (split panel)
   ═══════════════════════════════════════════════════════════════════════
   v0.9.5 rewrite. Replaces the v0.7.1 / v0.9.1 single-card layout with a
   two-column shell: form on the left, brand anchor on the right (a quiet
   "Quiet Cockpit" caption strip + a sample-opportunity micro-card). The
   form internals (security note, fields, button, error) carry the Lane
   B4 token vocabulary forward from v0.9.4; only the wrapper geometry is
   new.

   Both /login and /signup share class names — markup is identical except
   for the form id and the alt-link target. The legacy `.login-page` body
   class is aliased onto `.auth-page` so any external reference still
   reaches the new chrome.
   ═══════════════════════════════════════════════════════════════════════ */

body.auth-page,
body.login-page {
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: var(--s-5);
}

.auth-shell {
  width: 100%;
  max-width: 880px;
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.85fr);
  background: var(--surface-1);
  border: 1px solid var(--rule);
  border-radius: var(--r-3);
  overflow: hidden;
}

/* Below 760px the shell collapses to one column; the brand anchor moves
   beneath the form and shrinks to a thin support strip. */
@media (max-width: 760px) {
  .auth-shell {
    grid-template-columns: 1fr;
    max-width: 480px;
  }
}

.auth-form-col {
  padding: var(--s-7) var(--s-6);
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.auth-brand {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin-bottom: var(--s-4);
}

/* Reusable indigo brand-mark square. Three sizes — chrome (default 22px
   for the auth + landing headers), lg (32px for the auth-form column),
   xl (44px for the landing pitch hero). All use the same 'i' glyph;
   font-size scales with the box. The glyph pulls slightly low because
   Segoe UI 'i' has a bit of headroom — line-height: 1 + the flex
   centering handles the rest. */
.brand-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: var(--r-1);
  /* v0.9.7 — was --accent (3.08:1 white-i-on-indigo, FAIL WCAG AA);
     --accent-deep gives 6.01:1. Brand mark stays the same shape; only
     the indigo darkens slightly. Same swap applied to all primary buttons
     so the brand chip and primary CTAs remain visually unified. */
  background: var(--accent-deep);
  color: var(--text);
  font-weight: 500;
  font-size: 14px;
  line-height: 1;
  flex-shrink: 0;
}

.brand-mark-lg {
  width: 32px;
  height: 32px;
  border-radius: 6px;
  font-size: 22px;
}

.brand-mark-xl {
  width: 44px;
  height: 44px;
  border-radius: var(--r-2);
  font-size: 30px;
}

.auth-brand .brand-block {
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
}

/* v0.9.7 — .auth-wordmark also matches the new <a> form so the wordmark
   keeps its typography after being demoted from <h1> for single-H1
   heading hierarchy on /login and /signup. The .auth-title H1 is now
   the page's actual H1 ("Sign In" / "Create Account"). */
a.auth-wordmark,
.auth-wordmark {
  font-size: var(--t-h3);
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--text);
  text-decoration: none;
}

.auth-version {
  font-size: var(--t-micro);
  color: var(--text-muted);
}

/* v0.9.7 — .auth-title is now the page's H1 (was H2). Single-H1 heading
   hierarchy across the auth tier: the wordmark above is a non-H1 link;
   this is the true page topic ("Sign In" / "Create Account"). The :first-of-type
   reset normalises the H1 default (browser styling on H1 differs from H2). */
h1.auth-title,
.auth-title {
  font-size: var(--t-h2);
  font-weight: 500;
  color: var(--text);
  margin: 0 0 4px;
  letter-spacing: -0.005em;
}

.auth-subtitle {
  font-size: var(--t-body);
  color: var(--text-muted);
  margin-bottom: var(--s-5);
  line-height: 1.5;
}

.auth-security-note {
  background: var(--bg);
  border: 1px solid oklch(0.82 0.13 75 / 0.35);
  border-radius: var(--r-1);
  padding: 10px 12px;
  margin-bottom: var(--s-5);
  font-size: var(--t-small);
  line-height: 1.5;
  color: var(--text-muted);
}

.auth-security-note strong {
  display: block;
  margin-bottom: 2px;
  color: var(--amber);
  font-weight: 500;
}

.auth-security-note code {
  background: var(--surface-2);
  color: var(--text);
  padding: 1px 5px;
  border-radius: var(--r-1);
  font-size: 0.9em;
  font-family: var(--mono);
}

.auth-form {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

.auth-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.auth-field label {
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.auth-field input {
  background: var(--bg);
  /* v0.9.7 — was --rule (FAIL WCAG SC 1.4.11); --rule-strong passes 3:1. */
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-1);
  color: var(--text);
  font-size: var(--t-body);
  padding: 8px 10px;
  outline: none;
  transition: border-color 0.15s var(--easeOut);
}

.auth-field input:focus {
  border-color: var(--accent);
}

.auth-field-hint {
  margin-top: 2px;
  font-size: var(--t-micro);
  color: var(--text-faint);
  line-height: 1.4;
}

.auth-submit {
  margin-top: var(--s-2);
  /* v0.9.7 — primary-button background uses --accent-deep for WCAG AA. */
  background: var(--accent-deep);
  color: var(--text);
  border: none;
  border-radius: var(--r-2);
  padding: 10px 18px;
  font-size: var(--t-body);
  font-weight: 500;
  cursor: pointer;
  transition: opacity 0.15s var(--easeOut);
}

.auth-submit:not(:disabled):hover {
  opacity: 0.85;
}

.auth-submit:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.auth-error {
  min-height: 16px;
  margin-top: 4px;
  font-size: var(--t-small);
  color: var(--red);
}

.auth-altlink {
  margin-top: var(--s-5);
  font-size: var(--t-small);
  color: var(--text-muted);
}

.auth-altlink a {
  color: var(--accent);
  text-decoration: none;
}

.auth-altlink a:hover {
  text-decoration: underline;
}

/* ── Brand anchor (right rail) ─────────────────────────────────────── */
/* v0.9.8.1 — restructured. The anchor is now a plain positioning
   context: the scrolling-trades image fills it via .auth-anchor-rail,
   and the brand caption sits over it as a frosted-glass card via
   .auth-anchor-block. The old flex-column layout (anchor-block +
   sample-opportunity card) is retired along with the .auth-sample-*
   class family. min-height keeps the rail visible on the mobile
   single-column layout where the form column no longer dictates the
   row height. */

.auth-anchor {
  position: relative;
  background: var(--surface-2);
  border-left: 1px solid var(--rule);
  overflow: hidden;
  min-height: 360px;
}

@media (max-width: 760px) {
  .auth-anchor {
    border-left: none;
    border-top: 1px solid var(--rule);
    min-height: 360px;
  }
}

.caps-label {
  display: block;
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  margin-bottom: var(--s-3);
}

/* Scrolling active-trades rail. The image is set to width: 100% so it
   fills the rail horizontally; its natural height (much taller than
   the rail) becomes the scroll length. The keyframe animates a
   translateY between 0 and --rail-scroll-end, which an inline shim in
   login.html / signup.html computes from the actual image vs. rail
   heights. ease-in-out + alternate gives the slow up-and-down drift;
   prefers-reduced-motion freezes the first frame. */
.auth-anchor-rail {
  position: absolute;
  inset: 0;
  overflow: hidden;
  --rail-scroll-end: 0px;
}

.auth-anchor-rail-img {
  position: absolute;
  top: 0;
  left: 50%;
  width: 100%;
  height: auto;
  display: block;
  transform: translate(-50%, 0);
  animation: authRailScroll 48s ease-in-out infinite alternate;
  will-change: transform;
}

@keyframes authRailScroll {
  from { transform: translate(-50%, 0); }
  to   { transform: translate(-50%, var(--rail-scroll-end, 0px)); }
}

@media (prefers-reduced-motion: reduce) {
  .auth-anchor-rail-img { animation: none; }
}

/* Foreground caption — frosted-glass card sitting over the rail.
   width: fit-content so the right edge hugs the text the same way the
   left edge does (symmetric inner padding becomes symmetric visible
   margin around the text). backdrop-filter + 55% surface tint keeps
   the trades partially visible behind the words. white-space: nowrap
   on the body lines guarantees the box's right edge doesn't drift
   inward if the foreground card ever ends up width-constrained. */
.auth-anchor-block {
  position: absolute;
  top: var(--s-5);
  left: 14px;
  width: fit-content;
  max-width: calc(100% - 28px);
  padding: var(--s-3) var(--s-4);
  background: color-mix(in oklch, var(--surface-1) 55%, transparent);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  backdrop-filter: blur(10px) saturate(120%);
  -webkit-backdrop-filter: blur(10px) saturate(120%);
}

.auth-anchor-block .caps-label {
  color: var(--amber);
  margin-bottom: var(--s-2);
}

.auth-anchor-block p {
  font-size: var(--t-body);
  color: var(--text);
  line-height: 1.55;
  margin-bottom: 2px;
  white-space: nowrap;
}


/* ═══════════════════════════════════════════════════════════════════════
   §14  PORTFOLIO / PROFILE PAGE
   ═══════════════════════════════════════════════════════════════════════ */

.portfolio-main {
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding: 20px var(--s-5);
  max-width: 1100px;
  margin: 0 auto;
}

/* §14 #range-panel rules removed — the standalone Time-range section
   was retired in v0.9.3 round 3 follow-on; the dropdown moved inline
   with the Completed Trades heading. The .custom-range strip below
   stays — still used when the dropdown's "Custom range…" option is
   picked. */

/* v0.9.3 — #range-buttons + .range-btn rules removed. The preset-button
   row was replaced with a styled `<select>` dropdown (see §29
   `.range-select-wrap` / `.range-select`). The .custom-range strip below
   stays — it's reused by the new dropdown's "Custom range…" option. */

.custom-range {
  margin-top: var(--s-3);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
}

.custom-range label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 0.78rem;
  color: var(--text-muted);
}

.custom-range input[type="datetime-local"] {
  background: var(--bg);
  color: var(--text);
  /* v0.9.7 — was --rule (FAIL WCAG SC 1.4.11); --rule-strong passes 3:1. */
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-1);
  padding: 5px 8px;
  font-size: 0.85rem;
  font-family: inherit;
}

#custom-apply {
  /* v0.9.7 — primary-button background uses --accent-deep for WCAG AA. */
  background: var(--accent-deep);
  color: var(--text);
  border: none;
  border-radius: var(--r-1);
  padding: 7px 16px;
  font-size: 0.82rem;
  font-weight: 700;
  cursor: pointer;
  align-self: flex-end;
}

#custom-apply:hover {
  background: var(--accent-deep);
}

.range-summary {
  margin-top: var(--s-3);
  font-size: 0.82rem;
  color: var(--text-muted);
}

#metrics-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 14px;
}

.metric-card {
  background: var(--surface-1);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  padding: var(--s-4) 18px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-height: 110px;
}

.metric-card-wide {
  grid-column: span 2;
}

.metric-label {
  font-size: 0.74rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
}

.metric-value {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--amber);
  line-height: 1.2;
  word-break: break-word;
}

.metric-value.positive { color: var(--green); }
.metric-value.negative { color: var(--red); }

.metric-sub {
  font-size: 0.78rem;
  color: var(--text-muted);
  margin-top: auto;
}

/* On narrow screens the wide card collapses back to a single column */
@media (max-width: 700px) {
  .metric-card-wide {
    grid-column: span 1;
  }
}

/* ── Portfolio trade list ────────────────────────────────────────── */

#trades-panel {
  background: var(--surface-1);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  padding: var(--s-4);
}

#trades-panel h2 {
  font-size: var(--t-small);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  margin-bottom: 8px;
}

#trades-summary {
  margin: 0 0 12px 0;
}

.trade-list-portfolio {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.pt-row {
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  background: var(--bg);
}

.pt-row.pt-row-test {
  border-color: oklch(0.30 0.06 75);
}

/* v0.9.8.1 — `.pt-header-row` shares the same grid template as
   `.pt-row-header` so the sortable header cells line up exactly with
   the data cells in each trade row below. The ROI cell (5th data col)
   was inserted between Profit and Flip Time; Profit's max width
   trimmed from 2fr → 1.5fr to make room without expanding the row. */
.pt-row-header,
.pt-header-row {
  display: grid;
  grid-template-columns: 18px auto minmax(120px, 1.3fr) minmax(140px, 1.5fr) minmax(80px, 0.8fr) minmax(90px, 0.9fr) minmax(140px, 1fr);
  gap: 12px;
  align-items: center;
  padding: 10px 14px;
}

.pt-row-header {
  width: 100%;
  background: transparent;
  border: 0;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.pt-row-header:hover {
  background: var(--surface-1);
}

.pt-chevron {
  color: var(--text-muted);
  font-size: 0.85rem;
  width: 18px;
  text-align: center;
}

.pt-name {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.pt-details {
  color: var(--text-muted);
  font-size: 0.85rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.pt-tag {
  display: inline-block;
  padding: 1px 6px;
  border-radius: 3px;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  margin: 0 6px 0 0;
}

.pt-tag.pt-buy {
  background: var(--green-soft);
  color: var(--green);
}

.pt-tag.pt-sell {
  background: var(--amber-soft);
  color: var(--amber);
  margin-left: 12px;
}

.pt-profit {
  font-weight: 700;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.pt-profit.profit-positive { color: var(--price-up); }
.pt-profit.profit-negative { color: var(--price-down); }

.pt-date {
  color: var(--text-muted);
  font-size: 0.78rem;
  text-align: right;
  white-space: nowrap;
}

/* ── v0.9.8.1: ROI cell + sortable header row + search bar ──────────
   ROI sits between Profit and Flip Time. Same numeric tabular-num
   treatment as Profit (so paired columns visually align), with the
   profit-positive / profit-negative tint inherited from the existing
   colour pair so the gain / loss signal stays consistent across both
   absolute and relative columns. */
.pt-roi {
  font-weight: 600;
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.pt-roi.profit-positive { color: var(--price-up); }
.pt-roi.profit-negative { color: var(--price-down); }

/* Sortable header row above the trade list. Caps + letter-spacing
   matches the /market table's .scan-th treatment so the two surfaces
   read as siblings. The amber ▼/▲ glyph on the active column mirrors
   .scan-th-asc/.scan-th-desc exactly. */
.pt-header-row {
  background: var(--surface-1);
  border-bottom: 1px solid var(--rule);
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-faint);
  font-weight: 600;
  padding: 8px 14px;
}

.pt-header-cell {
  background: transparent;
  border: 0;
  color: inherit;
  font: inherit;
  text-transform: inherit;
  letter-spacing: inherit;
  text-align: left;
  cursor: pointer;
  padding: 0;
  white-space: nowrap;
  transition: color 0.15s var(--easeOut);
}

.pt-header-cell:hover,
.pt-header-cell:focus-visible {
  color: var(--amber);
  outline: none;
}

.pt-header-cell-active.pt-header-cell-desc::after { content: ' ▼'; color: var(--amber); }
.pt-header-cell-active.pt-header-cell-asc::after  { content: ' ▲'; color: var(--amber); }

/* Right-align the numeric and date header cells so the caps label sits
   above the right-aligned cell value below (matches the data row's
   text-align: right on .pt-profit / .pt-roi / .pt-date). */
.pt-header-cell[data-sort-key="profit"],
.pt-header-cell[data-sort-key="roi"],
.pt-header-cell[data-sort-key="time"],
.pt-header-cell[data-sort-key="date"] {
  text-align: right;
}

/* Toolbar above the trade list — currently just hosts the search input.
   Kept as its own container so future filters (date / status / ROI cut)
   can drop in alongside without restructuring the markup. */
.pt-toolbar {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin: var(--s-3) 0;
}

.pt-search-input {
  flex: 1 1 auto;
  max-width: 320px;
  /* Inherits background / border / focus from the .scan-filter-input
     base rule (style.css §"Inputs"); no need to redeclare those here. */
}

/* Expand panel */
.pt-expand {
  border-top: 1px dashed var(--rule);
  padding: 14px 18px 16px 36px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.pt-expand[hidden] {
  display: none;
}

.pt-section-heading {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  margin-bottom: 8px;
}

.pt-pva-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.85rem;
}

.pt-pva-table th,
.pt-pva-table td {
  text-align: left;
  padding: 6px 10px;
  border-bottom: 1px solid var(--rule);
  font-variant-numeric: tabular-nums;
}

.pt-pva-table th {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  font-weight: 600;
}

.pt-pva-table tbody tr:last-child td {
  border-bottom: 0;
}

.pt-pva-table tr.pt-pva-ctx td {
  color: var(--text-muted);
  font-style: italic;
}

/* Narrow layouts: header columns collapse to a stacked layout.
   v0.9.8.1 — ROI cell shares the profit row at right (numeric stack
   under the item name). The sortable .pt-header-row is hidden on
   narrow screens because there's no room for caps-style header cells
   above the stacked layout; users sort via the dropdown-equivalent
   they get on /market — desktop is the sort-target form factor for
   the Profile page. */
@media (max-width: 700px) {
  .pt-row-header {
    grid-template-columns: 18px auto 1fr auto auto;
    grid-template-areas:
      "chev image name    profit  roi"
      "chev image details details details"
      "chev image date    date    date";
    row-gap: 4px;
  }
  .pt-chevron                  { grid-area: chev; }
  .pt-row-header > .item-image { grid-area: image; align-self: start; }
  .pt-name                     { grid-area: name; }
  .pt-details                  { grid-area: details; white-space: normal; }
  .pt-profit                   { grid-area: profit; }
  .pt-roi                      { grid-area: roi; }
  .pt-date                     { grid-area: date; text-align: left; }

  .pt-header-row { display: none; }

  .pt-expand { padding-left: 18px; }
}

/* ── Profile page — Trading Defaults section ─────────────────────── */

#profile-defaults-panel {
  background: var(--surface-1);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  padding: var(--s-4);
}

#profile-defaults-panel h2 {
  font-size: var(--t-small);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  margin-bottom: 8px;
}

.profile-defaults-summary {
  font-size: 0.82rem;
  color: var(--text-muted);
  margin-bottom: var(--s-3);
}

.profile-defaults-summary a {
  color: var(--accent);
  text-decoration: none;
}

.profile-defaults-summary a:hover {
  text-decoration: underline;
}

.profile-defaults-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: var(--s-3);
  margin-bottom: 14px;
}

.profile-field {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.profile-field > label {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}

.profile-field-toggle .profile-checkbox {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  padding: 4px 0;
}

.profile-field-toggle .profile-checkbox span {
  font-size: 0.85rem;
  color: var(--text);
  text-transform: none;
  letter-spacing: 0;
}

.profile-subheading {
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  margin: 16px 0 6px 0;
}

/* v0.9.3 — .profile-columns-list / .profile-columns-item rules removed.
   The "Visible columns" subsection on /profile was deleted in the round-4
   cleanup (the `visible_columns` DB column on user_filter_prefs lies
   dormant; new saves silently null it). */

.profile-defaults-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

.profile-save-btn {
  /* v0.9.7 — primary-button background uses --accent-deep for WCAG AA. */
  background: var(--accent-deep);
  color: var(--text);
  border: none;
  border-radius: var(--r-1);
  padding: 8px 18px;
  font-size: 0.85rem;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.15s var(--easeOut);
}

.profile-save-btn:hover {
  background: var(--accent-pressed);
}

.profile-save-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.profile-reset-btn {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--rule);
  border-radius: var(--r-1);
  padding: 7px 14px;
  font-size: 0.82rem;
  cursor: pointer;
  transition: border-color 0.15s var(--easeOut), color 0.15s var(--easeOut);
}

.profile-reset-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.profile-reset-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.profile-save-status {
  font-size: 0.82rem;
  color: var(--text-muted);
  margin-left: 4px;
}

.profile-section-heading {
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  border-bottom: 1px solid var(--rule);
  padding-bottom: 6px;
  margin-bottom: 4px;
}

/* ── Edit/delete toolbar ─────────────────────────────────────────── */

.pt-edit-toolbar {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.pt-edit-btn,
.pt-delete-btn {
  background: transparent;
  border: 1px solid var(--rule);
  color: var(--text-muted);
  border-radius: var(--r-1);
  font-size: 0.75rem;
  padding: 3px 10px;
  cursor: pointer;
  transition: border-color 0.15s var(--easeOut), color 0.15s var(--easeOut), background 0.15s var(--easeOut);
}

.pt-edit-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.pt-delete-btn {
  font-size: 0.95rem;
  line-height: 1;
  padding: 1px 8px;
}

.pt-delete-btn:hover {
  border-color: var(--red);
  color: var(--red);
  background: var(--red-soft);
}

/* Confirm strip */
.pt-edit-confirm {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding-top: 6px;
  border-top: 1px dashed var(--rule);
  margin-top: 6px;
}

.pt-edit-confirm-label {
  font-size: 0.78rem;
  color: var(--text-muted);
  flex: 1;
}

.pt-edit-confirm-yes,
.pt-edit-confirm-no {
  font-size: 0.74rem;
  font-weight: 700;
  padding: 4px 12px;
  border-radius: var(--r-1);
  cursor: pointer;
  border: none;
  transition: opacity 0.15s var(--easeOut);
}

.pt-edit-confirm-yes {
  background: oklch(0.24 0.08 25);
  color: var(--red);
}

.pt-edit-confirm-no {
  background: var(--surface-1);
  color: var(--text-muted);
  border: 1px solid var(--rule);
}

.pt-edit-confirm-yes:not(:disabled):hover { opacity: 0.85; }
.pt-edit-confirm-no:not(:disabled):hover  { opacity: 0.8; }
.pt-edit-confirm-yes:disabled,
.pt-edit-confirm-no:disabled { opacity: 0.4; cursor: not-allowed; }

.pt-edit-error {
  font-size: 0.74rem;
  color: var(--red);
}

/* Edit form */
.pt-edit-form {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-top: 8px;
  margin-top: 6px;
  border-top: 1px dashed var(--rule);
}

.pt-edit-form-heading {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}

.pt-edit-form-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 10px;
}

.pt-edit-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 0.78rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.pt-edit-field-reason {
  grid-column: 1 / -1;
}

.pt-edit-form-buttons {
  display: flex;
  align-items: center;
  gap: 8px;
}


/* ═══════════════════════════════════════════════════════════════════════
   §15  READINESS / ADMIN PAGES
   ═══════════════════════════════════════════════════════════════════════

   v0.9.6 — Restyled around the new admin chrome:
     - .admin-page-head (H1 + amber-pulse subline) replaces the v0.7.4
       "Admin dashboard" H2 + intro paragraph. Same shape /profile and
       /market already use.
     - .panel-head (flex row: h2 + info-tip) replaces verbose per-section
       blurbs. The blurb text lives in .info-tip-pop popovers that
       surface on hover/focus of the `?` button.
     - Users table action column is nowrap + width:1% so the three
       buttons (Promote/Remove → Reset Password → Ban User)
       stay inside the table boundary on every row.
     - Scan cache panel splits into "Market scan" + "Live tracking"
       sub-groups via .admin-scancache-group / -group-label.
   The legacy .readiness-intro / .readiness-blurb / .config-history-note
   selectors were orphaned by the markup change and removed.
*/

.readiness-main {
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding: 20px var(--s-5);
  /* v0.9.6 round 3 widened to 1240px to fit the action dropdown +
     Save Change column. Round 4 reverted to 1100px because the Users
     table is now expandable row-cards. v1.0.4 removed the Email
     column (was always rendering as em-dash; the email-registration
     plan was retired with the Liability Sweep), leaving 2 data
     columns: Username / Last log-in. /admin reads as a calm sibling
     of /profile and /market. */
  max-width: 1100px;
  margin: 0 auto;
}

/* v0.9.6 — Page head chrome. Mirrors .profile-page-head from §29 so
   /admin reads as a sibling page inside the same chrome family. The H1
   sits flush at the top of the centred main column; the amber-pulse
   subline tells the admin in one line what the page is for. The
   .live-dot is static here (no animation) for the same reason it's
   static on /landing — the conic countdown belongs to /market only. */
.admin-page-head {
  margin-bottom: 4px;
}

.admin-page-head h1 {
  font-size: var(--t-h1);
  font-weight: 500;
  letter-spacing: -0.01em;
  margin: 0 0 6px;
  color: var(--text-strong, var(--text));
}

.admin-page-head .admin-page-sub {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.82rem;
  color: var(--text-muted);
  margin: 0;
}

.admin-page-head .live-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--amber);
  box-shadow: 0 0 0 4px var(--amber-soft);
  flex-shrink: 0;
}

/* v0.9.6 — Panel head: section title + `?` info-tip on the right.
   Used inside every <section> on /admin. The h2 keeps the same
   caps-label treatment the v0.7.4 panel headings had so the visual
   shape of the panels is preserved; the verbose paragraph moved into
   the info-tip popover.

   Background is explicitly transparent (matches the panel body surface)
   so the heading doesn't read as a "darker box" stacked on top of the
   panel. A 1px amber outline frames the heading row
   so it reads as a labelled boundary instead of a tonal step. */
.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--s-4);
  background: transparent;
  border: 1px solid var(--amber);
  border-radius: var(--r-1);
  padding: 8px 14px;
}

.panel-head h2 {
  font-size: var(--t-small);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0;
}

/* v0.9.6 — `?` info-tip primitive. The button is a 20px circle that
   sits at the right edge of every panel-head; on :hover or
   :focus-within (keyboard) the .info-tip-pop popover reveals below it.
   Pure CSS, no JS. The popover uses surface-2 + a 1px rule so it reads
   as a sibling of every other elevated surface in the system. The
   pointer-events: none on idle state lets clicks behind the popover
   pass through; we re-enable on hover. */
.info-tip {
  position: relative;
}

.info-tip-btn {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: transparent;
  border: 1px solid var(--rule);
  color: var(--text-muted);
  font-size: 0.70rem;
  font-weight: 600;
  line-height: 1;
  cursor: help;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  font-family: inherit;
  transition:
    border-color 0.15s var(--easeOut),
    color        0.15s var(--easeOut);
}

.info-tip-btn:hover,
.info-tip-btn:focus-visible {
  border-color: var(--accent);
  color: var(--accent);
  outline: none;
}

.info-tip-pop {
  position: absolute;
  top: 26px;
  right: 0;
  width: 280px;
  background: var(--surface-2, oklch(0.24 0.02 255));
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  padding: 10px 12px;
  font-size: 0.74rem;
  line-height: 1.55;
  color: var(--text);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  text-align: left;
  opacity: 0;
  visibility: hidden;
  transition:
    opacity    0.12s var(--easeOut),
    visibility 0.12s var(--easeOut);
  z-index: 10;
  pointer-events: none;
}

.info-tip:hover .info-tip-pop,
.info-tip:focus-within .info-tip-pop {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

.readiness-controls {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
  background: var(--surface-1);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  padding: var(--s-3) var(--s-4);
}

/* readiness-search input rules are in §5 core inputs */

#readiness-list-panel {
  background: var(--surface-1);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  padding: var(--s-3) var(--s-4);
}

.readiness-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.rd-row {
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  background: var(--bg);
}

.rd-row-header {
  width: 100%;
  background: transparent;
  border: 0;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
  display: grid;
  grid-template-columns:
    18px
    auto
    minmax(160px, 2fr)
    minmax(80px, 0.7fr)
    minmax(110px, 1fr)
    minmax(90px, 0.9fr)
    minmax(90px, 0.9fr);
  gap: 12px;
  align-items: center;
  padding: 8px 12px;
}

.rd-row-header:hover {
  background: var(--surface-1);
}

.rd-chevron {
  color: var(--text-muted);
  font-size: 0.85rem;
  width: 18px;
  text-align: center;
}

.rd-name {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.rd-count {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--text);
}

.rd-count.rd-count-ready {
  color: var(--price-up);
}

.rd-last {
  color: var(--text-muted);
  font-size: 0.82rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.rd-span {
  color: var(--text-muted);
  font-size: 0.82rem;
  white-space: nowrap;
}

.rd-warn {
  font-size: 0.78rem;
  font-weight: 600;
  white-space: nowrap;
  text-align: right;
}

.rd-warn-active {
  color: var(--amber);
}

.rd-expand {
  border-top: 1px dashed var(--rule);
  padding: 12px 16px 14px 36px;
}

.rd-expand[hidden] {
  display: none;
}

.rd-trades-table {
  width: 100%;
}

.rd-trade-num {
  color: var(--text-muted);
  width: 36px;
}

/* ── Optimizer Trade Readiness — Predicted-vs-Actual delta table (v0.9.6.1) ──
   Lives inside .rd-expand. The all-time row sits between thead and the
   per-trade rows; its label cell spans the # + Trade started columns.
   Tier classes (.delta-good / .delta-drift / .delta-off) carry the
   accuracy colouring; .delta-off includes a ⚠ glyph in JS for colour-
   blind-safe state per DESIGN.md's icon-and-colour rule. */
.rd-pva-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--t-small);
  font-variant-numeric: tabular-nums;
}

.rd-pva-table th,
.rd-pva-table td {
  text-align: left;
  padding: 6px 10px;
  border-bottom: 1px solid var(--rule);
}

.rd-pva-table th {
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  font-weight: 600;
}

.rd-pva-table th.rd-pva-num   { width: 40px; }
.rd-pva-table th.rd-pva-delta-h,
.rd-pva-table td.rd-pva-delta { text-align: right; }

.rd-pva-table tbody tr:last-child td {
  border-bottom: 0;
}

.rd-pva-alltime-label {
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
  color: var(--amber);
}

.rd-pva-table tr.rd-pva-alltime td.rd-pva-delta {
  font-weight: 600;
}

.rd-pva-table .delta-good  { color: var(--green); }
.rd-pva-table .delta-drift { color: var(--text); }
.rd-pva-table .delta-off   { color: var(--red); }
.rd-pva-table .rd-pva-empty { color: var(--text-muted); }

/* Narrow-screen variant */
@media (max-width: 760px) {
  .rd-row-header {
    grid-template-columns: 18px auto 1fr auto;
    grid-template-areas:
      "chev image name  count"
      "chev image last  warn"
      "chev image span  span";
    row-gap: 4px;
  }
  .rd-chevron                  { grid-area: chev; }
  .rd-row-header > .item-image { grid-area: image; align-self: start; }
  .rd-name                     { grid-area: name; }
  .rd-count                    { grid-area: count; text-align: right; }
  .rd-last                     { grid-area: last; }
  .rd-span                     { grid-area: span; }
  .rd-warn                     { grid-area: warn; text-align: right; }

  .rd-expand { padding-left: 18px; }
}

/* ── Config History ──────────────────────────────────────────────── */

#config-history {
  background: var(--surface-1);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  padding: var(--s-4);
  margin-top: var(--s-4);
}

/* v0.9.6 — #config-history h2 + .config-history-note rules removed.
   The h2 is now styled by the .panel-head h2 rule above (same caps
   treatment, same colour); the note paragraph was deleted from the
   markup and the same text lives in the info-tip popover instead. */

.config-history-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.82rem;
  margin: 0 auto;
}

.config-history-table th {
  text-align: left;
  color: var(--text-muted);
  font-weight: 600;
  font-size: 0.75rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 0 8px 6px 0;
  border-bottom: 1px solid var(--rule);
}

.config-history-table td {
  padding: 6px 8px 6px 0;
  border-bottom: 1px solid var(--rule);
  vertical-align: middle;
}

.config-history-table tr:last-child td {
  border-bottom: none;
}

/* Restore button */
.cfg-restore-btn {
  background: transparent;
  border: 1px solid var(--rule);
  color: var(--text-muted);
  border-radius: var(--r-1);
  padding: 3px 10px;
  font-size: 0.76rem;
  cursor: pointer;
  transition: border-color 0.15s var(--easeOut), color 0.15s var(--easeOut);
}

.cfg-restore-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.cfg-restore-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.config-history-table tr.cfg-current td {
  color: var(--text);
  font-weight: 500;
}

/* ── Admin dashboard ─────────────────────────────────────────────── */

.admin-main {
  /* Inherits .readiness-main spacing */
}

#admin-users-panel,
#admin-scancache-panel,
#admin-readiness-panel {
  background: var(--surface-1);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  padding: var(--s-4);
}

/* v0.9.6 — #admin-users-panel h2 / #admin-scancache-panel h2 /
   #admin-readiness-panel h2 selectors removed; the .panel-head h2 rule
   (defined above) is the single source of caps-label styling for every
   admin section. */

.admin-users-list {
  margin-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* v0.9.6 round 4 — Users table converted to expandable row-cards.
   Same pattern as /profile completed-trade rows + /admin readiness
   rows. The condensed row shows username + last log-in; clicking the
   row reveals an expand panel with date created + the three action
   buttons (Promote/Remove / Reset Password / Ban User). The
   .admin-users-table / .admin-users-table th / td selectors that
   styled the v0.7.4 tabular layout were removed alongside the markup
   that referenced them.

   v1.0.4 (2026-05-09) — Email column removed (was always em-dash; the
   email-registration plan was retired with the Liability Sweep).
   Grid templates collapsed from 4 columns
   (chevron / username / email / last-login) to 3
   (chevron / username / last-login).

   Round-3 selectors also removed (orphans):
     .admin-action-select  — dropdown was retired in round 4.
     .admin-save-btn       — Save Change button retired in round 4.
   The legacy round-1 buttons (.admin-toggle-btn, .admin-reset-btn,
   .admin-ban-btn) are kept since they're still used inside the
   expand panel.
*/

/* Caps-label header row above the user list — disambiguates the
   columns without a real <thead>. Same 3-column grid
   (chevron / username / last log-in) as every user row below it. */
.admin-users-list-head {
  display: grid;
  grid-template-columns:
    18px
    minmax(180px, 2fr)
    minmax(130px, 1fr);
  gap: 14px;
  align-items: center;
  padding: 0 14px 8px;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 4px;
}

.admin-users-head-chev {
  /* Empty placeholder so the username column lines up with the
     row's chevron column below. */
}

.admin-users-head-label {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* Row card. Same surface treatment as .rd-row in the readiness
   panel — 1px hairline + bg surface, hover bumps to surface-2. */
.admin-user-row {
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  background: var(--bg);
}

/* Row header (always visible; click toggles the expand panel). */
.admin-user-row-header {
  width: 100%;
  background: transparent;
  border: 0;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
  display: grid;
  grid-template-columns:
    18px
    minmax(180px, 2fr)
    minmax(130px, 1fr);
  gap: 14px;
  align-items: center;
  padding: 10px 14px;
}

.admin-user-row-header:hover {
  background: var(--surface-1);
}

.admin-user-chev {
  color: var(--text-muted);
  font-size: 0.85rem;
  width: 18px;
  text-align: center;
}

.admin-user-name {
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.admin-user-lastlogin {
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Expand panel — sits below the header row; revealed when the user
   clicks the row. Border-top dashed rule mirrors the readiness
   panel's expand chrome so the two surfaces feel like siblings. */
.admin-user-row-expand {
  border-top: 1px dashed var(--rule);
  padding: 14px 18px 14px 36px;
}

.admin-user-row-expand[hidden] {
  display: none;
}

/* The "Date created" line inside the expand panel. Compact dl/dt/dd
   pattern matching .admin-scancache-grid so admins read both
   surfaces the same way. */
.admin-user-row-meta {
  display: grid;
  grid-template-columns: max-content max-content;
  column-gap: 18px;
  row-gap: 4px;
  margin: 0 0 var(--s-3);
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}

.admin-user-row-meta dt {
  color: var(--text-muted);
  font-size: 0.74rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.admin-user-row-meta dd {
  margin: 0;
  color: var(--text);
}

/* Three action buttons inside the expand panel. */
.admin-user-row-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}

.admin-users-muted {
  color: var(--text-muted);
}

.admin-user-link {
  color: var(--accent);
  text-decoration: none;
  font-weight: 600;
}

.admin-user-link:hover {
  text-decoration: underline;
}

/* admin-role-badge is in §7 shared badges */

/* Reset Password button */
.admin-reset-btn {
  background: transparent;
  border: 1px solid var(--rule);
  color: var(--text-muted);
  border-radius: var(--r-1);
  padding: 4px 12px;
  font-size: 0.78rem;
  cursor: pointer;
  transition: border-color 0.15s var(--easeOut), color 0.15s var(--easeOut);
}

.admin-reset-btn:hover {
  border-color: oklch(0.50 0.10 75);
  color: var(--amber);
}

.admin-reset-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* Promote / Remove Admin button */
.admin-toggle-btn {
  background: transparent;
  border: 1px solid var(--rule);
  color: var(--text-muted);
  border-radius: var(--r-1);
  padding: 4px 12px;
  font-size: 0.78rem;
  cursor: pointer;
  transition: border-color 0.15s var(--easeOut), color 0.15s var(--easeOut);
}

.admin-toggle-btn:hover {
  border-color: var(--price-up);
  color: var(--price-up);
}

.admin-toggle-btn--demote:hover {
  border-color: var(--price-down);
  color: var(--price-down);
}

.admin-toggle-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* v0.9.6 round 4 — .admin-actions-cell removed (was the round-3
   table-cell wrapper for the dropdown + Save Change). Its successor
   .admin-user-row-actions lives inside the expand panel and is
   defined above. The round-3 .admin-action-select and .admin-save-btn
   rules were also removed; the dropdown/Save-Change pattern is
   retired in favour of the row-card expand. */

/* Ban User button. Visual-only for now; the click handler surfaces a
   "planned, not yet wired" notice. Destructive vocabulary at rest (red
   text) and on hover (red border + brighter red text); matches the
   Cancel Order / Delete Trade button shape used elsewhere in the system. */
.admin-ban-btn {
  background: transparent;
  border: 1px solid var(--rule);
  color: var(--red, oklch(0.65 0.20 25));
  border-radius: var(--r-1);
  padding: 4px 12px;
  font-size: 0.78rem;
  cursor: pointer;
  font-family: inherit;
  transition: border-color 0.15s var(--easeOut), color 0.15s var(--easeOut);
}

.admin-ban-btn:hover {
  border-color: var(--red, oklch(0.65 0.20 25));
  color: oklch(0.78 0.16 25);
}

/* scanCache stats panel */
/* Market scan + Live tracking sub-groups sit side-by-side rather than
   stacked vertically — shrinks the Scan cache panel's height. The flex
   container centres the two groups together; on narrow viewports they
   fold back to a stacked layout via flex-wrap so nothing clips. */
.admin-scancache-stats {
  margin-top: 10px;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: flex-start;
  gap: var(--s-6, 40px);
}

.admin-scancache-group {
  margin: 0;
  width: max-content;
  max-width: 100%;
}

.admin-scancache-group-label {
  font-size: 0.70rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 8px;
}

.admin-scancache-grid {
  display: grid;
  grid-template-columns: max-content max-content;
  column-gap: 28px;
  row-gap: 6px;
  margin: 0;
  font-size: 0.85rem;
}

.admin-scancache-grid dt {
  color: var(--text-muted);
  font-size: 0.74rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.admin-scancache-grid dd {
  margin: 0;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* v0.9.6 — Refresh stats button now sits in a centered row below both
   sub-groups, not stuck to the bottom of the second sub-group. */
.admin-scancache-refresh-row {
  display: flex;
  justify-content: center;
  margin-top: var(--s-4);
}

.admin-scancache-refresh {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--rule);
  border-radius: var(--r-1);
  padding: 6px 14px;
  font-size: 0.80rem;
  cursor: pointer;
  font-family: inherit;
  transition: border-color 0.15s var(--easeOut), color 0.15s var(--easeOut);
}

.admin-scancache-refresh:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* ─── v1.0.2.1 follow-on — /admin tab layout ────────────────────────────
   Splits the /admin page from a long vertical scroll into four tabs in a
   left rail (Overview / Users / Configs / Readiness). The page-head H1 +
   subline stay fixed at the top; below it, the new .admin-tab-layout
   grid carries a 180px rail on the left and the active tab's panels on
   the right. Each .admin-tab is a sibling of every other; setActiveTab()
   in admin.html toggles the .admin-tab--active class and the hidden
   attribute. URL hash drives initial selection so /admin#users loads
   the Users tab directly. */
.admin-tab-layout {
  display: grid;
  grid-template-columns: 180px 1fr;
  gap: var(--s-5, 24px);
  margin-top: var(--s-4, 16px);
}

.admin-tab-rail {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-right: 12px;
  border-right: 1px solid var(--rule-soft);
  /* Sticks just under the page-head as the user scrolls a long tab body
     (Readiness can scroll). 80px clears the header chrome with margin. */
  position: sticky;
  top: 80px;
  align-self: start;
}

.admin-tab-btn {
  text-align: left;
  background: transparent;
  border: none;
  border-left: 2px solid transparent;
  padding: 10px 14px;
  font-size: 0.86rem;
  font-family: inherit;
  color: var(--text);
  border-radius: 0 var(--r-1, 4px) var(--r-1, 4px) 0;
  cursor: pointer;
  transition:
    background-color 0.12s var(--easeOut),
    color            0.12s var(--easeOut),
    border-color     0.12s var(--easeOut);
}

.admin-tab-btn:hover {
  background: var(--surface-2);
  color: var(--text);
}

.admin-tab-btn--active {
  background: var(--accent-soft);
  border-left-color: var(--accent);
  color: oklch(0.78 0.10 255);
  font-weight: 500;
}

.admin-tab-btn--active:hover {
  background: var(--accent-soft);    /* hover doesn't fight the active state */
}

.admin-tab-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* Each tab panel is hidden via the [hidden] attribute and re-revealed by
   setActiveTab(). The .admin-tab--active class is purely visual (carries
   no display rule) — the hidden attribute is the structural gate so any
   accidental CSS conflict can't accidentally reveal a non-active panel. */
.admin-tab[hidden] {
  display: none;
}

.admin-tab-panels > .admin-tab {
  /* Vertical rhythm between stacked sections inside a tab (Overview holds
     two; the others hold one). */
  display: flex;
  flex-direction: column;
  gap: var(--s-5, 24px);
}

/* ─── v1.0.2.1 — Site activity panel ─────────────────────────────────────
   Anonymous-only funnel counters + 30d sparklines on /admin. The chrome
   reuses the existing #admin-scancache-panel section / .panel-head /
   .info-tip pattern; only the tile + sparkline grid is new. */
.admin-activity-stats {
  margin-top: 10px;
}

/* Four headline-number tiles. Same compact-card register as the existing
   .admin-scancache-grid stat cells. Auto-fit so the row collapses to two
   columns on narrow viewports without needing media queries. */
.admin-activity-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px;
  margin-bottom: 16px;
}

.admin-activity-tile {
  padding: 12px 14px;
  background: var(--bg);
  border: 1px solid var(--rule-soft);
  border-radius: var(--r-1, 4px);
}

.admin-activity-tile-label {
  font-size: 0.70rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  margin-bottom: 4px;
}

.admin-activity-tile-value {
  font-size: 1.55rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  line-height: 1.1;
}

.admin-activity-tile-sub {
  font-size: 0.72rem;
  color: var(--text-muted);
  margin-top: 2px;
}

/* Three sparklines below the tiles. One per visit-counter series; the
   registered-users tile gets no sparkline (it's an outcome metric, not a
   per-day count). */
.admin-activity-sparks {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 12px;
}

.admin-activity-spark {
  padding: 8px 10px;
  background: var(--bg);
  border: 1px solid var(--rule-soft);
  border-radius: var(--r-1, 4px);
  color: var(--accent);             /* polyline picks this up via currentColor */
}

.admin-activity-spark-label {
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  margin-bottom: 4px;
}

.admin-activity-spark-svg {
  display: block;
  width: 100%;
  height: 32px;
}

/* ─── v1.0.2.1 — Online-indicator dot in the Users panel ───────────────
   Filled --green when the user has been active in the last 5 minutes;
   empty rule-soft ring otherwise. Sits inside .admin-user-name, before
   the username link. */
.admin-user-online-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: transparent;
  border: 1px solid var(--rule-soft);
  flex-shrink: 0;
  vertical-align: middle;
  margin-right: 2px;
}

.admin-user-online-dot--on {
  background: var(--green);
  border-color: var(--green);
}

/* "Viewing user X (admin)" banner */
.admin-view-banner {
  background: var(--accent-soft);
  border-bottom: 1px solid oklch(0.30 0.06 255);
  color: var(--text);
  font-size: 0.85rem;
  padding: 10px var(--s-5);
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.admin-view-banner-icon {
  font-size: 1.0rem;
  line-height: 1;
}

.admin-view-banner-text strong {
  color: oklch(0.72 0.12 255);
  letter-spacing: 0.04em;
}

.admin-view-banner-note {
  color: var(--text-muted);
  font-size: 0.78rem;
  margin-left: 4px;
}

.admin-view-banner-link {
  margin-left: auto;
  color: var(--accent);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.78rem;
}

.admin-view-banner-link:hover {
  text-decoration: underline;
}


/* ═══════════════════════════════════════════════════════════════════════
   §16  ITEM IMAGES
   ═══════════════════════════════════════════════════════════════════════ */

.item-image {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  overflow: hidden;
  box-sizing: border-box;
}

.item-image-img {
  display: block;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  image-rendering: pixelated;
}

.item-image-detail {
  width: 64px;
  height: 64px;
  padding: 4px;
}

.item-image-icon {
  width: 36px;
  height: 36px;
  padding: 2px;
  background: transparent;
  border: 1px solid transparent;
}

.item-image.image-missing {
  background: var(--surface-1);
  border: 1px dashed var(--rule);
}

/* v0.9.1 — Letter-glyph fallback when the Wiki sprite is unavailable.
   A single uppercase letter centered in the image box, tinted with the
   muted text color so it reads as a quiet placeholder, not a label. */
.item-image-glyph {
  font-weight: 700;
  color: var(--text-muted);
  line-height: 1;
  user-select: none;
}
.item-image-detail .item-image-glyph { font-size: 1.4rem; }
.item-image-icon   .item-image-glyph { font-size: 0.85rem; }

.item-image-detail.image-missing::after {
  content: none; /* v0.9.1 — replaced by .item-image-glyph span */
}

.item-image-icon.image-missing {
  /* deliberate: glyph span handles the visual */
}


/* ═══════════════════════════════════════════════════════════════════════
   §17  SLOT CHART SVG
   ═══════════════════════════════════════════════════════════════════════ */

.slot-chart {
  position: relative;
  width: 100%;
  height: 60px;
  margin: 6px 0 4px;
}

.slot-chart-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* HTML axis overlays */
.chart-axis-y,
.chart-axis-x {
  position: absolute;
  pointer-events: none;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--text-muted);
  line-height: 1;
}

.chart-axis-y {
  left: 0;
  text-align: right;
  padding-right: 3px;
  font-size: 0.58rem;
}

.chart-large .chart-axis-y { font-size: 0.78rem; }

.chart-axis-x {
  transform: translateX(-50%);
  font-size: 0.55rem;
  font-style: italic;
  letter-spacing: 0.03em;
}

.chart-large .chart-axis-x { font-size: 0.72rem; }

/* Thicker strokes for the larger portfolio chart */
.slot-chart-svg.chart-strokes-large .chart-line-high,
.slot-chart-svg.chart-strokes-large .chart-line-low {
  stroke-width: 2;
}

.slot-chart-svg.chart-strokes-large .chart-kalman-high,
.slot-chart-svg.chart-strokes-large .chart-kalman-low {
  stroke-width: 1.5;
  stroke-dasharray: 3 3;
}

.slot-chart-svg.chart-strokes-large .chart-trade-start,
.slot-chart-svg.chart-strokes-large .chart-trade-filled,
.slot-chart-svg.chart-strokes-large .chart-trade-sold {
  stroke-width: 1.4;
  stroke-opacity: 0.8;
  stroke-dasharray: 4 3;
}

.chart-kalman-forward {
  stroke-dasharray: 1 3;
  stroke-opacity: 0.7;
}

.slot-chart-svg.chart-strokes-large .chart-kalman-forward {
  stroke-dasharray: 2 4;
  stroke-opacity: 0.75;
}

/* Y-axis gridlines */
.chart-gridline {
  stroke: var(--text-muted);
  stroke-opacity: 0.12;
  stroke-width: 0.4;
  vector-effect: non-scaling-stroke;
}

.slot-chart-waiting {
  font-size: 0.78rem;
  color: var(--text-muted);
  font-style: italic;
}

/* Solid price lines */
.chart-line-high {
  fill: none;
  stroke: var(--chart-high);
  stroke-width: 1.4;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.chart-line-low {
  fill: none;
  stroke: var(--chart-low);
  stroke-width: 1.4;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* Kalman lines */
.chart-kalman-high {
  fill: none;
  stroke: var(--chart-high);
  stroke-width: 1;
  stroke-dasharray: 2 2;
  stroke-opacity: 0.85;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.chart-kalman-low {
  fill: none;
  stroke: var(--chart-low);
  stroke-width: 1;
  stroke-dasharray: 2 2;
  stroke-opacity: 0.85;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* Forward cone of uncertainty */
.chart-cone-high {
  fill: var(--chart-high);
  fill-opacity: 0.16;
  stroke: none;
}

.chart-cone-low {
  fill: var(--chart-low);
  fill-opacity: 0.16;
  stroke: none;
}

/* Larger chart variant (portfolio/dashboard expand panels) */
.pt-chart-wrap {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Heading centred inside the wrap */
.pt-chart-wrap .pt-section-heading {
  text-align: center;
}

/* Four-item legend row */
.pt-chart-key {
  display: flex;
  justify-content: center;
  gap: 14px;
  flex-wrap: wrap;
}

.pt-key-item {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 0.72rem;
  color: var(--text-muted);
  white-space: nowrap;
}

.pt-chart {
  height: 200px;
  width: 100%;
  margin: 0;
  padding: 0;
}

/* Dashboard completed-trades chevron toggle + chart expand */
.trade-chart-toggle {
  cursor: pointer;
  margin-left: 6px;
  font-size: 0.75rem;
  font-weight: 400;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  text-transform: lowercase;
  user-select: none;
}

.trade-chart-toggle:hover {
  color: var(--accent);
}

.trade-chart-expand {
  grid-column: 1 / 4;
  grid-row: 4;
  margin-top: 8px;
  padding: 10px 12px;
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  height: 170px;
}

.trade-chart-expand[hidden] {
  display: none;
}

/* Vertical dotted markers for trade-lifecycle moments */
.chart-trade-start,
.chart-trade-filled,
.chart-trade-sold {
  stroke-width: 1;
  stroke-dasharray: 2 2;
  stroke-opacity: 0.55;
  vector-effect: non-scaling-stroke;
}

.chart-trade-start  { stroke: var(--chart-trade-start);  }
.chart-trade-filled { stroke: var(--chart-trade-filled); }
.chart-trade-sold   { stroke: var(--chart-trade-sold);   }


/* ═══════════════════════════════════════════════════════════════════════
   §18  SCAN EXPAND SPARKLINE
   ═══════════════════════════════════════════════════════════════════════ */

.scan-expand-sparkline {
  width: 100%;
  height: 100%;
  display: block;
}

/* Common stroke shape for both buy/sell solid lines + their projections */
.scan-expand-sparkline-line,
.scan-expand-sparkline-proj {
  fill: none;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* Solid history strokes */
.scan-expand-sparkline-line.scan-expand-sparkline-buy {
  stroke: var(--chart-high, var(--amber));
  stroke-width: 1.4;
  stroke-opacity: 1;
}

.scan-expand-sparkline-line.scan-expand-sparkline-sell {
  stroke: var(--chart-low, var(--accent));
  stroke-width: 1.0;
  stroke-opacity: 0.55;
}

/* Forward projections */
.scan-expand-sparkline-proj.scan-expand-sparkline-buy {
  stroke: var(--chart-high, var(--amber));
  stroke-width: 1.2;
  stroke-dasharray: 2 3;
  stroke-opacity: 0.85;
}

.scan-expand-sparkline-proj.scan-expand-sparkline-sell {
  stroke: var(--chart-low, var(--accent));
  stroke-width: 1.0;
  stroke-dasharray: 2 3;
  stroke-opacity: 0.45;
}

/* Vertical "now" separator */
.scan-expand-sparkline-now {
  stroke: var(--text-muted);
  stroke-width: 0.6;
  stroke-dasharray: 1 2;
  stroke-opacity: 0.5;
  vector-effect: non-scaling-stroke;
}

.scan-expand-sparkline-label {
  font-size: 0.58rem;
  font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", system-ui, sans-serif;
  fill: var(--text-muted);
}


/* ═══════════════════════════════════════════════════════════════════════
   §19  TOOLTIP SYSTEM
   ═══════════════════════════════════════════════════════════════════════ */

[data-tooltip] {
  position: relative;
}

[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 6px);
  left: 0;
  right: auto;
  z-index: 50;
  min-width: 220px;
  max-width: 320px;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--accent);
  border-radius: var(--r-2);
  padding: 8px 10px;
  font-size: 0.78rem;
  line-height: 1.35;
  text-transform: none;
  letter-spacing: 0;
  white-space: normal;
  pointer-events: none;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.12s ease, transform 0.12s ease;
  box-shadow: 0 6px 18px oklch(0.10 0.02 250 / 0.55);
}

[data-tooltip]:hover::after,
[data-tooltip]:focus-within::after {
  opacity: 1;
  transform: translateY(0);
}


/* ═══════════════════════════════════════════════════════════════════════
   §20  ACTIVE PULSE INDICATOR
   ═══════════════════════════════════════════════════════════════════════ */

.active-pulse {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.72rem;
  color: var(--text-muted);
  font-style: italic;
  white-space: nowrap;
}

.active-pulse[hidden] {
  display: none;
}

.active-pulse-circle {
  display: inline-block;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--poll-track);
}


/* ═══════════════════════════════════════════════════════════════════════
   §21  LANDING PAGE
   ═══════════════════════════════════════════════════════════════════════
   Centred-spine layout. The page is purely informational — brand-mark
   hero + wordmark on one row, h1 pitch, body copy, CTA stack, fineprint
   — all centred
   horizontally and vertically on the dark-navy field.
   ═══════════════════════════════════════════════════════════════════════ */

body.landing-page {
  background: var(--bg);
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s-6) var(--s-5);
}

/* §3 sets `main { display: grid; grid-template-columns: 1fr 260px }` for
   the cockpit layout (content + active-trades sidebar). /landing has no
   sidebar slot, so we reset the grid here — the shell becomes a normal
   block element that the body's flex centering can size and place freely.
   Without this override the content is squeezed into the 1fr column with
   260px reserved for a sidebar that does not exist on this page. */
body.landing-page main.landing-shell {
  display: block;
  grid-template-columns: none;
  padding: 0;
  margin: 0;
  width: 100%;
  max-width: 560px;
}

.landing-pitch {
  text-align: center;
}

/* ── Brand row (i + OSRS-INVEST inline) ──────────────────────────────── */

.landing-brand-row {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: var(--s-5);
}

.landing-wordmark-hero {
  font-size: var(--t-h2);
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--text);
  line-height: 1;
}

/* ── Pitch text ───────────────────────────────────────────────────────── */

.landing-pitch h1 {
  font-size: var(--t-h1);
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1.25;
  color: var(--text);
  margin-bottom: var(--s-3);
}

.landing-pitch-body {
  font-size: var(--t-body);
  color: var(--text-muted);
  line-height: 1.6;
  margin: 0 auto var(--s-5);
  max-width: 52ch;
}

/* ── CTAs (centred stack — primary on top, two ghosts beneath) ──────── */

.landing-cta-stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
}

.landing-cta-row {
  display: flex;
  gap: var(--s-2);
  justify-content: center;
}

.landing-cta-primary {
  display: inline-flex;
  align-items: center;
  /* v0.9.7 — primary-CTA background uses --accent-deep for WCAG AA. */
  background: var(--accent-deep);
  color: var(--text);
  border: 1px solid var(--accent-deep);
  border-radius: var(--r-2);
  padding: 10px 18px;
  font-size: var(--t-body);
  font-weight: 500;
  text-decoration: none;
  transition: opacity 0.15s var(--easeOut);
}

.landing-cta-primary:hover {
  opacity: 0.85;
}

.landing-cta {
  display: inline-flex;
  align-items: center;
  background: transparent;
  color: var(--text);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  padding: 9px 18px;
  font-size: var(--t-body);
  text-decoration: none;
  transition: border-color 0.15s var(--easeOut);
}

.landing-cta:hover {
  border-color: var(--accent);
}

.landing-fineprint {
  margin: var(--s-5) auto 0;
  font-size: var(--t-micro);
  color: var(--text-faint);
  line-height: 1.55;
  max-width: 52ch;
}


/* ═══════════════════════════════════════════════════════════════════════
   §22  LIVE PRICE + KALMAN ROWS
   ═══════════════════════════════════════════════════════════════════════ */

.slot-live-row {
  margin-top: 5px;
  padding-top: 5px;
  border-top: 1px solid var(--rule);
  color: var(--text-muted);
  font-size: 0.76rem;
}

.slot-live-label {
  color: var(--text-muted);
  font-size: 0.70rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.slot-live-sep {
  margin: 0 4px;
  color: var(--rule);
}

.slot-live-waiting {
  font-style: italic;
}

.slot-live-up   { color: var(--price-up);   }
.slot-live-down { color: var(--price-down); }

/* ── Kalman row ──────────────────────────────────────────────────── */

.slot-kalman-row {
  margin-top: 3px;
  padding-top: 4px;
  border-top: 1px dashed var(--rule);
  font-size: 0.76rem;
}

.slot-kalman-label {
  display: inline-block;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  padding: 1px 6px;
  border-radius: 3px;
  margin-right: 4px;
  vertical-align: middle;
  background: oklch(0.22 0.04 180 / 0.6);
  color: oklch(0.74 0.14 180);
}

.slot-kalman-up   { color: var(--price-up);   font-weight: 600; }
.slot-kalman-down { color: var(--price-down); font-weight: 600; }
.slot-kalman-flat { color: var(--text); }

.slot-kalman-time {
  color: var(--text-muted);
  font-size: 0.70rem;
  margin-left: 3px;
}

.slot-kalman-overdue {
  color: var(--amber);
}

.slot-kalman-waiting {
  font-style: italic;
}


/* ═══════════════════════════════════════════════════════════════════════
   §23  RESPONSIVE MEDIA QUERIES (PORTFOLIO)
   ═══════════════════════════════════════════════════════════════════════ */

/* The main responsive breakpoints for metrics and portfolio rows are
   already declared inline in §14 and §15 above. This section is a
   catch-all for any additional responsive rules. */


/* ═══════════════════════════════════════════════════════════════════════
   §24  /MARKET PAGE HEAD + STAT TILES + DEV-MODE GATE
   ═══════════════════════════════════════════════════════════════════════ */

/* Dev-only gate. Anything tagged `.dev-only-control` (dev-only column
   toggles, the Trading row badge, predicted-vs-actual columns) is
   hidden from public users and reveals when devMode.js stamps
   `body.dev-mode`. The `display: none` here loses to the page's own
   `display:` rules in the cascade without being scoped to a specific
   tag, but `:not(.dev-mode)` raises the specificity enough that it
   wins against the consumer rules. Items that already use `[hidden]`
   get re-promoted to display via CSS; the body-class gate trumps that
   for visibility. */
body:not(.dev-mode) .dev-only-control {
  display: none !important;
}

/* Page head. Sits above #scan-panel on /market and anchors the page with
   an H1, a live-status subline, and the 4 stat tiles. */
.page-head {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  margin-bottom: var(--s-2);
}

.page-head h1 {
  margin: 0;
  font-size: var(--t-h1);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text);
}

.page-sub {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  color: var(--text-muted);
  font-size: var(--t-body);
}

/* The amber pulse dot. A 6px circle wrapped in a faint amber halo so the
   "live" connotation reads at a glance — see DESIGN.md §5 (Pulse
   Indicator). The halo uses a layered box-shadow rather than a separate
   element. Animation is a quiet 1.6s opacity breath; reduced-motion strips
   it via the existing global rule in §1.
   v0.9.6.3: .pulse-scanned variant uses green for "fresh scan" state; reverts
   to amber once the scan age exceeds 60 s. */
.page-sub-pulse {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--amber);
  box-shadow: 0 0 0 4px var(--amber-soft);
  animation: page-sub-pulse 1.6s var(--easeOut) infinite;
}

.page-sub-pulse.pulse-scanned {
  background: var(--green);
  box-shadow: 0 0 0 4px oklch(from var(--green) l c h / 0.18);
}

@keyframes page-sub-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}

/* The 4-tile stat grid. 1px hairlines between tiles via the
   `gap: 1px; background: var(--rule)` trick — same shape as the Lane B4
   mockup. Tiles share a single rounded outer border. */
.market-stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  overflow: hidden;
}

.market-stat {
  background: var(--surface-1);
  padding: var(--s-3) var(--s-4);
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.market-stat-label {
  font-size: var(--t-micro);
  color: var(--text-muted);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: 600;
}

.market-stat-value {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 22px;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--text);
  line-height: 1.2;
  /* Stat values can be long ("1.2M gp/hr"); clip rather than wrap so the
     tile heights stay equal across the row. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Subline beneath each stat value. Tells the user where the number came
   from in 1-3 words ("Dragon dart", "5 free", "12 flips"). Stays muted by
   default; the .opp variant pushes amber for "good outcome here". */
.market-stat-delta {
  font-size: var(--t-small);
  color: var(--text-muted);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.market-stat-delta.opp,
.market-stat-value.opp {
  color: var(--amber);
}

.market-stat-value.profit,
.market-stat-delta.profit {
  color: var(--green);
}

.market-stat-value.loss,
.market-stat-delta.loss {
  color: var(--red);
}

/* v0.9.2 round 2 — Top Trade tile carries 4 pieces of info: item name +
   gp/hr · ROI · profit. Override the value style to use the body font (the
   item name is text, not a number) and add a meta row underneath that
   stacks the three numeric stats with the same mono treatment as the rest
   of the system. */
.market-stat-value.market-stat-value-name {
  font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", system-ui, sans-serif;
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.005em;
}

.market-stat-meta {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-small);
  color: var(--text-muted);
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.market-stat-meta-item {
  white-space: nowrap;
}

.market-stat-meta-item.opp    { color: var(--amber); }
.market-stat-meta-item.profit { color: var(--green); }
.market-stat-meta-item.loss   { color: var(--red);   }

.market-stat-meta-sep {
  color: var(--text-faint);
}

/* /market's main grid is owned by sidebar.css. The display /
   grid-template-columns / grid-template-areas / gap / align-items /
   grid-area assignments all live in sidebar.css's `body.market-page
   main` block. This rule preserves only the §24 contributions that
   don't conflict: the 1860px max-width that lets /market use more
   horizontal space than the 1500px default in §3 main. */
body.market-page main {
  grid-template-rows: auto auto;
  max-width: 1860px;
}


/* ═══════════════════════════════════════════════════════════════════════
   §25  v0.9.2 — EXPAND-ROW META DICTIONARY + ACTIONS ROW
   ═══════════════════════════════════════════════════════════════════════ */

/* The pre-v0.9.2 expand panel was a 2-column flex: chart on the left, six
   chips on the right, advisory caption beneath. v0.9.2 formalises the
   right column as a 2-column dt/dd dictionary (the meta block), then
   appends a row of action buttons spanning both columns. The chips are
   preserved inside the meta block — they pre-date v0.9.2 and the
   Lane B4 mockup uses a richer dictionary, so the meta block carries
   both the chips and the new key/value rows. */
.scan-expand-meta {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 6px var(--s-4);
  font-size: var(--t-small);
  align-content: start;
  padding-top: var(--s-2);
  margin-top: var(--s-2);
  border-top: 1px solid var(--rule-soft);
}

.scan-expand-meta dt {
  color: var(--text-muted);
  margin: 0;
}

.scan-expand-meta dd {
  margin: 0;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.scan-expand-meta dd.opp {
  color: var(--amber);
}

.scan-expand-meta dd.profit {
  color: var(--green);
}

.scan-expand-actions {
  margin-top: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px solid var(--rule-soft);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}

.scan-expand-action {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 6px 12px;
  border-radius: var(--r-1);
  border: 1px solid var(--rule);
  background: var(--surface-1);
  color: var(--text-muted);
  font-size: var(--t-small);
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s var(--easeOut),
              border-color 0.15s var(--easeOut),
              color 0.15s var(--easeOut);
}

.scan-expand-action:hover {
  background: var(--surface-2);
  border-color: var(--text-faint);
  color: var(--text);
}

.scan-expand-action.primary {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: oklch(0.82 0.12 255);
  font-weight: 600;
}

.scan-expand-action.primary:hover {
  /* v0.9.7 — was --accent (3.08:1, FAIL WCAG AA); --accent-deep gives 6.01:1. */
  background: var(--accent-deep);
  color: var(--text);
  border-color: var(--accent-deep);
}

.scan-expand-action.ghost {
  background: transparent;
  border-color: transparent;
}

.scan-expand-action.ghost:hover {
  background: var(--surface-2);
  border-color: var(--rule);
}


/* ═══════════════════════════════════════════════════════════════════════
   §26  v0.9.2 round 2 — EXPAND-ROW LAYOUT (chart fills, meta right-aligned)
   ═══════════════════════════════════════════════════════════════════════ */

/* Expand-panel flex-row layout. The chart takes the full remaining
   width on the left; the meta dictionary, actions, and note stack on
   the right at a fixed column width with text right-aligned (the
   "Margin" / "ROI" labels align to the right edge of the expand box;
   the chart takes the remaining space). */
/* v0.9.4 hotfix #4 — right column widened so the POST-TAX block, the
   actions row, and the advisory note no longer truncate ("346,554 g"
   was cut off "346,554 gp", the View item button was off-screen, etc.).
   Chart column gives up some horizontal real estate; the chart wrapper
   below claims back vertical space via flex: 1 to fill the column. */
.scan-expand-panel {
  display: grid;
  grid-template-columns: 1fr clamp(280px, 25%, 340px);
  align-items: stretch;
  gap: var(--s-5);
  padding: var(--s-4);
  border-top: 1px dashed var(--rule);
  flex-wrap: initial;
}

/* Chart column: stacks the SVG plot on top with an HTML legend row
   underneath. The legend lives outside the SVG so it doesn't get
   stretched by `preserveAspectRatio="none"` and doesn't collide with the
   in-plot zone labels (HISTORY / PROJECTION). */
.scan-expand-chart-col {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  width: 100%;
  min-width: 0;
}

/* v0.9.4 hotfix #4 — chart wrapper now flexes to fill the column rather
   than stamping a fixed clamp() height. The right column (POST-TAX +
   actions + note) is naturally tall; the chart column inherits that
   height via the panel's `align-items: stretch`. With `flex: 1` here,
   the chart stretches inside its flex column to consume all space the
   legend below doesn't need, eliminating dead space below the chart.
   min-height: 320px stops the chart from collapsing on hypothetical
   tiny right-column states. */
.scan-expand-chart {
  width: 100%;
  flex: 1;
  min-height: 320px;
  display: block;
}

.scan-expand-chart > svg.scan-expand-sparkline {
  width: 100%;
  height: 100%;
  display: block;
}

.scan-expand-chart > .scan-expand-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  border: 1px dashed var(--rule);
  border-radius: var(--r-2);
  color: var(--text-muted);
  background: var(--bg);
}

/* Legend row — sits below the chart, centred under the chart area.
   v0.9.6.3: changed from flex-end to center. */
.scan-expand-chart-legend {
  display: flex;
  gap: var(--s-4);
  justify-content: center;
  align-items: center;
  font-size: var(--t-micro);
  color: var(--text-muted);
  padding: 0 var(--s-2);
}

.scan-expand-chart-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.scan-expand-chart-legend-swatch {
  display: inline-block;
  width: 14px;
  height: 2px;
  border-radius: 1px;
}

.scan-expand-chart-legend-swatch.sell {
  background: var(--amber);
}

.scan-expand-chart-legend-swatch.buy {
  background: var(--accent);
}

/* Right-hand column: stacks the meta dict + actions + note vertically. The
   grid stretches across the right column's width so dt/dd rows align like
   a financial dashboard's key/value list (label left, value right). */
.scan-expand-right {
  flex: initial;
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  min-width: 0;
}

/* Meta dictionary. Lane B4 pattern: dt left-aligned to the column's left
   edge, dd right-aligned to the column's right edge, with the whole grid
   stretching full-width so the trailing edge of every value lines up
   vertically against the right edge of the panel and the leading edge of
   every label lines up vertically against the left edge of the rule above
   the actions row. The values are mono + tabular-nums so they read like a
   ledger column. */
.scan-expand-meta {
  display: grid;
  grid-template-columns: auto 1fr;
  align-content: start;
  gap: 6px var(--s-4);
  font-size: var(--t-small);
  padding-top: 0;
  margin-top: 0;
  border-top: 0;
  width: 100%;
}

.scan-expand-meta dt {
  text-align: left;
  color: var(--text-muted);
  margin: 0;
  white-space: nowrap;
}

.scan-expand-meta dd {
  text-align: right;
  margin: 0;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--text);
  white-space: nowrap;
}

.scan-expand-meta dd.opp     { color: var(--amber); }
.scan-expand-meta dd.profit  { color: var(--green); }
.scan-expand-meta dd.loss    { color: var(--red);   }

/* Actions row — right-justify so the primary Place button anchors to
   the right edge of the column, mirroring the dd values above it. The
   horizontal rule above the actions row spans the full column width;
   dt's left-aligned text meets the left edge of that rule. */
.scan-expand-actions {
  justify-content: flex-end;
  margin-top: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px solid var(--rule-soft);
}

/* Advisory caption — left-aligned to read as a sentence (right-aligned
   long-form text reads awkwardly even when the column is right-justified). */
.scan-expand-note {
  text-align: left;
  font-size: var(--t-micro);
  font-style: italic;
  color: var(--text-muted);
  line-height: 1.5;
  max-width: none;
}

/* v0.9.6.3 — Volume section heading row: "Volume" on the left, "Buy Limit — N"
   on the right, both on the same flex row with space-between. */
.scan-expand-vol-head-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s-3);
  margin-bottom: var(--s-2);
}

.scan-expand-buy-limit {
  font-size: var(--t-micro);
  color: var(--text-muted);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* On smaller windows where the meta column would crush the chart, drop
   to a stacked layout: chart full-width on top, meta below. The 1080px
   threshold matches the point at which the right rail takes too much of
   the available main width. */
@media (max-width: 1080px) {
  .scan-expand-panel {
    grid-template-columns: 1fr;
  }
  .scan-expand-actions { justify-content: flex-start; }
}


/* ─── Bigger chart — axes, gridlines, zone labels ────────────────────── */

/* The §22 width: 100%; height: 100% rule applies; no further size override
   needed here since the wrapper drives the SVG's render box. */

/* Y-axis horizontal gridlines + the bottom/left axis lines.
   v0.9.6.3: bumped visibility — rule-soft was too faint at 0.4 alpha; now
   uses a slightly brighter stroke at fuller opacity with wider dashes. */
.scan-expand-sparkline .scan-expand-grid {
  stroke: oklch(0.38 0.024 250 / 0.9);
  stroke-width: 1;
  stroke-dasharray: 3 4;
  vector-effect: non-scaling-stroke;
}

.scan-expand-sparkline .scan-expand-axis {
  stroke: var(--rule);
  stroke-width: 0.8;
  vector-effect: non-scaling-stroke;
}

.scan-expand-sparkline .scan-expand-axis-tick {
  stroke: var(--rule);
  stroke-width: 0.8;
  vector-effect: non-scaling-stroke;
}

/* Axis labels (price on the left, time below). Use a non-monospace font
   so the labels read more like axis text and less like data; tabular-nums
   keeps numeric alignment. Bumped a touch (12 → 13) to stay readable
   under the small horizontal stretch from preserveAspectRatio="none". */
.scan-expand-sparkline .scan-expand-axis-label {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  fill: var(--text-muted);
}

/* HISTORY / PROJECTION zone labels in the top margin. Caps + tracking. */
.scan-expand-sparkline .scan-expand-zone-label {
  font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", system-ui, sans-serif;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.12em;
  fill: var(--text-faint);
}


/* ═══════════════════════════════════════════════════════════════════════
   §27  v0.9.2 round 2 — STACKED PRICE CELLS + TREND CHIPS
   ═══════════════════════════════════════════════════════════════════════ */

/* Market Px and Recommendation cells: two-line stacks. The big number on
   top reads as the primary value; the muted line below carries either the
   signed post-tax margin (Market Px) or the spread (Recommendation). Both
   align to the right in the cell. */
.scan-px-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  line-height: 1.25;
  white-space: nowrap;
}

.scan-px-pair {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-body);
  font-weight: 500;
  color: var(--text);
}

.scan-px-margin,
.scan-px-spread {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-micro);
  color: var(--text-muted);
}

.scan-px-margin.scan-px-margin-pos  { color: var(--green); }
.scan-px-margin.scan-px-margin-neg  { color: var(--red);   }
.scan-px-margin.scan-px-margin-flat { color: var(--text-muted); }

/* v0.9.7.5 patch — Recommendation cell's "net … per item" line: the inner
   value span tints green / red on sign while the wrapper text ("net" /
   "per item") inherits the muted .scan-px-spread colour above. */
.scan-px-spread .scan-px-net-pos  { color: var(--green); }
.scan-px-spread .scan-px-net-neg  { color: var(--red);   }
.scan-px-spread .scan-px-net-flat { color: var(--text-muted); }

/* Trend chip — public-facing default column. Three states: uptrend (green
   tint), downtrend (red tint), sideways (muted). Slug derived from the
   raw label string; same shape as the existing tier chips so they sit
   together visually. */
.scan-trend-chip {
  display: inline-flex;
  align-items: center;
  padding: 2px var(--s-2);
  border-radius: var(--r-1);
  font-size: var(--t-micro);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
}

.scan-trend-chip.badge-trend-uptrend {
  background: var(--green-soft);
  color: var(--green);
}

.scan-trend-chip.badge-trend-downtrend {
  background: var(--red-soft);
  color: var(--red);
}

.scan-trend-chip.badge-trend-sideways {
  background: var(--surface-2);
  color: var(--text-muted);
}

/* Spike chip variants — currently only the existing badge-spike-* classes
   (from §22 / scan-tier-chip). Add gentle tints so the three states read
   distinctly even though they share the chip shape. */
.scan-tier-chip.badge-spike-spike-warning {
  background: var(--red-soft);
  color: var(--red);
}

.scan-tier-chip.badge-spike-elevated {
  background: var(--amber-soft);
  color: var(--amber);
}

.scan-tier-chip.badge-spike-normal {
  background: var(--surface-2);
  color: var(--text-muted);
}


/* v0.9.6.3 — Expand row chevron in the first pinned column. Small, muted;
   the triangle flips between ▸ (collapsed) and ▾ (expanded) via the render
   function in SCAN_COLUMNS. No additional transition — the row already
   rebuilds on toggle. */
.scan-expand-chevron {
  display: inline-block;
  width: 16px;
  text-align: center;
  font-size: 11px;
  color: var(--text-faint);
  cursor: pointer;
  user-select: none;
}


/* Columns drawer header + reset button — REMOVED v0.9.3 (round 4 cleanup).
   .scan-columns-header and .scan-columns-reset have been deleted along
   with the rest of the columns drawer chrome. */


/* ═══════════════════════════════════════════════════════════════════════
   §28  v0.9.2 ROUND 3 — /market page polish
   ═══════════════════════════════════════════════════════════════════════
   Round-3 changes consolidated below. Earlier sections kept intact so
   the diff stays small; round-3 markup uses different class names where
   it overrides round-2 behaviour, and the new style blocks below are
   the canonical source. When the Columns drawer is removed in round 4
   (post-confirmation cleanup), the round-2 .scan-expand-meta block
   above can also be deleted — round 3's expand panel uses
   .scan-expand-section / .scan-expand-kv-row instead.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Stat tiles (round 3 collapse) ─────────────────────────────────── */
/* Tiles 2-4 dropped their sub-line in round 3, leaving title + value.
   Tile 1 (Top Trade) keeps its meta line. The pre-round-3 .market-stat
   used flex-direction: column with a 4px gap, which still renders the
   collapsed shape correctly without further changes. The leftover
   .market-stat-delta rule above is harmless once the markup stops
   producing those <div>s. */

/* ── Scan controls (round 3 reshape) ───────────────────────────────── */

.scan-controls-title {
  margin: 0;
  font-size: var(--t-h3);
  font-weight: 600;
  letter-spacing: -0.005em;
}

.scan-result-count {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-small);
  color: var(--text-muted);
}

/* Page-size selector reshape: now reads "Show 25" with "Show" as a
   small muted label and the dropdown sitting tight beside it. */
.scan-control-pagesize {
  font-size: var(--t-small);
}

/* Manual Scan Market button — round 3 keeps it in the controls row but
   tightens it to a regular button height so it doesn't dominate the row.
   Inherits the indigo accent from #scan-btn above; the .scan-btn-primary
   class only adjusts size to match the toggles around it. */
.scan-btn-primary {
  padding: 6px 14px;
  font-size: var(--t-small);
  font-weight: 600;
  border-radius: var(--r-1);
  /* v0.9.7 — primary-button background uses --accent-deep for WCAG AA. */
  background: var(--accent-deep);
  color: var(--text);
  border: 1px solid var(--accent-deep);
  cursor: pointer;
  transition: background 0.15s var(--easeOut), border-color 0.15s var(--easeOut);
}

.scan-btn-primary:hover:not(:disabled) {
  background: var(--accent-pressed);
  border-color: var(--accent-pressed);
}

.scan-btn-primary:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* ── Active-filter pill row ────────────────────────────────────────── */

.scan-active-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: var(--s-2);
}

.scan-filter-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px 4px 10px;
  background: var(--accent-soft);
  border: 1px solid var(--accent);
  border-radius: var(--r-1);
  font-size: var(--t-small);
  color: oklch(0.82 0.13 255);
  white-space: nowrap;
}

.scan-filter-pill-label {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}

.scan-filter-pill-x {
  background: transparent;
  border: 0;
  padding: 0 2px;
  margin: 0;
  font-size: 14px;
  line-height: 1;
  color: oklch(0.82 0.13 255 / 0.7);
  cursor: pointer;
  border-radius: var(--r-1);
  transition: color 0.15s var(--easeOut), background 0.15s var(--easeOut);
}

.scan-filter-pill-x:hover {
  /* v0.9.7 — was --accent (3.08:1 white-×-on-indigo, FAIL WCAG AA);
     --accent-deep gives 6.01:1. Pill remove (×) is icon-only, applies
     either text-contrast (4.5:1) or non-text-contrast (3:1) rule
     depending on interpretation; --accent-deep clears both. */
  color: var(--text);
  background: var(--accent-deep);
}

/* ── Filter-bar Hide group (round 3) ───────────────────────────────── */

.scan-filter-hide-group {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  /* Explicit flex-direction: row overrides the base .scan-filter rule,
     which sets flex-direction: column to stack a label above its input.
     Without this override the two Hide checkboxes inherit column
     direction and stack vertically. flex-wrap: nowrap is kept as the
     row's correct cramped-state behaviour: when horizontal space gets
     tight, the WHOLE Hide group wraps to its own line inside the
     parent .scan-filters-bar (which still has flex-wrap: wrap). */
  flex-direction: row;
  flex-wrap: nowrap;
}

.scan-filter-hide-label {
  font-size: var(--t-small);
  color: var(--text-muted);
  font-weight: 600;
  letter-spacing: 0.02em;
}

/* The two checkboxes inside the Hide group share a single horizontal
   row. The base .scan-filter-checkbox styles already provide the
   checkbox + label flex layout; the inline variant just tightens
   spacing and keeps the row compact. */
.scan-filter-checkbox-inline {
  margin: 0;
  white-space: nowrap;
}

/* ── Expand panel — three grouped sections (round 3) ───────────────── */

.scan-expand-section {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  padding: var(--s-3) 0;
  border-top: 1px solid var(--rule-soft);
}

/* The first section in the right column doesn't need a top divider;
   the panel itself already has top chrome above it. */
.scan-expand-right > .scan-expand-section:first-of-type {
  padding-top: 0;
  border-top: 0;
}

.scan-expand-section-label {
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  font-weight: 600;
}

/* Volume row — three value-cells in a horizontal trio with sub-labels
   under each. Label-row floats below the value-row so the trio reads
   as one visual unit even though each cell stacks its own value/label
   pair. */
.scan-expand-volume-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-3);
}

.scan-expand-volume-cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-align: left;
}

.scan-expand-volume-value {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 17px;
  font-weight: 500;
  color: var(--text);
}

.scan-expand-volume-label {
  font-size: var(--t-micro);
  color: var(--text-muted);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Generic key/value row used by Buy Limit, Est. Fill/Sell/Flip, and
   the Post-Tax block. Label on the left, value right-aligned to the
   panel's right edge — the standard financial-dashboard treatment. */
.scan-expand-kv-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  font-size: var(--t-small);
}

.scan-expand-kv-label {
  color: var(--text-muted);
}

.scan-expand-kv-value {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--text);
  text-align: right;
}

.scan-expand-kv-value.profit { color: var(--green); }
.scan-expand-kv-value.loss   { color: var(--red);   }
.scan-expand-kv-value.opp    { color: var(--amber); }


/* ═══════════════════════════════════════════════════════════════════════
   §29  v0.9.3 — /PROFILE + /ADMIN-USER PAGE REDESIGN
   ═══════════════════════════════════════════════════════════════════════

   Lane B4 Schwab Navy + Amber treatment for the second-most-used surface
   in the app. Companion to §24 (/market). What this section delivers:

     · `.profile-page-head`  — H1 + amber-pulse subline. Reuses §24's
       `.page-head` chrome shape but namespaced so the right-rail-aware
       `body.market-page main` rule in §24 doesn't bleed onto /profile.

     · `.profile-defaults-collapse` — collapsible Trading Defaults panel.
       Always starts collapsed on every load. Click the header row to
       toggle. No localStorage persistence — every fresh page renders
       the same way for everyone.

     · `.range-select-wrap` — native `<select>` styled to the Lane B4
       dropdown pattern from the mockup. Replaces the v0.5.x nine-button
       row. Custom range strip (`.custom-range`) reveals when "Custom" is
       picked.

     · `.metrics-strip` — six metric tiles in the hairline-rule grid shape
       reused from §24's `.market-stats`. 4-column grid with Best Trade
       spanning 2 columns; collapses to 2 columns under 900px and 1 column
       under 540px.

     · Trade-row chrome refresh — `--surface-1` rest, `--surface-2` hover,
       refined chevron + dimensions. Existing class names preserved
       (`.pt-row`, `.pt-row-header`, `.pt-expand`) so HTML touches are
       additive only; later rules in the cascade win over §11 / §14.

     · `.dev-only-pva` — REMOVED v0.9.3 round 3 follow-on. The
       predicted-vs-actual + all-time per-item history surfaces moved
       exclusively to /admin-user with no dev-mode gate (admin role on
       the route is sufficient). The class is gone; the rule that
       targeted it (originally at the bottom of this section) is gone.

     · `.profile-empty-teaching` — empty-portfolio teaching state used by
       /impeccable onboard. When a brand-new authed user has zero trades
       in range, they see "No completed flips yet" with an inline link to
       /market, instead of the bare "No trades in this range" line.
   */


/* ── Profile main layout — single-column with right padding for sidebar ── */

/* /profile and /admin-user share `body.portfolio-page`. The right-rail
   active-trades sidebar's positioning + main padding is owned by
   sidebar.css (`body.has-persistent-sidebar main { padding-right: 300px }`)
   so we don't need to reserve it from this side. v0.9.3 round 2 dropped
   a too-aggressive `padding-right: calc(340px + var(--s-5))` rule that
   used to live here — it over-reserved space and caused a wide empty
   margin for anonymous visitors who never get the sidebar injected. */
body.portfolio-page main {
  max-width: none;
}


/* ── Profile page-head — H1 + amber-pulse subline ─────────────────────── */

.profile-page-head {
  margin-bottom: var(--s-5);
}

.profile-page-head h1 {
  margin: 0 0 var(--s-2) 0;
  font-size: var(--t-h1);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text);
}

.profile-page-head .profile-page-sub {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--t-body);
}

.profile-page-head .profile-page-sub .live-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--amber);
  margin-right: 6px;
  vertical-align: middle;
  box-shadow: 0 0 0 4px var(--amber-soft);
}


/* ── Collapsible Trading Defaults panel ───────────────────────────────── */

/* The panel itself overrides the §14 `#profile-defaults-panel` background
   chrome only when it carries the v0.9.3 collapse class. Keeps the pre-
   v0.9.3 admin-style "open all the time" panel safe in case it ever ships
   somewhere else. */
#profile-defaults-panel.profile-defaults-collapse {
  padding: 0;
  background: var(--surface-1);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  overflow: hidden;
}

/* The clickable header row. Caps "TRADING DEFAULTS" label on the left,
   one-line summary in the middle, chevron on the right. Treated as a
   button so screen readers + keyboards work. */
.profile-defaults-toggle {
  width: 100%;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  background: transparent;
  border: 0;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 0.15s var(--easeOut);
}

.profile-defaults-toggle:hover {
  background: var(--surface-2);
}

.profile-defaults-toggle:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 1px var(--accent);
}

.profile-defaults-toggle-label {
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  font-weight: 600;
}

.profile-defaults-toggle-summary {
  color: var(--text-muted);
  font-size: var(--t-small);
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.profile-defaults-toggle-chevron {
  color: var(--text-muted);
  font-size: 14px;
  width: 14px;
  text-align: center;
  transition: transform 0.15s var(--easeOut);
}

.profile-defaults-collapse[data-expanded="true"] .profile-defaults-toggle-chevron {
  transform: rotate(90deg);
}

/* The body of the collapsible panel — appears below the toggle row when
   `[data-expanded="true"]` is set. Contains the existing 11-field grid +
   Save/Reset actions. */
.profile-defaults-body {
  padding: var(--s-4);
  border-top: 1px solid var(--rule);
}

.profile-defaults-collapse:not([data-expanded="true"]) .profile-defaults-body {
  display: none;
}

/* The summary line inside the body (separate from the toggle-row summary).
   The §14 rule already styles `.profile-defaults-summary`; we override
   only the spacing here so it sits under the body padding cleanly. */
.profile-defaults-collapse .profile-defaults-summary {
  margin-top: 0;
  margin-bottom: var(--s-3);
}


/* ── Range dropdown ────────────────────────────────────────────────────
   v0.9.3 round 3 follow-on — the `#range-panel` standalone section is
   gone (dropdown moved inline with the Completed Trades heading). The
   panel-chrome rules that lived here (#range-panel, #range-panel h2,
   #range-panel .range-summary, etc.) were removed; only the dropdown
   widget primitives stay because they're reused on the new inline
   placement inside `.trades-panel-header`. */

.range-select-wrap {
  position: relative;
  display: inline-block;
}

.range-select {
  appearance: none;
  -webkit-appearance: none;
  padding: 7px 28px 7px 12px;
  border-radius: var(--r-1);
  /* v0.9.7 — was --rule (FAIL WCAG SC 1.4.11); --rule-strong passes 3:1. */
  border: 1px solid var(--rule-strong);
  background: var(--surface-1);
  color: var(--text);
  font-size: var(--t-small);
  font-family: inherit;
  cursor: pointer;
  transition: border-color 0.15s var(--easeOut);
}

.range-select:hover {
  border-color: var(--text-faint, var(--text-muted));
}

.range-select:focus {
  outline: none;
  border-color: var(--accent);
}

.range-select:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.range-select-wrap::after {
  content: "";
  position: absolute;
  right: 10px;
  top: 50%;
  width: 6px;
  height: 6px;
  border-right: 1.5px solid var(--text-muted);
  border-bottom: 1.5px solid var(--text-muted);
  transform: translateY(-70%) rotate(45deg);
  pointer-events: none;
}


/* ── Metric tiles (Lane B4 stat-tile shape) ──────────────────────────── */

#metrics-grid {
  /* Override §14's auto-fit grid with the locked Lane B4 hairline-rule
     pattern: 4 columns, 1px gap on a `--rule` background to draw the
     hairlines, surface-1 panels on top. */
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  overflow: hidden;
}

.metric-card {
  /* Clobber §14's panel chrome — the parent grid handles borders + rounding. */
  background: var(--surface-1);
  border: 0;
  border-radius: 0;
  padding: var(--s-3) var(--s-4);
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-height: 96px;
}

.metric-card-wide {
  grid-column: span 2;
}

.metric-label {
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  font-weight: 600;
  margin-bottom: 4px;
}

.metric-value {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 22px;
  font-weight: 500;
  letter-spacing: -0.01em;
  /* Default neutral value colour. amber/green/red are class-applied. */
  color: var(--text);
  line-height: 1.2;
  word-break: break-word;
}

/* The Best Trade tile gets the amber "opportunity" tint by default
   because the value is the user's best result — a "good outcome here"
   moment, per the Two-Accent Rule. */
.metric-card.metric-card-wide .metric-value {
  color: var(--amber);
}

.metric-value.positive { color: var(--green); }
.metric-value.negative { color: var(--red);   }

.metric-sub {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-small);
  color: var(--text-muted);
  margin-top: auto;
}

/* Responsive collapse for the metrics grid. Below 1100px (within the
   sidebar-reserved padding rule's 1280px breakpoint, so this hits when
   the page itself is narrowing) drop to 2 columns; below 600px drop to
   single-column. The wide tile recovers to span 1 in both cases. */
@media (max-width: 1100px) {
  #metrics-grid {
    grid-template-columns: repeat(2, 1fr);
  }
  .metric-card-wide {
    grid-column: span 2;
  }
}

@media (max-width: 600px) {
  #metrics-grid {
    grid-template-columns: 1fr;
  }
  .metric-card-wide {
    grid-column: span 1;
  }
}


/* ── Refined trade rows (Lane B4 chrome) ──────────────────────────────── */

/* Override §11/§14 row chrome: surface-1 background at rest, surface-2 on
   hover, hairline rule between rows via parent gap+border. */
.trade-list-portfolio {
  display: flex;
  flex-direction: column;
  /* Hairline rules between rows via 1px gap on a --rule background. */
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  overflow: hidden;
}

.trade-list-portfolio .empty-note {
  background: var(--surface-1);
  margin: 0;
  padding: var(--s-4);
  text-align: center;
}

.pt-row {
  background: var(--surface-1);
  border: 0;
  border-radius: 0;
}

.pt-row.pt-row-test {
  /* Subtle amber tint on test rows so they read distinct without
     dominating. Keeps the same row hierarchy. */
  background: oklch(0.20 0.024 250 / 0.95);
}

.pt-row-header {
  padding: var(--s-3) var(--s-4);
  transition: background 0.15s var(--easeOut);
}

.pt-row-header:hover {
  background: var(--surface-2);
}

.pt-row-header:focus-visible {
  outline: none;
  background: var(--surface-2);
  box-shadow: inset 0 0 0 1px var(--accent);
}

/* Refined badges in the row header — TEST + Edited. */
.pt-name .badge-test,
.pt-name .badge-edited {
  font-size: var(--t-micro);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-weight: 600;
  padding: 2px var(--s-2);
  border-radius: var(--r-1);
  margin-right: var(--s-2);
  vertical-align: middle;
}

.pt-name .badge-test {
  background: var(--amber-soft);
  color: var(--amber);
}

.pt-name .badge-edited {
  background: var(--surface-2);
  color: var(--text-muted);
}

/* Expand panel — restyled to sit cleanly inside the new row chrome. */
.pt-expand {
  background: var(--bg);
  border-top: 1px solid var(--rule);
  padding: var(--s-4) var(--s-4) var(--s-4) calc(var(--s-4) + 18px + var(--s-3));
  gap: var(--s-4);
}

@media (max-width: 700px) {
  .pt-expand {
    padding-left: var(--s-4);
  }
}


/* ── Edit / Delete toolbar ──────────────────────────────────────────── */

/* The toolbar floats top-right of the expand panel. The §14 rules already
   place + size it; this section sets the button vocabulary (indigo
   primary, ghost secondary). */
.pt-edit-btn {
  background: transparent;
  color: var(--text-muted);
  border: 1px solid var(--rule);
  border-radius: var(--r-1);
  padding: 5px 11px;
  font-size: var(--t-small);
  font-weight: 500;
  cursor: pointer;
  transition: border-color 0.15s var(--easeOut), color 0.15s var(--easeOut);
}

.pt-edit-btn:hover {
  border-color: var(--accent);
  color: var(--text);
}

.pt-delete-btn {
  background: transparent;
  color: var(--text-faint, var(--text-muted));
  border: 1px solid var(--rule);
  border-radius: var(--r-1);
  width: 26px;
  height: 26px;
  display: inline-grid;
  place-items: center;
  font-size: 16px;
  cursor: pointer;
  transition: color 0.15s var(--easeOut), border-color 0.15s var(--easeOut);
}

.pt-delete-btn:hover {
  color: var(--red);
  border-color: var(--red);
}

/* Confirm strip — Save/Confirm = primary indigo, Cancel = ghost. */
.pt-edit-confirm-yes {
  /* v0.9.7 — primary-button background uses --accent-deep for WCAG AA. */
  background: var(--accent-deep);
  color: var(--text);
  border: 1px solid var(--accent-deep);
  border-radius: var(--r-1);
  padding: 5px 11px;
  font-size: var(--t-small);
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s var(--easeOut);
}

.pt-edit-confirm-yes:hover:not(:disabled) {
  background: var(--accent-deep);
  border-color: var(--accent-deep);
}

.pt-edit-confirm-no {
  background: transparent;
  color: var(--text-muted);
  border: 1px solid var(--rule);
  border-radius: var(--r-1);
  padding: 5px 11px;
  font-size: var(--t-small);
  font-weight: 500;
  cursor: pointer;
}

.pt-edit-confirm-no:hover:not(:disabled) {
  border-color: var(--text-muted);
  color: var(--text);
}


/* ── Empty-portfolio teaching state ───────────────────────────────────── */

.profile-empty-teaching {
  background: var(--surface-1);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  padding: var(--s-5);
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  align-items: center;
}

.profile-empty-teaching-title {
  font-size: var(--t-body);
  color: var(--text);
  font-weight: 500;
  margin: 0;
}

.profile-empty-teaching-body {
  font-size: var(--t-small);
  color: var(--text-muted);
  margin: 0;
  max-width: 48ch;
}

.profile-empty-teaching-cta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 13px;
  margin-top: var(--s-2);
  /* v0.9.7 — primary-CTA background uses --accent-deep for WCAG AA. */
  background: var(--accent-deep);
  color: var(--text);
  border: 1px solid var(--accent-deep);
  border-radius: var(--r-1);
  font-size: var(--t-small);
  font-weight: 600;
  text-decoration: none;
  transition: background 0.15s var(--easeOut);
}

.profile-empty-teaching-cta:hover {
  background: var(--accent-deep);
}


/* ── Dev-only PVA gate — REMOVED v0.9.3 round 3 follow-on ─────────────
   The predicted-vs-actual + all-time per-item history surfaces moved
   exclusively to /admin-user (where they're visible to all admin
   viewers without a dev-mode gate). /profile no longer renders them at
   all. With both consumers gone, the `.dev-only-pva` class is unused;
   the rule has been deleted. The §24 `.dev-only-control` rule for
   /market's dev-only surfaces is unaffected. */


/* ── Section heading rhythm on /profile ───────────────────────────────── */

.profile-section-heading {
  font-size: var(--t-h2);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text);
  margin: 0 0 var(--s-3) 0;
}

#profile-portfolio-heading {
  margin-top: var(--s-5);
}

#trades-panel h2 {
  /* Restyled from §14's caps treatment to a regular section heading so
     it reads as a peer of the metrics grid, not a sub-label of it. */
  font-size: var(--t-h3);
  text-transform: none;
  letter-spacing: -0.005em;
  color: var(--text);
  font-weight: 600;
  margin-bottom: var(--s-3);
}

#trades-panel {
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 0;
  margin-top: var(--s-5);
}


/* ═══════════════════════════════════════════════════════════════════════
   §30  v0.9.3 ROUND 2 — /PROFILE 3-COLUMN LAYOUT
   ═══════════════════════════════════════════════════════════════════════

   Restructures /profile into three columns:

     · Left rail  (fixed-position, 300px wide) — H1 + identity + collapsible
       Market preferences + 6 lifetime metric cards stacked vertically.
       Always visible as the middle column scrolls. Mirrors the active-trades
       sidebar's "always-watching cockpit gauge" role on the opposite side.

     · Middle    — time-range filter + completed trades list. Range changes
       only affect this column; the lifetime metrics never refresh on a
       range change (they're scoped to range='all' permanently).

     · Right rail (existing 340px sidebar, owned by sidebar.js) — active
       trades. Untouched.

   Sub-version block. Earlier §29 rules are preserved as-is; this section
   adds new selectors and overrides only what the 3-column layout needs to
   reshape. The lifetime-metrics tiles reuse the existing `.metric-card`
   HTML/class but render through a different CSS path inside the left rail.
   ═══════════════════════════════════════════════════════════════════════ */


/* ── Body main: reserve gutter for the LEFT rail ─────────────────────── */

/* The left rail is 300px wide and floats at left:16px (fixed position).
   We reserve its footprint here as padding on `main`. The right rail's
   reservation is owned by sidebar.css's
   `body.has-persistent-sidebar main { padding-right: 300px }` — that
   only stamps when the right sidebar actually mounts (authenticated
   users), so anonymous users get no wasted right margin while still
   seeing the left rail's "Sign in" placeholder content. */
body.portfolio-page main {
  padding-left: calc(300px + var(--s-5));
}

/* Narrow-viewport overrides for `body.portfolio-page main` live at the
   end of §30 alongside the rail's narrow-viewport overrides — keeping
   them grouped + after the unconditional rules so source order honours
   the cascade. */


/* ── Left rail — fixed shell ─────────────────────────────────────────── */

/* Mirrors the active-trades sidebar pattern: position fixed at top:60px
   (below the header), hugs the left edge with a small gutter, scrolls
   internally if the rail's content is taller than the viewport. The
   max-height matches sidebar.css's right rail (`calc(100vh - 80px)`) so
   the two rails align visually at the bottom edge. */
.profile-left-rail {
  position: fixed;
  /* v0.9.3 round 3 follow-on — top dropped from 60px to 80px to match
     sidebar.css's `#persistent-sidebar { top: 80px }`. Both rails now
     start at the same Y, level with where main's content begins
     (60px header + 20px main padding-top). max-height adjusted to
     `calc(100vh - 100px)` to keep the same 20px gap at the bottom. */
  top: 80px;
  left: var(--s-4);
  width: 300px;
  max-height: calc(100vh - 100px);
  overflow-y: auto;
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
  /* No background/border on the rail itself — each section inside has its
     own panel chrome. Keeping the rail transparent matches the right
     sidebar's open-air pattern. */
  padding: var(--s-3) var(--s-2) var(--s-3) 0;
  z-index: 9;   /* below sidebar.css's z-index:10 — the right rail
                   carries action-availability state and wins ties. */
}

/* v0.9.3 round 3 — flex children shrink to fit by default. When the rail's
   content (H1 + identity + expanded Market preferences + 6 lifetime cards)
   exceeds the rail's max-height, the default `flex-shrink: 1` causes
   children to compress, and the panel's own `overflow: hidden` then
   clips the bottom of the field grid (visible symptom: the lower fields
   in Market preferences silently disappear). Pinning every direct child
   to flex-shrink: 0 forces the rail to honour its `overflow-y: auto`
   instead — the user gets a real scrollbar. */
.profile-left-rail > * {
  flex-shrink: 0;
}

/* Restrained scrollbar for the rail's overflow. Most of the time the rail
   fits without scrolling; on shorter viewports the user gets a thin track. */
.profile-left-rail::-webkit-scrollbar {
  width: 6px;
}
.profile-left-rail::-webkit-scrollbar-track {
  background: transparent;
}
.profile-left-rail::-webkit-scrollbar-thumb {
  background: var(--rule);
  border-radius: 3px;
}
.profile-left-rail::-webkit-scrollbar-thumb:hover {
  background: var(--text-muted);
}


/* ── Identity block (H1 + username + member-since) ───────────────────── */

.profile-left-head {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

.profile-left-head h1 {
  margin: 0;
  font-size: var(--t-h1);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text);
}

.profile-identity {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--s-2) 0;
  border-top: 1px solid var(--rule);
}

.profile-username {
  font-size: var(--t-body);
  font-weight: 500;
  color: var(--text);
  letter-spacing: -0.005em;
  /* Long usernames wrap rather than truncate — usernames are part of the
     user's identity and shouldn't be silently cut. */
  word-break: break-word;
}

.profile-member-since {
  font-size: var(--t-small);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.profile-member-since a {
  color: var(--accent);
  text-decoration: none;
}

.profile-member-since a:hover {
  text-decoration: underline;
}


/* ── Lifetime totals — caps label + vertical-stack metric cards ──────── */

.profile-lifetime-metrics {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

.profile-lifetime-label {
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  font-weight: 600;
  padding-bottom: var(--s-1);
}

/* Left-rail metric grid is a 2-column layout. Six tiles land in a 2x3
   block (Completed Trades / Total Time Trading; Total GP / Total GP Hr;
   Average GP/Flip / Average ROI), with the seventh tile (Best Trade)
   spanning the full row at the bottom — the grid reads 2 wide × 4 tall.

   IMPORTANT specificity note: §29 ships a bare `#metrics-grid` rule
   (specificity 100) that sets `grid-template-columns: repeat(4, 1fr)`
   for /admin-user's full-width layout. The selectors below use
   `.profile-left-rail #metrics-grid` (specificity 110) so they win
   on /profile while §29's rule continues to apply on /admin-user. The
   round-2-era `.profile-lifetime-grid` class selector (specificity 20)
   lost the cascade — round 3 corrects this. */
.profile-left-rail #metrics-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-2);
  background: transparent;
  border: 0;
  border-radius: 0;
  overflow: visible;
}

.profile-left-rail #metrics-grid .metric-card {
  background: var(--surface-1);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  padding: var(--s-3);
  min-height: 0;
  /* Prevent the grid track from forcing an unwanted minimum width — the
     `minmax(0, 1fr)` columns above already cover this, but cards with
     long mono values (like "Demon tear · 104K gp") would otherwise blow
     out the grid track. `min-width: 0` lets the card shrink to its
     column. */
  min-width: 0;
}

/* The "wide" tile (Best Trade) reclaims its 2-col span. The amber tint
   on its value (already established in §29) is the visual cue for
   "good outcome here" on the wide tile. */
.profile-left-rail #metrics-grid .metric-card-wide {
  grid-column: span 2;
}

/* Tile values: smaller mono font than the round 1 horizontal grid (which
   was 22px) so the numbers fit in the narrower 2-col tiles without
   wrapping. Combined with the new compact format ("610K gp" not
   "610,163 gp") most values fit on a single line. */
.profile-left-rail #metrics-grid .metric-value {
  font-size: 17px;
  line-height: 1.2;
  /* Long numbers should ellipsize before wrapping — wrapping a mono
     number across two lines reads worse than a clipped tail when the
     tile is narrow. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Best Trade's value is "Item name · 104K gp" so it benefits from being
   allowed to wrap across two lines (item names can be long). Override
   the no-wrap rule for the wide tile only. */
.profile-left-rail #metrics-grid .metric-card-wide .metric-value {
  white-space: normal;
  overflow: visible;
  font-size: 18px;
}

.profile-left-rail #metrics-grid .metric-label {
  /* Slightly smaller caps label so two-word labels like "TOTAL TIME
     TRADING" don't wrap inside the 2-col cells. */
  font-size: var(--t-micro);
  margin-bottom: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.profile-left-rail #metrics-grid .metric-sub {
  font-size: var(--t-micro);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Best Trade's sub-line is "MM/DD/YYYY · ROI X%" — needs to wrap on
   narrow rails since the date alone is ~10-12 chars. */
.profile-left-rail #metrics-grid .metric-card-wide .metric-sub {
  white-space: normal;
}


/* ── Middle column ───────────────────────────────────────────────────── */

/* The middle column is a regular flex container that sits inside main.
   Padding on `main` reserves the rail gutters; this just stacks the
   range filter + trades list with consistent spacing. */
.profile-content-col {
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
  min-width: 0;   /* Lets the inner trade table shrink properly. */
}


/* ── Trade-row column-grid override ──────────────────────────────────── */

/* The §11 `.pt-row-header` grid has 6 columns sized for the previous
   single-column layout. With the middle column now ~760-820px (depending
   on viewport), the header columns need to be tightened so item names
   don't truncate aggressively and the date column doesn't dominate. */
@media (min-width: 1280px) {
  .pt-row-header {
    grid-template-columns: 18px auto minmax(110px, 1.2fr) minmax(180px, 1.6fr) minmax(80px, 0.8fr) minmax(120px, 0.9fr);
  }
}


/* ── Range panel — slimmed for middle column ────────────────────────── */

/* The §29 range panel was sized for a full-width main; in the middle
   column it can wrap a touch differently. The flex-wrap behaviour
   already handles narrow widths; this is a no-op currently but the
   selector is reserved for future tweaks if a different shape is
   wanted inside the narrower middle. */


/* ── Narrow-viewport overrides ───────────────────────────────────────── */

/* Below 1280px the left rail un-fixes and becomes a regular block at the
   top of main, mirroring how sidebar.js's right rail behaves on narrow
   widths. The two-column intra-rail grid keeps the rail readable on
   ~700-1280px viewports without pushing the trade list down a full
   page. Order matters: this block MUST follow the unconditional
   `.profile-left-rail` rule above so its `position: static` override
   wins on equal specificity. */
@media (max-width: 1280px) {
  body.portfolio-page main {
    padding-left: var(--s-5);
  }

  .profile-left-rail {
    position: static;
    width: auto;
    max-width: none;
    max-height: none;
    overflow: visible;
    margin-bottom: var(--s-5);
    padding: 0;
    /* Stack the rail's own internal sections horizontally on narrow
       desktop / wide tablet. Below ~700px this collapses again to a
       single column. */
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
    gap: var(--s-5);
    align-items: start;
  }

  /* Identity + metrics live on the left at this breakpoint; preferences
     live on the right. The lifetime-metrics-grid reverts to a 2-column
     row inside its half. */
  .profile-left-rail .profile-left-head {
    grid-column: 1;
  }
  .profile-left-rail #profile-defaults-panel {
    grid-column: 2;
    grid-row: 1 / span 2;
  }
  .profile-left-rail .profile-lifetime-metrics {
    grid-column: 1;
  }
  /* v0.9.3 round 3 follow-on — keep the 2-col layout at narrow desktop;
     selector is `#metrics-grid` to beat §29's bare `#metrics-grid` rule
     on specificity (see the `.profile-left-rail #metrics-grid` block
     above for the full rationale). */
  .profile-left-rail #metrics-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 700px) {
  .profile-left-rail {
    grid-template-columns: 1fr;
  }
  .profile-left-rail .profile-left-head,
  .profile-left-rail #profile-defaults-panel,
  .profile-left-rail .profile-lifetime-metrics {
    grid-column: 1;
    grid-row: auto;
  }
  /* Below 700px the rail collapses to a single column; the lifetime
     metric grid does too. ID-based selector to win specificity. */
  .profile-left-rail #metrics-grid {
    grid-template-columns: 1fr;
  }
}


/* ═══════════════════════════════════════════════════════════════════════
   §30  /profile refinements
   ═══════════════════════════════════════════════════════════════════════

     · Toggle row simplified: caps label + chevron only. Grid template
       drops the middle "summary" column (1fr) that's now empty.
     · Best Trade tile gains an item picture beside the value/sub block.
     · Completed-trades rows: chevron pinned at 18px + 5 equal data
       columns (leader/details/profit/time/date).
     · Range dropdown moves inline with the "Completed Trades" heading
       (right-aligned via flex space-between).
   ═══════════════════════════════════════════════════════════════════════ */


/* ── Toggle row: drop the middle summary column ─────────────────────── */

/* §30 round 2 set `grid-template-columns: auto 1fr auto` for the
   3-cell layout. The middle 1fr cell held the saved-state summary
   span, which round 3 follow-on dropped. Now the row needs just label
   on the left + chevron on the right. */
.profile-defaults-collapse .profile-defaults-toggle {
  grid-template-columns: 1fr auto;
}


/* ── Best Trade tile body — image beside value/sub ──────────────────── */

.best-trade-body {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  /* min-width:0 lets the text block shrink properly inside the wide
     tile when the rail is narrow. */
  min-width: 0;
}

.best-trade-image {
  flex: 0 0 auto;
  /* The wrapper renders empty until renderMetrics injects a child
     `.item-image .item-image-detail` element via buildItemImage. The
     detail-size .item-image chrome from §16 carries the dimensions +
     border; we just provide the flex slot. */
}

.best-trade-text {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* When the image is inside the wide tile, the value font size from
   the round-3 §30 rule still applies (18px); the text block simply
   sits to the right of the image rather than below the label. */


/* ── Completed-trades row grid: 6 equal data columns + chevron ──────── */

/* v0.9.8.1 — was 5 equal data cells (chevron + leader + details +
   profit + time + date). The new ROI column inserted between profit
   and time bumps to 6 equal data cells. The header row (`.pt-header-row`,
   added v0.9.8.1) shares the same template so caps labels line up
   exactly with the data values below. Earlier comment for context:
   this overrides §11's `.pt-row-header` grid (which was variable
   columns sized by content) AND the round 3 `@media (min-width: 1280px)`
   override; both are now updated below to match. */
.pt-row-header,
.pt-header-row {
  grid-template-columns: 18px repeat(6, minmax(0, 1fr));
}

/* Override the round-3 @media rule too — same 6-equal layout. The
   media query lives in §30 round 3 above; we re-state for >= 1280px
   here so the same selector wins. */
@media (min-width: 1280px) {
  .pt-row-header,
  .pt-header-row {
    grid-template-columns: 18px repeat(6, minmax(0, 1fr));
  }
}

/* Leader cell — the row's "image + name" composite. Chevron stays in
   its own 18px column to its left. */
.pt-leader {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-width: 0;
}

.pt-leader > .item-image {
  flex: 0 0 auto;
}

.pt-leader > .pt-name {
  flex: 1 1 auto;
  min-width: 0;
  /* The name truncates instead of wrapping when the column is
     squeezed — wrapping would push the row to two lines, breaking
     the equal-spacing rhythm. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* New Time column (between profit and date). Mono numerics for
   tabular alignment. */
.pt-time {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-small);
  color: var(--text-muted);
  text-align: right;
  white-space: nowrap;
}

/* The .pt-details column gets `white-space: nowrap` so "BUY x SELL y"
   stays on one line even at narrow column widths; ellipsizes if it
   genuinely doesn't fit. */
.pt-details {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}


/* ── Narrow-viewport row layout (≤700px) ────────────────────────────── */

/* §11's narrow-layout grid-areas were keyed off `.item-image` being a
   direct child of `.pt-row-header`. The new markup wraps image+name
   in `.pt-leader`, so the old grid-areas no longer hit the right
   element. Restated here for the new markup.
   v0.9.8.1 — added the ROI cell on the same row as profit; .pt-header-row
   stays hidden on mobile (display: none from §11 mobile rule above)
   because there's no room for caps-style header cells above the
   stacked layout. */
@media (max-width: 700px) {
  .pt-row-header {
    grid-template-columns: 18px 1fr auto auto;
    grid-template-areas:
      "chev   leader   profit  roi"
      "chev   details  details details"
      "chev   time     date    date";
    row-gap: 4px;
  }
  .pt-chevron { grid-area: chev; }
  .pt-leader  { grid-area: leader; }
  .pt-details { grid-area: details; white-space: normal; }
  .pt-profit  { grid-area: profit; }
  .pt-roi     { grid-area: roi; }
  .pt-time    { grid-area: time; text-align: left; }
  .pt-date    { grid-area: date; text-align: right; }

  /* Reaffirm here in case the §11 mobile rule's hide gets undone by a
     later edit — the sortable header has nowhere to land at this width. */
  .pt-header-row { display: none; }
}


/* ── Trades panel header — heading + range dropdown inline ───────────── */

/* v0.9.3 round 3 follow-on — the standalone `#range-panel` section
   was dropped. The Time range dropdown moved inline with the
   "Completed Trades" heading, right-aligned via flex space-between.
   The range-summary line was also dropped (redundant with
   #trades-summary which carries the same info inside the trade list). */
.trades-panel-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  margin-bottom: var(--s-3);
}

/* §30 round 1 set `#trades-panel h2 { margin-bottom: var(--s-3) }`
   which still matches descendants inside `.trades-panel-header` (the
   h2 IS a descendant of #trades-panel). Use an ID-prefixed selector
   here to win the cascade and zero the bottom margin — the flex
   baseline-aligned header doesn't want a margin-bottom on the h2,
   and the wrapper itself already has margin-bottom. */
#trades-panel .trades-panel-header h2 {
  margin: 0;
  font-size: var(--t-h3);
  text-transform: none;
  letter-spacing: -0.005em;
  color: var(--text);
  font-weight: 600;
}

.trades-panel-header .range-select-wrap {
  /* Right-align the dropdown in the flex row. */
  flex: 0 0 auto;
}


/* ═══════════════════════════════════════════════════════════════════════
   §31  v0.9.7.4 — PAGE FOOTER (FAN-PROJECT DISCLAIMER)
   ═══════════════════════════════════════════════════════════════════════
   Disclaimer footer injected by `public/footer.js` on every page except
   the chromeless /active pop-out. Quiet treatment per the "Get out of the
   way" design principle: small type, --text-faint colour, --rule-soft top
   border, centred, max-width clamps the line length on wide displays.

   On pages where body carries .has-persistent-sidebar (the floating
   active-trades sidebar), the footer mirrors <main>'s 340px right
   reservation so the centred copy stays visually aligned with the
   page's content column rather than the body's full width.
   ═══════════════════════════════════════════════════════════════════════ */

.page-footer {
  border-top: 1px solid var(--rule-soft);
  padding: var(--s-5) var(--s-5) var(--s-5);
  margin-top: var(--s-5);
  text-align: center;
}

body.has-persistent-sidebar .page-footer {
  /* Match sidebar.css's `body.has-persistent-sidebar main` reservation
     so the footer's centred copy sits inside the same content column
     as <main>, not under the floating sidebar. */
  padding-right: 340px;
}

.page-footer p {
  margin: 0 auto;
  max-width: 540px;
  font-size: var(--t-small);
  color: var(--text-faint);
  line-height: 1.5;
}

/* v1.0.5 — Inline Terms / Help links injected by footer.js as a second
   <p> under the disclaimer. Both anchors pick up the same faint colour
   so the links sit quietly against the disclaimer; underline-on-hover
   gives the affordance without shouting. */
.page-footer p.page-footer-links {
  margin-top: var(--s-2);
}

.page-footer p.page-footer-links a {
  color: var(--text-muted);
  text-decoration: none;
}

.page-footer p.page-footer-links a:hover {
  color: var(--text);
  text-decoration: underline;
}

/* On the flex-centered pages (landing + auth tier), <body> is a flex
   container with `align-items: center` + `justify-content: center` and
   the default `flex-direction: row`, which centers its single content
   child (.landing-shell / .auth-shell) on both axes. Without this
   override, the body-level <footer> appended by footer.js becomes a
   second row-direction flex item sitting NEXT TO the content instead
   of below it — visible as the disclaimer floating in the right half
   of the viewport on /landing, /login, and /signup.

   The fix: swap the flex direction to column. The existing center-
   alignment rules then operate on the swapped axes — the shell stays
   horizontally centered, and shell + footer stack vertically as a
   group centered on the main axis. The footer reads as "end of the
   centered content card" rather than detached chrome pinned to the
   viewport edge, which fits the centered-spine aesthetic of these
   pages better than an absolute-positioned bottom pin would. */
body.landing-page,
body.auth-page {
  flex-direction: column;
}


/* ═══════════════════════════════════════════════════════════════════════
   §32  v1.0.5 — DOCS PAGES (HELP / TERMS)
   ═══════════════════════════════════════════════════════════════════════
   Quiet reading layout for /help and /terms. Single centred reading
   column at max-width 720px, brand chip + eyebrow at the top, jump-link
   nav, prose body, FAQ items separated by 1px hairlines.

   Body uses the default block layout (no flex), so the body-level
   <footer> appended by footer.js sits naturally below the .docs-shell.

   Tokens only: every colour, spacing, and type step is a var(...) read
   from §1, so any future palette retoken sweeps these pages along with
   the rest of the app.
   ═══════════════════════════════════════════════════════════════════════ */

body.docs-page {
  min-height: 100vh;
}

/* The global `main` rule (§3) declares a 2-column grid (1fr 260px) for
   the persistent active-trades sidebar layout used by /market, /profile,
   /admin, etc. /help and /terms use `<main class="docs-shell">` and need
   to opt OUT of that grid — otherwise the page renders as two columns
   with the brand block + lede on the left and the H1 + jump nav on the
   right, with section content split awkwardly down the middle.
   `display: block` resets the grid; the centred reading column is then
   handled by max-width + margin: 0 auto. */
main.docs-shell {
  display: block;
  max-width: 720px;
  margin: 0 auto;
  padding: var(--s-7) var(--s-5) var(--s-5);
  width: 100%;
  box-sizing: border-box;
}

.docs-brand {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: var(--s-2);
}

a.docs-wordmark,
.docs-wordmark {
  font-size: var(--t-h3);
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--text);
  text-decoration: none;
}

.docs-eyebrow {
  font-size: var(--t-small);
  color: var(--text-muted);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.docs-shell h1 {
  font-size: var(--t-h1);
  font-weight: 500;
  margin: 0 0 var(--s-2);
  letter-spacing: -0.005em;
}

.docs-lede {
  margin: 0 0 var(--s-5);
  color: var(--text-muted);
  font-size: var(--t-body);
  line-height: 1.55;
}

.docs-jumpnav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--rule-soft);
  border-radius: var(--r-2);
  background: var(--surface-1);
  margin: 0 0 var(--s-6);
}

.docs-jumpnav a {
  color: var(--accent);
  text-decoration: none;
  font-size: var(--t-small);
}

.docs-jumpnav a:hover {
  text-decoration: underline;
}

.docs-jumpnav-sep {
  color: var(--text-faint);
  font-size: var(--t-small);
}

.docs-section {
  margin: 0 0 var(--s-6);
}

.docs-section h2 {
  font-size: var(--t-h2);
  font-weight: 500;
  margin: 0 0 var(--s-3);
  letter-spacing: -0.005em;
}

.docs-section h3 {
  font-size: var(--t-h3);
  font-weight: 500;
  margin: var(--s-5) 0 var(--s-2);
}

.docs-section p,
.docs-section li {
  font-size: var(--t-body);
  line-height: 1.65;
  color: var(--text);
}

.docs-section p {
  margin: 0 0 var(--s-3);
}

.docs-section p:last-child {
  margin-bottom: 0;
}

.docs-section ol {
  padding-left: var(--s-5);
  margin: 0;
}

.docs-section ol li {
  margin-bottom: var(--s-3);
}

.docs-section ol li:last-child {
  margin-bottom: 0;
}

.docs-section a {
  color: var(--accent);
  text-decoration: none;
}

.docs-section a:hover {
  text-decoration: underline;
}

.docs-faq-item {
  border-top: 1px solid var(--rule-soft);
  padding: var(--s-4) 0;
}

.docs-faq-item:last-child {
  border-bottom: 1px solid var(--rule-soft);
}

.docs-faq-item h3 {
  font-size: var(--t-body);
  font-weight: 500;
  margin: 0 0 var(--s-2);
}

.docs-faq-item p {
  margin: 0;
}

/* The small ALL-CAPS warranty block at the end of /terms. The only place
   in the docs pages where uppercase running copy is allowed. Caps-as-
   labels rule (DESIGN.md §3) is bent here because the legalese register
   is the message. */
.docs-warranty {
  margin: var(--s-6) 0 0;
  padding: var(--s-4);
  border: 1px solid var(--rule-soft);
  border-radius: var(--r-2);
  background: var(--surface-1);
  font-size: var(--t-small);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  line-height: 1.7;
  color: var(--text-muted);
}

.docs-warranty p {
  margin: 0 0 var(--s-3);
  font-size: var(--t-small);
  color: var(--text-muted);
  line-height: 1.7;
}

.docs-warranty p:last-child {
  margin-bottom: 0;
}

.docs-warranty strong {
  color: var(--text);
  font-weight: 500;
}
