/* PlayBourse — Bootstrap 5.3.8 layered on top of the PlayBourse design tokens.
 *
 * Bootstrap CSS loads FIRST via CDN in App.razor; this file loads second and
 * wins the cascade. We override --bs-* variables under :root[data-bs-theme="dark"]
 * so every Bootstrap component (.card, .btn, .form-control, .navbar, .badge, .alert,
 * .modal, .pagination, .spinner-border, etc.) renders in the PlayBourse palette
 * without per-class re-skinning.
 *
 * Custom pieces we still own (genuinely game-specific):
 *   - .avatar / .logomark — tiny gradient monograms
 *   - .portrait (+ .sm/.lg/.buyer/.seller/.ai) — player avatar with role tint
 *   - .badge + color modifiers — subtler dim-bg + colored-text than Bootstrap's
 *     .bg-success (Bootstrap's is solid fill, ours keeps the restrained palette)
 *   - .offer-card / .oc-* / .hero-stat / .topbar / .gameclock-chip / etc. —
 *     design-system archetypes for the trader-terminal density signature.
 *   - .container override (1500px vs Bootstrap's 1320) for stat-heavy layouts
 *
 * 2026-05-15 — design refresh sprint started. See `memory/design_principles.md`
 * for the five principles (mobile-first, 44px touch targets, density tiers,
 * single-accent palette, mono numerics) every subsequent PR is judged against.
 * THIS PR: token-level identity refresh (accent blue → financial green) +
 * mobile-first foundation utilities (.touch-44, .mono, .num). Layout
 * migrations roll out per-surface in follow-up PRs.
 */

/* ─── PlayBourse design tokens ─── */
:root {
  /* Spacing scale (4px base) — used by tokens below and a few legacy spots. */
  --s-0: 0;
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 20px;
  --s-6: 24px;
  --s-8: 32px;
  --s-10: 40px;
  --s-12: 48px;
  --s-16: 64px;

  /* Dark palette — surfaces + text tiers. 2026-05-16 v2 foundation refresh
     deepened every bg + border 2-4% to lean more "trading-terminal" and
     less "consumer dark app." Token names unchanged; consumers re-render
     automatically. */
  --bg-0: #07080b;
  --bg-1: #0e1116;
  --bg-2: #161a22;
  --bg-3: #1f242e;
  --bg-hover: #131720;
  --bg-elev: #1a1f29;       /* v2 — elevated surface (modals, popovers) */

  --border: #1f2632;
  --border-strong: #2a3240;

  --text-1: #ebeef3;
  --text-2: #8b95a3;
  /* --text-3 = WCAG AA on body bg. v2 bundle picked #555d6a (3.0:1 on
     #07080b — fails AA for normal text). First retune to #6d7682 was
     4.35:1 — still under the 4.5:1 minimum (UX re-review on PR #203
     caught it). Final value #7a8390 = 5.22:1 with clear AA margin so
     small mono labels (.commodity-tile .ticker, .feed-row .ago,
     .text-secondary 11px copy) read comfortably on every surface. */
  --text-3: #7a8390;
  --text-4: #353c47;        /* v2 — disabled / placeholder only (non-text use) */

  /* 2026-05-15 design refresh — accent is now financial green, the single
     identity color. Old blue (#4f80ff) lived alongside green/red/amber/violet
     and read as generic "consumer dark app". Single saturated green +
     semantic green-for-gain / red-for-loss + amber-for-warn commits to the
     trading-floor thesis. See memory/design_principles.md, principle 4.
     The accent + green-success share a hue family by design — they read as
     "this is the same kind of thing" in chrome AND state.
     2026-05-16 v2 — `--green` unified with `--accent` (same hex); legacy
     `#3dd68c` was a slightly warmer green that drifted from --accent under
     side-by-side compare. v2 also adds --accent-press, --accent-on,
     --accent-dim-strong, --accent-glow for the button + chip variants the
     new surfaces use. */
  --accent: #00d97e;
  --accent-hover: #1be38d;
  --accent-press: #00b369;            /* v2 — :active state */
  --accent-dim: #0c3a24;
  --accent-dim-strong: rgba(0, 217, 126, 0.22);   /* v2 — selected chip ring */
  --accent-on: #052a18;                /* v2 — text-on-green for the .btn-px.primary */
  --accent-glow: 0 0 0 1px rgba(0, 217, 126, 0.5), 0 0 16px rgba(0, 217, 126, 0.18);

  --green: #00d97e;
  --green-dim: #1e4f35;
  --red: #ff5d6c;
  --red-dim: #4b1d1d;
  --amber: #ffba4d;
  --amber-dim: #4a321a;
  --blue: #5b8def;          /* v2 — informational, market-spot marker */
  --blue-dim: #1d2e54;
  /* Violet stays for player-portrait gradients (identity tints) but is no
     longer a chrome color. Don't use --violet on UI chrome going forward —
     the design system commits to single-accent. */
  --violet: #9e7cff;

  /* v2 — gold palette for celebratory / earned-achievement surfaces. */
  --gold: #f0c87a;
  --gold-bg: linear-gradient(135deg, #3a2e16, #1a1308);
  --gold-border: #5a4624;

  /* v2 — elevation shadow scale. */
  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-2: 0 8px 24px rgba(0, 0, 0, 0.5);
  --shadow-3: 0 24px 60px rgba(0, 0, 0, 0.6);

  /* Type stacks. 2026-05-16 v2 — Inter + JetBrains Mono loaded via Google
     Fonts in App.razor head. Web-loaded fonts win; system-font fallback
     runs unchanged when the network is offline / blocked. */
  --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", system-ui, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", "IBM Plex Mono", "Menlo", monospace;

  /* Radius scale. 2026-05-16 v2 — tightened by 1px across the small/medium
     tier (4→3, 6→5) so chip + button corners read sharper. lg/xl unchanged. */
  --r-sm: 3px;
  --r-md: 5px;
  --r-lg: 8px;
  --r-xl: 12px;

  /* v2 — left-rail width reserved for future rail-nav surface. Currently
     unused (top nav is the only nav today). */
  --rail-w: 56px;

  /* 2026-05-15 design refresh — standardized breakpoints. The legacy
     380/480/520/600px ad-hoc rules in this file are retiring per-surface
     as each migrates (see design/principles.md, principle 1). New
     code uses these four breakpoints only.

     NOT USABLE INSIDE @media queries — CSS custom properties resolve at
     element time, but @media needs literal values at parse time. Spec
     forbids `@media (max-width: var(--bp-md))`. These tokens exist for
     documentation + code-review enforcement only ("if your new @media
     uses 600px, reject — the standard set is sm/md/lg/xl"). Inside
     @media, write the literal: `@media (min-width: 768px)`. */
  --bp-md: 768px;
  --bp-lg: 1024px;
  --bp-xl: 1280px;
}

/* ─── Bootstrap token bridge (:root[data-bs-theme="dark"]) ─── */
:root[data-bs-theme="dark"] {
  /* Core surfaces — Bootstrap's default dark is slate; we use deeper black. */
  --bs-body-bg: var(--bg-0);
  --bs-body-color: var(--text-1);
  --bs-body-font-family: var(--font-sans);
  --bs-body-font-size: 14px;
  --bs-border-color: var(--border);
  --bs-border-color-translucent: var(--border);
  --bs-secondary-bg: var(--bg-2);
  --bs-tertiary-bg: var(--bg-1);
  --bs-emphasis-color: var(--text-1);
  --bs-secondary-color: var(--text-2);
  --bs-tertiary-color: var(--text-3);

  /* Semantic colors — map Bootstrap's states to PlayBourse's palette. */
  --bs-primary: var(--accent);
  --bs-primary-rgb: 0, 217, 126;
  --bs-primary-bg-subtle: var(--accent-dim);
  --bs-primary-border-subtle: var(--accent-dim);
  --bs-primary-text-emphasis: #9af3c8;

  --bs-success: var(--green);
  --bs-success-rgb: 61, 214, 140;
  --bs-success-bg-subtle: var(--green-dim);
  --bs-success-border-subtle: var(--green-dim);

  --bs-danger: var(--red);
  --bs-danger-rgb: 240, 100, 100;
  --bs-danger-bg-subtle: var(--red-dim);
  --bs-danger-border-subtle: var(--red-dim);

  --bs-warning: var(--amber);
  --bs-warning-rgb: 232, 161, 58;
  --bs-warning-bg-subtle: var(--amber-dim);
  --bs-warning-border-subtle: var(--amber-dim);

  --bs-info: var(--violet);
  --bs-info-rgb: 158, 124, 255;

  /* Links. */
  --bs-link-color: var(--accent);
  --bs-link-hover-color: var(--accent-hover);
  --bs-link-color-rgb: 0, 217, 126;
  --bs-link-hover-color-rgb: 27, 227, 152;
}

/* Bootstrap's .form-control uses --bs-body-bg for its background; force --bg-2
   so inputs read as "interactive" on our bg-0 body. */
.form-control, .form-select {
  background-color: var(--bg-2);
  border-color: var(--border);
  color: var(--text-1);
}
.form-control:focus, .form-select:focus {
  background-color: var(--bg-2);
  color: var(--text-1);
  border-color: var(--accent);
  box-shadow: 0 0 0 .2rem rgba(0, 217, 126, 0.18);
}
.form-control::placeholder { color: var(--text-3); }

/* Disabled + validation states — TL review on PR #29 flagged these as future
   landmines if we ship server-side validation without overrides. Defensive
   overrides on our palette so they don't fall back to Bootstrap's light-mode
   defaults. */
.form-control:disabled, .form-control[readonly],
.form-select:disabled {
  background-color: var(--bg-1);
  color: var(--text-3);
  border-color: var(--border);
  cursor: not-allowed;
}
.form-control.is-invalid, .form-select.is-invalid {
  border-color: var(--red);
  background-image: none;
}
.form-control.is-valid, .form-select.is-valid {
  border-color: var(--green);
  background-image: none;
}
.input-group-text {
  background-color: var(--bg-2);
  border-color: var(--border);
  color: var(--text-2);
}

/* Bootstrap card — force dark surface + subtle border (overrides the slate default). */
.card {
  background-color: var(--bg-1);
  border-color: var(--border);
}

/* .btn-outline-secondary renders too bright on dark out of the box — pin it to
   our muted border pair. */
.btn-outline-secondary {
  --bs-btn-color: var(--text-1);
  --bs-btn-border-color: var(--border);
  --bs-btn-hover-bg: var(--bg-3);
  --bs-btn-hover-border-color: var(--border-strong);
  --bs-btn-hover-color: var(--text-1);
}

/* Navbar link hover/active — give it a soft bg hop + white text instead of
   Bootstrap's color-only shift. */
.navbar .nav-link {
  color: var(--text-2);
  font-size: 13.5px;
  font-weight: 500;
}
.navbar .nav-link:hover,
.navbar .nav-link:focus,
.navbar .nav-link.active {
  color: var(--text-1);
  background: var(--bg-2);
}
.navbar-brand { color: var(--text-1); }
.navbar-brand:hover { color: var(--text-1); }

/* ─── Baseline ─── */

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg-0);
  color: var(--text-1);
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-feature-settings: "ss01", "cv02", "cv03", "cv04";
}

/* ─── 2026-05-15 design refresh — foundation utility classes ───
   See memory/design_principles.md for the five principles. Surfaces
   migrate to these utilities incrementally; existing one-off styles
   stay in place until each surface's migration PR lands. */

/* .mono — Trading-floor numeric rendering. Apply to any element whose
   text is primarily numeric (prices, volumes, %, time-ago, ids, ranks).
   Principle 5 — "numerics earn the mono". Replaces ad-hoc inline
   `style="font-family: var(--font-mono); font-variant-numeric: tabular-nums;"`
   peppered across the markup. */
.mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

/* .num — Right-aligned numeric cell, for use inside tables / data grids.
   Composes with .mono. Tabular contexts where alignment matters: stat
   columns, contract values, leaderboard rank cells. */
.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* .touch-44 — Mobile-friendly tap target. Principle 2: every clickable
   element on mobile is at least 44 × 44px (Apple HIG). On desktop the
   utility is a no-op so existing dense layouts keep their density. Apply
   to chips, paging buttons, nav links, badge-as-button surfaces — anything
   the player taps to do something.

   The min-height/width pair is the load-bearing rule; the inline-flex +
   centering means a small visual glyph (an icon or single char) still
   sits center-aligned inside the larger hit area. */
@media (max-width: 767.98px) {
  .touch-44 {
    min-height: 44px;
    min-width: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
}

/* ─── Container width override (Bootstrap's .container caps at 1320px @ xxl) ─── */
.container {
  max-width: 1500px;
  margin: 0 auto;
  width: 100%;
}

/* ─── Brand marks (used in AppBar + Onboarding) ─── */

.avatar {
  width: 28px; height: 28px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--violet), var(--accent));
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 600;
  color: #fff;
}

.logomark {
  width: 20px; height: 20px;
  border-radius: 4px;
  background: linear-gradient(135deg, var(--accent) 0%, var(--violet) 100%);
  display: inline-block;
}

/* ─── Portrait (player avatar monogram with role tint) ─── */

.portrait {
  width: 32px; height: 32px;
  border-radius: 7px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 700;
  color: #fff;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  flex-shrink: 0;
  position: relative;
  /* Default gradient; .buyer/.seller override for Contracts role coloring. */
  background: linear-gradient(135deg, var(--violet), var(--accent));
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.12), inset 0 -1px 0 rgba(0,0,0,0.25);
}
.portrait.sm { width: 22px; height: 22px; border-radius: 5px; font-size: 9px; }
.portrait.lg { width: 44px; height: 44px; border-radius: 9px; font-size: 14px; }
/* PR #213 v2 Passport cover header uses .portrait.xl. Was referenced
   in markup but missing from CSS — would have silently rendered at
   the base 32px size, visually undersized next to the 30px cover title.
   Reviewer-caught blocker. */
.portrait.xl { width: 48px; height: 48px; border-radius: 10px; font-size: 16px; }
.portrait.buyer { background: linear-gradient(135deg, var(--green), var(--green-dim)); }
.portrait.seller { background: linear-gradient(135deg, var(--accent), var(--violet)); }

/* Per-company portrait tints — eight gradients sampled from the design
   system. Picked deterministically via PortraitTint(name) so every
   company keeps the same gradient across pages without DB storage. The
   .buyer / .seller role-tints above still win when both classes are
   present — role identity > name identity on Contracts. */
.portrait.p-em { background: linear-gradient(135deg, #c87f4a, #6b3e1b); }
.portrait.p-hg { background: linear-gradient(135deg, #d89b3d, #8a5a1a); }
.portrait.p-av { background: linear-gradient(135deg, #b86535, #6d3416); }
.portrait.p-ls { background: linear-gradient(135deg, #5a6878, #2f3942); }
.portrait.p-rt { background: linear-gradient(135deg, #6b7a5c, #3d4632); }
.portrait.p-mr { background: linear-gradient(135deg, #4d6580, #2a3849); }
.portrait.p-kf { background: linear-gradient(135deg, #7d5a50, #45312b); }
.portrait.p-cf { background: linear-gradient(135deg, #666d7a, #3a4048); }
.portrait.ai::after {
  content: "AI";
  position: absolute;
  bottom: -3px; right: -3px;
  background: var(--amber);
  color: #1a1309;
  font-size: 7.5px;
  font-weight: 700;
  padding: 1px 3px;
  border-radius: 3px;
  line-height: 1;
  letter-spacing: 0.04em;
}

/* ─── Status badges (deal status, contract role) ─── */
/* Bootstrap's .badge is a solid fill + white text by default. Our variants
   (.badge.green/.red/.amber/.blue/.violet) layer a dim-bg + colored-text
   palette on top — more restrained on dark. Used together:
   `class="badge green"` / `.red` / `.amber` / `.blue` / `.violet`.
   IMPORTANT: the base override below DOES NOT set color/background — it only
   defines shape/padding. That way when Bootstrap's `.bg-danger` or `.bg-success`
   utility is applied (like the inbox-count badge in AppBar), Bootstrap's own
   text color wins. TL review on PR #29: previous version set color: var(--text-2)
   which rendered as gray digits on red for `.badge.bg-danger`. */

.badge {
  display: inline-flex;
  align-items: center;
  padding: 2px var(--s-2);
  border-radius: 4px;
  font-size: 11px;
  font-weight: 500;
  white-space: nowrap;
  line-height: 1.4;
}
/* Color modifiers — opt in only for our custom status palette (deal status,
   contract role). Order matters: these sit AFTER the base rule so they win
   when both classes are present. */
.badge.green   { background: var(--green-dim); color: var(--green); }
.badge.red     { background: var(--red-dim); color: var(--red); }
.badge.amber   { background: var(--amber-dim); color: var(--amber); }
/* 2026-05-15 — .badge.blue keeps a SELF-CONTAINED blue-on-blue pair after
   the accent token went green. Without this it would pick up the green
   --accent-dim background while keeping the light-blue text, producing a
   visibly broken badge. The literal hex values mirror the pre-refresh
   accent (#4f80ff → #1a2a4a dimmed bg) so callers using `.badge.blue` for
   "neutral info" semantics (seller role, tier badge, status timeline)
   still get a blue chip distinct from the green primary chip. */
.badge.blue    { background: #1a2a4a; color: #8fb1ff; }
.badge.violet  { background: #2c1f52; color: var(--violet); }
/* /players rank chips — neutral on every row, accent on the viewer's pinned
   row. Tabular digits + min-width keep the column edge straight at #1 vs #99. */
.badge.neutral { background: var(--bg-2); color: var(--text-2); }
.badge.primary { background: var(--accent-dim); color: var(--accent); }
.badge.rank    { min-width: 38px; justify-content: center; font-variant-numeric: tabular-nums; }

.badge-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: currentColor;
  margin-right: var(--s-2);
  display: inline-block;
}

/* ─── Pill-group — segmented view switcher on /bourse (All / Watching) ───
   Two buttons in a single bordered shell; the active one gets a brighter
   inset background. Counts ride along in a muted suffix so the player
   sees "Watching · 3" without leaving the toolbar. */
.pill-group {
  display: inline-flex;
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 2px;
}
.pill-group button {
  background: transparent;
  border: 0;
  padding: 4px 12px;
  font-size: 12.5px;
  color: var(--text-2);
  border-radius: 4px;
  cursor: pointer;
  font-weight: 500;
  display: inline-flex; align-items: center; gap: 6px;
}
.pill-group button:hover { color: var(--text-1); }
.pill-group button.active {
  background: var(--bg-2);
  color: var(--text-1);
}
.pill-group button .pill-count {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 11px;
  color: var(--text-3);
}
.pill-group button.active .pill-count { color: var(--text-2); }

/* ─── Composer — counter-form wrapper on /deals/{id} ───
   Single bordered surface that groups numeric inputs + textarea + action
   row. Replaces the Bootstrap .card the form was wrapped in; the inputs
   themselves stay .form-control so existing locale-safe parsing logic
   keeps working. */
.composer {
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
}
.composer-body { padding: var(--s-4) var(--s-5); }
.composer-foot {
  display: flex; justify-content: flex-end;
  align-items: center; gap: var(--s-2);
  padding: var(--s-3) var(--s-5);
  border-top: 1px solid var(--border);
  background: var(--bg-0);
  border-bottom-left-radius: var(--r-lg);
  border-bottom-right-radius: var(--r-lg);
}
.composer-foot .btn { padding: 6px var(--s-4); }

/* ─── Bourse sort chip — replaces the Bootstrap form-select sort picker ───
   Native <select> styled to match the design's .bourse-sort pill so the
   keyboard behavior + Razor @bind keep working without a custom dropdown. */
.bourse-sort {
  display: inline-flex; align-items: center;
  gap: var(--s-2);
  padding: 5px var(--s-3);
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font-size: 12.5px;
  color: var(--text-1);
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%239aa4af' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'/%3e%3c/svg%3e");
  background-repeat: no-repeat;
  background-position: right 8px center;
  padding-right: 24px;
}
.bourse-sort:hover { border-color: var(--border-strong); }
.bourse-sort:focus {
  outline: none;
  border-color: var(--accent);
}

/* ─── Modal — dimmed scrim + centered card ───
   Available for future use; not wired into a flow yet. The .modal-scrim
   captures the click-outside-to-close affordance; .modal stops propagation.
   Bootstrap's data-bs-toggle="modal" will not bind to these — the design
   archetype is for hand-controlled show/hide via Razor state. */
.modal-scrim {
  position: fixed; inset: 0;
  background: rgba(5, 7, 9, 0.62);
  display: flex; align-items: center; justify-content: center;
  z-index: 1050;
  padding: var(--s-6);
}
.ds-modal {
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  max-width: 100%;
  max-height: calc(100vh - 64px);
  display: flex; flex-direction: column;
  box-shadow: 0 24px 48px rgba(0,0,0,0.5);
  overflow: hidden;
}
.ds-modal-head {
  display: flex; align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-5) var(--s-6);
  border-bottom: 1px solid var(--border);
}
.ds-modal-title {
  font-size: 16px; font-weight: 600;
  letter-spacing: -0.01em;
}
.ds-modal-body {
  padding: var(--s-5) var(--s-6);
  overflow-y: auto;
}
.ds-modal-foot {
  padding: var(--s-4) var(--s-6);
  border-top: 1px solid var(--border);
  display: flex; gap: var(--s-2);
  justify-content: flex-end;
  background: var(--bg-0);
}

/* Post-offer modal — mobile goes full-screen so the form is reachable
   without swipes; padding tightens; foot buttons stretch to fill. */
/* PR #215 — retired the legacy 600px breakpoint, mapped to standard
   sm (575.98px) per design/principles.md. Visual difference is
   imperceptible (568-600px viewport range covers a handful of
   discontinued phones); the standard set keeps the codebase to one
   breakpoint vocabulary. Same rationale below for every 480/520/600
   → 575.98, 720 → 767.98, and 1100 → 1023.98 migration in this PR. */
@media (max-width: 575.98px) {
  .modal-scrim { padding: 0; }
  .ds-modal.post-offer-modal {
    width: 100% !important;
    height: 100vh;
    max-height: 100vh;
    border-radius: 0;
    border-left: 0; border-right: 0;
  }
  .ds-modal-head, .ds-modal-body, .ds-modal-foot {
    padding-left: var(--s-4);
    padding-right: var(--s-4);
  }
  .ds-modal-foot .btn { flex: 1; justify-content: center; }
}

/* ─── Toast — bottom-right stack of dismissible notifications ───
   Scaffolded; the actual notification engine is a future slice. */
.toast-stack {
  position: fixed;
  right: var(--s-5); bottom: var(--s-5);
  display: flex; flex-direction: column;
  gap: var(--s-2);
  z-index: 1060;
  max-width: 380px;
}
.ds-toast {
  display: flex; align-items: flex-start;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: 0 4px 12px rgba(0,0,0,0.35);
  animation: ds-toast-in 160ms ease-out;
}
.ds-toast-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: var(--bg-2);
  flex-shrink: 0; margin-top: 1px;
}
.ds-toast.success .ds-toast-icon { color: var(--green); background: var(--green-dim); }
.ds-toast.error .ds-toast-icon { color: var(--red); background: var(--red-dim); }
.ds-toast.warn .ds-toast-icon { color: var(--amber); background: var(--amber-dim); }
.ds-toast.info .ds-toast-icon { color: var(--accent); background: var(--accent-dim); }
.ds-toast-text { flex: 1; }
.ds-toast-title { font-size: 13px; font-weight: 500; color: var(--text-1); }
.ds-toast-body { font-size: 12px; color: var(--text-2); margin-top: 2px; line-height: 1.45; }
@keyframes ds-toast-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ─── Inline SVG icon (Icon.razor) ───
   Icons inherit currentColor + sit on the text baseline. The vertical
   alignment nudge avoids the raised-glyph look you get from default
   inline-block svg in flex-baseline rows. */
.icon {
  display: inline-block;
  vertical-align: -2px;
  flex-shrink: 0;
  stroke: currentColor;
  fill: none;
}

/* ─── Mobile responsive overrides ───
   The design archetypes default to desktop layouts; this block patches
   each for narrow viewports. Breakpoints chosen empirically:
     - 768px: tablet portrait — hide chrome that's nice-to-have but not
              critical (wordmark, org name, game-clock label).
     - 600px: small tablet / large phone — drop secondary chrome
              entirely (game-clock, org-pill).
     - 480px: phone — densest hot-fixes (smaller fonts, fewer columns).

   Bootstrap's container override at the top of this file already gives
   us a 100% width on small viewports. The fixes below only address
   design-system archetypes that bake in column counts or fixed widths.
*/

/* Topbar — collapse from "logo + 5 nav + clock + org + signout + avatar"
   to just the essentials as the viewport shrinks.
   PR #215 — bumped from `max-width: 768px` (1-px overlap with the
   md min-width tier) to standard `max-width: 767.98px`. Reviewer-
   caught straggler from the pre-PR codebase. */
@media (max-width: 767.98px) {
  .topbar { gap: var(--s-3); padding: 0 var(--s-3); }
  .topbar-logo span:not(.logomark) { display: none; }     /* hide "PlayBourse" wordmark */
  .topbar-nav { margin-left: var(--s-2); gap: 0; }
  .topbar-nav .nav-link { padding: var(--s-1) var(--s-2); font-size: 13px; }
  .org-pill .org-name { display: none; }                  /* keep just the .org-mark square */
  .org-pill { padding: 4px var(--s-2); max-width: none; }
  .gameclock-chip { font-size: 11px; padding: 3px 6px; }
}
@media (max-width: 575.98px) {
  .gameclock-chip { display: none; }                      /* ambient info, drop entirely */
}
@media (max-width: 575.98px) {
  .org-pill { display: none; }                            /* avatar covers identity */
  .trophy-pill { display: none; }                         /* ambient stat, drop at phone widths */
  .topbar-nav .nav-link { padding: 4px 8px; font-size: 12.5px; }
  /* Allow nav to scroll horizontally if 5 links still don't fit. The
     -webkit-overflow-scrolling preserves momentum on iOS. */
  .topbar-nav {
    overflow-x: auto;
    flex-wrap: nowrap;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .topbar-nav::-webkit-scrollbar { display: none; }
  .topbar-nav .nav-link { white-space: nowrap; }
}

/* 2026-05-15: legacy `@media (max-width: 480px)` block retired in PR #197.
   The .offer-grid container's mobile base (1-col stack) now owns the phone
   density; per-card padding stays at the standalone `.offer-card` rule's
   var(--s-6) on mobile, then tightens to var(--s-5) inside the grid at
   md+. Cleaner: one breakpoint set instead of "480 → 768 → grid override". */

/* Hero-stat — 56px display number can overflow on small screens with
   8-digit money. clamp() keeps it readable while never overflowing. */
@media (max-width: 575.98px) {
  .hero-stat { padding: var(--s-6); }
  .hero-stat .value { font-size: clamp(36px, 11vw, 56px); }
}

/* Pipeline rows — collapse 4-col grid to 3-col, stacking stage + amount
   on the right. Keeps name + portrait readable on phones. */
@media (max-width: 575.98px) {
  .pl-row {
    grid-template-columns: 28px 1fr auto;
    padding: 10px var(--s-3);
  }
  .pl-stage { display: none; }                            /* surfaced via amber dot on portrait if Your-turn */
  .pl-amt { font-size: 12px; }
}

/* Offer-diff — drop the delta column on phones. The value cells are
   already colored green/red/muted via .od-val.curr.up/down/flat, so
   the direction is preserved without the explicit ▲/▼ + delta number. */
@media (max-width: 575.98px) {
  .offer-diff { padding: var(--s-3) var(--s-4); }
  .od-head, .od-row {
    grid-template-columns: 70px 1fr 1fr;
  }
  .od-delta { display: none; }
  .od-val { font-size: 13px; }
  .od-key { font-size: 9.5px; letter-spacing: 0.06em; }
}

/* Composer — Bootstrap row.g-3 already stacks on xs; just trim padding. */
@media (max-width: 575.98px) {
  .composer-body { padding: var(--s-3) var(--s-4); }
  .composer-foot { padding: var(--s-3) var(--s-4); flex-wrap: wrap; }
}

/* Container — Bootstrap's .container is full-width on xs already, but
   our override forces max-width: 1500px. Reduce the .py-4 padding so
   content fits the viewport better. */
@media (max-width: 575.98px) {
  .container { padding-left: var(--s-3); padding-right: var(--s-3); }
}

/* PR #215 retired the 380px micro-tier here. .mini-stats already drops
   to 2-col at the standard sm breakpoint, and the .04em letter-spacing
   / 14px value-font delta this rule used to enforce was imperceptible
   against the 16/18px default — not worth the extra breakpoint. iPhone
   SE narrow (320px) tested fine without it. */

/* Empty-state — generous padding on desktop (--s-12 = 48px) eats too
   much content area on phones. Trim while keeping the medallion +
   text + CTA visually grouped. */
@media (max-width: 575.98px) {
  .empty-state { padding: var(--s-8) var(--s-4); }
  .empty-title { font-size: 15px; }
  .empty-body { font-size: 12.5px; }
}

/* Section-h — keep "Active pipeline" + "View all →" on the same line
   even at 320px by reducing the link's font size + tighter gap. */
@media (max-width: 575.98px) {
  .section-h { margin: var(--s-5) 0 var(--s-2); }
  .section-h .a { font-size: 11px; }
}

/* 2026-05-15: legacy 480px pill-group tweak retired in PR #197. The
   .touch-44 utility on each pill button now owns mobile sizing (44px
   min-height); the 0.5px font-size delta this rule used to enforce was
   imperceptible compared to the actual hit-target win. */

/* Login two-column — the existing 1100px breakpoint stacks the panels,
   but the right (form) panel still inherits .login-right's tall padding.
   Trim on phones so the form is reachable without scrolling.
   PR #215 review fix — `.login-hero h1` + `.login-hero p` font-size
   rules removed here; they were duplicates of the canonical block at
   ~line 1486 (which sets h1 to 30px line-height 1.15, intentional
   3-line wrap on iPhone-SE-class). Same-breakpoint duplicate would
   have lost the cascade race anyway (30px there wins source order). */
@media (max-width: 575.98px) {
  .login-left { padding: var(--s-6) var(--s-4); }
  .login-right { padding: var(--s-6) var(--s-4); }
}

/* ─── Topbar archetype — replaces Bootstrap navbar in AppBar.razor ───
   52px sticky bar, sits at the top of every authenticated page. The .topbar
   wrapper carries the surface + border; .topbar-logo / .topbar-nav /
   .topbar-right are the three structural slots. */
.topbar {
  height: 52px;
  background: var(--bg-1);
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center;
  padding: 0 var(--s-5);
  gap: var(--s-6);
  position: sticky; top: 0; z-index: 10;
}
.topbar-logo {
  font-weight: 600; font-size: 15px;
  letter-spacing: -0.01em;
  display: flex; align-items: center; gap: var(--s-2);
  color: var(--text-1);
  text-decoration: none;
}
.topbar-logo:hover { color: var(--text-1); }
.topbar-nav {
  display: flex; gap: var(--s-1);
  margin-left: var(--s-5);
  list-style: none;
  padding: 0; margin-top: 0; margin-bottom: 0;
}
.topbar-nav .nav-link {
  padding: var(--s-1) var(--s-3);
  color: var(--text-2);
  font-size: 13.5px;
  border-radius: var(--r-md);
  font-weight: 500;
  cursor: pointer;
  transition: background 0.08s, color 0.08s;
}
.topbar-nav .nav-link:hover { background: var(--bg-2); color: var(--text-1); }
.topbar-nav .nav-link.active { color: var(--text-1); background: var(--bg-2); }
.topbar-right {
  margin-left: auto;
  display: flex; align-items: center; gap: var(--s-2);
}

/* Icon button — square hit-target for bell / news / more / search.
   Solid variant (.icon-btn.solid) has the bg-2 background out of the gate;
   ghost variant (default) is transparent until hover. */
.icon-btn {
  width: 28px; height: 28px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-md);
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-2);
  cursor: pointer;
  transition: background 0.08s, color 0.08s, border-color 0.08s;
}
.icon-btn:hover {
  background: var(--bg-2);
  color: var(--text-1);
  border-color: var(--border);
}
.icon-btn.has-dot { position: relative; }
.icon-btn.has-dot::after {
  content: ""; position: absolute;
  top: 5px; right: 5px;
  width: 6px; height: 6px;
  background: var(--red);
  border-radius: 50%;
  box-shadow: 0 0 0 2px var(--bg-1);
}

/* Org pill — company name + small color mark on the right side of the topbar.
   Replaces the bare text rendering of the player's display name. */
.org-pill {
  display: flex; align-items: center;
  gap: var(--s-2);
  padding: 4px var(--s-3);
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font-size: 13px;
  color: var(--text-1);
  text-decoration: none;
  max-width: 220px;
}
.org-pill:hover { border-color: var(--border-strong); color: var(--text-1); }
.org-pill .org-mark {
  width: 18px; height: 18px;
  border-radius: 4px;
  background: var(--brand-gradient, linear-gradient(135deg, var(--accent), var(--violet)));
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 9px; font-weight: 700;
  color: #fff;
  flex-shrink: 0;
}
.org-pill .org-name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Game-clock chip — restyled to the design's small mono pill on bg-2. */
.gameclock-chip {
  display: inline-flex; align-items: center;
  gap: var(--s-2);
  padding: 3px var(--s-2);
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  color: var(--text-2);
}
.gameclock-chip-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--green);
  animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.4; }
}
/* PR #211 — close the a11y motion-opt-out pattern started in PR #208
   (login-live-dot). vestibular-sensitive players see a static dot, not
   a pulsing one — the green color alone still reads as "live". Same
   .prod-pulse animation lower down has its own opt-out below. */
@media (prefers-reduced-motion: reduce) {
  .gameclock-chip-dot { animation: none; }
}

/* Trophy / achievement pill (2026-05-13). Persistent progress counter in
   the topbar — emoji + monospace ratio so the visual weight reads as
   "ambient stat" not "alert." Same chip envelope as .gameclock-chip so
   the topbar-right cluster has consistent shape; subtle hover lift to
   communicate clickability since the rest of the cluster mixes static
   chips and anchors. */
.trophy-pill {
  display: inline-flex; align-items: center;
  gap: var(--s-2);
  padding: 3px var(--s-2);
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  font-size: 12px;
  color: var(--text-2);
  text-decoration: none;
  transition: border-color 0.08s, color 0.08s;
}
.trophy-pill:hover { border-color: var(--border-strong); color: var(--text-1); }
.trophy-pill-icon { font-size: 13px; line-height: 1; }
.trophy-pill-count {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* ─── Design-system component archetypes (PlayBourse Design System 2026-04-28) ─── */
/* Three archetypes, used alongside Bootstrap utilities, NOT in place of them.
   Reach for these when a screen needs the trader-terminal density signature
   (offer-card / hero-stat) or structured comparison (offer-diff). Bootstrap's
   .card stays the workhorse for everything else. */

/* Offer card — the big-bet trader card on the Bourse.
   Replaces the prior .card.mb-3 with row/col guts. Bigger radius, more padding,
   monospace 22px stat values, hover-lift. */
.offer-card {
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  padding: var(--s-6);
  margin-bottom: var(--s-4);
  transition: border-color 0.12s, transform 0.12s;
}
.offer-card:hover { border-color: var(--border-strong); transform: translateY(-1px); }
/* Keyboard focus indication for .offer-card rendered as <a> (e.g. /players
   rows). Border emphasis + an accent ring so tab-stops are visible.
   Skips ":focus" without :focus-visible so mouse clicks don't render
   the ring.
   2026-05-15 UX review on PR #201: rgba on the FULL accent (not
   --accent-dim) so the ring clears WCAG 1.4.11 (3:1 non-text contrast)
   against --bg-0. --accent-dim #0c3a24 vs --bg-0 #0b0d0f is ~1.41:1,
   fails spec. Full accent #00d97e is ~10:1; 55% alpha keeps the ring
   from feeling loud while preserving the contrast headroom.
   Dual-state note: .mine-pin-card already uses border-color: var(--accent)
   so on a focused-and-pinned row the border doesn't visibly change —
   the box-shadow ring alone carries the focus signal. */
.offer-card:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(0, 217, 126, 0.55);
}
/* /players viewer's pinned card — same shape as the leaderboard rows below
   but with a brighter accent border so "your row" reads at a glance. */
.offer-card.mine-pin-card { border-color: var(--accent); }

/* ─── Offer grid — /bourse responsive card layout (2026-05-15) ───
   Principle 3 (density tiers): card stack on mobile, 2-col data grid on
   tablet, 3-col on wide desktop. Standard md/lg/xl breakpoints — no more
   ad-hoc 480/520/600 inside this surface.
   .offer-card's own margin-bottom is zeroed inside the grid; the grid gap
   owns row+column spacing so cards line up flush. */
.offer-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-4);
}
.offer-grid .offer-card { margin-bottom: 0; }
@media (min-width: 768px) {
  .offer-grid { grid-template-columns: repeat(2, 1fr); }
  /* Tighten padding so 2-col cards feel like a grid, not a stack of phones. */
  .offer-grid .offer-card { padding: var(--s-5); }
  /* Stat values trimmed so two-cards-side-by-side don't overflow on tablet. */
  .offer-grid .oc-stat .value { font-size: 19px; }
  .offer-grid .oc-commodity-title { font-size: 22px; }
}
@media (min-width: 1024px) {
  /* lg = real desktop. 3-col grid here (not at xl=1280) — 1024–1279 has
     enough room (~330px/card minus padding) to fit the 19px stat trim.
     Matches the lg=1024 breakpoint in design/principles.md. */
  .offer-grid { grid-template-columns: repeat(3, 1fr); }
}
.oc-head { display: flex; gap: var(--s-4); align-items: center; margin-bottom: var(--s-5); }
.oc-name { font-size: 16px; font-weight: 600; }
.oc-sub { color: var(--text-2); font-size: 12.5px; margin-top: 2px; }
.oc-side { display: flex; align-items: center; gap: var(--s-2); color: var(--text-3); font-size: 11.5px; }
.oc-commodity-title {
  font-size: 26px; font-weight: 500;
  letter-spacing: -0.015em; line-height: 1.2;
}
.oc-commodity-spec {
  color: var(--text-2); font-size: 14px;
  margin-top: 6px; margin-bottom: var(--s-5);
}
.oc-stats {
  display: grid; grid-template-columns: 1fr 1fr 1fr;
  gap: var(--s-4);
  padding: var(--s-4) 0;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--s-5);
}
.oc-stat .label {
  font-size: 10.5px; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--text-3);
  margin-bottom: 6px; font-weight: 500;
}
.oc-stat .value {
  font-family: var(--font-mono);
  font-size: 22px; font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
.oc-actions { display: flex; gap: var(--s-3); align-items: center; }

/* Price-fit chip — small badge on each offer card comparing the offer's
   price to the commodity reference. Three semantic buckets:
     Good (-5%+ below ref) → green
     Fair (within ±5%)     → muted
     High (+5%+ above ref) → amber (not red — high price is friction, not danger)
   Each carries a sparkles icon for visual rhythm with the design's
   "match-fit" pattern. */
.price-fit {
  display: inline-flex; align-items: center;
  gap: 4px;
  padding: 2px var(--s-2);
  border-radius: 3px;
  font-size: 11px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  margin-left: auto;
}
.price-fit-good { background: var(--green-dim); color: var(--green); }
.price-fit-fair { background: var(--bg-3);      color: var(--text-3); }
.price-fit-high { background: var(--amber-dim); color: var(--amber); }
/* 2026-05-15: legacy 480px font-size shrink retired in PR #197 — 11→10px
   was a marginal tweak that isn't worth a per-surface breakpoint. */

/* Hero stat — the big-number block at the top of CompanyDashboard.
   Net worth at 56px monospace, optional sparkline + delta pill below. */
.hero-stat {
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  padding: var(--s-8);
  margin-bottom: var(--s-5);
  text-align: center;
}
.hero-stat .label {
  font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.1em; color: var(--text-3);
  font-weight: 500; margin-bottom: var(--s-3);
}
.hero-stat .value {
  font-family: var(--font-mono);
  font-size: 56px; font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.03em; line-height: 1;
  margin-bottom: var(--s-3);
}
.hero-stat .delta { font-size: 14px; color: var(--green); }

/* Mini stats — a compact 4-up grid below the hero-stat. */
/* Mini-stats — mobile-first 2-col, 4-col at md+ (replaces the legacy
   640px desktop-first rule retired 2026-05-15 PR /company migration).
   2 cols at narrow widths keeps the 4 stat values readable at 375px
   instead of cramming 4 tiny columns. */
.mini-stats {
  display: grid; grid-template-columns: repeat(2, 1fr);
  gap: var(--s-3);
  background: var(--bg-1); border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s-4) var(--s-5);
  margin-bottom: var(--s-5);
}
.mini-stat { text-align: center; }
.mini-stat .label {
  font-size: 10.5px; color: var(--text-3);
  text-transform: uppercase; letter-spacing: 0.08em;
  margin-bottom: 4px;
}
.mini-stat .value {
  font-family: var(--font-mono);
  font-size: 16px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
@media (min-width: 768px) {
  .mini-stats { grid-template-columns: repeat(4, 1fr); }
}

/* Spark row — sparkline + delta pill alignment helper for hero-stat. */
.spark-row {
  display: flex; align-items: center;
  gap: var(--s-3); justify-content: center;
  margin-top: var(--s-3);
}
.delta-pill {
  display: inline-flex; align-items: center;
  gap: 4px; font-size: 11.5px;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  padding: 1px 6px; border-radius: 3px;
}
.delta-pill.up { color: var(--green); background: var(--green-dim); }
.delta-pill.down { color: var(--red); background: var(--red-dim); }
.delta-pill.flat { color: var(--text-3); background: var(--bg-3); }

/* Section head — small caption row above grouped lists ("Active pipeline",
   "Diff vs. your last counter"). */
.section-h {
  display: flex; align-items: baseline;
  justify-content: space-between;
  margin: var(--s-6) 0 var(--s-3);
}
.section-h .t { font-size: 13px; color: var(--text-2); font-weight: 500; }
.section-h .a {
  font-size: 12px; color: var(--text-3);
  cursor: pointer; text-decoration: none;
}
.section-h .a:hover { color: var(--text-1); }

/* Offer diff — structured your-last-vs-their-counter table on the Deal page. */
.offer-diff {
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s-4) var(--s-5);
  margin-bottom: var(--s-5);
}
.od-head, .od-row {
  display: grid;
  grid-template-columns: 90px 1fr 1fr 110px;
  gap: var(--s-3);
}
.od-head {
  padding-bottom: var(--s-2);
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--s-2);
}
.od-row {
  align-items: center;
  padding: var(--s-2) 0;
  border-bottom: 1px solid var(--border);
}
.od-row:last-child { border-bottom: 0; }
.od-col, .od-key {
  font-size: 10.5px; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--text-3);
  font-weight: 500;
}
.od-val {
  font-size: 14px;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
.od-val.prev {
  color: var(--text-3);
  text-decoration: line-through;
  text-decoration-color: var(--text-3);
  text-decoration-thickness: 1px;
}
.od-val.curr { color: var(--text-1); font-weight: 500; }
.od-val.curr.up { color: var(--green); }
.od-val.curr.down { color: var(--red); }
.od-delta {
  font-size: 11.5px;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.delta-up { color: var(--green); }
.delta-down { color: var(--red); }
.delta-flat { color: var(--text-3); }

/* Action tiles — three-up grid of CTA tiles ("Explore the Bourse",
   "Post an offer", "Forecast Q3"). Hover-lift, no shadow. */
.action-tiles {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: var(--s-3);
}
@media (max-width: 767.98px) {
  .action-tiles { grid-template-columns: 1fr; }
}
.action-tile {
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s-5);
  cursor: pointer;
  transition: border-color 0.12s, transform 0.12s;
  display: block; color: inherit; text-decoration: none;
}
.action-tile:hover {
  border-color: var(--border-strong);
  transform: translateY(-1px);
  color: inherit;
}
.action-tile .at-label {
  font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--text-3);
  margin-bottom: var(--s-2); font-weight: 500;
}
.action-tile .at-title {
  font-size: 17px; font-weight: 600;
  letter-spacing: -0.01em; margin-bottom: var(--s-1);
}
.action-tile .at-sub {
  color: var(--text-2); font-size: 13px; line-height: 1.4;
}

/* Pipeline — list of active negotiation rows on Company dashboard.
   Each row is the portrait + counterparty + stage badge + indicative total.
   Whole row is a link; hover lifts the row's background. */
.pipeline {
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
}
.pl-row {
  display: grid;
  grid-template-columns: 32px 1fr 130px 110px;
  gap: var(--s-3);
  align-items: center;
  padding: 10px var(--s-4);
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  text-decoration: none;
  color: inherit;
  transition: background 0.08s;
}
.pl-row:last-child { border-bottom: 0; }
.pl-row:hover { background: var(--bg-hover); color: inherit; }
.pl-name {
  font-size: 13.5px; color: var(--text-1);
  font-weight: 500;
}
.pl-sub {
  font-size: 11.5px; color: var(--text-3);
  margin-top: 1px;
}
.pl-stage {
  font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--text-2);
}
.pl-amt {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 13px; color: var(--text-1);
  text-align: right;
}

/* ─── Marketing/login landing (Home.razor unauthenticated state) ───
   Two-column split-screen with the brand + hero copy on the left and a
   sign-in CTA panel on the right. Atmospheric radial gradients on the
   left panel give it a "live market opening" feel without competing with
   the typography. Collapses to single column under 1100px. */
.login-wrap {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 1fr 1fr;
}
@media (max-width: 1023.98px) {
  .login-wrap { grid-template-columns: 1fr; }
}
.login-left {
  padding: var(--s-10) var(--s-12);
  display: flex; flex-direction: column;
  justify-content: space-between;
  background: var(--bg-0);
  position: relative; overflow: hidden;
}
.login-left::before {
  content: ""; position: absolute; inset: 0;
  /* 2026-05-15 — rebased from rgba(79,128,255,…) to the new green accent
     so the login backdrop matches the cascade. The violet half stays as
     identity-tinted secondary radial. */
  background:
    radial-gradient(1000px 600px at 80% -10%, rgba(0,217,126,0.12), transparent 60%),
    radial-gradient(800px 500px at -10% 100%, rgba(158,124,255,0.08), transparent 60%);
  pointer-events: none;
}
@media (max-width: 1023.98px) {
  .login-left { padding: var(--s-6); }
}
.login-brand {
  display: flex; align-items: center;
  gap: var(--s-3);
  font-size: 18px; font-weight: 600;
  position: relative;
  flex-wrap: wrap;
}
.login-brand .logomark {
  width: 28px; height: 28px;
  border-radius: 6px;
}

/* PR #208 v2 login refresh: "Live · Day N · X operators" badge sits
   next to the brand wordmark. Pulsing dot matches the AppBar
   gameclock-chip dot animation (same live-pulse keyframes). Wraps
   to its own line on narrow viewports via .login-brand flex-wrap. */
.login-live-badge {
  display: inline-flex; align-items: center; gap: 6px;
  margin-left: auto;
  padding: 4px 10px;
  background: var(--green-dim);
  color: var(--green);
  border-radius: 999px;
  font-size: 11.5px; font-weight: 500;
  font-family: var(--font-sans);
  letter-spacing: 0;
  white-space: nowrap;
}
.login-live-dot {
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--green);
  box-shadow: 0 0 0 3px rgba(0, 217, 126, 0.18);
  animation: login-live-pulse 2s ease-in-out infinite;
  flex-shrink: 0;
}
@keyframes login-live-pulse {
  0%, 100% { box-shadow: 0 0 0 3px rgba(0, 217, 126, 0.18); }
  50%      { box-shadow: 0 0 0 6px rgba(0, 217, 126, 0.04); }
}
/* PR #208 review: respect prefers-reduced-motion so vestibular-sensitive
   players see a static dot, not a pulsing one. Live ≠ animated; the
   green-on-green-dim badge still reads as "live." Same opt-out
   pattern should land on .gameclock-chip-dot (pre-existing pulse, not
   in scope here) in a follow-up. */
@media (prefers-reduced-motion: reduce) {
  .login-live-dot { animation: none; }
}

/* PR #208 v2 login refresh: 4-stat strip at the bottom of .login-left
   (operators / AI / volume signed / ∞). Sits above .login-foot;
   wraps onto two lines on narrow viewports via flex-wrap. */
.login-stats {
  display: flex; gap: var(--s-7);
  flex-wrap: wrap;
  padding-top: var(--s-5);
  border-top: 1px solid var(--border);
  max-width: 520px;
  position: relative;
}
.login-stat .v {
  font-size: 22px; font-weight: 600;
  color: var(--text-1);
  line-height: 1;
  letter-spacing: -0.02em;
}
.login-stat .k {
  font-size: 10.5px; color: var(--text-3);
  margin-top: 6px;
  text-transform: uppercase; letter-spacing: 0.06em;
  font-weight: 600;
}
@media (max-width: 575.98px) {
  /* PR #208 review: original draft used flex-wrap alone, which produced
     a 4×1 vertical stack at 375px (each stat takes full width). 2×2 grid
     is denser + matches the bottom of the v2 prototype's left panel.
     Explicit grid-template-columns + gap delivers it. */
  .login-stats {
    display: grid; grid-template-columns: 1fr 1fr;
    gap: var(--s-4) var(--s-5);
  }
  .login-stat .v { font-size: 18px; }
}
.login-hero { max-width: 520px; position: relative; }
.login-hero h1 {
  font-size: 42px; font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.1;
  margin: 0 0 var(--s-5);
}
.login-hero p {
  color: var(--text-2);
  font-size: 16px; line-height: 1.6;
  margin: 0 0 var(--s-6);
}
.login-hero ul {
  list-style: none;
  padding: 0; margin: 0;
  display: grid; gap: var(--s-3);
}
.login-hero li {
  display: flex; gap: var(--s-3);
  align-items: baseline;
  color: var(--text-1);
  font-size: 14px;
}
.login-hero li::before {
  content: "";
  width: 5px; height: 5px;
  background: var(--accent);
  border-radius: 50%;
  display: inline-block;
  margin-top: 7px;
  flex-shrink: 0;
}
.login-foot {
  font-size: 12px; color: var(--text-3);
  position: relative;
  font-family: var(--font-mono);
}
.login-right {
  background: var(--bg-1);
  border-left: 1px solid var(--border);
  display: flex; align-items: center; justify-content: center;
  padding: var(--s-10) var(--s-8);
}
@media (max-width: 1023.98px) {
  .login-right { border-left: 0; border-top: 1px solid var(--border); }
}
.login-card { width: 100%; max-width: 380px; }
.login-card h2 {
  font-size: 22px; font-weight: 600;
  margin: 0 0 var(--s-2);
  letter-spacing: -0.01em;
}
.login-card .sub {
  color: var(--text-2);
  font-size: 14px;
  margin-bottom: var(--s-6);
}
.login-card .login-cta {
  display: block;
  width: 100%; padding: 12px;
  border-radius: var(--r-md);
  border: 1px solid var(--accent);
  background: var(--accent);
  color: #fff;
  font-size: 14px; font-weight: 500;
  text-align: center;
  text-decoration: none;
  transition: background 0.08s;
}
.login-card .login-cta:hover {
  background: var(--accent-hover);
  color: #fff;
}
.login-meta {
  font-size: 12.5px;
  color: var(--text-2);
  margin-top: var(--s-5);
  text-align: center;
}
.login-meta a { color: var(--accent); }
.login-card.login-card-wide { max-width: 560px; }

/* Landing-page typography scales down for narrow viewports. The 42px hero
   wraps into 4 lines on iPhone-SE-class (375px) widths and dwarfs the
   bullets — at 30px it sits in three lines and matches the visual weight
   of the eyebrow / bullets. Bullet bodycopy goes 14→13 to recover line
   length. Target: warm-launch invitees opening the URL on a phone. */
@media (max-width: 575.98px) {
  .login-hero h1 { font-size: 30px; line-height: 1.15; }
  .login-hero p { font-size: 15px; }
  .login-hero li { font-size: 13px; }
}
.btn-link-plain {
  background: none; border: 0; padding: 0;
  color: var(--accent); font-size: inherit; cursor: pointer;
}
.btn-link-plain:hover { text-decoration: underline; }

/* M9.5 slice 1 — business-type picker grid. 2x2 on desktop, single column under
   ~520px so the cards don't squish. Selected state lifts the border to .accent +
   tints the background; click target = whole card. */
.biz-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-3);
  margin-bottom: var(--s-4);
}
@media (max-width: 575.98px) {
  .biz-grid { grid-template-columns: 1fr; }
}
.biz-card {
  display: flex; flex-direction: column; gap: var(--s-1);
  text-align: left;
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--s-4);
  cursor: pointer;
  transition: border-color 0.08s, background 0.08s, transform 0.08s;
}
.biz-card:hover {
  border-color: var(--accent);
  transform: translateY(-1px);
}
.biz-card:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent);
}
.biz-card.selected {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, var(--bg-1));
}
.biz-card-name { font-weight: 600; font-size: 15px; }
.biz-card-prod { color: var(--text-2); font-size: 13px; }
.biz-card-rate { color: var(--text-3); font-size: 12px; font-variant-numeric: tabular-nums; }
/* Dollar anchor — reframes "2 mbf vs 130 bu" as equal-dollars throughput.
   Without it players pattern-match on raw quantity and read Lumber Mill as wimpy. */
.biz-card-anchor { color: var(--text-3); font-size: 12px; font-style: italic; margin-top: 2px; }

/* PR #220 — 5-step onboarding gauntlet (v3 design bundle).
   Layered on top of the existing .login-wrap / .login-card-wide shell so
   the gauntlet shares the centered-card chrome with the rest of the auth
   surface (sign-in, sign-up, name-company). Adds:
     .step-progress       — top progress bar with 5 .step-pip segments
     .onboard-h           — primary heading for each step
     .onboard-sub         — subtitle under the heading
     .onboard-step-cards  — welcome step's 3 explainer cards (1col mobile, 3col ≥md)
     .onboard-commodity-* — commodity preview step
     .onboard-stat-*      — small stat row used in commodity + sign steps
     .onboard-zopa-*      — ZOPA-demo step layout
     .onboard-sign-*      — celebratory sign step
     .onboard-nav         — footer Back / next-label / Continue row
   All classes are .onboard-* prefixed so they don't collide with the
   broader v2 token vocabulary. */

.step-progress {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: var(--s-5);
}
.step-pip {
  height: 4px; border-radius: 2px;
  background: var(--bg-3);
  width: 24px;
  transition: width 0.25s ease, background 0.25s ease;
}
.step-pip.done    { background: var(--accent); width: 28px; }
.step-pip.current { background: var(--accent); width: 36px; }
.step-progress-label {
  font-size: 10.5px; color: var(--text-3);
  margin-left: 6px; letter-spacing: 0.06em; font-weight: 600;
}

.onboard-h {
  font-size: 22px; font-weight: 600; letter-spacing: -0.02em;
  line-height: 1.2; margin: 0 0 8px;
}
.onboard-sub {
  font-size: 14px; color: var(--text-2);
  line-height: 1.55; margin-bottom: var(--s-5);
}

/* PR #228 — onboarding step-1 archetype grouping. Each category gets a
   small heading above its biz-grid so the player sees the economy's
   structure (Agriculture / Metals / Energy / Industrial) before they
   pick. Heading is text-3 uppercase tracking; grid below is the existing
   .biz-grid styling — no other changes. */
.onboard-archetype-group {
  margin-bottom: var(--s-4);
}
.onboard-archetype-heading {
  font-size: 10.5px;
  color: var(--text-3);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 600;
  margin-bottom: 8px;
  padding-left: 2px;
}

/* Welcome step: 3 explainer cards. Single column under 768px (mobile),
   3-column grid at ≥768px (tablet+). */
.onboard-step-cards {
  display: grid; grid-template-columns: 1fr; gap: var(--s-3);
  margin-bottom: var(--s-4);
}
@media (min-width: 768px) {
  .onboard-step-cards { grid-template-columns: repeat(3, 1fr); }
}
.onboard-step-card {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px;
}
.onboard-step-card-eyebrow {
  font-size: 10.5px; color: var(--accent);
  font-weight: 700; letter-spacing: 0.08em;
  margin-bottom: 8px;
}
.onboard-step-card-h {
  font-size: 14px; font-weight: 600; margin-bottom: 6px;
}
.onboard-step-card-b {
  font-size: 12px; color: var(--text-2); line-height: 1.55;
}

/* Commodity preview step: spot price card + 3-stat row. */
.onboard-commodity-card {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-3); padding: 16px 18px;
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: 10px; margin-bottom: var(--s-3);
}
.onboard-commodity-name { font-size: 16px; font-weight: 600; }
.onboard-commodity-sub  { font-size: 12px; margin-top: 2px; }
.onboard-commodity-price {
  font-size: 28px; font-weight: 700; color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.onboard-commodity-grid {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: var(--s-2);
  padding: 12px 14px;
  background: var(--bg-2); border-radius: 6px;
}
.onboard-stat-label {
  font-size: 10px; color: var(--text-3);
  text-transform: uppercase; letter-spacing: 0.06em;
  font-weight: 600;
}
.onboard-stat-value {
  font-size: 18px; font-weight: 600; color: var(--text-1);
  font-variant-numeric: tabular-nums;
  margin-top: 2px;
}
.onboard-stat-sub { font-size: 11px; color: var(--text-3); margin-top: 2px; }

/* ZOPA-demo step: chart + status + slider stacked vertically. */
.onboard-zopa-wrap { margin-bottom: var(--s-3); }
.onboard-zopa-status {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: var(--s-3);
  margin-bottom: var(--s-2);
}
.onboard-zopa-coach {
  margin-top: var(--s-3); padding: 12px 14px;
  background: var(--bg-2); border-radius: 6px;
  line-height: 1.55;
}

/* Sign step: success checkmark badge + summary card. */
.onboard-sign-mark {
  width: 80px; height: 80px; border-radius: 50%;
  background: var(--accent); color: var(--accent-on);
  display: inline-grid; place-items: center;
  box-shadow: 0 0 0 8px rgba(0,217,126,0.16),
              0 12px 40px rgba(0,217,126,0.20);
}
.onboard-sign-card {
  max-width: 480px; margin: var(--s-4) auto var(--s-3);
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: 10px; padding: 18px 22px;
  text-align: center;
}

/* Footer nav row: Back · next-label · Continue. Wraps under mobile so
   the buttons stay full-width-readable on a 360px viewport. */
.onboard-nav {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: var(--s-2);
  margin-top: var(--s-4); padding-top: var(--s-3);
  border-top: 1px solid var(--border);
}
.onboard-nav-label { white-space: nowrap; }

/* M9.5 slice 3 — production card foot row. Sits inside .offer-card below the
   .oc-stats grid; "rate" left, sparkline + label right, wraps to stack on mobile. */
.prod-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-3); flex-wrap: wrap;
  margin-top: var(--s-3); padding-top: var(--s-3);
  border-top: 1px solid var(--border);
}
.prod-rate { font-size: 13px; color: var(--text-2); }
.prod-rate-anchor { color: var(--text-3); font-size: 12px; font-style: italic; margin-top: 2px; }
.prod-spark { display: inline-flex; align-items: center; gap: var(--s-2); }
/* M9.5 slice 4b — production-tier upgrade row. Sits inside .offer-card below
   .prod-foot; left side is the next-tier copy ("Upgrade to Tier 2 — 1.5× output"),
   right side is the cost button. Stacks under flex-wrap on mobile so the
   button stays full-width-readable. */
.prod-upgrade {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-3); flex-wrap: wrap;
  margin-top: var(--s-3); padding-top: var(--s-3);
  border-top: 1px solid var(--border);
}
.prod-upgrade-copy { min-width: 0; flex: 1 1 auto; }
.prod-upgrade .btn { font-variant-numeric: tabular-nums; min-width: 110px; }
@media (max-width: 575.98px) {
  .prod-upgrade { flex-direction: column; align-items: stretch; }
  .prod-upgrade .btn { width: 100%; }
}

/* Imminent pulse — tiny green dot before "Next run any second now" so the
   player gets a visual signal that the system is alive, even without JS. */
.prod-pulse {
  display: inline-block;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--green);
  margin-right: 5px;
  vertical-align: middle;
  animation: prod-pulse 1.6s ease-in-out infinite;
}
@keyframes prod-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.4; transform: scale(1.4); }
}
/* PR #211 — match the motion-opt-out pattern on .gameclock-chip-dot +
   .login-live-dot. .prod-pulse adds a scale transform on top of the
   opacity fade, which is the most vestibular-triggering of the three.
   Static green dot is still legible as "alive". */
@media (prefers-reduced-motion: reduce) {
  .prod-pulse { animation: none; }
}

/* Commodity exposure bars — flat horizontal bars on Company dashboard. */
.exposure {
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s-4) var(--s-5);
  margin-bottom: var(--s-5);
}
.exp-row {
  display: grid;
  grid-template-columns: 1fr 1fr 60px;
  gap: var(--s-3);
  align-items: center;
  padding: 6px 0;
}
.exp-bar {
  height: 6px; background: var(--bg-2);
  border-radius: 3px; overflow: hidden;
}
.exp-bar > i {
  display: block; height: 100%;
  border-radius: 3px;
  background: var(--accent);
}
.exp-pct {
  font-family: var(--font-mono);
  font-size: 11.5px; color: var(--text-2);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.exp-name {
  font-size: 12.5px; color: var(--text-2);
  text-transform: capitalize;
}

/* ─── Error page (Components/Pages/Error.razor) ───
   Reached via UseStatusCodePagesWithReExecute("/error/{0}") for any non-2xx
   and UseExceptionHandler("/error/500") for thrown exceptions. Same dark
   palette as the login landing so a 404 doesn't read as "the site broke" —
   reads as "we know, here's the way home." */
.error-wrap {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-0);
  padding: var(--s-6);
  position: relative;
  overflow: hidden;
}
.error-wrap::before {
  content: ""; position: absolute; inset: 0;
  /* 2026-05-15 — rebased from rgba(79,128,255,…) to the new green accent
     so the error backdrop matches the cascade. */
  background:
    radial-gradient(800px 500px at 80% -10%, rgba(0,217,126,0.10), transparent 60%),
    radial-gradient(700px 450px at -10% 110%, rgba(158,124,255,0.07), transparent 60%);
  pointer-events: none;
}
.error-card {
  position: relative;
  max-width: 480px;
  width: 100%;
  text-align: center;
}
.error-brand { margin-bottom: var(--s-10); }
.error-brand .login-brand { justify-content: center; }
.error-status {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--accent);
  letter-spacing: 0.16em;
  margin-bottom: var(--s-4);
}
.error-headline {
  font-size: 32px; font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
  margin: 0 0 var(--s-4);
}
.error-body {
  color: var(--text-2);
  font-size: 15px; line-height: 1.55;
  margin: 0 0 var(--s-8);
}
.error-actions {
  margin-bottom: var(--s-10);
}
.error-actions .login-cta {
  display: inline-block;
  padding: 10px 24px;
  width: auto;
}
.error-foot {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-3);
}
@media (max-width: 575.98px) {
  .error-headline { font-size: 26px; }
  .error-brand { margin-bottom: var(--s-6); }
}

/* ────────────────────────────────────────────────────────────────────────
   PR #214 — DealRoom v2 ZOPA chart (self-side-only) on /deals/{id}
   Collapsible <details> block above the current-terms card. Header has
   an eyebrow + a one-line summary; expanded body shows two inputs
   (Walk + Target) and the ZopaChart SVG. CSS for the chart SVG
   primitives (axis line, dots, halos) lives in the ZopaChart component
   itself via inline styles + var() tokens.
   ──────────────────────────────────────────────────────────────────────── */

.zopa-block {
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
}
.zopa-summary {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  cursor: pointer;
  list-style: none;
}
.zopa-summary::-webkit-details-marker { display: none; }
.zopa-eyebrow {
  font-size: 10.5px; color: var(--accent);
  text-transform: uppercase; letter-spacing: 0.08em;
  font-weight: 700; margin-bottom: 4px;
}
.zopa-summary-sub {
  font-size: 12.5px; color: var(--text-2);
  line-height: 1.4;
}
.zopa-chevron {
  font-size: 14px; color: var(--text-3);
  transition: transform 0.12s;
  flex-shrink: 0;
}
.zopa-block[open] .zopa-chevron { transform: rotate(180deg); }
.zopa-body {
  padding: 0 var(--s-4) var(--s-4);
  border-top: 1px solid var(--border);
  padding-top: var(--s-3);
}
.zopa-svg {
  /* SVG fills the body width; height is the viewBox-defined 130px so
     the marker tags above + below don't get clipped. */
  width: 100%; height: 130px; display: block;
}
@media (max-width: 575.98px) {
  .zopa-summary { padding: var(--s-3); }
  .zopa-body { padding: var(--s-3) var(--s-3) var(--s-4); }
}

/* ────────────────────────────────────────────────────────────────────────
   PR #213 — Passport (/players/{id} v2 refresh)
   New surface primitives: cover header (gradient backdrop + portrait +
   name + rank pill), commodity-mix bar (stacked horizontal share + legend),
   and an earned/unearned badge grid (gold for earned, dim with progress
   bar for unearned).
   ──────────────────────────────────────────────────────────────────────── */

/* Cover header — gradient backdrop + radial-glow accent in the corner.
   Mobile collapses to portrait-on-top, rank-pill-below stack via the
   flex-direction switch. */
.passport-cover {
  background: linear-gradient(135deg, rgba(0, 217, 126, 0.04), rgba(91, 141, 239, 0.04)), var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  padding: 28px 32px;
  margin-bottom: var(--s-3);
  position: relative;
  overflow: hidden;
}
.passport-cover-glow {
  position: absolute; top: -100px; right: -100px;
  width: 320px; height: 320px;
  background: radial-gradient(circle, rgba(0, 217, 126, 0.08), transparent 70%);
  pointer-events: none;
}
.passport-cover-row {
  display: flex; gap: var(--s-4);
  flex-direction: row; align-items: flex-end;
  position: relative;
}
.passport-cover-name { flex: 1; min-width: 0; }
.passport-eyebrow {
  font-size: 10.5px; color: var(--accent);
  text-transform: uppercase; letter-spacing: 0.1em;
  font-weight: 700; margin-bottom: 4px;
}
.passport-cover-title {
  font-size: 30px; font-weight: 600;
  letter-spacing: -0.025em; line-height: 1.1;
  margin: 0;
  display: flex; align-items: center; gap: var(--s-2);
  flex-wrap: wrap;
}
.passport-cover-sub {
  font-size: 13.5px; color: var(--text-2);
  margin-top: 6px;
  line-height: 1.4;
}
.passport-rank-pill {
  display: inline-flex; align-items: center; gap: var(--s-2);
  padding: 5px 12px;
  background: var(--gold-bg);
  border: 1px solid var(--gold-border);
  border-radius: var(--r-md);
  color: var(--gold);
  white-space: nowrap;
  text-decoration: none;
  flex-shrink: 0;
  font-size: 11.5px;
}
.passport-rank-pill:hover { filter: brightness(1.15); }
.passport-rank-dot {
  width: 5px; height: 5px; border-radius: 50%;
  background: #ffd78a;
  box-shadow: 0 0 6px rgba(255, 215, 138, 0.6);
}
.passport-rank-num { font-weight: 700; color: #ffd78a; }
@media (max-width: 767.98px) {
  .passport-cover { padding: 20px 18px; border-radius: var(--r-lg); }
  .passport-cover-row { flex-direction: column; align-items: flex-start; gap: var(--s-3); }
  .passport-cover-title { font-size: 24px; }
}

/* Commodity-mix bar — stacked horizontal share + legend rows below.
   Segments are gradient-filled (same swatch map as the Floor tile);
   legend has commodity name + share %. */
.passport-mix-body { padding: 18px; }
.passport-mix-bar {
  display: flex;
  height: 10px;
  border-radius: 5px;
  overflow: hidden;
  margin-bottom: var(--s-3);
  background: var(--bg-2);
}
.passport-mix-segment {
  display: block;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
  transition: filter 0.12s;
}
.passport-mix-segment:hover { filter: brightness(1.2); }
.passport-mix-legend {
  display: flex; flex-direction: column; gap: 6px;
}
.passport-mix-row {
  display: flex; align-items: center; gap: var(--s-2);
  font-size: 12px;
}
.passport-mix-swatch {
  width: 10px; height: 10px; border-radius: 2px;
  flex-shrink: 0;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
}
.passport-mix-name { flex: 1; color: var(--text-1); }
.passport-mix-share {
  color: var(--text-2); font-size: 11.5px;
  font-variant-numeric: tabular-nums;
}

/* Badge grid — earned in gold, unearned dim with progress bar.
   4-col desktop, 2-col mobile. */
.passport-badges { padding: 0; }
.passport-badge-grid {
  padding: 14px;
  display: grid; gap: 10px;
  grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 768px) {
  .passport-badge-grid { grid-template-columns: repeat(4, 1fr); }
}
.passport-badge {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 12px;
  display: flex; flex-direction: column; gap: 8px;
  opacity: 0.7;
  position: relative;
  overflow: hidden;
}
.passport-badge.earned {
  background: var(--gold-bg);
  border-color: var(--gold-border);
  opacity: 1;
}
.passport-badge-icon {
  width: 36px; height: 36px; border-radius: 18px;
  background: var(--bg-3);
  color: var(--text-3);
  display: grid; place-items: center;
  font-family: var(--font-mono); font-size: 14px; font-weight: 700;
}
.passport-badge.earned .passport-badge-icon {
  background: linear-gradient(135deg, #f0c87a, #b89058);
  color: #1a1308;
  box-shadow: 0 0 0 1px var(--gold-border), 0 4px 12px rgba(240, 200, 122, 0.18);
}
.passport-badge-name {
  font-size: 13px; font-weight: 600;
  color: var(--text-2);
  line-height: 1.2;
}
.passport-badge.earned .passport-badge-name { color: var(--gold); }
.passport-badge-body {
  font-size: 11px; color: var(--text-3);
  line-height: 1.4;
}
.passport-badge-progress {
  height: 3px; background: var(--bg-3); border-radius: 2px; overflow: hidden;
  margin-top: auto;
}
.passport-badge-progress > span {
  display: block; height: 100%;
  background: var(--accent-dim-strong);
}

/* ────────────────────────────────────────────────────────────────────────
   v2 foundation refresh — new primitives (2026-05-16)
   Per the Claude Design v2 handoff bundle. These are additive: existing
   surfaces continue to use the legacy .offer-card / .oc-* / .hero-stat
   archetypes; new surfaces (Floor / Portfolio / DealRoom — landing in
   subsequent PRs) opt into the v2 primitives by class name.
   ──────────────────────────────────────────────────────────────────────── */

/* Semantic text-color utilities. v2 leans on .up / .down for inline P&L
   readouts; .dim is a quieter alternative to .text-secondary when you
   want a slightly heavier muting. */
.up   { color: var(--green); }
.down { color: var(--red); }
.dim  { color: var(--text-3); }
.muted { color: var(--text-2); }

/* .kbd — inline keyboard-shortcut hint. Used in tooltips + the empty-state
   composer hints once the v2 shell lands. */
.kbd {
  display: inline-flex; align-items: center;
  padding: 1px 5px; border: 1px solid var(--border-strong);
  border-radius: 3px; background: var(--bg-2);
  font-family: var(--font-mono); font-size: 10.5px;
  color: var(--text-2);
}

/* .term glossary tooltip DEFERRED from this PR. UX review on PR #203
   flagged two real issues that need surface-PR ownership to solve right:
     1. :hover doesn't fire on touch; the docs example wasn't focusable
        so keyboard + mobile users can't read tooltips.
     2. Tooltip clips off-screen at viewport edge — `left: 50%;
        transform: translateX(-50%)` ignores edge proximity.
   The primitive's first real consumer (currently no surface needs it)
   will own the touch-friendly + edge-aware version. Shipping a broken
   primitive nobody uses today just risks bad early adoption. */

/* .commodity-grid + .commodity-tile — Floor surface (PR-B). A market-tape
   grid of commodity tiles (one per catalog row) with spot price + 24h
   delta + a sparkline. Click drills into /commodities/{code}. Mobile
   collapses to 2-col; auto-fill on desktop keeps the tiles 220px+ wide.

   The .held::after corner-tag fires when the viewer's company owns
   inventory of that commodity. .selected ring is for keyboard nav. */
.commodity-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 10px;
}
/* Mobile cascade matches the v2 prototype bundle (`max-width: 767.98px`
   = below Bootstrap's md breakpoint). At 576-767px we still get the
   2-col layout, then auto-fill takes over at md+. */
@media (max-width: 767.98px) {
  .commodity-grid { grid-template-columns: 1fr 1fr; gap: 8px; }
}
.commodity-tile {
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px;
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s;
  position: relative;
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  display: block;
}
.commodity-tile:hover {
  border-color: var(--border-strong);
  background: var(--bg-hover);
}
.commodity-tile:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(0, 217, 126, 0.55);
}
.commodity-tile.held::after {
  content: "HELD";
  position: absolute; top: 0; right: 0;
  background: var(--accent); color: var(--accent-on);
  font-family: var(--font-mono); font-size: 9px;
  font-weight: 700; padding: 2px 6px;
  letter-spacing: 0.08em;
  border-radius: 0 10px 0 6px;
}
.commodity-tile.selected {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent-dim-strong);
}
.commodity-tile .top {
  display: flex; align-items: center; gap: 8px; margin-bottom: 12px;
  min-width: 0;
}
.commodity-tile .swatch {
  width: 16px; height: 16px; border-radius: 4px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15);
  flex-shrink: 0;
}
.commodity-tile .name {
  font-size: 13px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  min-width: 0;
}
.commodity-tile .ticker {
  font-family: var(--font-mono); font-size: 10px;
  color: var(--text-3); margin-left: auto; flex-shrink: 0;
}
.commodity-tile .spot {
  font-family: var(--font-mono); font-size: 22px; font-weight: 600;
  letter-spacing: -0.02em; line-height: 1.1;
}
.commodity-tile .delta {
  display: inline-flex; align-items: center; gap: 4px;
  font-family: var(--font-mono); font-size: 11.5px; font-weight: 600;
  margin-top: 2px;
}
.commodity-tile .spark {
  width: 100%; height: 28px; margin-top: 10px;
  display: block;
}
.commodity-tile .foot {
  display: flex; justify-content: space-between;
  font-size: 10.5px; color: var(--text-3); font-family: var(--font-mono);
  margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border);
  /* PR #204 UX review: at 375px viewport (~171px tile width) a 6-digit
     volume like "999k" pushed VOL/OFFERS past the available width and
     wrapped. `nowrap` on each span keeps both labels on the single foot
     row; `min-width: 0` lets flex shrink either label if both expand. */
  gap: 4px;
}
.commodity-tile .foot span { white-space: nowrap; min-width: 0; }
.commodity-tile .foot .v { color: var(--text-2); }
/* PR #204 UX review note: --text-3 is verified ≥4.5:1 on --bg-0 (5.22:1
   per PR #203 retune). On the .commodity-tile background (--bg-1
   #0e1116) the same #7a8390 measures ~4.55:1 — passes AA with a thin
   margin. If --bg-1 is ever lightened, re-check this contrast pair. */

/* .svg-chart — block-level SVG container, full-width. Used for sparklines
   inside tiles + the equity-curve hero on Portfolio (PR-C). */
.svg-chart { width: 100%; display: block; }

/* PR #223 Atlas — spatial map canvas. Dark stylized surface, no real
   continent outlines; operator pins + flow lines do the heavy lifting.
   Aspect ratio fixed at 1000:460 (≈ the equirectangular ratio used by
   the projection helpers in Atlas.razor) so the canvas reads as a
   map shape on any viewport width. */
.atlas-canvas {
  position: relative;
  width: 100%;
  aspect-ratio: 1000 / 460;
  background: radial-gradient(ellipse at center, var(--bg-2) 0%, var(--bg-1) 100%);
  border-radius: 8px;
  overflow: hidden;
}
.atlas-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
.atlas-grid-line {
  stroke: var(--bg-3);
  stroke-width: 0.5;
  stroke-dasharray: 2 4;
}
.atlas-grid-equator {
  stroke: var(--bg-3);
  stroke-width: 1;
  stroke-dasharray: 0;
  opacity: 0.7;
}
.atlas-flow-market {
  stroke: var(--text-3);
  stroke-width: 0.5;
  /* PR #224 hotfix — dropped from 0.25 to 0.15. Even with the 200-flow
     sample cap, 200 overlapping dashed lines still produce a haze if
     they're too dark. 0.15 keeps "the market exists" texture without
     obscuring the pins or the player's accent flows. */
  stroke-opacity: 0.15;
  stroke-dasharray: 3 3;
  pointer-events: none;
  vector-effect: non-scaling-stroke;
}
.atlas-flow-mine {
  stroke: var(--accent);
  stroke-width: 1.2;
  stroke-opacity: 0.6;
  pointer-events: none;
  vector-effect: non-scaling-stroke;
}
/* PR #240 a11y audit fix #1 — bot pin contrast lifted from #4a5867
   (2.16:1 vs bg-1, FAILS 1.4.11 non-text 3:1 minimum) to #5e6c7e
   (3.33:1, passes AA). 256 of 257 pins on the map are bots — virtually
   the entire visual field was below the WCAG floor. Hierarchy preserved:
   bots still visibly subtler than human pins (#6b8475 at 4.03:1) and
   the player's accent pin (~9:1), but each one now passes for low-
   vision users. */
.atlas-pin-bot {
  fill: #5e6c7e;
  stroke: var(--bg-1);
  stroke-width: 0.5;
  vector-effect: non-scaling-stroke;
}
.atlas-pin-human {
  fill: #6b8475;
  stroke: var(--bg-1);
  stroke-width: 0.8;
  vector-effect: non-scaling-stroke;
}
.atlas-pin-me {
  fill: var(--accent);
  stroke: var(--bg-0);
  stroke-width: 1.2;
  vector-effect: non-scaling-stroke;
}
.atlas-pin-me-halo {
  fill: var(--accent);
  opacity: 0.20;
}

/* PR #230 — left-rail icon nav. Hidden on mobile (<768px) — there the
   existing .topbar wins. At ≥768px the rail is fixed to the left edge,
   the topbar is hidden, and document body gets padding-left so main
   content respects the rail. 64px keeps icons readable while staying
   tight at smaller desktop sizes. The rail's icon labels are .left-
   rail-label (visually-hidden — readable by screen readers + via the
   link title attribute). */
.left-rail { display: none; }
@media (min-width: 768px) {
  .left-rail {
    display: flex;
    flex-direction: column;
    position: fixed;
    left: 0;
    top: 32px; /* below the .ticker-bar */
    bottom: 0;
    width: 64px;
    background: var(--bg-2);
    border-right: 1px solid var(--border);
    z-index: 999;
    padding: 12px 0;
  }
  .topbar { display: none; }
  /* PR #230 review fix — scope to body that CONTAINS the rail. Six
     pages don't render <AppBar /> (Home / Onboarding / PickBusiness
     Type / AdminMetrics / AdminWaitlist / Error); without :has, those
     pages got a 64px empty gutter on desktop with no rail to fill it.
     The :has selector is widely supported (Chrome 105+, Safari 15.4+,
     Firefox 121+) — safe for prod. */
  body:has(.left-rail) { padding-left: 64px; }
  body:has(.left-rail) .ticker-bar { margin-left: -64px; padding-left: 64px; }
}
.left-rail-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 44px;
  margin-bottom: 8px;
  color: var(--text-1);
  text-decoration: none;
}
.left-rail-nav {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1;
  overflow-y: auto;
}
.left-rail-foot {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-top: 8px;
  border-top: 1px solid var(--border);
}
.left-rail-link {
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  height: 44px;
  margin: 0 6px;
  border-radius: 8px;
  color: var(--text-3);
  text-decoration: none;
  transition: background 0.12s, color 0.12s;
}
.left-rail-link:hover { background: var(--bg-3); color: var(--text-1); }
.left-rail-link.active { background: var(--accent-dim); color: var(--accent); }
.left-rail-label {
  position: absolute;
  left: -10000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
.left-rail-badge {
  position: absolute;
  top: 4px;
  right: 4px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  background: #dc3545; /* matches Bootstrap bg-danger used by the topbar pending badge */
  color: white;
  border-radius: 8px;
  font-size: 10px;
  line-height: 16px;
  text-align: center;
}
.left-rail-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--accent-dim);
  color: var(--accent);
  font-weight: 600;
  font-size: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 4px auto 0;
}

/* PR #229 — persistent commodity-price ticker. Pure-CSS marquee, no
   JS interval, no SignalR cost. Hidden on mobile (<768px) to preserve
   vertical space. The track is rendered twice in markup; CSS animates
   a -50% transform over 60s to seamlessly loop without a gap.
   prefers-reduced-motion respected — animation disabled, ticker stays
   static (still useful as a snapshot bar). */
.ticker-bar {
  display: none;
  background: var(--bg-2);
  border-bottom: 1px solid var(--border);
  overflow: hidden;
  height: 32px;
  position: relative;
  z-index: 998;
}
@media (min-width: 768px) {
  .ticker-bar { display: block; }
}
.ticker-track {
  display: flex;
  align-items: center;
  gap: 36px;
  padding: 0 24px;
  height: 100%;
  white-space: nowrap;
  animation: ticker-scroll 60s linear infinite;
  will-change: transform;
}
.ticker-item {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--text-2);
}
.ticker-name {
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
  color: var(--text-3);
}
.ticker-price {
  color: var(--text-1);
  font-variant-numeric: tabular-nums;
}
@keyframes ticker-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
  .ticker-track { animation: none; }
}

/* PR #227 — .term primitive. Dotted-underline link that jumps to the
   matching /learn anchor, with the short definition surfaced via the
   native browser tooltip. Inherits text color so the term reads as
   part of the sentence (not a hyperlink "click me" affordance) —
   the dotted underline is the only visual cue. */
.term {
  color: inherit;
  text-decoration: underline dotted;
  text-decoration-color: var(--text-3);
  text-underline-offset: 2px;
  cursor: help;
}
.term:hover {
  text-decoration-color: var(--accent);
  color: var(--accent);
}
/* PR #239 a11y audit fix #3 — .term is keyboard-focusable (it's an <a>);
   :focus-visible draws a clear ring so keyboard users can see which
   tooltip target they're on. Mirrors Bootstrap's default focus-ring
   shape but uses the accent token. */
.term:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 2px;
}

/* PR #239 a11y audit fix #2 — Bootstrap .form-range thumb defaults to
   ~16×16 which fails WCAG 2.5.5 (44×44 minimum touch target). Bump to
   24×24 + track padding so the EFFECTIVE target via top+bottom hit area
   reaches the 44-CSS-pixel minimum. Applied to the WebKit + Firefox
   pseudo-selectors separately because there's no cross-browser
   shorthand. */
.form-range::-webkit-slider-thumb {
  width: 24px;
  height: 24px;
}
.form-range::-moz-range-thumb {
  width: 24px;
  height: 24px;
}
.form-range {
  /* 14px top/bottom padding around the 16px native track gets the total
     vertical hit-area to 44px without visibly shifting the slider. */
  padding-top: 14px;
  padding-bottom: 14px;
}

/* PR #226 — ByCommodity mode pin colors. Picked from the same palette
   the v3 prototype uses for the per-category swatches (Agriculture =
   amber, Metals = silver-grey, Energy = warm orange, Industrial =
   sage green). */
.atlas-pin-cat-agri        { fill: #ffba4d; stroke: var(--bg-1); stroke-width: 0.5; vector-effect: non-scaling-stroke; }
.atlas-pin-cat-metal       { fill: #c7c7d6; stroke: var(--bg-1); stroke-width: 0.5; vector-effect: non-scaling-stroke; }
.atlas-pin-cat-energy      { fill: #ff8a4d; stroke: var(--bg-1); stroke-width: 0.5; vector-effect: non-scaling-stroke; }
.atlas-pin-cat-industrial  { fill: #6b8475; stroke: var(--bg-1); stroke-width: 0.5; vector-effect: non-scaling-stroke; }

/* PR #226 — ByRegion mode background bands. Drawn as full-height rects
   behind the grid + pins; very low alpha so they read as tints, not
   blocks. Each region picks a hint color matching the prototype's
   region-density panel palette. */
.atlas-region-americas  { fill: rgba(91, 141, 239, 0.06); }
.atlas-region-eurafrica { fill: rgba(0, 217, 126, 0.06); }
.atlas-region-asia      { fill: rgba(255, 186, 77, 0.06); }
.atlas-region-label {
  fill: var(--text-3);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-anchor: middle;
  text-transform: uppercase;
  font-family: 'JetBrains Mono', monospace;
}

/* PR #226 — side-panel chrome. Stacks under the canvas on mobile;
   sits to the right at ≥lg. .atlas-side-divider draws a hairline top
   border + a small top pad so sections inside the panel read distinct
   without nesting more `.panel`s. */
.atlas-side-panel {
  height: 100%;
}
.atlas-side-divider {
  border-top: 1px solid var(--border);
  padding-top: 14px;
}

/* PR #226 — density histogram bar. Width set inline via @style; the
   track is the full-width-of-cell groove the bar paints into. */
.atlas-density-track {
  display: inline-block;
  width: 80px;
  height: 4px;
  background: var(--bg-3);
  border-radius: 2px;
  overflow: hidden;
}
.atlas-density-bar {
  display: block;
  height: 100%;
  background: var(--accent);
}

/* .equity-curve — Portfolio hero (PR #205) — bigger area-fill SVG line
   chart of cash over time, with a topline that splits the big mono
   number on the left and the period-selector pills on the right. Used
   on /company below the .hero-stat (net worth) to give the player a
   "is my cash trending up?" read at a glance instead of squinting at a
   140×32 inline sparkline. */
.equity-curve {
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 22px 24px;
  position: relative;
}
.equity-curve .topline {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 20px; flex-wrap: wrap;
}
.equity-curve .pre {
  font-size: 10.5px; font-weight: 600; color: var(--text-3);
  text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 4px;
}
/* PR #205 UX review: `.big` was 32px (visually competing with the
   `.hero-stat .value` 36px above it — two hero-weight readouts stacked
   read as "co-equal" not "supporting"). Demoted to 24px so the player's
   eye lands on Net Worth first, then absorbs Cash + trend as the
   detail story below. Type-size hierarchy now reads as a clear demotion. */
.equity-curve .big {
  font-family: var(--font-mono); font-size: 24px; font-weight: 600;
  letter-spacing: -0.02em; line-height: 1; color: var(--text-1);
}
.equity-curve .deltas {
  display: flex; gap: 16px; margin-top: 8px; font-size: 12px;
  align-items: baseline; flex-wrap: wrap;
}
.equity-curve .delta { display: flex; flex-direction: column; }
.equity-curve .delta .k {
  color: var(--text-3); font-size: 10.5px;
  text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600;
}
.equity-curve .delta .v {
  font-family: var(--font-mono); font-size: 14px; font-weight: 600; margin-top: 2px;
}
.equity-curve .chart-wrap { margin-top: 18px; height: 180px; position: relative; }
@media (max-width: 575.98px) {
  .equity-curve { padding: 16px 14px; }
  .equity-curve .big { font-size: 20px; }
  .equity-curve .chart-wrap { height: 140px; }
}

/* .feed-row — dense activity-feed row. Used by Portfolio recent-activity
   panel + DealRoom move log. Three columns: icon · body · ago. */
.feed-row {
  display: grid;
  grid-template-columns: 18px 1fr auto;
  gap: 12px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  align-items: flex-start;
  font-size: 12.5px;
}
.feed-row:last-child { border-bottom: 0; }
/* PR #211 — `> .icon` direct-child selector (not descendant `.icon`)
   so the 18×18 circle styling doesn't propagate into the SVG that the
   <Icon> component renders with `class="icon"` for its own root. Inner
   SVG inherits `currentColor` from this span's `color` declaration so
   .pos / .neg variant colors still pass through. */
.feed-row > .icon {
  width: 18px; height: 18px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--bg-3); color: var(--text-2);
  font-family: var(--font-mono); font-size: 10px; font-weight: 700;
}
.feed-row.fill    > .icon { background: var(--green-dim); color: var(--green); }
.feed-row.counter > .icon { background: var(--amber-dim); color: var(--amber); }
.feed-row.walk    > .icon { background: var(--red-dim);   color: var(--red); }
/* PR #207: generic +/- variants for non-deal feeds (cash-ledger,
   inventory-ledger). .fill / .counter / .walk are deal-specific verbs;
   .pos / .neg map to "money/inventory increased/decreased" for any
   chronological ledger stream. */
.feed-row.pos     > .icon { background: var(--green-dim); color: var(--green); }
.feed-row.neg     > .icon { background: var(--red-dim);   color: var(--red); }
.feed-row .body { color: var(--text-1); line-height: 1.5; }
.feed-row .ago {
  font-family: var(--font-mono); font-size: 10.5px;
  color: var(--text-3); white-space: nowrap;
}

/* .panel — generic dark-on-dark surface with a header band. v2 prefers
   this over Bootstrap's .card on dense data surfaces (Floor depth ladder,
   Portfolio positions table) because the header band reads as a column
   spec instead of a card title. */
.panel {
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
}
.panel-head {
  padding: var(--s-3) var(--s-4);
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between;
  font-size: 10.5px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--text-3);
}
.panel-head .end {
  text-transform: none; letter-spacing: 0;
  color: var(--text-2); font-weight: 500; font-size: 11.5px;
}
