/* Dark Broadcast (BLUEPRINT §5.2)
   Chassis: deep charcoal near-black, quiet surfaces, restrained utility type.
   Team colors are the only accents. Broadcast energy is reserved for the
   verdict banner and the share card. Money is always tabular. */

:root {
  --bg: #0e0f12;            /* near-black base */
  --surface: #16171c;       /* quiet surface */
  --surface-2: #1c1e24;
  --line: #26282f;
  --text: #d9dade;
  --muted: #8b8e98;
  --legal: #19c37d;         /* broadcast green */
  --fails: #ff3b30;         /* broadcast red */
  --caution: #e6b800;
  --display: "Bahnschrift SemiCondensed", "Bahnschrift", "Arial Narrow", "Helvetica Neue", sans-serif;
  --body: system-ui, "Segoe UI", sans-serif;
}

* { box-sizing: border-box; margin: 0; }
html, body { height: 100%; }
body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--body);
  font-size: 15px;
  line-height: 1.45;
}
#app { display: flex; flex-direction: column; min-height: 100dvh; max-width: 760px; margin: 0 auto; }

button { font: inherit; cursor: pointer; }
button:focus-visible, input:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid #6ea8ff; outline-offset: 2px; border-radius: 4px;
}

/* ---------- Trade Slip (signature element) ---------- */
.slip {
  position: sticky; top: 0; z-index: 20;
  background: linear-gradient(180deg, #14151a, #101116);
  border-bottom: 1px solid var(--line);
  padding: 8px 12px 10px;
}
/* MOBILE VIEWPORT ARC (2026-08-05): the header row MUST wrap. Without
   flex-wrap this row of credibility surfaces (season selector, freshness
   badge, verified stamp, technical toggle) + Tray/New session/account set
   a ~628px minimum width — wider than any phone — forcing the whole page
   to load zoomed-out with horizontal scroll (the root cause of the "loads
   zoomed in on mobile" report; measured at 360/390/430px). Wrapping is a
   no-op on desktop (it already fits one line inside #app's 760px cap) and
   the structural fix on mobile; the @media block below then COLLAPSES the
   wrapped surfaces into one compact status line, never deletes them. */
.slip-brand { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; row-gap: 6px; }
.brand-mark {
  font-family: var(--display); font-weight: 700; font-size: 13px;
  background: var(--surface-2); border: 1px solid var(--line);
  padding: 1px 6px; border-radius: 3px; letter-spacing: 1px;
}
.brand-name { font-family: var(--display); letter-spacing: 3px; font-size: 12px; color: var(--muted); }
.epoch-select {
  font-size: 11px; color: var(--text); background: var(--surface-2);
  border: 1px solid var(--line); border-radius: 3px; padding: 2px 4px;
  font-family: inherit; max-width: 40vw;
}
/* New session (DESIGN GAP fix, 2026-07-24): a separate, always-reachable
   action from "Clear trade" (which lives inside the slip body, hidden
   whenever nothing's pinned — exactly when a fresh start is often most
   wanted). Pushed to the far end of the brand row, quiet/small so it
   never competes with the brand itself. */
.new-session { margin-left: auto; font-size: 10px; padding: 3px 8px; }
/* GM LIVE AFFORDANCE (FINAL POLISH ARC item 4, 2026-07-29): a status
   light, not a primary action — no border/hover emphasis that reads as
   "click me to do something," a help cursor signaling "tap for info"
   instead of the pointer cursor every real action button uses. */
#gm-lock[data-mode="status"] {
  background: none; border-color: transparent; color: var(--muted);
  cursor: help;
}
#gm-lock[data-mode="status"]:hover { background: var(--surface); }
.current-build-status {
  font-size: 10px; padding: 1px 6px; border-radius: 3px; letter-spacing: 0.3px;
}
.current-build-status[data-status="in-progress"] { color: var(--caution); border: 1px solid var(--caution); }
.current-build-status[data-status="failed"] { color: var(--fails); border: 1px solid var(--fails); }
/* EMERGENCY COST STOP (2026-07-30): a static, hand-set flag — see the
   HTML comment on #maintenance-paused for why this is never a live
   infrastructure check. */
.maintenance-paused {
  font-size: 10px; padding: 1px 6px; border-radius: 3px; letter-spacing: 0.3px;
  color: var(--caution); border: 1px solid var(--caution);
}
/* RE-RUN ROUND item 10 (2026-08-02): the "verified" stamp is now an
   actual button (a real provenance popover behind it) — reset button
   chrome so it still reads as the same plain text it always was, with
   just enough affordance (underline on hover/focus) to signal it's
   tappable. */
.slip-snapshot {
  font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums;
  background: none; border: none; padding: 0; font-family: inherit; cursor: pointer;
  text-align: left;
}
.slip-snapshot:hover, .slip-snapshot:focus-visible { text-decoration: underline; color: var(--text); }

/* HEADER STATUS CHIP (Option A, 2026-08-09, Matt ruling) — one compact
   chip fronts the five provenance surfaces; the panel below holds them
   intact. The chip wears the ✓ at rest and ⚠ while a build-status badge
   is showing (web/app.js updateStatusChip). */
.status-chip {
  font-size: 11px; padding: 2px 10px; border-radius: 999px;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.status-chip-warn { color: var(--caution, #e8c15a); border-color: var(--caution, #e8c15a); }
.status-panel {
  position: absolute; top: 100%; left: 12px; z-index: 40;
  display: flex; flex-direction: column; gap: 10px; align-items: flex-start;
  max-width: min(340px, calc(100vw - 24px));
  background: var(--surface, #15171f); border: 1px solid var(--line);
  border-radius: 10px; padding: 12px 14px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.45);
}
.status-panel[hidden] { display: none; }
.status-panel .epoch-select { width: 100%; max-width: none; }
.status-panel .maintenance-paused { margin: 0; }

/* CAPOLOGIST FINALE item 8d (2026-08-02, Matt ruling — "sticky slip
   panel gets a viewport height cap"): .slip is `position: sticky; top:
   0` with no height limit — a many-team trade, or a team carrying an
   unusually long roster line-up, can grow the team-columns row tall
   enough to push the entire chat feed below it off-screen (worst on a
   short mobile viewport, where the sticky header could consume the
   whole visible area with nothing else reachable without first
   scrolling the PAGE, which the sticky positioning itself resists).
   Capped at 40% of the viewport height with its own internal scroll —
   the brand/badge row above stays fixed height and always visible; only
   the team-columns content scrolls internally past the cap. */
/* FAN RE-RUN item 8: sticky-panel growth eases in rather than snapping
   content under a press. */
.slip-body { margin-top: 8px; gap: 10px; align-items: stretch; max-height: 40vh; overflow-y: auto; transition: max-height .25s ease; }
/* :not([hidden]) — see the identical composer-hint note above: a bare
   `display: flex` here outranks the UA stylesheet's `[hidden]` rule and
   was showing the "NO VERDICT" badge + a "Clear trade" button for a trade
   that doesn't exist yet, on the pristine home screen (patch-13; found by
   the same class of bug caught in the composer hint, fixed the same way). */
.slip-body:not([hidden]) { display: flex; }
.slip-teams { display: flex; gap: 10px; flex: 1; min-width: 0; overflow-x: auto; }
.slip-team {
  flex: 1; min-width: 130px;
  background: var(--surface); border: 1px solid var(--line);
  border-left: 4px solid var(--team, #555);
  border-radius: 6px; padding: 6px 8px;
}
.slip-team h3 { font-family: var(--display); font-size: 14px; letter-spacing: 1px; }
.slip-team .cap { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
.slip-team ul { list-style: none; padding: 0; margin: 4px 0 0; }
.slip-team li { display: flex; justify-content: space-between; gap: 8px; font-size: 12px; padding: 1px 0; }
.slip-team li .in { color: var(--legal); }
.slip-team li .money { font-variant-numeric: tabular-nums; color: var(--muted); white-space: nowrap; }

.slip-side { display: flex; flex-direction: column; gap: 6px; align-items: stretch; align-self: center; }
.slip-clear { font-size: 11px; padding: 3px 8px; color: var(--muted); background: var(--surface); border: 1px solid var(--line); border-radius: 6px; }
.slip-badge {
  align-self: center; flex-shrink: 0;
  font-family: var(--display); font-weight: 700; letter-spacing: 2px; font-size: 14px;
  padding: 8px 10px; border-radius: 6px; border: 1px solid var(--line);
  color: var(--muted); background: var(--surface);
  transition: box-shadow 220ms ease, color 220ms ease;
}
.slip-badge[data-status="legal"]  { color: var(--legal);  border-color: var(--legal);  box-shadow: 0 0 14px rgba(25,195,125,.35); }
.slip-badge[data-status="illegal"]{ color: var(--fails);  border-color: var(--fails);  box-shadow: 0 0 14px rgba(255,59,48,.35); }
.slip-badge[data-status="cannot_evaluate"] { color: var(--caution); border-color: var(--caution); }
/* BADGE SEMANTICS (FINAL POLISH ARC item 3, 2026-07-29): a visible
   explainer for PINNED, not a hover-only title attribute — see
   web/app.js's renderSlip() for the full note. */
.slip-badge-note {
  font-size: 10.5px; color: var(--muted); text-align: center;
  max-width: 160px; line-height: 1.3;
}

/* ---------- Verdict banner: the one orchestrated moment ---------- */
/* FAN ROUND 3 item 10 — "sticky-panel overlap while scrolling": the banner
   and the slip header were BOTH sticky at top:0, so during the banner's
   5.2s life a scroll stacked it over the header (higher z-index won).
   The banner now sticks just below the slip's REAL height (--slip-h,
   published by a ResizeObserver in app.js) — reserved space, no overlap. */
.banner {
  position: sticky; top: var(--slip-h, 0px); z-index: 30;
  margin: 10px 12px 0; padding: 12px 16px;
  border-radius: 8px; border: 1px solid var(--line);
  background: linear-gradient(90deg, #14151a 0%, #101116 100%);
  transform-origin: top center;
}
@media (prefers-reduced-motion: no-preference) {
  .banner.land { animation: land 480ms cubic-bezier(.2,.9,.25,1.2); }
  @keyframes land {
    0% { transform: translateY(-18px) scaleY(.7); opacity: 0; }
    60% { transform: translateY(2px) scaleY(1.03); opacity: 1; }
    100% { transform: none; }
  }
}
.banner-status { font-family: var(--display); font-weight: 800; font-size: clamp(28px, 8vw, 44px); letter-spacing: 3px; }
.banner[data-status="legal"] .banner-status { color: var(--legal); }
.banner[data-status="illegal"] .banner-status { color: var(--fails); }
.banner[data-status="cannot_evaluate"] .banner-status { color: var(--caution); font-size: clamp(20px, 6vw, 30px); }
.banner-detail { font-size: 13px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* ---------- Home (patch-13/14: composer is the hero — everything up
   here stays quiet so the eye lands on the input, not a wall of chrome) */
.home { text-align: center; padding: 32px 16px 20px; }
.home .gm-avatar { opacity: .8; }
.home h1 { font-family: var(--display); letter-spacing: 2px; font-size: 22px; margin-top: 10px; font-weight: 600; }
.home-sub { color: var(--muted); margin-top: 6px; font-size: 13px; }
/* Scope orientation, surfaced on load before the first message (2026-08-06).
   A quiet, readable paragraph — same left-of-center calm as the examples, a
   touch dimmer than body text so it orients without shouting. */
.home-scope {
  color: var(--muted);
  max-width: 340px;
  margin: 16px auto 0;
  font-size: 13px;
  line-height: 1.55;
  text-align: left;
}
.home-scope strong { color: var(--text, #e6e6e6); font-weight: 600; }

/* FREE-VS-METERED CAPABILITIES ON ARRIVAL (WR items 11 + 12, 2026-08-06 —
   CONSTITUTION.md STANDING INVARIANT #10, honest scarcity). The FREE card
   leads (source order) and carries the legal-green accent: the genuinely
   unlimited capabilities must never read as subordinate to, or governed by,
   the metered counter. Two columns on desktop, stacked on phones. */
.home-caps {
  display: flex; gap: 12px; flex-wrap: wrap;
  max-width: 620px; margin: 18px auto 0; text-align: left;
}
.home-cap {
  flex: 1 1 260px; border: 1px solid var(--line); border-radius: 12px;
  padding: 12px 14px; background: var(--surface);
}
.home-cap-free { border-color: rgba(25, 195, 125, 0.42); }
.home-cap-tag {
  display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; padding: 2px 8px; border-radius: 999px; margin-bottom: 8px;
}
.home-cap-tag.free { color: var(--legal); border: 1px solid var(--legal); }
.home-cap-tag.metered { color: var(--accent, #8ab4ff); border: 1px solid var(--accent, #8ab4ff); }
.home-cap ul { margin: 0; padding-left: 18px; }
.home-cap li { font-size: 13px; color: var(--muted); margin: 5px 0; line-height: 1.5; }
.home-cap p { font-size: 13px; color: var(--muted); line-height: 1.5; margin: 0; }
.home-cap strong { color: var(--text, #e6e6e6); font-weight: 600; }

/* Example prompts (patch-14): plain fan language, not product
   categories — full sentences read better as quiet left-aligned rows
   than as pill buttons (which suit short labels, not "How do the Cavs
   get better this offseason?"). Tapping fills the composer; never
   auto-sends. */
.examples {
  display: flex; flex-direction: column; gap: 8px;
  margin: 18px auto 0; max-width: 340px;
}
.example {
  background: var(--surface); color: var(--text); text-align: left;
  border: 1px solid var(--line); border-radius: 10px; padding: 10px 14px; font-size: 13.5px;
}
.example:hover { background: var(--surface-2); border-color: #3a3d47; }

/* Recent (patch-14): collapsed behind a small toggle by default — raw
   past messages shouldn't compete with the front door on first load. */
.recent { margin-top: 16px; }
.recent details { display: inline-block; }
.recent summary {
  cursor: pointer; list-style: none; color: var(--muted); font-size: 12px;
  border: 1px solid var(--line); border-radius: 999px; padding: 4px 12px;
}
.recent summary::-webkit-details-marker { display: none; }
.recent summary::before { content: "▸ "; }
.recent details[open] summary::before { content: "▾ "; }
.recent details[open] summary { margin-bottom: 8px; }
.recent-item {
  display: block; margin: 0 auto; background: none; border: none;
  color: var(--muted); font-size: 13px; text-decoration: underline dotted; padding: 3px 0;
}

/* ---------- Conversation ---------- */
/* ROBOT ROUND TWO item 1 (P0, 2026-07-30): `display: flex` here has the
   same specificity as the UA stylesheet's `[hidden] { display: none }`
   and comes later in the cascade, so it WON — setting `$("feed").hidden
   = true` (openWarRoom/openShareCard) never actually hid the feed. War
   Room/Share Card then rendered far below the fold behind the
   full-height feed, looking exactly like "click, no visible effect, no
   modal" from the outside. Same `:not([hidden])` guard `.slip-body`/
   `.composer-hint` already use below — `.feed` itself now stays
   attribute-agnostic. */
/* WRITER V8 item 2 adjacency: the bottom pad tracks the REAL dock height
   (--dock-h, kept current by a ResizeObserver in app.js) instead of a
   hardcoded 90px sized for a one-line composer — the zero-quota relabel
   wraps the GM button on phones, the dock grows, and a fixed pad left
   the feed's last buttons unclickable underneath it (caught by the
   nativeShare ui-check lane). Fallback preserves the old 90px total. */
.feed { padding: 14px 12px calc(var(--dock-h, 76px) + 14px); flex-direction: column; gap: 10px; }
.feed:not([hidden]) { flex: 1; display: flex; }
.msg { max-width: 92%; padding: 10px 12px; border-radius: 10px; border: 1px solid var(--line); }
.msg.user { align-self: flex-end; background: #1b2432; }
.msg.gm { align-self: flex-start; background: var(--surface); }
.msg .who { font-size: 11px; letter-spacing: 1px; color: var(--muted); font-family: var(--display); margin-bottom: 4px; }
.msg ul.qlist { margin: 6px 0 0 18px; }
/* ROUND SIX item 2 (2026-08-03): GM prose tables (md()'s new GFM pipe-
   table support) — a real, scrollable table instead of raw "| a | b |"
   text. overflow-x on its own wrapper, never the page body, matching
   this app's standing wide-content discipline. */
.msg table { width: 100%; border-collapse: collapse; margin: 6px 0; font-size: 12px; display: block; overflow-x: auto; }
.msg th, .msg td { padding: 4px 8px; border: 1px solid var(--line); text-align: left; white-space: nowrap; }
.msg th { font-family: var(--display); letter-spacing: .02em; color: var(--muted); background: #161c27; }

/* Failure / repair cards */
.card {
  align-self: stretch; background: var(--surface); border: 1px solid var(--line);
  border-left: 4px solid var(--fails); border-radius: 8px; padding: 12px;
}
.card.repair { border-left-color: var(--legal); }
.card.caution { border-left-color: var(--caution); }
/* SPOT-CHECK FINDINGS item 2a (2026-08-01): the "NEXT" (War Room/Share
   Card) navigation card — same neutral border treatment as .repair
   used to give it for free, kept as its own class now that it also
   renders on illegal verdicts, where it must stay visually distinct
   from a genuine repair card. */
.card.next { border-left-color: var(--legal); }
.card h4 { font-family: var(--display); letter-spacing: 1px; font-size: 14px; }
.card .num { font-variant-numeric: tabular-nums; font-family: var(--display); font-size: 20px; margin: 4px 0; }
.card p { font-size: 13px; color: var(--muted); }
.card .actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.card .actions button, .ghost {
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--line); border-radius: 6px; padding: 7px 10px; font-size: 13px;
}
.card .actions button.primary { border-color: var(--legal); color: var(--legal); }
/* PARTIAL-FIX CLARITY (2026-07-24): a repair that clears the shown
   violation but leaves the trade illegal elsewhere must never read like
   the fully-legal action next to it — muted border/text instead of the
   legal-colored .primary treatment. */
.card .actions button.partial { border-color: var(--caution); color: var(--caution); }
/* WRITER RE-REVIEW item 7 (2026-08-05, Matt — "some verdicts offer real
   free 'Apply this fix' buttons, others ... bill a chat. Same failure
   class, two prices. Unify — and never label a billed action like a free
   one"): a .gm-ask button pre-fills a message to the GM, and SENDING it
   costs a free-tier chat — so it must never wear the free, deterministic
   green .primary treatment of the engine "Apply this fix" actions beside
   it. A dashed link-blue border + the 🗨 glyph read as "talks to the GM,"
   the hover title states the cost, and the handler never one-taps a send
   (it fills the composer), so the visual cue and the behavior agree. */
.card .actions button.gm-ask { border-style: dashed; border-color: var(--accent, #8ab4ff); color: var(--accent, #8ab4ff); }
.badge-partial {
  display: inline-block; font-size: 10px; letter-spacing: 1px; font-weight: 700;
  color: var(--caution); border: 1px solid var(--caution); border-radius: 4px;
  padding: 1px 5px; margin-left: 6px; vertical-align: middle;
}
/* REPAIR-SCOPE LABELING (CHATGPT ANCHOR-ROUND FINDING #3, 2026-07-28):
   which proposal a repair card fixes — always rendered, visually
   distinct from the description text so it can never be skimmed past
   as ordinary prose (a "fully legal" card must never be mistaken for
   preserving assets the reply's own prose already said no fix does). */
.repair-scope {
  font-size: 11px; letter-spacing: 0.4px; font-weight: 700; text-transform: uppercase;
  color: var(--muted); background: var(--line); border-radius: 3px;
  display: inline-block; padding: 2px 6px; margin: 4px 0 6px;
}
.rule-detail { margin-top: 8px; font-size: 12px; color: var(--muted); border-top: 1px dashed var(--line); padding-top: 8px; }
.rule-detail code { color: var(--text); }
.rule-citation { margin-top: 6px; font-size: 11px; opacity: 0.85; }
.stale-action { opacity: 0.5; cursor: not-allowed; text-decoration: line-through; }
/* WRITER RE-REVIEW item 5 (2026-08-05): a feed Share/War-room button for a
   scenario OTHER than the one pinned in the header — still fully clickable
   (its own verdict remains shareable, WP-1), just visibly scoped to an
   older, named trade so it can never be mistaken for "share the current
   trade." Muted + dotted underline, NOT the line-through not-allowed look
   of a disabled .stale-action. */
/* WRITER V8 item 6 ("dead italic text"): the old opacity+italic+dotted
   treatment read as DISABLED — visually indistinguishable from
   .stale-action to a skimming eye, on buttons that are fully live. The
   scope cue survives (the label already names the trade; a dotted
   underline marks "not the pinned one") without any disabled cues. */
.foreign-scope { text-decoration: underline dotted; text-underline-offset: 3px; }
.engine-trace summary { cursor: pointer; }
.engine-trace ul { margin: 6px 0 0; padding-left: 18px; }
.engine-trace li { font-family: monospace; font-size: 11.5px; margin-bottom: 3px; }

/* Severity tiers (post-v1-patch-5) */
.vline { border-left: 3px solid var(--line); padding: 3px 8px; margin: 4px 0; font-size: 13px; }
.vline.tier-red { border-left-color: var(--fails); }
.vline.tier-yellow { border-left-color: var(--caution); color: var(--text); }
.vline.tier-blue { border-left-color: #4d7fd1; color: var(--muted); }

.conditions { font-size: 12px; color: var(--muted); }
.conditions summary { cursor: pointer; color: var(--text); font-size: 12px; }
.conditions li { margin: 4px 0 4px 16px; }

/* FAN-LANGUAGE RENDERING LAYER (FINAL POLISH ARC item 1, 2026-07-29,
   Matt ruling): plain English by default everywhere in the app; the raw
   technical form (RULES.md citations, all-caps enum codes, "Show the
   rule" buttons) is written into the SAME markup at render time, just
   hidden — so flipping the toggle affects every message already on
   screen too, not only new ones. Pure CSS visibility switch, no re-
   render logic (web/ui-lib.js's dualHtml). */
body:not(.show-tech) .tech-detail { display: none; }
body.show-tech .plain-detail { display: none; }

/* FAN ROUND 4 item 4 — the verdict's fan sentence is the answer, so it
   carries the weight; the teaching line under it is the rule, in the
   glossary's register, and sits back a step. */
.verdict-answer { font-weight: 600; }
.verdict-teaching { margin-top: .25rem; opacity: .85; font-weight: 400; }
#tech-toggle[aria-pressed="true"] { color: var(--text); border-color: var(--muted); }

/* CAPOLOGIST FINALE item 8c (2026-08-02): default order (tech OFF)
   mirrors the pre-existing ROBOT ROUND THREE item 5 ruling — FAILS
   leads with prose, LEGAL/CANNOT_EVALUATE leads with tiers. Technical
   detail ON always leads with the trace + conditions instead, on every
   status — a power user wants the receipts before the narrative. Same
   "pure CSS switch, no re-render" discipline as the tech-detail toggle
   itself: this reorders already-rendered cards too, not just new ones. */
.verdict-body { display: flex; flex-direction: column; }
.verdict-body .verdict-prose { order: 2; }
.verdict-body .verdict-tiers { order: 1; }
.verdict-body[data-illegal="true"] .verdict-prose { order: 1; }
.verdict-body[data-illegal="true"] .verdict-tiers { order: 2; }
body.show-tech .verdict-body .verdict-prose { order: 2 !important; }
body.show-tech .verdict-body .verdict-tiers { order: 1 !important; }

/* GLOSSARY (ROUND FIVE FINDING #4g, 2026-08-01): a tap-to-define surface
   for CBA acronyms in engine output — dotted underline signals "there's
   more here" without looking like a hyperlink (nothing navigates away). */
.glossary-term {
  text-decoration: underline dotted; text-decoration-thickness: 1px;
  text-underline-offset: 2px; cursor: help;
}

/* VERDICT IDENTITY (FINAL POLISH ARC item 10, 2026-07-29, Matt ruling):
   sits ABOVE the Legality line on its own card — a compact, always-
   present subject line so the judgment below it is never mistaken for
   judging some other trade discussed earlier in a long session. */
.verdict-subject { font-size: 12px; color: var(--muted); margin-bottom: 6px; letter-spacing: .01em; }

/* SPOT-CHECK FINDINGS item 4c (2026-08-01): the deterministic
   "compiled accordingly" roster-resolution note — sits between the
   subject line and the Legality headline, same muted treatment as
   .verdict-subject (compile-time information, not judgment, not the
   verdict itself). */
.compile-note { font-size: 12px; color: var(--caution); margin-bottom: 6px; }

/* ROBOT ROUND TWO item 2 (2026-07-30): the compact "Verdict unchanged:
   ..." reference shown instead of the full panel on an opinion/off-
   topic turn — a bit more visual weight than .verdict-subject since it
   carries the actual verdict word, not just a supplementary label. */
.verdict-compact { font-size: 13px; color: var(--text); margin-bottom: 6px; font-weight: 600; }

/* ---------- War room ---------- */
.warroom { padding: 12px; }
.warroom-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.warroom-head h2 { font-family: var(--display); letter-spacing: 2px; }
.warroom-track {
  display: flex; gap: 12px; overflow-x: auto; scroll-snap-type: x mandatory;
  padding-bottom: 8px;
}
.room {
  scroll-snap-align: center; flex: 0 0 88%; max-width: 420px;
  background: var(--surface); border: 1px solid var(--line);
  border-top: 5px solid var(--team, #555); border-radius: 10px; padding: 14px;
}
.room h3 { font-family: var(--display); letter-spacing: 1px; font-size: 18px; }
.room .payroll { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; margin: 4px 0 10px; }
.room h5 { font-size: 11px; letter-spacing: 1px; color: var(--muted); margin-top: 10px; font-family: var(--display); }
.room ul { list-style: none; padding: 0; }
.room li { display: flex; justify-content: space-between; font-size: 13px; padding: 2px 0; }
.room .money { font-variant-numeric: tabular-nums; color: var(--muted); }
.room .judgment { font-size: 12px; color: var(--muted); border-top: 1px dashed var(--line); margin-top: 10px; padding-top: 8px; }

/* ---------- Share card ---------- */
.sharewrap { padding: 14px 12px calc(var(--dock-h, 76px) + 14px); }
.sharecard {
  background: radial-gradient(120% 140% at 20% 0%, #191b22 0%, #0e0f12 70%);
  border: 1px solid var(--line); border-radius: 12px; padding: 18px; overflow: hidden;
}
.sharecard .sc-top { display: flex; justify-content: space-between; align-items: baseline; }
.sharecard .sc-brand { font-family: var(--display); letter-spacing: 3px; font-size: 12px; color: var(--muted); }
.sharecard .sc-badge {
  font-family: var(--display); font-weight: 800; font-size: 30px; letter-spacing: 3px;
}
.sharecard[data-status="legal"] .sc-badge { color: var(--legal); }
.sharecard[data-status="illegal"] .sc-badge { color: var(--fails); }
.sharecard[data-status="cannot_evaluate"] .sc-badge { color: var(--caution); font-size: 20px; }
.sharecard .sc-teams { display: flex; gap: 12px; margin-top: 14px; flex-wrap: wrap; }
.sharecard .sc-team { flex: 1; min-width: 140px; border-left: 4px solid var(--team, #555); padding-left: 10px; }
.sharecard .sc-team h3 { font-family: var(--display); letter-spacing: 1px; }
.sharecard .sc-team li { display: flex; justify-content: space-between; font-size: 13px; list-style: none; }
.sharecard .sc-team .money { font-variant-numeric: tabular-nums; color: var(--muted); }
.sharecard .sc-verdict { margin-top: 14px; font-size: 14px; }
/* FAN ROUND 5 item 1 (P0) — the roster caveat on the exported card, above
   the verdict line: an artifact that leaves the product must carry the
   partial-roster disclosure on its own face. */
.sharecard .sc-caveat {
  margin-top: 14px; padding: 8px 10px;
  font-size: 12px; line-height: 1.45; color: var(--caution);
  border-left: 3px solid var(--caution); background: rgba(232, 193, 90, 0.07);
  border-radius: 0 6px 6px 0;
}
.sharecard .sc-meta { margin-top: 10px; font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
.sharecard details { margin-top: 10px; font-size: 12px; color: var(--muted); }
.sharecard details pre { white-space: pre-wrap; font-size: 11px; }
.share-actions { display: flex; justify-content: center; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.share-actions button.primary { border-color: var(--legal); color: var(--legal); font-weight: 600; }
/* PHASE W3 — permalink opt-in + minted-link output. */
.share-optin { display: flex; align-items: center; gap: 7px; justify-content: center; margin-top: 10px; font-size: 12px; color: var(--muted); }
.share-link-out { margin: 12px auto 0; max-width: 520px; padding: 12px 14px; border: 1px solid var(--line); border-radius: 10px; font-size: 13px; text-align: center; }
.share-link-out .sl-row { margin: 4px 0; word-break: break-all; }
.share-link-out .sl-row a { color: var(--accent, #8ab4ff); }
.share-link-out .sl-note { margin-top: 6px; font-size: 12px; color: var(--muted); }

/* ---------- PHASE W1: Scenario Tray ---------- */
.tray-count {
  display: inline-block; margin-left: 5px; font-size: 10px; font-variant-numeric: tabular-nums;
  background: var(--line); color: var(--text); border-radius: 8px; padding: 0 5px; line-height: 15px;
}
.tray { padding: 12px; }
.tray-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px; }
.tray-head h2 { font-family: var(--display); letter-spacing: 2px; }
.tray-sub { font-size: 12px; color: var(--muted); margin-bottom: 12px; }
/* WR item 14 — durability line: same muted scale as the sub, set apart with a
   subtle left rule so "how long is this kept" reads as its own fact. */
.tray-durability {
  font-size: 12px; color: var(--muted); margin: -4px 0 12px;
  padding-left: 10px; border-left: 2px solid var(--line);
}
/* HEADER/ACCOUNT AUDIT (2026-08-06) — the tray's Export-my-season button. */
.tray-export { margin: 0 0 10px; font-size: 12px; }
.tray-list { display: flex; flex-direction: column; gap: 8px; }
.tray-empty { font-size: 13px; color: var(--muted); padding: 16px 0; text-align: center; }
.tray-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 10px 12px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 8px; cursor: pointer;
}
/* CAP-NERD ROUND item 6c ruling (2026-08-02): "frozen history, visible
   staleness, one-tap fresh truth" — a scenario evaluated under an
   earlier server build. flex-basis: 100% forces it onto its own line
   below the row's normal content rather than squeezing between it. */
.tray-stale {
  flex-basis: 100%; font-size: 11px; color: var(--caution);
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.tray-stale button { font-size: 11px; padding: 3px 7px; }
#warroom-track .tray-stale { margin-bottom: 10px; }
.tray-row.selected { border-color: var(--legal); box-shadow: 0 0 0 1px var(--legal) inset; }
.tray-status { font-family: var(--display); font-size: 11px; letter-spacing: 1px; white-space: nowrap; }
.tray-row[data-status="legal"] .tray-status { color: var(--legal); }
.tray-row[data-status="illegal"] .tray-status { color: var(--fails); }
.tray-row[data-status="cannot_evaluate"] .tray-status { color: var(--caution); }
.tray-name { flex: 1; font-size: 13px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tray-note { font-size: 11px; color: var(--muted); display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tray-epoch { font-size: 10px; color: var(--muted); border: 1px solid var(--line); border-radius: 5px; padding: 1px 5px; white-space: nowrap; }
/* RE-RUN ROUND item 7 (2026-08-02): "timestamps ... surfaced on rows" —
   the row's own title attribute carries the exact local date/time. */
.tray-time { font-size: 10px; color: var(--muted); white-space: nowrap; }
.tray-row button { font-size: 11px; padding: 3px 7px; }
.tray-compare-check { display: flex; align-items: center; }
.tray-compare { margin-top: 16px; border-top: 1px solid var(--line); padding-top: 14px; }
.tray-compare-cols { display: flex; gap: 12px; flex-wrap: wrap; }
.tray-compare-col { flex: 1; min-width: 220px; border-left: 4px solid var(--team, #555); padding-left: 10px; }
.tray-compare-col h3 { font-family: var(--display); letter-spacing: 1px; font-size: 15px; }
.tray-compare-col .tray-compare-meta { font-size: 11px; color: var(--muted); margin-bottom: 6px; }
.tray-compare-col ul { list-style: none; padding: 0; margin: 6px 0; }
.tray-compare-col li { display: flex; justify-content: space-between; font-size: 12px; padding: 1px 0; }
.tray-compare-col .money { font-variant-numeric: tabular-nums; color: var(--muted); }
/* RE-RUN ROUND item 7: per-team distance-to-tax/apron columns. */
.tray-compare-distances { display: flex; flex-wrap: wrap; gap: 6px 10px; margin-bottom: 8px; }
.tray-compare-distance { font-size: 10px; color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ROBOT ROUND THREE item 1 (P0, 2026-07-30): non-blocking replacement
   for every native alert()/confirm()/prompt() in the app — see
   showActionPopover's own note in web/app.js. `position: fixed` + a
   dimmed backdrop, centered, small enough to read as a popover rather
   than a full panel.
   `:not([hidden])` guard: an unconditional `display: flex` here has the
   SAME specificity as the UA stylesheet's `[hidden] { display: none }`
   and would win the cascade — exactly the ROBOT ROUND TWO item 1 bug
   (`.feed`) recurring on a NEW element. Caught here by ui-check.ts's
   own click-responsiveness pass: a `position: fixed; inset: 0` overlay
   that's supposedly hidden but still rendered as `display: flex` covers
   the ENTIRE viewport and silently intercepts every click on the page
   — not a small cosmetic miss, a full click-blocking regression. */
.action-popover:not([hidden]) {
  position: fixed; inset: 0; z-index: 50;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.5);
  padding: 20px;
}
.action-popover-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: 10px;
  padding: 16px; max-width: 340px; width: 100%;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
  /* RE-RUN ROUND item 10: the new snapshot-provenance popover can list a
     dozen-plus sources — scrolls inside the card rather than overflowing
     the viewport; harmless for every existing short single-line use. */
  max-height: 80vh; overflow-y: auto;
}
.action-popover-card p {
  font-size: 14px; line-height: 1.4;
  /* RE-RUN ROUND item 10: preserves the \n-separated source list's line
     breaks; a plain single-line message is unaffected (no \n to collapse
     differently either way). */
  white-space: pre-line;
}
.action-popover-card input {
  width: 100%; margin-top: 10px; padding: 8px 10px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 6px;
  color: var(--text); font: inherit;
}
.action-popover-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }

/* PHASE W2 — account menu (signed-in): stacked actions, with the
   destructive Delete visually distinct (never mistakable for Sign out). */
.account-menu-actions { flex-direction: column; }
.account-menu-note { color: var(--muted); font-size: 12px; }
.account-delete {
  padding: 8px 12px; border-radius: 8px; font-size: 13px; cursor: pointer;
  color: var(--fails); background: transparent; border: 1px solid var(--fails);
}
.account-delete:hover, .account-delete:focus-visible { background: rgba(255,59,48,.12); }

/* ---------- Composer ---------- */
/* COMPOSER CLICK-DEAD-ZONE fix (FREE-TIER FIX ARC item 2, 2026-08-01):
   the fixed positioning lives on the DOCK (composer + build-stamp
   together), not on .composer alone — see index.html's footer-dock
   comment for why. The dock's own solid background (not just the
   composer's fade gradient) covers the strip behind build-stamp too, so
   nothing from the scrolled feed can show or click through underneath. */
.footer-dock {
  position: fixed; bottom: 0; left: 50%; transform: translateX(-50%);
  width: 100%; max-width: 760px;
  display: flex; flex-direction: column;
  background: var(--bg);
  padding-bottom: env(safe-area-inset-bottom);
}
.composer {
  position: relative; /* keeps .composer-hint's absolute positioning scoped to the input row, unchanged from before this element moved out of .footer-dock's fixed box */
  display: flex; gap: 8px; padding: 10px 12px;
  /* MOBILE COMPOSER ARC item 2: the textarea auto-grows — buttons pin to
     the row's bottom instead of stretching to match its height. */
  align-items: flex-end;
  background: linear-gradient(180deg, transparent, var(--bg) 30%);
}
/* MOBILE COMPOSER ARC item 2 (2026-08-05): the text box is a TEXTAREA
   now — auto-grown by JS (autosizeComposer) up to ~4 lines, then it
   scrolls internally. Radius softens from the pill once it can be
   multi-line; font stays inherited (textarea defaults to monospace). */
.composer textarea {
  flex: 1; background: var(--surface-2); color: var(--text);
  border: 1px solid #3a3d47; border-radius: 18px; padding: 11px 16px; font-size: 15px;
  caret-color: #6ea8ff;
  font-family: inherit; line-height: 1.4; resize: none; overflow-y: hidden;
  max-height: 132px; min-width: 0;
  transition: border-color 160ms ease, box-shadow 160ms ease, background 160ms ease;
}
.composer textarea::placeholder { color: var(--muted); }
.composer textarea:hover { border-color: #474b57; }
.composer textarea:focus {
  outline: none; background: var(--surface);
  border-color: #6ea8ff; box-shadow: 0 0 0 3px rgba(110, 168, 255, .22);
}
.composer-buttons { display: flex; gap: 8px; }
.composer button {
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--line); border-radius: 999px; padding: 0 18px;
  font-family: var(--display); letter-spacing: 1px;
}
.composer button[disabled] { opacity: .45; cursor: wait; }

/* First-visit hint (patch-13): one-time, dismissed forever once closed or
   once the user starts typing — see web/app.js dismissHint(). */
.composer-hint {
  position: absolute; bottom: calc(100% + 4px); left: 12px; right: 12px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 10px;
  padding: 8px 10px 8px 14px; font-size: 12.5px; color: var(--text);
  box-shadow: 0 -8px 24px rgba(0, 0, 0, .45);
  z-index: 41;
}
/* Scoped to :not([hidden]) — a bare `display: flex` on the class would
   outrank the UA stylesheet's `[hidden]{display:none}` (equal specificity,
   later source wins) and leave a dismissed hint invisibly intercepting
   clicks over the composer. Caught by scripts/ui-check.ts. */
.composer-hint:not([hidden]) {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.composer-hint button {
  background: none; border: none; color: var(--muted);
  font-size: 16px; line-height: 1; padding: 2px 6px; border-radius: 4px;
}
.composer-hint button:hover { color: var(--text); background: var(--surface); }

/* Build stamp (LIVE BUG follow-up, 2026-07-24): deliberately quiet — a
   diagnostic anchor for "which app.js am I actually running," not a UI
   feature — low-contrast, out of the tab order, tucked under the composer. */
.build-stamp {
  text-align: center; font-size: 10px; color: var(--muted);
  opacity: .5; padding: 4px 0 2px; user-select: text;
}
/* PHASE W4 — the quiet Plans link. Deliberately understated: muted, small,
   no accent, no pulse — discovery, never a nag. */
.plans-link {
  display: block; text-align: center; font-size: 10px;
  color: var(--muted); opacity: .55; text-decoration: none; padding-bottom: 2px;
}
.plans-link:hover, .plans-link:focus-visible { opacity: .9; text-decoration: underline; }

/* Restored-session marker (TRANSCRIPT REHYDRATION, 2026-07-24):
   deliberately subtle — a caption, not a chat bubble, so it never reads
   as coming from the user or the GM. Sits above the replayed turns. */
.restored-marker {
  text-align: center; font-size: 11px; color: var(--muted);
  padding: 10px 0 6px; letter-spacing: .02em;
}

/* Prune-visibility marker (STRANGER-READINESS PATCH, 2026-07-25, Matt
   ruling item 5): same subtle-caption treatment as .restored-marker
   above — a dashed top border sets it apart as a boundary, not a
   continuation of the conversation above it. */
.prune-marker {
  text-align: center; font-size: 11px; color: var(--muted);
  padding: 10px 0 6px; letter-spacing: .02em;
  border-top: 1px dashed var(--line); margin-top: 6px;
}

/* Thinking indicator (patch-6): the one allowed micro-motion outside the
   verdict — typing dots; static ellipsis under reduced motion. */
.dots { display: inline-flex; gap: 5px; padding: 4px 2px; }
.dots i { width: 7px; height: 7px; border-radius: 50%; background: var(--muted); display: block; }
@media (prefers-reduced-motion: no-preference) {
  .dots i { animation: dotpulse 1.1s infinite ease-in-out; }
  .dots i:nth-child(2) { animation-delay: .18s; }
  .dots i:nth-child(3) { animation-delay: .36s; }
  @keyframes dotpulse { 0%, 70%, 100% { opacity: .25; transform: translateY(0); } 35% { opacity: 1; transform: translateY(-3px); } }
}

/* HONEST WAIT CUES (FINAL POLISH ARC item 5, 2026-07-29, Matt ruling):
   the stage-truthful label leads; the dots are now a secondary motion
   cue beside it, not the whole indicator. */
.wait-cue { color: var(--muted); font-size: 13px; margin-right: 6px; }

/* Composer typeahead (patch-6) */
#typeahead {
  position: absolute; bottom: calc(100% + 4px); left: 12px; right: 12px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 10px;
  overflow: hidden; box-shadow: 0 -8px 24px rgba(0,0,0,.45); z-index: 40;
}
.ta-item {
  display: flex; justify-content: space-between; gap: 12px; width: 100%;
  background: none; border: none; border-bottom: 1px solid var(--line);
  color: var(--text); padding: 10px 14px; text-align: left; font-size: 14px;
}
.ta-item:last-child { border-bottom: none; }
.ta-item.active, .ta-item:hover { background: var(--surface-2); }
.ta-item .ta-meta { color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; font-size: 12px; }

@media (max-width: 420px) {
  .slip-team { min-width: 118px; }
  .room { flex-basis: 94%; }

  /* Mobile slip wrapping fix (patch-13): the default flex row (name left,
     money right, space-between) let a long name wrap mid-line while the
     money floated beside the wrapped fragment — "+ Trae / $45,999,660 /
     Young". Stacking name-then-amount keeps each pair visually together
     regardless of name length. Same fix, same root cause, in the share
     card (found via the 390px screenshot — "Trae / $45,999,660 / Young"
     reproduced there too; it renders the same slip data at an even
     narrower per-team width, two columns across 390px).
  */
  .slip-team li,
  .sharecard .sc-team li {
    flex-direction: column; align-items: flex-start; gap: 0;
    padding: 3px 0;
  }
  .slip-team li .money,
  .sharecard .sc-team li .money { font-size: 11px; }

  .example { padding: 12px 14px; min-height: 44px; }
  /* The stacked composer + 48px composer targets moved to the ≤600px
     phone block below (MOBILE REGRESSION 2026-09-04) — they were only
     ever here, so a 430px-wide phone kept the one-row slit. */
}

/* MOBILE VIEWPORT (rewritten 2026-08-09, HEADER STATUS CHIP — Option A,
   Matt ruling): the status chip IS the collapse now, at every width, so
   the three phone-only hacks this block used to carry (hidden wordmark,
   hidden freshness badge, verified stamp promoted to its own full-width
   row) are retired — the provenance surfaces live in the chip's panel on
   desktop and phone alike. What remains here is genuinely phone-only:
   thumb-sized touch targets and stacking rules for wide layouts. The
   flex-wrap above still guarantees no horizontal overflow at ANY width. */
@media (max-width: 600px) {
  .slip-brand { align-items: center; row-gap: 8px; }

  /* MOBILE COMPOSER ARC item 1 (2026-08-05, owner finding from a real
     phone: "the composer is barely visible"): the one-row layout put
     ~250px of lane buttons beside the input, squeezing the product's
     front door to a slit (input had flex:1 + min-width:0, so it absorbed
     ALL the crowding). Stacked instead: full-width text box on top, the
     two lane buttons as equal halves beneath — a real geometry pass, not
     another pad patch (the dock's height was already made honest by
     --dock-h in V8). 16px font kills iOS's focus auto-zoom — 15px inputs
     trigger it, which is its own hostility.
     MOBILE REGRESSION (2026-09-04, owner finding — "a slit again"): the
     arc's record said "at ≤600px the composer STACKS", but the rules
     lived in the max-width:420px block above, so any phone reporting a
     CSS width over 420 (iPhone Pro Max class at 430/440) never stacked
     and got the one-row slit back — measured: 390px input 366×48 in a
     column, 430px input 69×45 in a row. Signed-in state was the other
     hypothesis and is FALSIFIED by the same measurement (the account
     chrome only widens the header button, 51→143px; the dock is
     unaffected). Root fix: the stack lives at the phone breakpoint the
     design named; the signed-in 390/430 lane pins it. */
  .composer { flex-direction: column; gap: 6px; align-items: stretch; }
  /* flex: 0 0 auto — in a COLUMN flexbox the base `flex: 1` makes height
     the flex main size (basis 0, floored by min-height), which silently
     overrides autosizeComposer's measured height (caught by the 390px
     lane: style.height said 114px, the rect stayed 48). */
  .composer textarea { flex: 0 0 auto; width: 100%; font-size: 16px; }
  .composer-buttons { width: 100%; }
  .composer-buttons button { flex: 1; padding: 0 8px; }
  /* Thumb-sized composer targets (patch-13) — every phone width, not
     just ≤420. */
  .composer textarea, .composer button { min-height: 48px; }

  /* Thumb-sized header controls; the panel's own controls too. */
  .slip-brand > button, .status-panel .epoch-select, .status-panel > button, #account-btn { min-height: 40px; }

  /* Compare view: two 220px columns can't sit side by side on a phone —
     stack them full-width (each still scrolls its own long content). */
  .tray-compare-cols { flex-direction: column; }
  .tray-compare-col { min-width: 0; }

  /* Share card teams: stack full-width rather than force a min-width row. */
  .sharecard .sc-teams { flex-wrap: wrap; }
  .sharecard .sc-team { min-width: 0; flex-basis: 100%; }

  /* PHASE W3 minted-link output: never wider than the phone. */
  .share-link-out { max-width: 100%; }
}

/* ================= WRITER V7 PATCH (2026-08-05) ================= */

/* Item 1 — the explicit engine-only lane beside Send. Free actions wear
   the legal-green treatment (same family as the free Apply-fix buttons);
   Send keeps the neutral look, so the free/billed boundary is visible at
   the point of use (STANDING INVARIANT #10). */
.check-free {
  color: var(--legal); border-color: var(--legal) !important;
  background: rgba(25, 195, 125, 0.08);
  white-space: nowrap; font-size: 12px;
}
.check-free:hover { background: rgba(25, 195, 125, 0.16); }
.engine-free-line {
  color: var(--legal); font-size: 12px; font-weight: 600;
  margin-bottom: 4px;
}

/* CASUAL FAN ROUND item 10 — the banner leaves through a collapse, not
   a snap (content used to jump under the cursor when it vanished). */
.banner.leaving {
  opacity: 0; max-height: 0; padding-top: 0; padding-bottom: 0; margin-top: 0;
  overflow: hidden;
  transition: opacity 280ms ease, max-height 300ms ease, padding 300ms ease, margin 300ms ease;
}
.banner { max-height: 200px; }

/* FAN RE-RUN item 1 (face d): the ONE visible truth cue — every row used
   to wear a "current" pill (the EPOCH key!) while the real selection was
   an invisible border. The epoch pill now shows only for non-default
   epochs; the on-screen scenario wears this instead. */
.tray-onscreen {
  display: inline-block; font-size: 9px; font-weight: 700; letter-spacing: .08em;
  color: var(--legal); border: 1px solid var(--legal); border-radius: 999px;
  padding: 1px 7px; margin-right: 6px; text-transform: uppercase;
}

/* CASUAL FAN ROUND item 10 — the one-word kicker on slip salary figures. */
.money-kick { font-style: normal; font-size: 9px; color: var(--muted); margin-left: 4px; letter-spacing: .04em; text-transform: uppercase; }

/* CASUAL FAN ROUND item 10 — roster names are tappable (fill composer). */
.roster-player-name {
  background: none; border: none; padding: 0; color: var(--text); font-size: 13px;
  cursor: pointer; text-align: left; text-decoration: underline dotted; text-underline-offset: 3px;
}
.roster-player-name:hover { color: #8ab4ff; }

/* MOBILE COMPOSER ARC item 3 — the thinking bubble's Stop control. */
.stop-turn {
  margin-left: 10px; padding: 4px 14px; font-size: 12px; cursor: pointer;
  color: var(--fails); background: transparent;
  border: 1px solid var(--fails); border-radius: 999px;
  vertical-align: middle; min-height: 32px;
}
.stop-turn:hover, .stop-turn:focus-visible { background: rgba(255, 59, 48, .12); }

/* Item 8a — clarification correction chips (fill-not-send). */
.chip-row { margin-top: 6px; display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  background: var(--surface-2); color: var(--text);
  border: 1px solid #3a3d47; border-radius: 999px;
  padding: 4px 12px; font-size: 12px; cursor: pointer;
}
.chip:hover { border-color: #6ea8ff; }

/* Item 9a — the chat budget, visible inside panels that hide the header. */
.panel-quota {
  font-size: 12px; color: var(--muted);
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--surface); padding: 6px 10px; margin-bottom: 10px;
}

/* Item 6 — non-blocking toast for copy confirmations/failures.
   MOBILE COMPOSER ARC: anchored above the REAL dock height (--dock-h),
   not a hardcoded 86px sized for the one-row composer — the stacked
   mobile dock moved the text box up into the toast's band, where a
   showing toast (pointer-events:auto) covered it and ate taps (caught
   by main()'s click-dead-zone probe the moment the layout stacked). */
#toast {
  position: fixed; left: 50%; bottom: calc(var(--dock-h, 76px) + 10px); transform: translateX(-50%) translateY(8px);
  max-width: min(480px, calc(100vw - 32px));
  background: var(--surface-2); color: var(--text);
  border: 1px solid #3a3d47; border-radius: 10px;
  padding: 10px 14px; font-size: 13px; line-height: 1.45;
  opacity: 0; pointer-events: none; transition: opacity 160ms ease, transform 160ms ease;
  z-index: 60;
}
/* FAN ROUND 3 item 3 — the toast NEVER takes pointer events, even while
   showing: a fixed overlay with pointer-events:auto sat over the bottom
   band of the feed and silently ate glossary-term taps (and anything else)
   for its whole 6s life. It's a status readout, not a control — the timer
   dismisses it; taps pass through to the real UI beneath. */
#toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* Item 9b — War Room at desktop width: the swipe-carousel layout (88%-wide
   snap cards inside a 736px track) guaranteed horizontal clipping on a
   pointer device with no swipe gesture. Above phone width the cards WRAP
   into a grid instead — no horizontal scrolling, no snap fighting the
   edges; the carousel remains the phone layout below 601px. */
@media (min-width: 601px) {
  .warroom-track { flex-wrap: wrap; overflow-x: visible; scroll-snap-type: none; }
  .room { flex: 1 1 320px; max-width: none; min-width: 0; scroll-snap-align: none; }
}
/* Long player names must wrap inside their card, never widen it. */
.room li { flex-wrap: wrap; gap: 0 8px; min-width: 0; }

/* Item 8b — the free roster/cap-sheet browser. */
.rosterview { padding: 12px 12px calc(var(--dock-h, 76px) + 14px); }
.rosterview-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
.rosterview-head h2 { font-family: var(--display); letter-spacing: 2px; }
.rosterview-sub { font-size: 12.5px; color: var(--muted); margin-bottom: 10px; }
.rosterview-teams { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 6px; margin-bottom: 14px; }
.roster-team {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--line); border-left: 4px solid var(--team, #555);
  border-radius: 8px; padding: 6px 8px; font-size: 13px; font-weight: 700; cursor: pointer;
}
.roster-team:hover { background: var(--surface-2); }
.roster-team-name { font-size: 10px; font-weight: 400; color: var(--muted); }
/* WRITER V8 item 7 — the panel's player search. */
.rosterview-search {
  box-sizing: border-box; width: 100%;
  background: var(--surface-2); color: var(--text);
  border: 1px solid #3a3d47; border-radius: 999px; padding: 10px 16px;
  font-size: 15px; margin-bottom: 8px;
}
.rosterview-search::placeholder { color: var(--muted); }
.rosterview-search:focus { outline: none; border-color: #6ea8ff; box-shadow: 0 0 0 3px rgba(110, 168, 255, .22); }
.rosterview-search-results { margin-bottom: 10px; }
.roster-search-hit {
  display: flex; justify-content: space-between; flex-wrap: wrap; gap: 0 10px;
  box-sizing: border-box; width: 100%; text-align: left; cursor: pointer;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--line); border-radius: 8px;
  padding: 8px 12px; margin-bottom: 6px; font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.roster-search-hit:hover { background: var(--surface-2); }
.roster-search-hit span:first-child { font-weight: 700; overflow-wrap: anywhere; }
.roster-search-hit span:last-child { color: var(--muted); }
.roster-search-empty { color: var(--muted); font-size: 13px; margin: 0 0 10px; }
.rosterview-detail h3 { font-family: var(--display); letter-spacing: 1px; font-size: 18px; margin-bottom: 4px; }
.roster-meta { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; margin-bottom: 10px; }
.roster-cap-rows, .roster-players { list-style: none; padding: 0; margin: 0 0 12px; }
.roster-cap-rows li, .roster-players li {
  display: flex; justify-content: space-between; flex-wrap: wrap; gap: 0 8px;
  font-size: 13px; padding: 3px 0; border-bottom: 1px dashed var(--line);
  font-variant-numeric: tabular-nums;
}
.roster-cap-rows li span:first-child { color: var(--muted); }

@media (max-width: 600px) {
  /* MOBILE COMPOSER ARC item 1: the button is a full-width half of its
     own row now (not squeezed beside the input), so it can afford 12px. */
  .check-free { font-size: 12px; padding: 0 10px; min-height: 48px; }
}

/* FAN ROUND 4 item 1 (P0) — the incomplete-roster disclosure. Caution
   colour and a left rule, same visual language as every other honesty
   surface here (.tray-stale, .compile-note): a fact the reader must not
   skim past, not an error. It sits directly under the cap-sheet meta so
   the "9 players" count and its explanation read as one thought. */
.roster-incomplete {
  margin: 6px 0 10px; padding: 8px 10px;
  font-size: 12px; line-height: 1.45; color: var(--caution);
  border-left: 3px solid var(--caution); background: rgba(232, 193, 90, 0.07);
  border-radius: 0 6px 6px 0;
}
.roster-incomplete strong { display: block; margin-bottom: 3px; }

/* FAN ROUND 4 item 7 — the share card's stale-vintage line, quieter than
   the card's own meta so it reads as a footnote, not a second verdict. */
.sc-stale { color: var(--caution); }

/* FAN ROUND 4 item 8 — a chat-costing button at zero. Muted rather than
   disabled: it still explains itself on tap/hover, which a disabled
   control cannot do. */
.gm-ask-spent { opacity: 0.65; }

/* STREAMING PHASE 1 (2026-08-13) — the still-writing slot under the
   streamed verdict skeleton: the ruled line says the verdict above is
   final and engine-owned while the prose is opinion in progress. */
.stream-writing {
  margin-top: 8px; font-size: 12px; font-style: italic; color: var(--muted);
}

/* FAN ROUND 4 item 1 (P0) — the roster-completeness caveat on a verdict.
   Its own channel, above the compile notes: it qualifies the entire
   verdict rather than explaining one compile-time resolution. */
.roster-caveat {
  margin: 0 0 8px; padding: 8px 10px;
  font-size: 12px; line-height: 1.45; color: var(--caution);
  border-left: 3px solid var(--caution); background: rgba(232, 193, 90, 0.07);
  border-radius: 0 6px 6px 0;
}

/* ================= MOBILE LANDING PASS (2026-09-04, Matt owner review on a real phone) ================= */

/* Item 1 — THE COMPOSER LEADS. While #home shows, web/app.js moves the
   composer form (+ the chat counter) into #home-dock-slot, in flow under
   the subtitle. In flow it drops the dock's fade gradient and pins its
   width to the landing column; the first-visit hint sits above the box
   as a normal block instead of floating over the subtitle. */
.home-dock-slot { max-width: 520px; margin: 14px auto 0; }
.home-dock-slot .composer { padding: 0; background: none; gap: 8px; }
.home-dock-slot .composer-hint { position: static; margin-bottom: 8px; transform: none; }
.home-dock-slot .dock-meta { margin-top: 8px; }
/* The toast anchors above the fixed dock; on the landing the dock is
   just the footer strip, so keep the toast near the bottom edge. */
body.home-mode #toast { bottom: 24px; }
/* Landing rhythm: tighter top padding, the title first. */
.home { padding: 20px 16px 20px; }
.home h1 { margin-top: 0; }

/* Item 2 — the chat counter rides the dock (#dock-meta), centered under
   the buttons, muted, always visible. */
.dock-meta { display: flex; justify-content: center; align-items: center; gap: 8px; padding: 0 12px 8px; min-height: 22px; }
.dock-meta #gm-lock { margin: 0; white-space: nowrap; }
.dock-meta:empty { display: none; }

/* Item 2 — header to ONE row on phones: the wordmark text and the word
   "session" step aside (the TE mark and the ✓ chip carry the identity),
   Sign in sits beside Rosters (web/app.js), the account label is compact. */
@media (max-width: 600px) {
  .brand-name, .phone-hide { display: none; }
  .slip-brand { flex-wrap: nowrap; gap: 6px; overflow-x: auto; scrollbar-width: none; }
  .slip-brand::-webkit-scrollbar { display: none; }
  .slip-brand > button { white-space: nowrap; padding-left: 10px; padding-right: 10px; }
  .new-session { margin-left: auto; }
}

/* Item 6 — example prompts match the two lanes: a lane tag on each,
   centered at the landing column width. */
.examples { max-width: 520px; }
.example { display: flex; align-items: center; justify-content: space-between; gap: 10px; width: 100%; }
.example .example-text { flex: 1; text-align: left; }
.lane-tag {
  flex: none; font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  padding: 2px 8px; border-radius: 999px; border: 1px solid;
}
.lane-tag.free { color: var(--legal); border-color: var(--legal); }
.lane-tag.metered { color: var(--accent, #8ab4ff); border-color: var(--accent, #8ab4ff); }

/* Item 5 — ONE card above the fold: the free card stays; the metered
   card folds behind "How it works" (collapse, never delete). */
.home-caps { max-width: 520px; flex-direction: column; }
.home-cap-free { flex: none; }
.home-how { width: 100%; }
.home-how summary {
  cursor: pointer; list-style: none; color: var(--muted); font-size: 12px;
  border: 1px solid var(--line); border-radius: 999px; padding: 6px 12px; display: inline-block;
}
.home-how summary::-webkit-details-marker { display: none; }
.home-how summary::before { content: "▸ "; }
.home-how[open] summary::before { content: "▾ "; }
.home-how[open] summary { margin-bottom: 8px; }
.home-how .home-cap { margin-top: 0; }

/* Item 4 — two fan-voice lines, then the tagline on its own line. */
.home-scope { max-width: 520px; text-align: center; margin-top: 18px; }
.home-tagline { display: block; margin-top: 8px; color: var(--text, #e6e6e6); font-weight: 600; letter-spacing: .02em; }

/* Item 7 — BUTTONS EQUAL: same height and font at every width; neither
   label wraps taller than its sibling. The free button is the funnel and
   wears the filled treatment. */
.composer-buttons button {
  min-height: 44px; font-size: 12px; line-height: 1.1; white-space: nowrap;
  display: inline-flex; align-items: center; justify-content: center;
}
.composer button.check-free {
  background: var(--legal); color: #0b1a12; border-color: var(--legal) !important;
  font-weight: 700;
}
.composer button.check-free:hover, .composer button.check-free:focus-visible { background: #22d98a; }
@media (max-width: 600px) {
  .composer-buttons button { min-height: 48px; font-size: 12px; padding: 0 8px; }
}
/* Item 2 follow-through (measured at 390: the row overflowed by 3-21px):
   tighter header chrome on phones so six controls fit one row; the TE
   mark steps aside on the narrowest phones. */
@media (max-width: 600px) {
  .slip-brand { gap: 4px; }
  .slip-brand > button { padding-left: 8px; padding-right: 8px; font-size: 11px; }
  .status-chip { padding-left: 8px; padding-right: 8px; }
}
@media (max-width: 380px) {
  .brand-mark { display: none; }
}
/* Landing mode: the fixed footer strip (build stamp · Plans) still sits
   at the bottom, so the landing's last lines need clearance under it. */
body.home-mode #home { padding-bottom: calc(var(--dock-h, 37px) + 16px); }
/* Item 7 exception: the ruled zero-quota relabel ("Ask the GM (0 left —
   back in ~Nh, HH:MM your time)") is long by design and may wrap; the
   row stretches BOTH buttons to the same height, so the sibling never
   ends up shorter, and nothing overflows the phone. */
.composer-buttons { align-items: stretch; }
.composer-buttons button[data-zero] { white-space: normal; line-height: 1.2; padding-top: 4px; padding-bottom: 4px; }

/* ---------- MOBILE LANDING PASS 2 (Matt on-device review, 2026-09-04) ---------- */
/* Item 1 — expanders must be collapsed AT REST in every engine. The
   on-device report ("closed chevron, content rendered beneath") did not
   reproduce in headless Chromium or WebKit 26.5: both hide closed
   <details> content through the UA shadow slot. What both expanders DID
   rely on was that UA behaviour alone, while carrying author `display`
   rules on the very nodes it hides (`.recent details` was inline-block;
   `.recent-item` is display:block) — the exact shape older WebKit builds
   are known to leak through, since any author display wins over the UA
   sheet. Collapsed-at-rest is now author-owned: closed content has NO
   box, so it can't render in any engine, and the lane pins that. */
/* (PASS 3 retired the <details> rule that lived here: the expanders are no
   longer <details>, and a closed-child rule would hide the new toggle.) */
.recent details { display: block; }
.recent summary { display: inline-block; }
/* A hair of air between the last expander and the explainer, so the
   paragraph never reads as the expander's body. */
.home-scope { margin-top: 22px; }

/* Item 2 — body text left-aligned; only the tagline closer stays centered
   and bold. */
.home-scope { text-align: left; }
.home-tagline { text-align: center; font-weight: 700; margin-top: 12px; }

/* Item 4 — the "New session started" line is a session log, not a
   conversation. On a fresh landing (body.home-mode) it stays out of sight;
   the moment a real exchange exists (#home hides, home-mode drops) it
   shows above that exchange. `#feed` is display:flex, so a class rule
   with higher specificity is the honest way to hide one child. */
body.home-mode #feed .msg.session-note { display: none; }

/* Item 5 — no header control may wrap ("Tray 0" on a 360px phone); the
   healthy chip is the season alone (web/app.js updateStatusChip). */
.slip-brand button { white-space: nowrap; }

/* ---------- MOBILE LANDING PASS 3 (Matt on-device review, 2026-09-04) ---------- */
/* Item 1 — the expanders are no longer <details> (Samsung Internet, a
   real-user engine Playwright cannot run, rendered the closed body under a
   closed chevron on a Galaxy S25+). State lives in aria-expanded; the body
   is closed by the hidden attribute AND an inline display:none set by
   web/app.js, which no stylesheet — ours, the UA's, or Samsung's — can
   override. These rules only style the toggle and the open body. */
.expander-toggle {
  cursor: pointer; color: var(--muted); font-size: 12px; background: none;
  border: 1px solid var(--line); border-radius: 999px; padding: 6px 12px; display: inline-block;
  font-family: inherit;
}
.expander-toggle:hover { background: var(--surface); }
.expander-body[hidden] { display: none !important; }
.expander.open .expander-body { margin-top: 8px; }
.home-how .expander-body .home-cap { margin-top: 0; }
.recent .expander-toggle { padding: 4px 12px; }

/* Item 2 — no dead zone under "Recent (n)": on the landing the feed is
   empty (or holds only the hidden session log) yet still laid out its
   dock clearance, on top of #home's own — 118 CSS px (~350 device px on a
   3x phone) of nothing above the fixed footer. One clearance, on #home. */
body.home-mode #feed { padding: 0; }
body.home-mode #home { padding-bottom: calc(var(--dock-h, 37px) + 8px); }

/* Item 3 — the Plans link rides the chats-left pill as one joined pill. */
.dock-meta.has-plans { gap: 0; }
.dock-meta.has-plans #gm-lock { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.pill-plans {
  font-size: 11px; padding: 2px 10px; min-height: 22px; display: inline-flex; align-items: center;
  border: 1px solid var(--line); border-left: none; border-radius: 0 999px 999px 0;
  color: var(--accent, #8ab4ff); text-decoration: none; background: var(--surface); white-space: nowrap;
}
.pill-plans:hover { background: var(--surface-2); }
.pill-plans[hidden] { display: none !important; }

/* ---------- DATA INTEGRITY ARC B5 — report a row (2026-09-04) ---------- */
.row-report {
  background: none; border: 1px solid var(--line); color: var(--muted); border-radius: 999px;
  font-size: 11px; line-height: 1; padding: 0 7px; margin-left: 6px; min-height: 22px; min-width: 26px;
  cursor: pointer; vertical-align: middle; font-family: inherit;
}
.row-report:hover { color: var(--caution, #e8c15a); border-color: var(--caution, #e8c15a); }
.roster-players li { display: flex; align-items: center; gap: 6px; }
.roster-players li > span { margin-left: auto; }

/* ---------- SITE PAGES (2026-09-05) — quiet footer links, one row ---------- */
.site-links { display: flex; justify-content: center; gap: 12px; flex-wrap: wrap; padding-bottom: 2px; }
.site-links .plans-link { display: inline-block; padding-bottom: 0; }
.account-menu-legal a { color: var(--muted); }

/* Owner ruling 2026-09-06 — the cookie one-liner: landing only, quiet. */
.cookie-note { display: none; }
body.home-mode .cookie-note { display: block; }
