/* The Ledger — dark trading terminal theme. */

/* Midnight Brass — near-black ledger-ink canvas, brass-led highlights. */
:root {
  /* Base is pure black — panels/lines float on the void. The radial wash stays
     near-black so depth reads without ever lifting off black. */
  --bg: #000000;
  --panel: #0d1017;
  --panel-2: #121722;
  --border: #1b2432;
  --text: #d3dcea;
  --muted: #74809a;
  --accent: #3aa8e0;
  --accent-2: #7ee0a3;
  --up: #38d39f;
  --down: #ff5c7a;
  --warn: #f0b429;
  --brass: #d4b47a;
  --mono: "SF Mono", "Cascadia Code", Consolas, "Roboto Mono", monospace;
  --sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-2: 0 10px 28px rgba(0, 0, 0, 0.5);
  --ring: 0 0 0 3px rgba(58, 168, 224, 0.18);
  --grad-accent: linear-gradient(135deg, #3aa8e0, #38d39f);
}

* { box-sizing: border-box; }

/* The `hidden` attribute must always win, even over elements with an explicit
   display (e.g. .auth flex, .auth-form flex). Without this, toggling a section
   hidden is a no-op and the login card stays stacked on top of the app. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.45;
}

h1, h2, h3 { font-weight: 650; letter-spacing: 0.02em; }
h2 { font-size: 15px; text-transform: uppercase; color: var(--muted); margin: 0 0 12px; }
h3 { font-size: 13px; margin: 20px 0 8px; color: var(--accent); text-transform: uppercase; letter-spacing: 0.06em; }

.brand {
  font-family: var(--mono);
  font-weight: 700;
  letter-spacing: 0.18em;
  color: var(--accent);
  margin: 0;
  font-size: 34px;
}
.brand.small { font-size: 16px; }

/* ── Auth ─────────────────────────────────────────────────────────── */
.auth {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.auth-card {
  width: 100%;
  max-width: 380px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 28px;
  text-align: center;
}
.tagline { color: var(--muted); margin: 4px 0 24px; font-size: 12px; letter-spacing: 0.12em; text-transform: uppercase; }
.tabs { display: flex; gap: 6px; margin-bottom: 18px; }
.tab {
  flex: 1;
  padding: 9px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
}
.tab.is-active { background: var(--panel-2); border-color: var(--accent); color: var(--text); }
/* Leaderboard period switcher — compact so the tight casual rail doesn't grow (§11.1).
   5 tabs (Daily/7-day/30-day/90-day/All-time) share the width; each stays on one line
   (ellipsis) and never wraps the rail taller. */
.lb-periods { gap: 4px; margin-bottom: 10px; flex-wrap: nowrap; }
.lb-periods .tab {
  flex: 1 1 0;
  min-width: 0;
  padding: 6px 2px;
  font-size: 11px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.auth-form { text-align: left; display: flex; flex-direction: column; gap: 12px; }
label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--muted); }

input, select {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  padding: 9px 10px;
  font: inherit;
  width: 100%;
}
input:focus, select:focus { outline: none; border-color: var(--accent); }

/* ── Avatar picker (register) ────────────────────────────────────────────── */
/* One of the 12 Chinese zodiac animals, chosen on signup. The animal is the
   shared choice; each tile tints in the two tones derived from the username
   (live, as you type), so the grid previews your own duotone stamp. */
.avatar-picker {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 2px 0 4px;
}
.avatar-picker-label {
  font-size: 11px; letter-spacing: 0.1em; color: var(--muted);
  text-transform: lowercase; line-height: 1;
}
.avatar-grid {
  display: grid; grid-template-columns: repeat(6, 1fr);
  gap: 6px; max-width: 280px; width: 100%;
}
.avatar-opt {
  aspect-ratio: 1; padding: 5px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  border: 1.5px solid var(--border);
  background: var(--panel-2);
  cursor: pointer;
}
.avatar-opt img { width: 100%; height: 100%; display: block; }
.avatar-opt:hover { border-color: var(--accent); }
.avatar-opt.is-selected {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 28%, transparent);
}
.avatar-duo-def { position: absolute; width: 0; height: 0; overflow: hidden; }

/* ── Buttons ──────────────────────────────────────────────────────── */
.btn {
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 9px 14px;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.btn:hover { border-color: var(--accent); }
.btn-primary { background: var(--accent); border-color: var(--accent); color: #04121f; }
.btn-primary:hover { filter: brightness(1.08); }
.btn-primary:disabled { opacity: 0.5; cursor: wait; }
.btn-ghost { background: transparent; border-color: transparent; color: var(--muted); white-space: nowrap; }
.btn-block { width: 100%; }

/* ── Layout ───────────────────────────────────────────────────────── */
.topbar {
  display: flex;
  align-items: center;
  padding: 12px 18px;
  border-bottom: 1px solid var(--border);
  background: var(--panel);
  position: sticky;
  top: 0;
  z-index: 10;
}
/* Justify the header: auto margins on the two sides absorb ALL free space, so
   the gap left of the center chip always equals the gap right of it (equal
   breathing room between the three zones), the right zone stays flushed to the
   edge, and a wide right cluster can never overflow left under the chip — the
   grid's `1fr auto 1fr` let that happen when the center track was narrow. */
.topbar-left { margin-right: auto; min-width: 0; }
.topbar-center {
  min-width: 0;
  max-width: 52vw;
  /* Box-align the two-line chip instead of baseline-aligning it: an inline
     chip rides the line baseline ~1.5px low, which puts the username's
     centerline below the brand / net / clock / logout on desktop. Centered as
     a flex item it shares the exact line the rest of the header sits on. */
  display: flex;
  align-items: center;
}
.topbar-right { margin-left: auto; min-width: 0; }
.chip { color: var(--accent-2); font-family: var(--mono); font-size: 13px; }
/* Header identity chip: username primary line, active buff as a smaller
   centered subtitle underneath. The whole chip is a profile read target, so
   hover still opens the self card (which also lists the buff). */
#user-chip {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  line-height: 1.25;
}
/* profileTarget wraps the chip content in a .profile-target span — inside the
   chip it must inherit the column so the buff stacks under the name. */
#user-chip .profile-target {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
}
#user-chip .hdr-uname {
  color: var(--text);
  font-size: 13px;
  /* One line always: a long name ellipsizes instead of wrapping into the
     buff subtitle and growing the center chip. */
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
#user-chip .hdr-origin {
  color: var(--muted);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
/* Each origin gets its own muted hue in the header chip — still quiet next to
   the username, but a new player, a steady hand and an established account
   read apart at a glance. */
#user-chip .hdr-origin[data-origin="FRESH_START"] { color: #5b8db0; }
#user-chip .hdr-origin[data-origin="STEADY_HAND"]  { color: #c2a469; }
#user-chip .hdr-origin[data-origin="ESTABLISHED"]  { color: #a07bbf; }
#user-chip .hdr-buff { color: var(--muted); font-size: 10px; letter-spacing: 0.02em; }
#user-chip .hdr-buff b { color: var(--c-gold); font-weight: 600; }

.ticker {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  padding: 10px 18px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
}
/* Center the strip when it fits, scroll from the LEFT when it overflows —
   `justify-content:center` + `overflow-x:auto` clips the start on mobile
   (Chrome: the first symbols are unreachable, and every tick re-render snaps
   back to center). Auto-margin pseudo spacers absorb free space to center the
   content when it fits, and collapse to 0 on overflow so scrolling starts at
   the first symbol. Do NOT combine with justify-content:center (that is what
   re-centers and clips overflowing content). */
.ticker::before, .ticker::after, .venue-strip::before, .venue-strip::after {
  content: '';
  margin: auto;
}
.tick {
  flex: 0 0 auto;
  min-width: 96px;
  padding: 6px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-family: var(--mono);
  font-size: 12px;
  text-align: center;
}
.tick .sym { color: var(--muted); }
.tick .px { display: block; font-size: 14px; }
.tick .src { font-size: 10px; color: var(--muted); }
.px.up { color: var(--up); }
.px.down { color: var(--down); }

/* Market venues — board focus strip + session dots. The pills center under
   the board title (BUG-014, 2026-08-26); `safe center` falls back to
   start-alignment when the row overflows so sideways scrolling still reaches
   every pill instead of clipping the first one. */
.venue-strip {
  display: flex;
  align-items: center;
  justify-content: safe center;
  gap: 8px;
  padding: 8px 18px;
  background: var(--panel-2);
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
}
.venue-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  white-space: nowrap;
  transition: border-color 0.15s, color 0.15s;
}
.venue-chip:hover { border-color: var(--accent); color: var(--text); }
.venue-chip.is-active { border-color: var(--accent); color: var(--accent); background: rgba(58, 168, 224, 0.08); }
.venue-chip .dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  border: 1px solid var(--muted);
  flex: 0 0 auto;
}
.venue-chip .dot.open { background: var(--up); border-color: var(--up); box-shadow: 0 0 6px rgba(56, 211, 159, 0.6); }
.venue-chip .dot.closed { background: transparent; border-color: var(--muted); }

.tick .vtag {
  display: inline-block;
  margin-left: 4px;
  padding: 0 4px;
  border: 1px solid var(--border);
  border-radius: 3px;
  font-size: 9px;
  line-height: 1.5;
  color: var(--muted);
  vertical-align: 1px;
  letter-spacing: 0.05em;
}

/* Market activity tape — passive roll of the shared fill feed. */
.activity-tape {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px 18px 8px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  font-family: var(--mono);
  font-size: 11px;
  overflow-x: auto;
  white-space: nowrap;
}
/* Dual-tick guardrails: every row is EXACTLY one 16px line — a fixed line-height
   + min-height + inherited nowrap means an individual update (a wider number, a
   longer name, a new corps line) can never change a row's height or reflow its
   siblings; the two columns can never crowd into each other because the right
   column's text truncates (min-width:0 → ellipsis engages) instead of widening. */
.activity-tape .act-line { display: flex; align-items: baseline; gap: 6px; line-height: 16px; min-height: 16px; }
.activity-tape .act-ts { color: var(--muted); }
.activity-tape .act-name { color: var(--text); }
.activity-tape .act-side { font-weight: 600; letter-spacing: 0.04em; }
.activity-tape .act-line.act-buy .act-side { color: var(--up); }
.activity-tape .act-line.act-sell .act-side { color: var(--down); }
/* Phase B — a SHORT-OPEN sell carries a compact marker so the tape distinguishes
   a liability-opening flat sell from a plain long sale at a glance. */
.activity-tape .act-short { color: var(--down); font-size: 9px; font-weight: 800; letter-spacing: 0.06em; border: 1px solid rgba(224, 89, 89, 0.4); border-radius: 3px; padding: 0 3px; margin-left: 2px; }
.activity-tape .act-qty { color: var(--muted); }
.activity-tape .act-sym { color: var(--accent); }
.activity-tape .act-ex { color: var(--accent); font-size: 9px; font-weight: 700; letter-spacing: 0.06em; }
.activity-tape .act-px { color: var(--muted); }

/* Market activity wrap — the on-screen feed is TWO columns: player fills on
   the LEFT (scroll strip), corps trades + market news on the RIGHT. Both stay
   on screen at every width; the Events page is the full archive. */
.activity-wrap {
  display: flex;
  align-items: stretch;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  font-family: var(--mono);
  font-size: 11px;
}
.activity-wrap[hidden] { display: none; }
.activity-tape {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px 18px 8px;
  overflow-x: auto;
  white-space: nowrap;
  flex: 1 1 auto;
  min-width: 0;
  background: transparent;
  border-bottom: 0;
}
.news-tape {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px 12px 8px;
  border-left: 1px solid var(--border);
  overflow-y: auto;
  overflow-x: hidden;
  white-space: normal;
  flex: 0 0 auto;
  max-width: 46%;
  min-width: 0;
  max-height: 256px; /* a rolling ~30-min window; anything older scrolls */
  overscroll-behavior: contain;
  color: var(--muted);
}
.news-tape[hidden] { display: none; }
.news-tape .news-line {
  display: flex;
  align-items: baseline;
  gap: 8px;
  line-height: 16px;
  min-height: 16px;
  padding: 1px 4px;
  margin: 0 -4px;
  border-radius: 4px;
  cursor: pointer;
  overflow: hidden;
  text-align: left;
}
.news-tape .news-line:hover { background: rgba(116, 128, 154, 0.07); }
.news-tape .news-line:focus-visible { outline: 1px solid var(--brass); outline-offset: -1px; }
.news-tape .news-ts { flex: 0 0 auto; opacity: 0.7; }
.news-tape .news-body { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.news-tape .news-src {
  flex: 0 0 auto;
  font-size: 9px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  opacity: 0.55;
}
.news-tape .news-more { flex: 0 0 auto; color: var(--brass); opacity: 0; }
.news-tape .news-line:hover .news-more,
.news-tape .news-line:focus-visible .news-more { opacity: 0.9; }
.news-tape .news-line.news-trade .news-body { color: var(--brass); }
.news-tape .news-line.news-rebalance .news-body { color: var(--brass); }
.news-tape .news-line.news-news .news-body { color: var(--muted); }
/* Real-market news lines read green on the way up, red on the way down. */
.news-tape .news-line.news-up .news-body { color: var(--up); }
.news-tape .news-line.news-down .news-body { color: var(--down); }
/* Caught-trader red headline (§5.96): who the caught-trader ladder hit —
   players and bots read red on the on-screen tape and in the events archive.
   Color + tint carry the emphasis; weight is NOT forced here so the arriving
   .news-latest line inherits the tape's bold while older rows stay regular —
   a font-weight on .news-body would flatten the newest caught line to match
   its red background instead of standing out like its siblings. */
.news-tape .news-line.news-caught { background: rgba(255, 92, 122, 0.08); }
.news-tape .news-line.news-caught:hover { background: rgba(255, 92, 122, 0.14); }
.news-tape .news-line.news-caught .news-body { color: var(--down); }
#events-feed .event-row.event-caught .ev-event { color: var(--down); }
/* The arriving line fades up at every width (mobile included) — the rest of
   the window renders static so a re-render never flashes the whole list. */
.news-tape .news-latest { animation: news-fade-up 0.5s cubic-bezier(0.2, 0.7, 0.3, 1) backwards; }

/* Desktop: the newest line reads BOLD and bright at the top of the rolling
   window, older lines settle dimmed beneath it. Only the arriving line fades
   up — the rest of the window renders static so a re-render never flashes the
   whole list. renderNewsTape rebuilds this markup only on change (state.events
   holds corps/news only), so the animation fires exactly when news arrives,
   never on a player fill. */
@media (min-width: 1025px) {
  .news-tape { max-height: 288px; }
  /* `backwards` fill (not `both`) so the animation releases after it runs and
     the sides settle at their real dimmed opacity instead of being pinned at 1. */
  .news-tape .news-latest { animation: news-fade-up 0.5s cubic-bezier(0.2, 0.7, 0.3, 1) backwards; }
  .news-tape .news-side { font-size: 10.5px; opacity: 0.68; }
  .news-tape .news-latest {
    font-size: 12.5px;
    font-weight: 700;
    letter-spacing: 0.01em;
    animation-duration: 0.55s;
  }
  /* The current item always reads bright: neutral headlines = text, moves keep
     their semantic up/down tint, corps trades stay brass. */
  .news-tape .news-latest .news-body { color: var(--text); }
  .news-tape .news-latest.news-news .news-body { color: var(--text); }
  .news-tape .news-latest.news-up .news-body { color: var(--up); }
  .news-tape .news-latest.news-down .news-body { color: var(--down); }
  .news-tape .news-latest.news-trade .news-body { color: var(--brass); }
  .news-tape .news-latest.news-caught .news-body { color: var(--down); }
  .news-tape .news-latest .news-ts { opacity: 0.85; }
}
@keyframes news-fade-up {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes news-fade-up-side {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 0.68; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .news-tape .news-line { animation: none; }
}

/* Topbar bell — the desktop Events button (mobile uses the hamburger entry). */
.events-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.events-toggle:hover { color: var(--brass); border-color: var(--brass); }
.events-toggle svg { width: 15px; height: 15px; }

/* Events overlay — full-screen archive (time · entity · event · effect). */
.events-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(3, 5, 9, 0.78);
  backdrop-filter: blur(3px);
}
.events-overlay[hidden] { display: none; }
.events-sheet {
  width: min(860px, 100%);
  max-height: min(82vh, 720px);
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
  overflow: hidden;
}
.events-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px 10px;
  border-bottom: 1px solid var(--border);
}
.events-title {
  margin: 0;
  font-family: Georgia, "Times New Roman", serif;
  font-size: 17px;
  letter-spacing: 0.08em;
  color: var(--text);
}
.events-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.events-close:hover { color: var(--brass); border-color: var(--brass); }
.events-close svg { width: 14px; height: 14px; }
.events-cols,
.event-row {
  display: grid;
  grid-template-columns: 128px 84px minmax(0, 1fr) 170px;
  gap: 12px;
  padding: 8px 18px;
  align-items: baseline;
}
.events-cols {
  font-size: 9px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
  opacity: 0.6;
  border-bottom: 1px solid var(--border);
}
.events-feed { overflow-y: auto; padding: 6px 0 10px; }
.event-row {
  font-family: var(--mono);
  font-size: 11.5px;
  border-bottom: 1px dashed var(--border);
}
.event-row:last-child { border-bottom: 0; }
.ev-time { color: var(--muted); font-size: 10.5px; }
.ev-entity { color: var(--brass); font-weight: 600; }
.ev-event { color: var(--text); }
.ev-effect { color: var(--muted); font-size: 10.5px; }
.ev-src { color: var(--accent); font-size: 10px; opacity: 0.8; }
.event-news .ev-entity { color: var(--accent); }
.event-row { cursor: pointer; transition: background-color 0.12s ease; }
.event-row:hover { background: rgba(116, 128, 154, 0.07); }
.event-row:focus-visible { outline: 1px solid var(--brass); outline-offset: -1px; }
.events-empty {
  padding: 40px 18px;
  text-align: center;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 12px;
}

/* ── History overlay — the player's realized trade history ──────────────── */
.history-cols,
.trade-row {
  display: grid;
  grid-template-columns: 118px 74px 42px 52px minmax(0, 1fr) 92px 100px;
  gap: 12px;
  padding: 8px 18px;
  align-items: baseline;
}
.history-cols {
  font-size: 9px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
  opacity: 0.6;
  border-bottom: 1px solid var(--border);
}
.trade-row {
  font-family: var(--mono);
  font-size: 11.5px;
  border-bottom: 1px dashed var(--border);
  cursor: pointer;
  transition: background-color 0.12s ease;
}
.trade-row:last-child { border-bottom: 0; }
.trade-row:hover { background: rgba(116, 128, 154, 0.07); }
.trade-row:focus-visible {
  outline: 1px solid var(--brass);
  outline-offset: -1px;
}
.tr-time { color: var(--muted); font-size: 10.5px; }
.tr-sym { color: var(--brass); font-weight: 600; }
.tr-ex { color: var(--muted); font-size: 10.5px; }
.tr-fill { color: var(--text); }
.tr-comm { color: var(--muted); font-size: 10.5px; }
.tr-pnl { text-align: right; }
.tr-side { font-weight: 700; }
.tr-side.up { color: var(--up); }
.tr-side.down { color: var(--down); }
.tr-pnl.up { color: var(--up); }
.tr-pnl.down { color: var(--down); }

/* Trade-detail popup — a compact label/value sheet over the history feed. */
.tdetail-sheet { width: min(460px, 100%); }
.tdetail-body { padding: 8px 18px 14px; }
.tdetail-row {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 7px 0;
  border-bottom: 1px dashed var(--border);
  font-family: var(--mono);
  font-size: 11.5px;
}
.tdetail-row:last-child { border-bottom: 0; }
.tdetail-row span { color: var(--muted); font-size: 10.5px; }
.tdetail-row b { color: var(--text); font-weight: 600; }
.activity-tape .bot-mark { margin-left: 1px; }

/* News-detail popup — the "full thing" behind a corps/news line. */
.news-detail-type {
  padding: 10px 18px 0;
  font-size: 9px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--brass);
  opacity: 0.8;
}
.news-story { align-items: flex-start !important; }
.news-story b {
  text-align: right;
  white-space: normal;
  max-width: 72%;
  line-height: 1.45;
}
.news-link { color: var(--accent); text-decoration: underline; }
.news-link:hover { color: var(--brass); }

.grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
  padding: 18px;
  max-width: 1240px;
  margin: 0 auto;
  /* Dense lets portfolio backfill the cell beside trade when world's full-width
     row lands on the line below it — keeps the immersive 2-col grid gap-free. */
  grid-auto-flow: dense;
}
@media (max-width: 900px) { .grid { grid-template-columns: minmax(0, 1fr); } }
/* COUPLED BREAKPOINT: the immersive .rail dissolve below (§11.1) MUST move
   with this 900px — if the grid stops collapsing here but the rails still
   dissolve into a 2-col grid, the six rail panels scramble across both columns
   in DOM order; if the grid collapses but the rails stay flex, the implicit-
   column squeeze this fix removed silently returns. Keep both at 900px. */

/* ── Presentation-layer layout (§11.1) ─────────────────────────────────────
   Immersive (default): the stacked 2-col grid above — chart + world full
   width, everything else in pairs. Casual: a meta-trader terminal — chart +
   active instrument own the center, the order ticket docks persistent on the
   right, gig queue + leaderboard rail the left, positions live under the
   chart, and the social/lore panels (corps, dark board) collapse to a
   full-width band below. Purely CSS on body[data-ui-mode]; no JS re-flow. */
/* Panels live inside .rail wrappers for the immersive skin's two rails
   (index.html). Outside immersive the wrappers dissolve (display: contents) so
   the same DOM serves the casual skin, where each panel is routed individually
   by grid-area below. */
.rail { display: contents; }

body[data-ui-mode="casual"] .app-layout {
  display: grid;
  grid-template-columns: 252px minmax(0, 1fr) 300px;
  grid-template-areas:
    "gigs   dashboard  ticket"
    "gigs   chart      ticket"
    "leader portfolio  ticket"
    "leader orders     ticket"
    "leader annex      ticket"
    "footer footer     footer";
  gap: 14px;
  align-items: start; /* no panel stretches — everything is content height */
  max-width: 1560px;
  margin: 0 auto;
  padding: 14px 18px 26px;
}
/* The chart panel participates as a grid cell, not a stacked block. */
body[data-ui-mode="casual"] .chart-panel {
  max-width: none;
  margin: 0;
}
body[data-ui-mode="casual"] .chart-panel { grid-area: chart; }
/* .grid and the .rail wrappers dissolve into the outer grid so their panels
   place freely (the casual rail assignments changed on 09-04 when the panels
   moved into the immersive rails — the gig queue now spans the dashboard+chart
   rows top-left, the leaderboard the portfolio/orders/annex rows beneath it,
   and the order ticket docks full-height on the right). */
body[data-ui-mode="casual"] .grid { display: contents; }
body[data-ui-mode="casual"] #dashboard { grid-area: dashboard; }
body[data-ui-mode="casual"] #portfolio { grid-area: portfolio; }
body[data-ui-mode="casual"] #orders { grid-area: orders; }
body[data-ui-mode="casual"] #panel-gigs { grid-area: gigs; }
body[data-ui-mode="casual"] #leaderboard { grid-area: leader; }
body[data-ui-mode="casual"] #trade { grid-area: ticket; align-self: start; }
body[data-ui-mode="casual"] #panel-darkboard { grid-area: darkboard; }
body[data-ui-mode="casual"] .annex-hero { grid-area: annex; }
body[data-ui-mode="casual"] .app-footer { grid-area: footer; border-top: 0; }

/* Immersive — two independent rails plus two full-width bands. #panel-world
   leads as a full-width band, then .rail-a (trade / open orders / gig queue)
   and .rail-b (portfolio / leaderboard / dark board) sit side by side, then the
   corps board closes as a full-width band. Each rail is its OWN flex column:
   its panels stack back-to-back at content height, so a short panel never
   leaves a dead void beneath it for a taller mate in the same grid row — the
   "space under the section" (trade, gigs) is gone. The rails are independent,
   so the shorter rail simply ends where its content ends. */
body[data-ui-mode="immersive"] #dashboard {
  max-width: 1240px;
  margin: 18px auto 0;
}
body[data-ui-mode="immersive"] #panel-world { grid-column: 1 / -1; }
body[data-ui-mode="immersive"] .rail {
  display: flex;
  flex-direction: column;
  gap: 16px;
  align-self: start;
  /* Zero the min-content width so a wide panel can never push the track (and
     the page) wider than the viewport — panels keep their own scroll instead. */
  min-width: 0;
}
body[data-ui-mode="immersive"] .rail-a { grid-column: 1; }
body[data-ui-mode="immersive"] .rail-b { grid-column: 2; }
body[data-ui-mode="immersive"] .rail .panel { flex: 0 0 auto; min-width: 0; }
/* Corps board (Trading Groups) spans the full row on immersive (09-04): as a
   full-width band its .c-list auto-fill grid (minmax 220px) reflows to balanced
   tiles across instead of a tall list. */
body[data-ui-mode="immersive"] #panel-corps { grid-column: 1 / -1; }

/* Narrow immersive: the .grid collapses to ONE track at ≤900px (above), but
   the rails' explicit grid-column (1 and 2) would then open an IMPLICIT second
   track — dense auto-flow backfills rail-b into it, and the 1fr track is left
   with the remainder (~60px on a phone). Result on mobile: rail-b (portfolio /
   leaderboard / dark board) grabs the fat 270px column at the top, the world
   band and corps board get clipped to a sliver beside it, and rail-a's trade
   ticket is stranded at the bottom in a ~60px column — every panel text
   stacked and hugging the left. Fix: at the same ≤900px breakpoint, dissolve
   both rails back to display:contents so their panels rejoin the grid and auto-
   place ONE FULL-WIDTH ROW EACH in DOM order — world → trade → orders → gigs →
   portfolio → leaderboard → dark board → corps (the pre-rail one-handed stack,
   trade ticket back near the top). No box, no grid-column on a contents
   element — safe above 900px where the rails stay real flex columns. */
@media (max-width: 900px) {
  body[data-ui-mode="immersive"] .rail { display: contents; }
}

/* Lore-essential gameplay stays behind the Immersive skin. A Casual player
   (the default) sees a clean trading terminal — no Third Shift, no dark
   board, no Trading Groups — until they opt in to the lore layer. (Corps
   became immersive-only 2026-08-28: the casual icon-grid is gone.) */
body[data-ui-mode="casual"] #panel-world,
body[data-ui-mode="casual"] #panel-darkboard,
body[data-ui-mode="casual"] #panel-corps,
body[data-ui-mode="casual"] .dash-groups { display: none; }
body[data-ui-mode="immersive"] .annex-hero { display: none; }
/* CEX is an immersive-only trading surface: the chart-book toggle is the only
   path onto the CEX book, so Casual hides it and GEX stays the casual market.
   (The order-ticket CEX option is hidden in JS — <option> display:none is
   unreliable across browsers.) */
body[data-ui-mode="casual"] .chart-book-btn[data-book="CEX"] { display: none; }

/* The Circuit (arena) is immersive-only — it's a deep sim; there is no
   presentation layer to toggle. The app.js init forces IMMERSIVE on that
   brand, and this hides the footer switch that would try to leave it. */
body[data-brand-key="circuit"] .app-footer-mode { display: none; }

/* Quiet footer strip — status note + the presentation-layer switch, small and
   out of the way. Deliberately reads like terminal furniture, not a menu. */
.app-footer {
  display: flex;
  align-items: center;
  /* The practice note was retired; with only the presentation switch left, a
     lone flex child under space-between would hug the left edge. Keep the
     switch at the right where it always sat on desktop. */
  justify-content: flex-end;
  gap: 12px;
  padding: 10px 14px;
  border-top: 1px solid var(--border);
  color: var(--muted);
  font-size: 10.5px;
  letter-spacing: 0.05em;
}
.app-footer-note { opacity: 0.7; }
.app-footer-mode { display: inline-flex; align-items: center; gap: 8px; }
.app-footer-mode-label {
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  opacity: 0.5;
}
.app-footer .mode-toggle {
  font-size: 9px;
  padding: 2px 8px;
  border-color: transparent;
  opacity: 0.55;
}
.app-footer .mode-toggle:hover { opacity: 1; color: var(--text); border-color: var(--brass); box-shadow: none; }

/* Tighter panel padding so the terminal rails breathe without wasting width. */
body[data-ui-mode="casual"] #panel-gigs,
body[data-ui-mode="casual"] #leaderboard,
body[data-ui-mode="casual"] #trade { padding: 14px; }
body[data-ui-mode="casual"] #panel-gigs .gigs-board { max-height: 46vh; }

/* Narrow viewports collapse Casual back to a single trading stack. */
@media (max-width: 1180px) {
  body[data-ui-mode="casual"] .app-layout {
    /* minmax(0, 1fr) — a plain 1fr track has a min-content minimum, so a wide
       heading stretches the column past the viewport and every panel inside
       overflows sideways with it. */
    grid-template-columns: minmax(0, 1fr);
    /* Narrow desktop: the app collapses to ONE content stack. Bookkeeping leads
       (dashboard → chart), the order ticket sits right under the chart it prices,
       portfolio/orders follow, and the gigs + leaderboard rails drop below the
       fold rather than sandwich between the ledger panels. Every name is a real
       panel (the .casual-left/.casual-right wrappers are gone with the 09-04
       rail DOM — an orphan area name would auto-place its panel). Phones restore
       their own order below so the ticket hugs the chart. */
    grid-template-areas:
      "dashboard" "chart" "ticket" "portfolio" "orders"
      "gigs" "leader" "annex" "footer";
  }
}

.panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 18px;
}

/* ── Dashboard ────────────────────────────────────────────────────── */
.balances { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 14px; }
.balance { background: var(--panel-2); border-radius: 10px; padding: 12px; text-align: center; }
.balance .lbl { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }
.balance .val { font-family: var(--mono); font-size: 19px; font-weight: 700; margin-top: 4px; }

.metrics {
  display: flex;
  flex-wrap: wrap;     /* may wrap; a wrapped line never leads with a bullet
                          (markMetricWraps drops it — see the .m-group rule) */
  gap: 8px 0;
  margin-bottom: 12px;
  font-size: 12px;
  color: var(--muted);
}
.dash-extras {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px 12px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.dash-badges { display: flex; flex-wrap: wrap; gap: 6px; font-size: 11px; color: var(--warn); }
.dash-badges b { font-variant-numeric: tabular-nums; }
.dash-actions { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; }
.dash-actions a {
  font-size: 11px;
  text-decoration: none;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 3px 10px;
  line-height: 1.4;
}
.dash-actions a:hover { color: var(--accent); border-color: rgba(58, 168, 224, 0.5); }
/* Stat strip — related facts separated by brass bullets: employment, activity,
   standing. Each stat is label-over-value so the numbers read as a table of
   record, not a wrapping sentence. The bullet sits in the gap BETWEEN groups
   (a true separator) and never starts or ends a line: the strip may wrap, and
   any group that lands at the start of a new flex line carries .m-group-wrap
   (added by markMetricWraps when its offsetTop passes the previous group's),
   which hides its leading bullet. So a bullet renders only between groups
   that share a visual line — never leading or trailing one. Phones drop
   bullets entirely (see the ≤640px block): wrapped spacing is the separator.

   The bullet is ABSOLUTE inside a fixed 23px gutter (margin-left) rather than
   an in-flow element so hiding it on a wrapped line never changes the group's
   width — otherwise the strip reflows on toggle and the wrap measurement
   fights itself (removing a bullet can pull a group back onto the line it was
   measured off of). The 23px indent on a wrapped line reads as a continuation. */
.m-group {
  display: flex;
  gap: 16px;
}
.m-group + .m-group {
  position: relative;
  margin-left: 23px;
}
.m-group-wrap::before { display: none; }
.m-group + .m-group::before {
  content: '';
  position: absolute;
  left: -14px; /* centered in the 23px gutter (11.5px in, 5px wide) */
  top: 50%;
  transform: translateY(-50%);
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--brass);
}
.m-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 1px;
  min-width: 0;
}
.m-item .m-label {
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  opacity: 0.65;
  white-space: nowrap;
}
.m-item b {
  color: var(--text);
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}

.inline-form { display: flex; gap: 8px; }
.inline-form input { flex: 1; }

.quote {
  margin-top: 10px;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  font-family: var(--mono);
  background: var(--panel-2);
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}
.quote .sym { color: var(--accent); font-weight: 600; }
.quote .px { font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; }
.quote .src { color: var(--muted); font-size: 11px; margin-left: auto; }

.order-form { display: flex; flex-direction: column; gap: 12px; }
.order-form .row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.order-form label.is-dim { opacity: 0.45; }
.order-form input:disabled { opacity: 0.55; cursor: not-allowed; }

/* ── Tables ───────────────────────────────────────────────────────── */
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th { text-align: left; color: var(--muted); font-weight: 600; text-transform: uppercase; font-size: 11px; letter-spacing: 0.05em; }
th, td { padding: 8px 8px 8px 0; border-bottom: 1px solid var(--border); }
td { font-family: var(--mono); font-variant-numeric: tabular-nums; }
td.num, th.num { text-align: right; }
.sym-cell { color: var(--accent); font-weight: 600; }
.muted { color: var(--muted); }
.pos { color: var(--up); }
.neg { color: var(--down); }
.warn { color: var(--warn); }
.short-ok { color: var(--accent); }
/* Open short-book rows (§5.80): faint red tint so shorts scan apart from longs. */
.short-row td { background: rgba(224, 89, 89, 0.05); }
/* Short lender never stacks onto its own line — "Short · ABM" stays one line; a
   long corp name lives in the cell's tooltip, the row shows the ticker (§short-lender). */
.short-row td.ex-short,
.short-row td.ex-short .badge,
.short-lender { white-space: nowrap; }
.short-lender { font-size: 0.86em; }
.short-row td { vertical-align: middle; }
/* Short Ex cell (09-04): the lender tag (Reserve / lending-corp ticker) stacks
   CENTERED above a compact Short badge — the controller's deliberate override of
   the no-text-stacking rule. Kept small (mini badge, tight line-height, 1px gap)
   so a short row stays ~one normal line tall and the QTY beside it reads center-
   aligned (short-row cells middle-aligned above). */
.ex-short .short-id {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  vertical-align: middle;
  line-height: 1.1;
}
.ex-short .short-lender { font-size: 0.72em; letter-spacing: 0.04em; }
.ex-short .badge-short { font-size: 9px; padding: 1px 6px; line-height: 1.35; }

.empty { color: var(--muted); font-size: 13px; padding: 12px 0; }

/* ── Column stability (#29): fixed table layout + ellipsis ───────────────────
   Auto-sized tables reflow as values change — a wider "Unrealized" pushes every
   earlier column. Pin the three live tables to a fixed layout with per-column
   widths, and let long cells ellipsize instead of wrapping mid-token (which the
   global `overflow-wrap:anywhere` above otherwise permits). */
#portfolio-body table, #orders-body table, #leaderboard-body table {
  table-layout: fixed;
  width: 100%;
}
#portfolio-body th, #portfolio-body td,
#orders-body th, #orders-body td,
#leaderboard-body th, #leaderboard-body td {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow-wrap: normal;
}
/* Portfolio: Symbol · Ex · Qty · Avg · Last · Value · Unrealized */
#portfolio-body th:nth-child(1), #portfolio-body td:nth-child(1) { width: 17%; }
#portfolio-body th:nth-child(2), #portfolio-body td:nth-child(2) { width: 12%; }
#portfolio-body th:nth-child(3), #portfolio-body td:nth-child(3) { width: 11%; }
#portfolio-body th:nth-child(4), #portfolio-body td:nth-child(4) { width: 13%; }
#portfolio-body th:nth-child(5), #portfolio-body td:nth-child(5) { width: 13%; }
#portfolio-body th:nth-child(6), #portfolio-body td:nth-child(6) { width: 17%; }
#portfolio-body th:nth-child(7), #portfolio-body td:nth-child(7) { width: 17%; }
/* Orders: Symbol · Ex · Side · Type · Qty · Limit · Filled · Avg · Status · ✕ */
#orders-body th:nth-child(1), #orders-body td:nth-child(1) { width: 13%; }
#orders-body th:nth-child(2), #orders-body td:nth-child(2) { width: 9%; }
#orders-body th:nth-child(3), #orders-body td:nth-child(3) { width: 8%; }
#orders-body th:nth-child(4), #orders-body td:nth-child(4) { width: 9%; }
#orders-body th:nth-child(5), #orders-body td:nth-child(5) { width: 10%; }
#orders-body th:nth-child(6), #orders-body td:nth-child(6) { width: 11%; }
#orders-body th:nth-child(7), #orders-body td:nth-child(7) { width: 9%; }
#orders-body th:nth-child(8), #orders-body td:nth-child(8) { width: 11%; }
#orders-body th:nth-child(9), #orders-body td:nth-child(9) { width: 11%; }
#orders-body th:nth-child(10), #orders-body td:nth-child(10) { width: 8%; }
/* Ex column: the CEX label "Trading Groups" must read fully — the fixed-layout
   ellipsis above truncates it to "…" at 7–8% width (mobile AND desktop). Let the
   cell wrap at the space into two clean lines ("Trading" / "Groups") instead of
   clipping. break-word (not anywhere): words stay whole unless one alone can't
   fit the column. */
#portfolio-body td:nth-child(2),
#orders-body td:nth-child(2) {
  white-space: normal;
  overflow-wrap: break-word;
  text-overflow: clip;
  vertical-align: middle;
}
/* Leaderboard: # · Player · P&L
   # and P&L are fixed px so the money column never crushes: P&L is a signed
   money() string ("-$123,456.78") that needs ~100px at 11px mono; the old 22%
   of a 252px rail was ~50px and truncated every value. Player takes the rest
   and ellipsizes inside the seal+name block (below). */
#leaderboard-body th:nth-child(1), #leaderboard-body td:nth-child(1) { width: 24px; }
#leaderboard-body th:nth-child(3), #leaderboard-body td:nth-child(3) { width: 104px; }
/* The name cell is the live one: the seal stays put and the name ellipsizes
   inside the rail width instead of the whole seal+name block clipping at the
   cell edge (that was hiding names on desktop's 252px rail). */
#leaderboard-body .lb-sealwrap { min-width: 0; display: flex; align-items: center; gap: 6px; }
#leaderboard-body .lb-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.alert {
  background: rgba(255, 92, 122, 0.12);
  border: 1px solid var(--down);
  color: var(--down);
  border-radius: 8px;
  padding: 10px 12px;
  font-size: 13px;
  margin-top: 14px;
}
.alert.center { margin: 14px auto 0; max-width: 640px; text-align: center; }

.cancel-btn { background: transparent; border: 1px solid var(--border); color: var(--muted); border-radius: 6px; padding: 3px 8px; font-size: 11px; cursor: pointer; }
.cancel-btn:hover { border-color: var(--down); color: var(--down); }

/* ── Live chart ──────────────────────────────────────────────────── */
.chart-panel { margin: 18px auto 0; max-width: 1240px; padding: 12px 18px 14px; }
.chart-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.chart-controls { display: flex; align-items: baseline; gap: 10px; }
.chart-select { width: auto; padding: 4px 8px; font-family: var(--mono); }
.chart-price { font-family: var(--mono); font-size: 18px; font-weight: 700; min-width: 92px; font-variant-numeric: tabular-nums; }
.chart-src { color: var(--muted); font-size: 11px; white-space: nowrap; }
.chart-expand {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  border-radius: 6px;
  padding: 4px 10px;
  font: inherit;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
}
.chart-expand:hover { color: var(--brass); border-color: var(--brass); }
/* GEX/CEX book toggle — the chart picker's universe. Segmented pair so the
   two books read as one switch: CEX lists the player-corp order book. */
.chart-book { display: flex; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
.chart-book-btn {
  background: transparent;
  border: 0;
  border-right: 1px solid var(--border);
  color: var(--muted);
  font: inherit;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  padding: 4px 10px;
  cursor: pointer;
}
.chart-book-btn:last-child { border-right: 0; }
.chart-book-btn:hover { color: var(--accent); }
.chart-book-btn.is-active { background: var(--brass); color: #04121f; }
.chart-canvas {
  display: block;
  width: 100%;
  height: 300px;
  margin-top: 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--panel-2);
}

/* Immersive fullscreen chart — the panel becomes a terminal-wall overlay. */
.chart-panel.is-fullscreen {
  position: fixed;
  inset: 0;
  z-index: 40;
  max-width: none;
  width: 100%;
  height: 100vh;
  margin: 0;
  border: none;
  border-radius: 0;
  background: var(--bg);
  padding: 20px 28px 26px;
  display: flex;
  flex-direction: column;
}
.chart-panel.is-fullscreen .chart-head {
  border-bottom: 1px solid var(--border);
  padding-bottom: 12px;
  align-items: center;
}
.chart-panel.is-fullscreen .chart-canvas {
  flex: 1;
  height: auto;
  min-height: 0;
  border-radius: 12px;
  background: var(--panel);
}
html.chart-fullscreen, body.chart-fullscreen { overflow: hidden; }

/* ── Polish pass ─────────────────────────────────────────────────────── */

/* Subtle top glow behind everything. */
body { background: radial-gradient(1100px 500px at 50% -8%, #0a0e16 0%, var(--bg) 60%) fixed; }

.auth-card { box-shadow: var(--shadow-2); border-color: rgba(58, 168, 224, 0.22); }

/* Brand + panels: gradient identity, soft depth. */
.brand {
  color: var(--accent);
}
.panel { box-shadow: var(--shadow-1); }
.panel h2 { display: flex; align-items: center; gap: 8px; }
.panel h2::before {
  content: '';
  width: 3px;
  height: 14px;
  border-radius: 2px;
  background: var(--accent);
  flex: 0 0 auto;
}

/* Frosted topbar. */
.topbar { background: var(--panel); }
.topbar-right { display: flex; align-items: center; gap: 14px; }
.topbar .meta { display: flex; align-items: center; gap: 10px; }
/* Market status cluster (clock + source badge) reads as one unit, separated
   from the account actions by a hairline divider. */
.meta.market-status {
  border-right: 1px solid var(--border);
  padding-right: 14px;
}
.clock {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.06em;
  color: var(--text);
}
.src-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 3px 10px;
  font-family: var(--mono);
  white-space: nowrap;
}
.src-badge::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 6px currentColor;
  flex: 0 0 auto;
}
.src-badge.live { color: var(--up); border-color: rgba(56, 211, 159, 0.4); }
.src-badge.mock { color: var(--warn); border-color: rgba(240, 180, 41, 0.4); }
.src-badge.secondary { color: #4cc9d4; border-color: rgba(76, 201, 212, 0.4); }
.src-badge.afterburner { color: var(--accent); border-color: rgba(58, 168, 224, 0.4); }

/* Interactive polish. */
.btn { transition: border-color 0.15s ease, background-color 0.15s ease, transform 0.06s ease, box-shadow 0.15s ease; }
.btn:hover { transform: translateY(-1px); box-shadow: var(--shadow-1); }
.btn:active { transform: translateY(0); }
.btn:focus-visible { outline: none; box-shadow: var(--ring); }
input:focus, select:focus { border-color: var(--accent); box-shadow: var(--ring); }
tbody tr { transition: background-color 0.12s ease; }
@media (hover: hover) { tbody tr:hover { background: rgba(58, 168, 224, 0.05); } }

/* Ticker: flash the chip on tick, thin scrollbar. */
.tick.flash-up { border-color: var(--up); animation: flash-up 0.7s ease; }
.tick.flash-down { border-color: var(--down); animation: flash-down 0.7s ease; }
@keyframes flash-up { 0% { background: rgba(56, 211, 159, 0.3); box-shadow: 0 0 10px rgba(56, 211, 159, 0.25); } 100% { background: transparent; box-shadow: none; } }
@keyframes flash-down { 0% { background: rgba(255, 92, 122, 0.3); box-shadow: 0 0 10px rgba(255, 92, 122, 0.25); } 100% { background: transparent; box-shadow: none; } }
.ticker::-webkit-scrollbar { height: 6px; }
.ticker::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
.ticker::-webkit-scrollbar-track { background: transparent; }

/* Order status badges. */
.badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}
.badge-fill { background: rgba(56, 211, 159, 0.14); color: var(--up); border: 1px solid rgba(56, 211, 159, 0.35); }
.badge-short { background: rgba(224, 89, 89, 0.12); color: var(--down); border: 1px solid rgba(224, 89, 89, 0.35); }
.badge-open { background: rgba(58, 168, 224, 0.14); color: var(--accent); border: 1px solid rgba(58, 168, 224, 0.35); }
.badge-pending { background: rgba(240, 180, 41, 0.14); color: var(--warn); border: 1px solid rgba(240, 180, 41, 0.35); }
.badge-cancel { background: rgba(139, 150, 168, 0.14); color: var(--muted); border: 1px solid rgba(139, 150, 168, 0.35); }

/* Toast notifications. */
.toast-wrap {
  position: fixed;
  right: 18px;
  bottom: 18px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  z-index: 50;
  pointer-events: none;
}
.toast {
  min-width: 240px;
  max-width: 340px;
  padding: 11px 14px;
  border-radius: 10px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-2);
  font-size: 13px;
  animation: toast-in 0.22s ease;
  pointer-events: auto;
}
.toast b { display: block; margin-bottom: 2px; font-family: var(--mono); }
.toast-success { border-left: 3px solid var(--up); }
.toast-error { border-left: 3px solid var(--down); }
.toast-info { border-left: 3px solid var(--accent); }
.toast.leaving { animation: toast-out 0.25s ease forwards; }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
@keyframes toast-out { to { opacity: 0; transform: translateY(8px); } }

/* Net-worth hero + sparkline. */
.net-hero {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 16px;
  border-radius: 12px;
  background: var(--panel-2);
  border: 1px solid rgba(58, 168, 224, 0.25);
  margin-bottom: 14px;
}
.net-hero .lbl { font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.net-hero .val {
  font-family: var(--mono);
  font-size: 26px;
  font-weight: 700;
  color: var(--text);
}
.net-spark { width: 140px; height: 44px; }
.balances { grid-template-columns: repeat(2, 1fr); }
.bot-mark { color: var(--muted); font-size: 0.65em; opacity: 0.4; margin-left: 2px; }

/* Order estimate strip. */
.order-estimate {
  margin-top: 10px;
  padding: 10px 12px;
  border-radius: 10px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  font-size: 12px;
  color: var(--muted);
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
}
.order-estimate b { color: var(--text); font-family: var(--mono); font-variant-numeric: tabular-nums; }
.order-estimate .total { font-size: 15px; }
.order-estimate .total b { color: var(--accent); }
.order-estimate .bal { color: var(--muted); }
.order-estimate .bal-lbl { font-size: 11px; }
.order-estimate .bal-arrow {
  color: var(--muted);
  margin: 0 5px;
  opacity: 0.7;
}
.order-estimate b.neg { color: var(--down); }
/* Pre-fill cash never reads negative (it's the live balance). */
.order-estimate #est-cash { color: var(--text); }
.order-estimate .warn { color: var(--down); }
/* Insufficient-cash (and short) messages sit on their own centered line under
   the balance row — never inline beside the number where they shove the strip. */
.order-estimate .est-msg {
  flex-basis: 100%;
  text-align: center;
  margin-top: 6px;
  font-size: 11px;
}
.order-estimate .est-msg:not([hidden]):empty { display: none; }

/* Portfolio summary strip. */
.portfolio-summary { display: flex; gap: 8px 20px; flex-wrap: wrap; font-size: 12px; color: var(--muted); margin-bottom: 10px; }
.portfolio-summary b { color: var(--text); font-family: var(--mono); }

/* Loading skeletons. */
.skeleton {
  border-radius: 8px;
  background: linear-gradient(90deg, var(--panel-2) 25%, #1c2431 50%, var(--panel-2) 75%);
  background-size: 200% 100%;
  animation: shimmer 1.2s infinite linear;
  height: 14px;
  margin: 8px 0;
}
@keyframes shimmer { to { background-position: -200% 0; } }

/* Highlight the signed-in player in tables. */
tr.me { background: rgba(58, 168, 224, 0.08); }
tr.pinned td { border-top: 1px dashed var(--border); }

/* Table headers stay put within their panel. */
thead th { position: sticky; top: 0; background: var(--panel); z-index: 1; }

/* #panel-world (the world-status band) is a grid child spanning 1 / -1 in the
   rail DOM (immersive full-width band; display:none in casual) — the pre-rail
   standalone .world-row max-width + margin ("sits outside the grid") is gone:
   inside the 1240px grid the max-width was a no-op and the margin only added a
   surplus ~18px top gap above the band in the phone stack (§5.102/§5.103). */

/* ── Header chrome ───────────────────────────────────────────────────────── */
.topbar-brand, .auth-brand { display: flex; align-items: center; gap: 10px; }
.auth-brand { justify-content: center; margin-bottom: 2px; }

/* Brass wax-seal badge (§11): marks the wordmark / certified feed. The seal
   carries the brass; the wordmark color differs by presentation layer. */
.seal-mark {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 1px solid var(--brass);
  color: var(--brass);
  background: rgba(201, 168, 106, 0.06);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: Georgia, "Times New Roman", serif;
  font-weight: 700;
  font-size: 10px;
  letter-spacing: 0.02em;
}

.net-chip {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--text);
  /* The number must never wrap into a vertical digit stack (the overflow guard
     below would happily break it word-by-word when the 1fr track tightens). One
     line, ellipsis when squeezed. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 32vw;
}
.net-chip::before { content: 'NW '; color: var(--muted); font-size: 10px; letter-spacing: 0.08em; }

.feed-seal {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  color: var(--brass);
  text-transform: uppercase;
  white-space: nowrap;
}
.feed-seal::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--brass);
  box-shadow: 0 0 0 2px rgba(201, 168, 106, 0.18);
}

.mode-toggle {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 10px;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.mode-toggle:hover { color: var(--text); border-color: var(--brass); box-shadow: 0 0 0 2px rgba(201, 168, 106, 0.14); }
.mode-toggle.is-immersive { color: var(--accent); border-color: rgba(58, 168, 224, 0.45); }
.mode-toggle.is-immersive:hover { border-color: var(--accent); box-shadow: var(--ring); }

/* ── Casual layer (default) — the meta-trader screen (§1 p.4 / §2.1 / §11) ── */
/* Immersive keeps the mono/cyan identity below; Casual swaps the wordmark to
   a serif institutional face and surfaces the brass seal + feed verification. */
body[data-ui-mode="casual"] .brand {
  font-family: Georgia, "Times New Roman", serif;
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--text);
}
body[data-ui-mode="casual"] .auth-card .brand { color: var(--text); }
body[data-ui-mode="casual"] .seal-mark {
  color: var(--brass);
  border-color: var(--brass);
  background: rgba(201, 168, 106, 0.06);
}
body[data-ui-mode="casual"] .tagline { letter-spacing: 0.14em; }

/* ── Text overflow guards: no section may overflow horizontally ──────────── */
body, .topbar, .topbar-brand, .auth-brand, .seal-mark, .net-chip, .feed-seal,
.mode-toggle, .ticker, .chart-panel, .world-row, .app-layout, .grid, .panel,
.net-hero, .balances, .balance, .metrics, .portfolio-summary, .quote, table,
th, td, .sym-cell, .chip, .src-badge, .toast, .alert, .empty {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Sideways scroll is never intentional — clip anything that escapes the
   viewport instead of letting the page pan horizontally. (clip, not hidden,
   so the sticky topbar keeps working.) */
html, body { overflow-x: clip; }

/* ── Compact header (narrow laptops / zoomed desktops) ──────────────────────
   Below ~1180px the topbar grid starts squeezing the right 1fr track, and
   without this the net worth number wraps or collides with the centered
   username chip. The feed source badge is the lowest-value topbar chrome
   (the venue strip below already names it), so it steps aside first and the
   ET clock shrinks to a single quiet line — the right cluster then fits next
   to the username instead of pushing into it. */
@media (max-width: 1180px) {
  .topbar .src-badge { display: none; }
  .topbar .clock { font-size: 10px; }
  .topbar-right { gap: 8px; }
  .net-chip { font-size: 12px; }
  /* The bell costs ~36px of right-track width the compact header can't spare;
     narrow desktops reach Events through the hamburger instead. */
  .events-toggle { display: none; }
}

/* ── Mobile — one-handed stack (phones, ≤640px) ─────────────────────────────
   The 1180px breakpoint above already collapses Casual to a single stack (its
   order is set there; this pass re-orders the same panels for one-handed use).
   This pass makes that stack work at phone widths: the market clock steps
   aside, the order ticket rows stack, balances go single-column, and the
   leaderboard/orders tables scroll inside their panel instead of pushing the
   page wide. Everything else is content-height, so nothing blows the stack. */
@media (max-width: 640px) {
  body[data-ui-mode="casual"] .app-layout {
    padding: 10px 10px 18px;
    gap: 10px;
    /* Phones keep the ticket by the chart; the bookkeeping follows, then the
       gigs + leaderboard rails, then the annex note. Overrides the tablet order
       above. Every name is a real panel — no orphan areas (the old
       .casual-left/.casual-right wrapper names are gone with the rail DOM). */
    grid-template-areas:
      "chart" "ticket" "portfolio" "orders" "dashboard"
      "gigs" "leader" "annex" "footer";
  }
  .topbar { padding: 10px 12px; }
  /* The market clock + feed source badge are the lowest-value topbar chrome
     on a phone; net worth, the user chip and logout stay reachable. */
  .topbar .meta.market-status { display: none; }
  /* Phones: the on-screen feed stacks — player fills strip on top, corps/news
     below. The news feed REMAINS on screen (it's the whole point). */
  .activity-wrap { flex-direction: column; }
  .news-tape {
    max-width: none;
    border-left: 0;
    border-top: 1px solid var(--border);
  }
  /* Events overlay on phones: drop the column header, stack each row. */
  .events-overlay { padding: 10px; align-items: flex-start; }
  .events-sheet { max-height: 92vh; border-radius: 12px; }
  .events-cols { display: none; }
  .event-row {
    grid-template-columns: auto 1fr;
    grid-template-areas: "time entity" "event event" "effect effect";
    gap: 4px 10px;
    padding: 10px 14px;
    align-items: baseline;
  }
  .ev-time { grid-area: time; }
  .ev-entity { grid-area: entity; text-align: right; }
  .ev-event { grid-area: event; }
  .ev-effect { grid-area: effect; }
  /* History rows on phones: drop the column header + the exchange/commission
     columns; stack time/symbol/side/fill with P&L pinned right. */
  .history-cols { display: none; }
  .trade-row {
    grid-template-columns: auto 1fr auto;
    grid-template-areas: "time sym pnl" "side fill pnl";
    gap: 4px 10px;
    padding: 10px 14px;
    align-items: baseline;
  }
  .tr-time { grid-area: time; }
  .tr-sym { grid-area: sym; text-align: right; }
  .tr-side { grid-area: side; }
  .tr-fill { grid-area: fill; }
  .tr-pnl { grid-area: pnl; text-align: right; }
  .tr-ex, .tr-comm { display: none; }
  .tdetail-sheet { max-height: 92vh; }
  /* Only the name line clamps on phones — the buff subtitle stays whole. */
  #user-chip .hdr-uname {
    max-width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .panel { padding: 12px; }
  /* Section titles shrink on phones so they stay on ONE line — no wrap, no
     ellipsis. (Grid-track math can't fix a title that's too wide for the
     screen; the text itself has to get smaller.) */
  .panel h2 { font-size: 12.5px; white-space: nowrap; }
  /* The auth wordmark is the widest title on the login screen — shrink it so
     THE&nbsp;LEDGER stays on ONE line on phones instead of breaking. */
  .auth-brand .brand {
    font-size: 24px;
    letter-spacing: 0.1em;
    white-space: nowrap;
  }
  body[data-ui-mode="casual"] #panel-gigs,
  body[data-ui-mode="casual"] #leaderboard,
  body[data-ui-mode="casual"] #trade { padding: 12px; }
  .order-form .row { grid-template-columns: 1fr; gap: 8px; }
  .balances { grid-template-columns: 1fr; gap: 8px; }
  .chart-controls { flex-wrap: wrap; }
  .net-hero .val { font-size: 22px; }
  .net-spark { width: 110px; }
  /* Portfolio + open orders + leaderboard tables stay inside their panel on
     phones. The desktop sticky headers (thead th { top:0 }) mean nothing on a
     page that scrolls — they detach and float over content — so headers go
     static and scroll with their columns. Their per-table floor widths live
     at the end of this block (the single ≤640px pass). */
  thead th { position: static; }

  /* Phones: lean chrome — no divider stubs, drawer menu instead of a rail,
     footer wraps. (This whole block is the ONE ≤640px pass — keep every phone
     rule here so desktop and mobile never fight each other.) */
  .topbar .meta::before { display: none; }
  /* Mobile header — the hamburger owns the right side, so the rest of the
     strip clears out: the wordmark yields to the brass seal (Torn keeps just
     its logo mark), net worth rides on the username chip's hover, and Log out
     lives under the hamburger menu. Nothing here may overflow the viewport. */
  .topbar .brand.small { display: none; }
  .topbar .feed-seal { display: none; }
  /* BUG-010 (2026-08-26): net worth is the header's primary stat — keep it on
     phones instead of hiding it (it was reachable only via the profile card).
     Compact + ellipsis'd; the phone right cluster is otherwise just the
     hamburger (market status / feed seal / logout / bell all step aside), so
     there's room and nothing can overflow. */
  .net-chip { font-size: 11px; max-width: 30vw; }
  /* Needs the full `.topbar .meta` specificity to beat the base
     `display: flex` on `.topbar .meta` — a bare `.meta-logout` loses. */
  .topbar .meta.meta-logout { display: none; }
  .topbar-left, .topbar-right { min-width: 0; }
  #user-chip { max-width: 34vw; }
  /* Venue boards stay pills on phones: the strip already scrolls sideways,
     so every board stays reachable and the active one (accent border + tint)
     stays visible. Just tighten the pills so more fit per screen. */
  .venue-chip { padding: 3px 8px; font-size: 10px; }
  .app-footer {
    flex-wrap: wrap;
    row-gap: 6px;
    justify-content: center;
    text-align: center;
  }
  /* On phones the metrics strip centers and may wrap — the brass bullets are
     dropped entirely so a wrapped line never starts or ends with a divider;
     spacing is the separator. The column-gap (16px) is the replaced gutter:
     it sits only BETWEEN groups that share a line (never at a line start),
     so a wrapped group doesn't pick up a continuation indent. Without it a
     wrapped pair of groups butted together — "Credit —" landed flush against
     "Boards open 10/10". */
  .metrics { justify-content: center; flex-wrap: wrap; gap: 10px 16px; }
  .m-group + .m-group { margin-left: 0; }
  .m-group + .m-group::before { display: none; }
  /* Fixed-layout live tables stay straight but get room to scroll sideways on
     phones rather than crushing 10 columns into ~35px each. */
  #portfolio-body, #orders-body, #leaderboard-body { overflow-x: auto; }
  #orders-body table { min-width: 620px; }
  #portfolio-body table { min-width: 500px; }
  /* The leaderboard is only three short columns (# · player · P&L, fixed px
     above) — it must FIT the rail. The old 340px floor propagated up through
     the .casual-right flex rail and side-scrolled the whole page on phones. */
  #leaderboard-body table { min-width: 0; }
  /* Mobile: never clip a holding symbol. Tickers are short but the base
     fixed-layout rule nowraps every cell, so a narrow phone crushes the 17%
     Symbol column to an ellipsis. Widen it and let a long ticker wrap instead
     of truncating. */
  #portfolio-body th:nth-child(1), #portfolio-body td:nth-child(1) { width: 20%; }
  #portfolio-body td:nth-child(1) { white-space: normal; overflow-wrap: break-word; text-overflow: clip; }
  .app-footer .app-footer-note {
    flex: 1 1 100%;
    order: 2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .app-footer .app-footer-mode { order: 1; }
}

/* ── Torn City accents (v2) — dark chrome, paper as accent, section menu ──
   From the torn.com dark-mode reference: the chrome stays dark and clean;
   "torn" lives in the small details — the notched vertical dividers between
   topbar groups (Torn's torn-divider) and the left-rail section menu that
   folds to a drawer on phones and in the Immersive layer. */

/* Torn vertical dividers — a hairline with a torn gap in its middle, used
   between the topbar's group clusters. (.feed-seal::before keeps its brass
   status dot; only the .meta groups get the notch.) */
/* BUG-011 (2026-08-26): the logout cluster must never fracture mid-word on
   desktop — "Log out" already rides a nowrap ghost button, but the divider
   group itself now wraps instead of splitting a label across lines. */
.topbar .meta { position: relative; white-space: nowrap; }
.topbar .meta::before {
  content: '';
  position: absolute;
  left: -13px;
  top: 50%;
  transform: translateY(-50%);
  width: 1px;
  height: 16px;
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.13) 0%, rgba(255, 255, 255, 0.13) 32%,
    transparent 32%, transparent 68%,
    rgba(255, 255, 255, 0.13) 68%, rgba(255, 255, 255, 0.13) 100%);
}
.topbar .meta.market-status { border-right: 0; }

/* ── Section menu — slide-in drawer at every width (desktop rail retired) ── */
.topbar-left { display: flex; align-items: center; gap: 10px; }

.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex: 0 0 auto;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.nav-toggle:hover { color: var(--brass); border-color: var(--brass); }
.nav-toggle svg { width: 16px; height: 16px; }
/* Ledger toggle — a closed ledger (book + strap) that opens into a spread
   book when the menu is out, with a small "unfold" tilt on the way in. */
.nav-toggle .nav-ico-open { display: none; }
body.nav-open .nav-toggle .nav-ico-closed { display: none; }
body.nav-open .nav-toggle .nav-ico-open {
  display: block;
  animation: nav-unfold 0.32s ease;
}
@keyframes nav-unfold {
  from { transform: perspective(48px) rotateY(-60deg); opacity: 0.3; }
  to   { transform: perspective(48px) rotateY(0deg); opacity: 1; }
}

/* The desktop menu rail is retired (2026-08-22): the section menu lives in the
   slide-in drawer at every width, matching mobile. */
.nav-rail { display: none; }

.nav-kicker {
  font-family: var(--mono);
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--muted);
  opacity: 0.6;
  padding: 2px 9px 7px;
}
.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 9px;
  border-radius: 8px;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.05em;
  text-decoration: none;
  transition: color 0.15s ease, background-color 0.15s ease;
}
.nav-item svg { width: 15px; height: 15px; flex: 0 0 auto; }
.nav-item:hover { color: var(--brass); background: rgba(212, 180, 122, 0.08); }
.nav-item.is-active { color: var(--brass); background: rgba(212, 180, 122, 0.1); }
.nav-sep {
  height: 1px;
  margin: 7px 4px;
  border: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.09), transparent);
}

/* Mode-filtered rows — casual hides Immersive-only sections and vice-versa. */
body[data-ui-mode="casual"] .nav-item:not([data-casual]) { display: none; }
body[data-ui-mode="immersive"] .nav-item:not([data-immersive]) { display: none; }

/* Drawer — used under 1180px and everywhere in the Immersive layer. */
.nav-drawer {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  width: 276px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 16px 10px;
  overflow-y: auto;
  background: var(--panel-2);
  border-right: 1px solid var(--border);
  box-shadow: 14px 0 34px rgba(0, 0, 0, 0.45);
  transform: translateX(-102%);
  transition: transform 0.28s ease;
  z-index: 70;
}
body.nav-open .nav-drawer { transform: none; }
/* The ledger stays open in the background: a faint spread-book watermark
   behind the menu items while the drawer is showing. */
.nav-drawer::before {
  content: '';
  position: absolute;
  top: 84px;
  left: 50%;
  transform: translateX(-50%);
  width: 128px;
  height: 128px;
  z-index: 0;
  pointer-events: none;
  opacity: 0.05;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23d4b47a' stroke-width='1.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 7v14'/%3E%3Cpath d='M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z'/%3E%3C/svg%3E")
    center / contain no-repeat;
}
.nav-drawer > * { position: relative; z-index: 1; }
/* Nav items fill the drawer on desktop: the list is a flex:1 column with
   justify-between so the items distribute down the full height instead of
   bunching under the head with a dead void below (controller: "justify-between
   for the navitems"). Events + logout stay pinned under the list. */
.nav-drawer .nav-items {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}
.nav-drawer-head { display: flex; align-items: center; justify-content: space-between; padding: 0 5px 10px; }
.nav-close {
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}
.nav-close:hover { color: var(--brass); border-color: var(--border); }
.nav-close svg { width: 14px; height: 14px; }
.nav-logout {
  width: 100%;
  background: transparent;
  border: 0;
  cursor: pointer;
  text-align: left;
}
.nav-logout:hover { color: var(--down); background: rgba(255, 92, 122, 0.08); }
/* The drawer's Events entry is a button, not an anchor — strip the UA chrome
   so it reads like the other nav rows. */
.nav-events {
  width: 100%;
  background: transparent;
  border: 0;
  cursor: pointer;
  text-align: left;
}
.nav-events:hover { color: var(--brass); background: rgba(212, 180, 122, 0.08); }
.nav-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(3, 5, 9, 0.55);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.28s ease;
  z-index: 65;
}
body.nav-open .nav-backdrop { opacity: 1; pointer-events: auto; }
body.nav-open { overflow: hidden; }

/* Toggle visibility: hamburger at every width (drawer is the only menu). */
body[data-ui-mode="casual"] .nav-toggle { display: inline-flex; }
body[data-ui-mode="immersive"] .nav-toggle { display: inline-flex; }

/* Mid widths: identical three-column layout (no menu rail to collapse). */
@media (max-width: 1319px) and (min-width: 1181px) {
  body[data-ui-mode="casual"] .app-layout { grid-template-columns: 252px minmax(0, 1fr) 300px; }
}

/* Anchor targets clear the sticky topbar. */
.panel, .chart-panel, .world-row, .annex-hero { scroll-margin-top: 66px; }

/* Click-to-quote: any symbol stamped [data-quote] opens the shared quote panel.
   Kept minimal — accent color already lives on .sym-cell / .act-sym, so a
   pointer + hover underline is all the affordance the app needs. */
[data-quote] { cursor: pointer; }
[data-quote]:hover { text-decoration: underline; }
