/* The web companion — one visual system for the public entrance and
   the private player workspace. Static, framework-free, phone-first.
   Semantics (results, tints, type/space/radius scales) come from the
   shared layer: tokens.css loads BEFORE this file on every page. */

:root {
  /* ---- IDENTITY: the warmmono skin — palette E from the 2026-08-08 lab
          (Nick's pick): near-monochrome warm ground carrying the board's
          gold as the single accent, so the warmth arrives with the board
          art and the chrome agrees with it. (parity Tier 3.2 — the ONE
          block a rebrand or reskin swaps on this surface) ---- */
  --void: #08090a;
  --bg: #0d0f11;
  --surface: #15181b;
  --surface-2: #1c2024;
  --surface-3: #25292e;
  --ink: #f4f2ee;
  --muted: #9c9a95;
  --faint: #656360;
  --line: rgba(160, 150, 132, .16);
  --line-strong: rgba(160, 150, 132, .30);
  --ember: #d8a44a;
  --ember-soft: #f0cf8d;
  --ember-ink: #171004;
  --accent-ink: var(--ember-ink);   /* the bridging token tokens.css's tour reads (tokens-shared) */
  --sunken: #0a101c;   /* the ground inside a field or a caption strip */
  --chrome: #060a13;   /* the L of rail + topbar — ONE ground, no seam between them (R-29) */
  --rail-w: 232px;
  --rail-w-min: 66px;  /* the folded rail: icons, no words */
  /* The RAISED ground — the web's half of the shared contract. tokens.css is
     byte-copied from the desktop and named --bg-1, which does not exist here,
     so the highlight toolbar painted onto nothing (Nick, 2026-08-18). */
  --raised: var(--surface-3);
  --raised-hi: var(--surface-2);   /* a raised thing's own buttons */
  --raised-ink: var(--ink);
  --arcane: #8fb3ae;
  --frost: #73e5f3;
  --accent: var(--ember);   /* the cross-surface identity contract */
  --copy-trial: #EA923E;    /* Nick's 2026-08-13 trial color for the deck copy action */
  --shadow: 0 30px 80px rgba(0, 0, 0, .46), 0 0 45px color-mix(in srgb, var(--ember) 8%, transparent);

  /* aliases onto the shared scales (tokens.css) */
  --radius: var(--r-2);
  --radius-sm: var(--r-1);
}

:root[data-theme="slate"] {
  /* ---- SLATE (Nick 2026-08-26, R-27's Appearance tab): the cooler, neutral
          dark of the reference he pointed at, wearing the same gold. A second
          identity block, so the ratchet's ceiling rose by exactly its
          literals (check_parity check_token_discipline). ---- */
  --void: #010409;
  --bg: #0d1117;
  --surface: #161b22;
  --surface-2: #1c2128;
  --surface-3: #262c36;
  --ink: #f0f6fc;
  --muted: #8b949e;
  --faint: #6e7681;
  --line: rgba(139, 148, 158, .18);
  --line-strong: rgba(139, 148, 158, .32);
  --sunken: #0b0f14;
  --chrome: #090c12;
  --shadow: 0 30px 80px rgba(0, 0, 0, .5), 0 0 45px color-mix(in srgb, var(--ember) 6%, transparent);
}

* { box-sizing: border-box; margin: 0; }
html { scroll-behavior: smooth; background: var(--void); }
html, body { min-height: 100%; }
body {
  background: var(--bg);
  color: var(--ink);
  font: 15px/1.5 var(--font);
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}
button, input, textarea { font: inherit; }
button, a { -webkit-tap-highlight-color: transparent; }
button { border: 0; background: none; color: inherit; cursor: pointer; }
a { color: inherit; text-decoration: none; }
[hidden] { display: none !important; }
.view { min-height: 100dvh; }

/* Shared identity --------------------------------------------------------- */
/* THE NAME IS THE LOGO (docs/rebrand-spec.md §1/§5) — no symbol, no mark, no
   container. The four-bar glyph that used to sit beside the wordmark was the
   last symbol on the page; it is DELETED, not hidden. §5 is explicit and
   holds NO exception — nothing is pre-approved to take its place (the old
   named hatch was struck 2026-08-15). The generic ascending swoosh in
   particular was measured, disliked and dropped.
   The wordmark is WEIGHT, not colour (Nick, 2026-08-11: "we dont need color
   break.. OPG bold en not bold AI bold"): the <b> runs are heavy, the bare
   middle run is thin. Same three runs the DESKTOP renders, from the same
   brand.js fields, so the two surfaces cannot drift — check_parity pins both.
   The thin run is exactly the letters that otherwise make the name read as
   another company's. */
.brand { display: inline-flex; align-items: center; gap: 8px; color: var(--ink); letter-spacing: -.02em; }
.brand > .brand-name { font-size: 20px; }
.brand.muted { color: #cbd3e5; }
.brand-name { font-weight: 300; }
.brand-name b { font-weight: 800; }
/* The stage word beside the wordmark (brand.js `stage`) — the word is pinned by
   check_parity, the treatment is Nick's. It sits OUTSIDE .brand-name on purpose:
   that span must stay the bare lockup, and the selectors above target it by
   class now (they were `span:last-child` from the symbol era — a sibling tag
   would have silently un-styled the name). */
.brand-stage { font-size: 11px; font-weight: 600; letter-spacing: .02em; color: var(--faint); white-space: nowrap; }
.button { display: inline-flex; align-items: center; justify-content: center; min-height: 48px; border-radius: 7px; padding: 0 22px; font-weight: 760; transition: transform .18s ease, background .18s ease, box-shadow .18s ease; }
.button:hover { transform: translateY(-1px); }
.button.primary { color: white; background: linear-gradient(135deg, color-mix(in srgb, var(--ember) 84%, black), color-mix(in srgb, var(--ember) 80%, white)); box-shadow: 0 9px 28px color-mix(in srgb, var(--ember) 27%, transparent), inset 0 1px rgba(255,255,255,.16); }
.button.primary:hover { background: linear-gradient(135deg, var(--ember), var(--ember-soft)); box-shadow: 0 12px 35px color-mix(in srgb, var(--ember) 36%, transparent); }
.button:disabled { opacity: .48; cursor: wait; transform: none; }
.text-link, .text-button { color: #d9dde4; font-weight: 640; }
.text-link span { color: var(--ember); margin-left: 5px; }
.text-button { font-size: 13px; color: var(--muted); }
.text-button:hover, .text-link:hover { color: var(--ink); }
.err { padding: 10px 12px; color: #ffb4b7; background: color-mix(in srgb, var(--err) 9%, transparent); border: 1px solid color-mix(in srgb, var(--err) 20%, transparent); border-radius: 9px; font-size: 13px; }

/* Public entrance — one bounded decision surface, never a product tour. */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
/* The entrance SCROLLS (Nick, 2026-09-06, rulings R-56: "lets try it"). Until
   today it was one fixed 100dvh box with overflow hidden - one screen, never
   a tour (R-19). Now it is a document: the FIRST screen is the pitch, centred
   in what the nav leaves, with the top edge of the proof frame showing under
   it (product-first survives: the product is visible before anyone moves);
   the frame gets its whole width on the next screen, the blocks under it
   keep their sentences at every height, and the footer closes the page.
   Still one decision - nothing new to click appears below. --nav-h is the
   nav's height at each breakpoint (the pitch's first-screen budget reads
   it); overflow: clip keeps the backdrop disc's overhang from widening the
   page without making this box a scroller. */
.public-view {
  --nav-h: 68px;
  position: relative;
  min-height: 100dvh;
  overflow: clip;
  display: grid;
  grid-template-rows: var(--nav-h) auto auto;
  background: var(--void);
}
.public-view::before {
  content: "";
  position: absolute;
  z-index: 0;
  top: -48vmin;
  left: 30%;
  width: 88vmin;
  height: 88vmin;
  border: 1px solid color-mix(in srgb, var(--ember) 14%, transparent);
  border-radius: 50%;
  background: radial-gradient(circle at 50% 55%, color-mix(in srgb, var(--arcane) 4%, transparent), transparent 58%);
  box-shadow: 0 0 150px color-mix(in srgb, var(--ember) 16%, transparent), inset 0 0 120px color-mix(in srgb, var(--arcane) 2.5%, transparent);
  pointer-events: none;
}
.public-view::after {
  content: "";
  position: absolute;
  z-index: 0;
  top: 0; left: 0; right: 0; height: 100dvh;
  opacity: .16;
  pointer-events: none;
  background-image: linear-gradient(rgba(124,144,188,.08) 1px, transparent 1px), linear-gradient(90deg, rgba(124,144,188,.08) 1px, transparent 1px);
  background-size: 72px 72px;
  mask-image: linear-gradient(to bottom, black, transparent 78%);
}
.public-nav, .public-stage, .public-footer { position: relative; z-index: 1; }
.public-nav {
  width: min(1240px, calc(100% - 40px));
  height: 68px;
  margin: 0 auto;
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.public-auth-actions { display: flex; align-items: center; gap: 7px; }
.nav-auth { min-height: 35px; padding: 0 14px; border: 1px solid var(--line-strong); border-radius: 6px; color: var(--ink); background: color-mix(in srgb, var(--surface-2) 46%, transparent); display: inline-flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 680; }
.nav-auth.primary { border-color: color-mix(in srgb, var(--ember) 42%, transparent); background: color-mix(in srgb, var(--ember) 16%, var(--surface)); color: var(--ember-soft); }
.nav-auth:hover { border-color: color-mix(in srgb, var(--ember) 45%, var(--line)); }
/* One column (R-55) in a scrolling document (R-56): the stage carries the
   pitch and then the proof, and nothing about it is sized to a screen - the
   pitch sizes ITSELF to the first screen below. */
.public-stage {
  width: min(1240px, calc(100% - 40px));
  margin: 0 auto;
  padding: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto;
  justify-items: center;
  gap: 0;
}
/* Centered (Nick, 2026-09-06: "center it"): one focal point, read top to
   bottom - the face and the line, the lede, the one CTA, then the proof
   under it as the second beat. The pitch is centered text at EVERY width;
   the one-column desktop stage is the min-width block further down. */
/* The first screen: the pitch is centred in what the nav leaves, less the
   PEEK - the top of the proof frame that shows under it so a visitor sees
   the product before scrolling and knows there is more. Below the fold
   nothing is budgeted; the page simply continues. */
.public-pitch { --peek: clamp(96px, 16dvh, 176px); width: 100%; min-width: 0; min-height: calc(100dvh - var(--nav-h) - var(--peek)); display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; padding: clamp(20px, 4vh, 48px) 0 clamp(28px, 5vh, 56px); }
/* Display face (Nick 2026-08-13): Hearthstone's own stack via
   --font-display (tokens.css). The Inter-era negative tracking is eased on
   these slots — a serif set that tight collides. The column is narrower than
   it was before the product-first pass (the frame beside it got the width),
   so the lockup + "your AI" sits on line one and "gaming companion" on two. */
/* The largest text is the brand's own sans (Nick, 2026-09-06: "font type i
   dont like anymore for the largest text") - Inter Tight, the variable face
   tokens.css already serves, at a display weight with the tracking pulled in
   the way a grotesque wants at this size. The serif display stack stays on
   the smaller display slots. */
.public-pitch h1 { max-width: 20ch; margin: 0 auto clamp(12px, 2.2vh, 20px); font: 800 clamp(36px, 3.5vw, 52px)/1.0 var(--font); letter-spacing: -.035em; text-wrap: balance; }
.public-pitch h1 em { color: var(--ember-soft); font-style: normal; font-weight: inherit; text-shadow: 0 0 34px color-mix(in srgb, var(--ember) 18%, transparent); }
/* The headline's OP: md.js's real face and NOTHING else — R-50 (2026-09-01)
   retired the letters beside it, so the face stands at headline scale: a
   1.3em box is ~1em of disc (the disc is 22/29 of the box), a hair over the
   caps. One dial, Nick's. The face inherits
   the em's ember through currentColor, so it needs no colour of its own.
   Sized in `em` so it tracks every h1 clamp below instead of being re-pinned
   at each breakpoint — the same override .panel-mark-face already uses. The
   nowrap keeps face and word on one line; only the sentence after them wraps. */
.hero-op { display: inline-flex; align-items: center; gap: .18em; white-space: nowrap; vertical-align: -.16em; }
.hero-op-face { display: inline-flex; align-items: center; }
.hero-op-face .op-face { display: inline-flex; }
.hero-op-face .op-face-svg { display: block; width: 1.3em; height: 1.3em; }
/* The footer mission line. Its face is the h1's engine at footer scale -
   the container is 9px, so 1.25em lands ~11px: present, never a logo. */
.footer-mission { display: block; margin: 0 0 3px; color: var(--muted); }
.footer-op-face { display: inline-flex; align-items: center; vertical-align: -.2em; }
.footer-op-face .op-face { display: inline-flex; }
.footer-op-face .op-face-svg { display: block; width: 1.25em; height: 1.25em; }
.hero-lede { max-width: 46ch; margin-inline: auto; color: color-mix(in srgb, var(--ink) 70%, var(--muted)); font-size: clamp(15px, 1.25vw, 18px); line-height: 1.55; }
/* free-lane 10.3: the public download, on the entrance. Sized to STAY inside
   100dvh at every shape the web rig drives - the entrance is one decision
   surface, and a CTA that pushes it into document scroll breaks that law
   rather than decorating it. Both breakpoints below tighten it with the lede. */
.hero-get { display: flex; align-items: center; justify-content: center; gap: 14px; flex-wrap: wrap; margin: clamp(18px, 3vh, 28px) 0 0; }
.hero-get-btn { min-height: 44px; padding: 0 22px; font-size: 15px; text-decoration: none; }

/* The proof slot: real product UI that slides (2026-08-21 Nick, reshaped
   2026-08-22). The entrance still has to fit 100dvh with no document scrolling
   at every shape, so the frame is the image's own 16:10 CAPPED by a dvh
   clamp and never the image's own size; under the cap `contain` letterboxes
   on the frame's ground, which is the app's ground, so a bar is invisible.
   The pictures are 1800x1125 (scripts/build_public_screens.py), 2x of the
   widest this frame gets, so they stay crisp on a retina display. */
/* The second beat. The frame keeps its 16:10 and takes the column, capped so
   the whole picture fits one screen once scrolled to; the blocks under it
   are exactly as wide as the picture because they share this box. */
.public-tiles { --frame-cap: calc(100dvh - 96px); width: min(100%, calc(var(--frame-cap) * 1.6)); min-width: 0; display: flex; flex-direction: column; gap: clamp(12px, 2vh, 20px); }
.tiles-frame { position: relative; overflow: hidden; aspect-ratio: 16 / 10; max-height: var(--frame-cap); border: 1px solid var(--line-strong); border-radius: 12px; background: var(--bg); box-shadow: var(--shadow); }
.tiles-frame::after { content: ""; position: absolute; inset: 0; pointer-events: none; border-radius: 12px; box-shadow: inset 0 1px rgba(255,255,255,.04); }
.tiles-track { display: flex; height: 100%; margin: 0; padding: 0; list-style: none; transition: transform .5s cubic-bezier(.4, 0, .2, 1); }
.tile { flex: 0 0 100%; min-width: 0; height: 100%; }
.tile img { display: block; width: 100%; height: 100%; object-fit: contain; user-select: none; -webkit-user-drag: none; }
/* THE CONTROLS. Nick, 2026-08-21: "not being able to click next is at tiny
   bubble on bottom." The arrows are the verbs ON the picture, 44px — the
   floor a finger needs — and the feature blocks under the frame are the
   other way in. Both are pinned by check_web_auth so neither can shrink. */
.tiles-arrow {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
  width: 44px; height: 44px; display: grid; place-items: center;
  border: 1px solid var(--line-strong); border-radius: 999px;
  background: color-mix(in srgb, var(--void) 70%, transparent);
  color: var(--ink); cursor: pointer;
  transition: background .18s, border-color .18s, color .18s;
}
.tiles-arrow:hover { background: var(--surface-2); border-color: var(--accent); color: var(--accent); }
.tiles-arrow .icon { width: 20px; height: 20px; }
.tiles-prev { left: 10px; }
.tiles-next { right: 10px; }
/* The feature blocks (Nick 2026-08-22): the name of the thing and one
   sentence about it, three across, and each one is the tab that puts its
   picture in the frame. The lit one is the one you are looking at. Built
   from PUBLIC_TILES with the slide, so the words and the picture are one
   entry. Treatment is a starting point; the react pass is Nick's. */
.tiles-tabs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.tiles-tab { min-width: 0; min-height: 44px; padding: 10px 12px 11px; border: 1px solid var(--line); border-radius: 9px; background: color-mix(in srgb, var(--surface) 55%, transparent); color: inherit; text-align: left; display: grid; gap: 4px; align-content: start; cursor: pointer; transition: border-color .18s, background .18s; }
.tiles-tab b { font-size: 12.5px; font-weight: 720; letter-spacing: -.004em; color: var(--ink); }
.tiles-tab span { font-size: 10.5px; line-height: 1.42; color: var(--muted); }
.tiles-tab:hover { border-color: var(--line-strong); }
.tiles-tab[aria-selected="true"] { border-color: color-mix(in srgb, var(--ember) 55%, transparent); background: color-mix(in srgb, var(--ember) 9%, var(--surface)); }
.tiles-tab[aria-selected="true"] b { color: var(--ember-soft); }
.tiles-tab[aria-selected="true"] span { color: color-mix(in srgb, var(--ink) 70%, var(--muted)); }
@media (prefers-reduced-motion: reduce) { .tiles-track { transition: none; } }
/* The next-game poll (Nick, 2026-09-10): one question under the proof, before
   the footer. The chips are the tiles-tab family (same line, same ember
   select) so the page keeps one vocabulary for "a thing you press to choose";
   a pill rather than a card because a chip is a word, not a sentence. */
.public-poll { width: min(100%, 880px); margin: clamp(40px, 7vh, 88px) auto 0; text-align: center; display: grid; gap: clamp(14px, 2.2vh, 22px); justify-items: center; }
.public-poll h2 { margin: 0; max-width: 24ch; font: 800 clamp(22px, 2.2vw, 30px)/1.1 var(--font); letter-spacing: -.025em; text-wrap: balance; }
.poll-chips { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; }
.poll-chip { min-height: 40px; padding: 0 14px; border: 1px solid var(--line); border-radius: 999px; background: color-mix(in srgb, var(--surface) 55%, transparent); color: var(--ink); font-size: 13.5px; font-weight: 640; line-height: 1; transition: border-color .18s, background .18s, color .18s; }
.poll-chip:hover { border-color: var(--line-strong); }
.poll-chip[aria-pressed="true"] { border-color: color-mix(in srgb, var(--ember) 55%, transparent); background: color-mix(in srgb, var(--ember) 9%, var(--surface)); color: var(--ember-soft); }
.poll-chip[aria-pressed="true"]::before { content: "\2713\00a0"; }
.poll-act { display: flex; flex-direction: column; align-items: center; gap: 10px; margin: 0; }
.poll-cast { min-height: 44px; padding: 0 22px; font-size: 15px; }
.poll-note { color: color-mix(in srgb, var(--ink) 70%, var(--muted)); font-size: 13.5px; line-height: 1.4; }
@media (max-width: 760px) {
  .poll-chips { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .poll-chip { min-height: 44px; }
  .poll-act { width: 100%; }
  .poll-cast { width: 100%; }
}

/* THE FULL-SCREEN VIEWER (Nick, 2026-08-25: "let the hero be clickable so it
   then is full screen ... swipe right and left then swipe down naturally or x
   out"). The frame above is capped at 26dvh on a phone; these are 1800x1125
   captures of real product UI, so in the frame the thing they prove is too
   small to read. The picture is a button (.tile-open) and this is where it
   goes. */
.tile-open { display: block; width: 100%; height: 100%; padding: 0; border: 0; background: none; color: inherit; cursor: zoom-in; }
.tile-open:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }

.lightbox { width: 100vw; max-width: 100vw; height: 100dvh; max-height: 100dvh; margin: 0; padding: 0; border: 0; overflow: hidden; background: color-mix(in srgb, var(--void) 96%, transparent); color: var(--ink); transition: opacity .2s ease; }
.lightbox::backdrop { background: var(--void); }
/* While a finger is on it NOTHING animates — the picture has to sit exactly
   where the thumb is, or the drag feels like it is being argued with. */
.lightbox.dragging, .lightbox.dragging .lightbox-track, .lightbox.dragging .lightbox-stage { transition: none; }
.lightbox-stage { position: absolute; inset: 0; overflow: hidden; display: block; transition: transform .26s cubic-bezier(.4, 0, .2, 1);
  /* The browser must NOT claim the vertical drag for pull-to-refresh or
     overscroll, or swipe-down-to-dismiss never sees its own pointermove. */
  touch-action: none; }
.lightbox-track { display: flex; height: 100%; margin: 0; padding: 0; list-style: none; transition: transform .32s cubic-bezier(.4, 0, .2, 1); }
.lightbox-slide { position: relative; flex: 0 0 100%; min-width: 0; height: 100%; display: grid; place-items: center; padding: max(12px, env(safe-area-inset-top)) 12px 64px; box-sizing: border-box; }
.lightbox-slide img { display: block; max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; border-radius: 8px; user-select: none; -webkit-user-drag: none; }

/* Chrome sits ABOVE the stage and outside its drag transform, so the way out
   never slides away with the picture. */
.lightbox-close, .lightbox-arrow {
  position: absolute; z-index: 2; width: 44px; height: 44px; display: grid; place-items: center;
  border: 1px solid var(--line-strong); border-radius: 999px;
  background: color-mix(in srgb, var(--void) 74%, transparent);
  color: var(--ink); cursor: pointer;
  transition: background .18s, border-color .18s, color .18s;
}
.lightbox-close:hover, .lightbox-arrow:hover { background: var(--surface-2); border-color: var(--accent); color: var(--accent); }
.lightbox-close { top: max(12px, env(safe-area-inset-top)); right: max(12px, env(safe-area-inset-right)); }
.lightbox-close svg { width: 20px; height: 20px; }
.lightbox-arrow { top: 50%; transform: translateY(-50%); }
.lightbox-arrow .icon { width: 20px; height: 20px; }
.lightbox-prev { left: max(10px, env(safe-area-inset-left)); }
.lightbox-next { right: max(10px, env(safe-area-inset-right)); }
/* Which of them you are looking at. The SENTENCE stays on the page — this
   names the picture, it does not re-pitch it. */
.lightbox-cap { position: absolute; z-index: 2; left: 0; right: 0; bottom: max(14px, env(safe-area-inset-bottom)); margin: 0; text-align: center; font-size: 12px; font-weight: 660; letter-spacing: -.004em; color: var(--muted); pointer-events: none; }

@media (max-width: 760px) {
  /* A thumb reaches the picture and the X; the arrows are the desktop's way
     in and would sit under the swipe on a phone. */
  .lightbox-arrow { display: none; }
  .lightbox-slide { padding: max(52px, env(safe-area-inset-top)) 8px 54px; }
}
/* THE PICTURE TURNS, AND THIS IS THE WHOLE FEATURE — NICK'S TO VETO.
   MEASURED 2026-08-25, before this block existed: the frame on a 390x844
   phone renders the tile at 360x217, and a full-screen fit of the same 16:10
   capture renders it at 374x234. That is 1.04x — the viewer opened, and the
   product UI it exists to prove was exactly as unreadable as before, because
   the frame ALREADY spans the phone's width and a landscape picture on a
   portrait screen is bounded by width either way.

   The phone's long axis is the only unused room there is: laid out against
   the slide's height and turned a quarter, the same capture renders 598x374
   — 1.66x linear, 2.8x the area, and legible. The `orientation: portrait`
   guard is what makes it self-explaining rather than a trick: turn the phone
   and the rule drops, the picture is upright again, and it is still bigger
   than the frame. Deleting this block is a one-block revert to fit-in-place;
   the numbers above are what it costs. */
@media (max-width: 760px) and (orientation: portrait) {
  .lightbox-slide img {
    /* Laid out with the axes SWAPPED, then turned — the browser rotates after
       layout, so a plain max-width:100% would overflow instead of fitting.
       ABSOLUTE centring, not the grid's: laid out this way the box is WIDER
       than its column (598 in 374), and Chrome start-aligns an overflowing
       item instead of centring it — "safe" alignment, which exists to protect
       content off the start edge and here pushed 104px off the RIGHT one
       (measured 2026-08-25: x=120 on a 390 viewport, clipped). translate
       -50%/-50% puts the box's centre on the slide's centre whatever its
       size, and the rotation turns about that same centre. */
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%) rotate(90deg);
    max-width: calc(100dvh - 106px);
    max-height: calc(100vw - 16px);
  }
}
@media (prefers-reduced-motion: reduce) {
  .lightbox, .lightbox-track, .lightbox-stage { transition: none; }
}

.public-footer { width: min(1240px, calc(100% - 40px)); margin: clamp(56px, 10vh, 128px) auto 0; padding: 0 0 clamp(20px, 3vh, 32px); color: var(--faint); font-size: 9px; line-height: 1.35; text-align: center; /* centred with the entrance (Nick, 2026-09-06) */ }
.public-footer strong { color: var(--muted); font-weight: 650; }
.legal-links a { color: var(--muted); text-decoration: underline; text-underline-offset: 2px; }

body.modal-open { overflow: hidden; }
.auth-modal, .utility-modal { width: 100%; max-width: none; height: 100%; max-height: none; margin: 0; padding: 0; border: 0; background: transparent; color: var(--ink); }
.auth-modal::backdrop, .utility-modal::backdrop { background: color-mix(in srgb, var(--void) 78%, transparent); backdrop-filter: blur(8px); }
.modal-card { position: absolute; left: 50%; top: 50%; width: min(440px, calc(100% - 24px)); max-height: calc(100dvh - 24px); padding: clamp(18px, 3vw, 28px); overflow: auto; transform: translate(-50%, -50%); border: 1px solid var(--line-strong); border-radius: 12px; background: linear-gradient(145deg, color-mix(in srgb, var(--surface-2) 96%, var(--void)), var(--surface)); box-shadow: var(--shadow); }
.modal-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; margin-bottom: 20px; }
.modal-close { flex: none; min-height: 30px; padding: 0 9px; border: 1px solid var(--line); border-radius: 6px; color: var(--muted); font-size: 10px; }
.modal-close:hover { color: var(--ink); border-color: var(--line-strong); }
.modal-kicker { display: block; margin-bottom: 5px; color: var(--ember); font-size: 9px; font-weight: 740; letter-spacing: .1em; text-transform: uppercase; }
.access-copy h2 { margin: 6px 0 3px; font-family: var(--font-display); font-size: clamp(20px, 2vw, 27px); line-height: 1.05; letter-spacing: -.01em; }
.access-copy > p:last-child { color: var(--muted); font-size: 10px; line-height: 1.4; }
/* The account card: one mode at a time (app.js AUTH_MODES), fields swapped
   in place. Every mode must stay inside the entrance's 100dvh budget, which
   is why the lead line is optional and the mode switches are one text row. */
.login-card { min-width: 0; display: grid; gap: 7px; }
.auth-lead { color: var(--muted); font-size: 10px; line-height: 1.35; }
.auth-lead:empty { display: none; }
.token-wrap { min-width: 0; height: 40px; padding: 0 12px; border: 1px solid var(--line-strong); border-radius: 7px; background: var(--sunken); display: flex; align-items: center; gap: 8px; }
.token-wrap > span { color: var(--ember); }
.token-wrap input { width: 100%; min-width: 0; border: 0; outline: 0; color: var(--ink); background: transparent; letter-spacing: .05em; }
.token-wrap input::placeholder { color: #596372; letter-spacing: 0; }
.token-wrap input:-webkit-autofill { -webkit-text-fill-color: var(--ink); -webkit-box-shadow: 0 0 0 40px var(--sunken) inset; }
.auth-switch { display: flex; flex-wrap: wrap; gap: 3px 14px; }
.auth-switch button { color: var(--muted); font-size: 11px; font-weight: 640; }
.auth-switch button:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 2px; }
.login-card .button { min-height: 40px; padding: 0 15px; border-radius: 7px; font-size: 11px; white-space: nowrap; }
.login-card .err { padding: 9px 11px; font-size: 11px; }
/* The Google door (onboarding spec §9). Neutral chrome: the provider's mark
   carries the identity, the button carries none of ours. */
.auth-or { display: flex; align-items: center; gap: 10px; color: var(--muted); font-size: 10px; }
.auth-or::before, .auth-or::after { content: ""; flex: 1; border-top: 1px solid var(--line-strong); }
.button.provider { gap: 9px; color: var(--ink); background: var(--sunken); border: 1px solid var(--line-strong); font-weight: 640; }
.button.provider:hover { border-color: color-mix(in srgb, var(--line-strong) 55%, var(--ink)); }
.g-mark { width: 15px; height: 15px; flex: none; }
.capture-note { margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--line); color: #6e798b; font-size: 9px; line-height: 1.35; }
.capture-note strong { color: #aeb7c5; font-weight: 650; }
.capture-note .legal-links a { color: #8d9ab3; text-decoration: underline; text-underline-offset: 2px; }

/* Private workspace ------------------------------------------------------ */
.member-shell { min-height: 100dvh; background-color: var(--bg); background-image: linear-gradient(rgba(119,141,186,.025) 1px, transparent 1px), linear-gradient(90deg, rgba(119,141,186,.025) 1px, transparent 1px); background-size: 64px 64px; display: grid; grid-template-columns: var(--rail-w) minmax(0, 1fr); transition: grid-template-columns .2s ease; }
body.rail-collapsed .member-shell { grid-template-columns: var(--rail-w-min) minmax(0, 1fr); }
.app-rail { position: fixed; inset: 0 auto 0 0; z-index: 20; width: var(--rail-w); padding: 25px 15px 20px; background: var(--chrome); display: flex; flex-direction: column; transition: width .2s ease, padding .2s ease; }
.rail-brand { gap: 12px; padding: 0 10px 24px; }
.rail-brand > .brand-name { font-size: 20px; font-weight: 760; }
.rail-nav { display: flex; flex-direction: column; gap: 4px; }
.rail-link { width: 100%; min-height: 43px; padding: 0 11px; border-radius: 6px; display: grid; grid-template-columns: 25px 1fr auto; align-items: center; color: #7f8ba1; text-align: left; transition: background .16s, color .16s; }
.rail-link:hover { color: #c8cdd5; background: rgba(255,255,255,.025); }
/* operator-spec §4.1 (R-47): the allowlist's rail entry — hidden until the
   projection says operator (body.operator, set by renderShell) */
.op-only { display: none; }
body.operator .op-only { display: grid; }
/* the operator's page (operator-spec §4.4): a row is a fact, a source and a
   freshness — a first cut in the workspace's own tokens, Nick's to reshape */
.op-groups { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.op-group { display: grid; gap: 6px; padding: 16px 18px; border: 1px solid var(--line); border-radius: var(--radius); background: color-mix(in srgb, var(--surface) 92%, transparent); align-content: start; }
.op-group h2 { margin: 0; font: 760 18px/1.2 var(--font-display); letter-spacing: -.01em; }
/* R-53: the needs-you strip — a group header's type on a group's ground, above the grid */
.op-needs { margin: 0 0 14px; padding: 12px 18px; font: 760 18px/1.2 var(--font-display); letter-spacing: -.01em; border: 1px solid var(--line); border-radius: var(--radius); background: color-mix(in srgb, var(--surface) 92%, transparent); }
.op-needs.op-notread { color: var(--muted); }
.op-known { display: block; color: var(--muted); font-weight: 400; margin-top: 2px; }
.op-src { color: var(--muted); font-size: 11px; line-height: 1.4; }
.op-row { display: grid; grid-template-columns: minmax(120px, 40%) 1fr; gap: 10px; padding: 6px 0; border-top: 1px solid var(--line); font-size: 13px; }
.op-row span { color: var(--muted); }
.op-row b { font-weight: 650; color: var(--ink); overflow-wrap: anywhere; }
.op-row.op-notread b { color: var(--muted); font-style: italic; }
.op-row button { justify-self: start; padding: 2px 0; border: 0; background: none; font: inherit; font-size: 13px; font-weight: 650; color: var(--ember); cursor: pointer; text-align: left; }
.op-row button:hover { text-decoration: underline; }
.rail-link.active { color: var(--ink); background: linear-gradient(90deg, color-mix(in srgb, var(--ember) 19%, transparent), color-mix(in srgb, var(--ember) 5.5%, transparent)); box-shadow: inset 2px 0 var(--ember), inset 0 0 0 1px color-mix(in srgb, var(--ember) 11%, transparent); }
.rail-link span { color: #7d8795; font-size: 15px; }
.rail-link.active span { color: var(--ember-soft); }
.rail-link b { font-size: 13px; font-weight: 630; }
.rail-link i { min-width: 21px; padding: 2px 5px; border-radius: 999px; background: #222a37; color: #7f8a99; font-size: 9px; font-style: normal; text-align: center; }
/* Sync truth rides the rail's machine cluster (2026-08-13): same pill, same
   truth-or-nothing contract, no longer a topbar ornament. */
.rail-bottom { margin-top: auto; display: flex; flex-direction: column; }
.rail-intel { width: 100%; margin-top: 7px; display: flex; align-items: center; gap: 10px; padding: 11px; border: 1px solid var(--line); border-radius: 7px; background: #0d1422; text-align: left; }
.status-light { width: 7px; height: 7px; border-radius: 50%; background: var(--win); box-shadow: 0 0 9px color-mix(in srgb, var(--win) 65%, transparent); }
.status-light.bad { background: var(--loss); box-shadow: 0 0 9px color-mix(in srgb, var(--loss) 50%, transparent); }
.rail-intel b, .rail-intel small { display: block; }
.rail-intel b { font-size: 11px; font-weight: 650; }
.rail-intel small { margin-top: 2px; color: #616c7b; font-size: 9px; }
.app-stage { grid-column: 2; min-width: 0; min-height: 100dvh; }
.app-topbar { position: sticky; top: 0; z-index: 15; height: 68px; padding: 0 34px 0 22px; background: var(--chrome); display: flex; align-items: center; gap: 14px; }
/* THE FILLET (Nick 2026-08-26, rulings R-29): rail and topbar are one L of
   chrome, and the content meets it on a curve rather than a corner — a
   concave quarter-circle painted where the two used to butt with two
   borders. It hangs off the sticky topbar, so it holds while the page
   scrolls under it. */
.app-topbar::after { content: ""; position: absolute; left: 0; top: 100%; width: 20px; height: 20px; pointer-events: none; background: radial-gradient(circle at 100% 100%, transparent 19.4px, var(--chrome) 20px); }
.mobile-brand { display: none; }
.topbar-context b { display: block; }
.topbar-context b { margin-top: 2px; font-size: 13px; font-weight: 620; }
.topbar-actions { margin-left: auto; display: flex; align-items: center; gap: 10px; }
/* Sign-out on the phone: labeled words at the end of the games list (the
   top-right arrow icon read as navigation, not logout — Nick 2026-08-13).
   Desktop widths hide it; the rail's "Leave workspace" is that surface's. */
.mobile-signout { display: none; }
/* New chat on the phone, for the same reason and by the same rule: the rail
   carries it at desktop widths, so this one only exists below 760px. */
.mobile-newchat { display: none; }
.workspace-main { max-width: 1460px; margin: 0 auto; padding: 40px 42px 80px; }
.app-view { animation: viewIn .22s ease; }
@keyframes viewIn { from { opacity: 0; transform: translateY(4px); } }

.page-heading h1, .game-detail-head h1 { margin: 10px 0 8px; font: 760 clamp(31px, 3.3vw, 45px)/1.04 var(--font-display); letter-spacing: -.01em; }
.page-heading > div > p:last-child { max-width: 620px; color: var(--muted); }
.workspace-panel { background: color-mix(in srgb, var(--surface) 92%, transparent); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: 0 12px 35px rgba(0,0,0,.12); }
.panel-head { min-height: 70px; padding: 17px 19px; border-bottom: 1px solid var(--line); display: flex; align-items: center; justify-content: space-between; gap: 20px; }
.panel-head h2 { font-size: 19px; font-weight: 720; letter-spacing: -.025em; }
.game-stack { display: flex; flex-direction: column; }
.game-row { position: relative; width: 100%; min-height: 72px; padding: 12px 16px; border-bottom: 1px solid var(--line); display: grid; grid-template-columns: 38px 34px minmax(0,1fr) auto; gap: 13px; align-items: center; text-align: left; transition: background .15s; }
.game-row:last-child { border-bottom: 0; }
.game-row:hover { background: color-mix(in srgb, var(--ember) 5.5%, transparent); }
.game-row::after { content: "→"; position: absolute; right: 16px; opacity: 0; color: var(--ember); transform: translateX(-5px); transition: .16s; }
.game-row:hover::after { opacity: 1; transform: translateX(0); }
.game-row:hover .game-when { opacity: 0; }
.result-chip { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 10px; font-size: 12px; font-weight: 800; }
.result-chip.W { background: var(--win-tint); color: var(--win); }
.result-chip.L { background: var(--loss-tint); color: var(--loss); }
.result-chip.T, .result-chip.U { background: var(--surface-3); color: var(--muted); }
.game-main { min-width: 0; }
.game-opp { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 13px; font-weight: 650; }
.game-opp, .game-sub { display: block; }
/* the desktop rail's round hero-art portrait (md.js avatarEl — one element,
   both surfaces); the text fallback is the class initial when the CDN fails */
.game-row .avatar { width: 34px; height: 34px; border-radius: 50%; overflow: hidden; display: grid; place-items: center; background: var(--surface-3); color: var(--muted); font-size: 13px; font-weight: 700; text-transform: uppercase; }
.game-row .avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.game-sub { margin-top: 3px; color: #697484; font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.game-when { padding-right: 3px; color: #667181; font-size: 10px; transition: opacity .15s; }

/* the shared panel row (md.js:panelRowEl, read-spec A4) — the same anatomy
   the desktop paints: [mark] [title / subtitle] [time]. The result-chip
   column is gone with ruling 4; the result is a word in the subtitle now, so
   the grid drops from four columns to three. The .game-row rules above still
   style the LIVE view's own game strip, so they stay. */
.panel-row { position: relative; width: 100%; min-height: 72px; padding: 12px 16px; border-bottom: 1px solid var(--line); display: grid; grid-template-columns: 34px minmax(0,1fr) auto; gap: 13px; align-items: center; text-align: left; background: none; border-left: 0; border-right: 0; border-top: 0; font: inherit; color: inherit; cursor: pointer; transition: background .15s; }
.panel-row:last-child { border-bottom: 0; }
.panel-row:hover { background: color-mix(in srgb, var(--ember) 5.5%, transparent); }
.panel-row::after { content: "→"; position: absolute; right: 16px; opacity: 0; color: var(--ember); transform: translateX(-4px); transition: opacity .15s, transform .15s; }
.panel-row:hover::after { opacity: 1; transform: translateX(0); }
.panel-row:hover .panel-when { opacity: 0; }
.panel-row:focus-visible { outline: 2px solid var(--ember); outline-offset: -2px; }
.panel-row .avatar { width: 34px; height: 34px; border-radius: 50%; overflow: hidden; display: grid; place-items: center; }
.panel-row .avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.panel-mark { background: var(--surface-3); color: var(--muted); font-size: 15px; }
/* the lil guy's container (Nick 2026-08-20) — twin of theme.css's rule:
   the blooming OP face on no-game chat rows, chat-colored, avatar-sized */
.panel-mark-face { color: var(--ink); }
.panel-mark-face .op-face-svg { width: 26px; height: 26px; display: block; }
.panel-main { min-width: 0; }
.panel-title, .panel-sub { display: block; }
.panel-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 13px; font-weight: 650; }
/* derived, not copied: the two rules this replaced carried raw literals, and
   duplicating them is what the token ratchet exists to stop */
.panel-sub { margin-top: 3px; color: var(--muted); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.panel-when { padding-right: 3px; color: var(--muted); font-size: 10px; transition: opacity .15s; }

/* the room's entry (read-spec A3) — an action, not a nav link, so it reads
   as one: accented rather than borrowing .rail-link's resting state */
.rail-newchat { display: flex; align-items: center; gap: 10px; width: 100%; padding: 9px 11px; margin-bottom: 10px; border: 1px dashed color-mix(in srgb, var(--ember) 45%, transparent); border-radius: 9px; background: color-mix(in srgb, var(--ember) 7%, transparent); color: var(--ember); font: inherit; font-size: 13px; font-weight: 650; cursor: pointer; text-align: left; }
.rail-newchat:hover { background: color-mix(in srgb, var(--ember) 13%, transparent); }
.rail-newchat:focus-visible { outline: 2px solid var(--ember); outline-offset: 1px; }
.rail-newchat .icon { width: 16px; height: 16px; }

/* THE PLAN CARD (Nick 2026-08-26, rulings R-26): the account's current
   plan, where the sync pill stood. Solid on the accent so it reads apart
   from the translucent nav above it; a fresh account reads Free. Pressing it
   opens the Plans view — the same content area every rail entry opens into,
   so .active is the same state the nav links wear. */
/* R-37: the rail's Subscribe card — above the plan card, quieter than it */
.rail-nudge { display: flex; align-items: stretch; width: 100%; margin-bottom: 10px; border: 1px solid color-mix(in srgb, var(--ember) 60%, transparent); border-radius: 10px; overflow: hidden; }
.rail-nudge-go { flex: 1; display: grid; gap: 1px; padding: 8px 12px; min-width: 0; border: 0; background: none; font: inherit; text-align: left; cursor: pointer; color: var(--ink); }
.rail-nudge-go b { font-size: 14px; font-weight: 760; letter-spacing: -.01em; color: var(--ember); }
.rail-nudge-go small { font-size: 10px; font-weight: 600; opacity: .8; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rail-nudge-go:hover { background: color-mix(in srgb, var(--ember) 10%, transparent); }
.rail-nudge-x { flex: none; width: 28px; border: 0; background: none; font: inherit; cursor: pointer; color: var(--muted); font-size: 16px; line-height: 1; }
.rail-nudge-x:hover { color: var(--ink); background: color-mix(in srgb, var(--ember) 10%, transparent); }
body.rail-collapsed .rail-nudge { display: none; }
/* operator-spec §3.3 (R-47): the operator's switch — five positions, the
   pressed one filled; quieter than the plan card, like the Subscribe card */
.rail-drive { display: grid; gap: 4px; width: 100%; margin-bottom: 10px; padding: 8px 10px; border: 1px dashed color-mix(in srgb, var(--ember) 55%, transparent); border-radius: 10px; }
.rail-drive-kicker { font-size: 9px; font-weight: 740; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
.rail-drive-row { display: flex; gap: 3px; }
.rail-drive-row button { flex: 1; min-width: 0; padding: 4px 0; border: 1px solid var(--line); border-radius: 6px; background: none; font: inherit; font-size: 11px; font-weight: 700; color: var(--ink); cursor: pointer; }
.rail-drive-row button:hover { border-color: var(--ember); }
.rail-drive-row button[aria-pressed="true"] { background: var(--ember); border-color: var(--ember); color: var(--ember-ink); }
.rail-drive small { font-size: 10px; line-height: 1.35; color: var(--muted); }
body.rail-collapsed .rail-drive { display: none; }
/* R-37: the refusal (free-lane §7's web half) — one quiet centred line that
   IS the door, never the red error box; gone after five seconds */
.refusal { display: block; width: fit-content; margin: 8px auto; padding: 2px 4px; border: 0; background: none; font: inherit; font-size: 13px; letter-spacing: .2px; color: var(--ember); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; opacity: 1; transition: opacity .55s ease; }
.refusal:hover { filter: brightness(1.15); }
.refusal.gone { opacity: 0; pointer-events: none; }
.rail-plan { display: grid; gap: 1px; width: 100%; margin-bottom: 10px; padding: 10px 12px; border-radius: 10px; text-align: left; color: var(--ember-ink); background: linear-gradient(135deg, color-mix(in srgb, var(--ember) 86%, black), color-mix(in srgb, var(--ember) 72%, white)); box-shadow: inset 0 1px rgba(255,255,255,.22), 0 1px 3px rgba(0,0,0,.45); transition: filter .15s, transform .15s, box-shadow .15s; }
.rail-plan:hover { filter: brightness(1.07); }
.rail-plan:active { transform: scale(.985); }
.rail-plan.active { box-shadow: inset 0 1px rgba(255,255,255,.22), 0 0 0 2px var(--chrome), 0 0 0 3.5px color-mix(in srgb, var(--ember-soft) 70%, transparent); }
.rail-plan:focus-visible { outline: 2px solid var(--ember-soft); outline-offset: 2px; }
.rail-plan-kicker { font-size: 9px; font-weight: 740; letter-spacing: .1em; text-transform: uppercase; opacity: .72; }
.rail-plan b { font-size: 14px; font-weight: 760; letter-spacing: -.01em; }
.rail-plan small { font-size: 10px; font-weight: 600; opacity: .8; }
.rail-plan-short { display: none; font-size: 10px; font-weight: 800; letter-spacing: .02em; }

/* THE PROFILE AREA (Nick 2026-08-26, rulings R-25): the row is the person,
   the menu is what they can do. It opens UPWARD from the row (the row sits
   on the rail's floor, so up is the only way that stays on screen) and holds
   two standard verbs: Settings behind a gear, Log out behind the exit glyph
   in the loss color — nothing a person has to learn. */
.rail-profile-wrap { position: relative; }
.rail-profile { width: 100%; display: grid; grid-template-columns: 34px minmax(0, 1fr) 14px; gap: 10px; align-items: center; padding: 8px; border-radius: 9px; text-align: left; transition: background .15s; }
.rail-profile:hover, .rail-profile[aria-expanded="true"] { background: rgba(255,255,255,.045); }
.rail-profile:focus-visible { outline: 2px solid var(--ember); outline-offset: 1px; }
.rail-avatar { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: linear-gradient(135deg, var(--ember), var(--ember-soft)); color: var(--ember-ink); font-size: 14px; font-weight: 760; box-shadow: 0 0 0 2px var(--chrome), 0 0 0 3px var(--line-strong); }
.rail-profile-text { min-width: 0; }
.rail-profile-text b, .rail-profile-text small { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rail-profile-text b { font-size: 13px; font-weight: 650; }
.rail-profile-text small { margin-top: 1px; color: var(--muted); font-size: 10px; }
.rail-profile-text small:empty { display: none; }
/* the sprite is shared with the desktop (a new symbol there is an exe rebuild), so
   the chevron is the existing right-pointing one turned up; open turns it down */
.rail-profile-chev { width: 14px; height: 14px; color: var(--muted); transform: rotate(-90deg); transition: transform .18s; }
.rail-profile[aria-expanded="true"] .rail-profile-chev { transform: rotate(90deg); }
.rail-menu { position: absolute; left: 0; right: 0; bottom: calc(100% + 6px); z-index: 2; display: grid; gap: 2px; padding: 6px; border: 1px solid var(--line-strong); border-radius: 10px; background: var(--surface); box-shadow: 0 16px 40px rgba(0,0,0,.5); animation: menuIn .14s ease; }
@keyframes menuIn { from { opacity: 0; transform: translateY(4px); } }
.rail-menu-item { display: flex; align-items: center; gap: 10px; width: 100%; padding: 9px 10px; border-radius: 7px; color: var(--ink); font-size: 13px; text-align: left; }
.rail-menu-item .icon { width: 16px; height: 16px; color: var(--muted); }
.rail-menu-item:hover, .rail-menu-item:focus-visible { background: rgba(255,255,255,.055); outline: 0; }
.rail-menu-item.danger, .rail-menu-item.danger .icon { color: var(--loss); }
.rail-menu-item.danger:hover { background: color-mix(in srgb, var(--loss) 10%, transparent); }

/* THE FOLD (Nick 2026-08-26, rulings R-29): the rail keeps its icons and
   loses its words; the choice is remembered per browser (app.js RAIL_KEY).
   Nothing here is a second nav — the same buttons, narrower, with the
   label moved into each button's title. */
.rail-toggle { flex: none; width: 34px; height: 34px; display: grid; place-items: center; border-radius: 8px; color: var(--muted); }
.rail-toggle:hover { color: var(--ink); background: rgba(255,255,255,.05); }
.rail-toggle .icon { width: 18px; height: 18px; }
.brand-mini { display: none; font-size: 20px; font-weight: 760; letter-spacing: -.02em; }
body.rail-collapsed .app-rail { width: var(--rail-w-min); padding: 25px 9px 16px; }
body.rail-collapsed .rail-brand { justify-content: center; padding: 0 0 22px; }
body.rail-collapsed .rail-brand .brand-name, body.rail-collapsed .rail-brand .brand-stage { display: none; }
body.rail-collapsed .brand-mini { display: inline; }
body.rail-collapsed .rail-newchat { justify-content: center; padding: 9px 0; }
body.rail-collapsed .rail-newchat b, body.rail-collapsed .rail-link b, body.rail-collapsed .rail-link i:not(.live-dot) { display: none; }
body.rail-collapsed .rail-link { position: relative; grid-template-columns: 1fr; justify-items: center; padding: 0; }
body.rail-collapsed .rail-link .live-dot { position: absolute; top: 5px; right: 7px; }
body.rail-collapsed .rail-plan { place-items: center; padding: 9px 4px; }
body.rail-collapsed .rail-plan-kicker, body.rail-collapsed .rail-plan b, body.rail-collapsed .rail-plan small { display: none; }
body.rail-collapsed .rail-plan-short { display: block; }
body.rail-collapsed .rail-profile { grid-template-columns: 34px; justify-content: center; padding: 6px; }
body.rail-collapsed .rail-profile-text, body.rail-collapsed .rail-profile-chev { display: none; }
body.rail-collapsed .rail-menu { right: auto; width: 190px; }

/* MESSAGES (rulings R-30): the one-thread room, and the operator's list
   beside it. The unread count wears the accent on the rail and the dock —
   it is the one number the nav ever raises unasked. */
.rail-link i.nav-unread { background: var(--ember); color: var(--ember-ink); }
.mobile-dock button { position: relative; }
.dock-unread { position: absolute; top: 5px; right: calc(50% - 18px); min-width: 15px; height: 15px; padding: 0 4px; border-radius: 999px; display: grid; place-items: center; background: var(--ember); color: var(--ember-ink); font-size: 9px; font-weight: 800; font-style: normal; }
.inbox-layout { display: grid; gap: 14px; }
.inbox-layout.operator { grid-template-columns: 280px minmax(0, 1fr); align-items: start; }
.inbox-operator { border: 1px solid var(--line); border-radius: var(--radius); background: color-mix(in srgb, var(--surface) 92%, transparent); overflow: hidden; }
.inbox-threads-head { display: flex; align-items: baseline; justify-content: space-between; padding: 12px 14px; border-bottom: 1px solid var(--line); font-size: 13px; }
.inbox-threads-head small { color: var(--muted); font-size: 11px; }
.inbox-threads { max-height: calc(100dvh - 320px); overflow-y: auto; }
.inbox-thread-row { width: 100%; display: grid; grid-template-columns: 8px minmax(0, 1fr) auto; gap: 10px; align-items: center; padding: 11px 14px; border-bottom: 1px solid var(--line); text-align: left; }
.inbox-thread-row:last-child { border-bottom: 0; }
.inbox-thread-row:hover, .inbox-thread-row.active { background: color-mix(in srgb, var(--ember) 6%, transparent); }
.inbox-thread-row i { width: 8px; height: 8px; border-radius: 50%; }
.inbox-thread-row.unread i { background: var(--ember); box-shadow: 0 0 8px color-mix(in srgb, var(--ember) 60%, transparent); }
.inbox-thread-row b, .inbox-thread-row small { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.inbox-thread-row b { font-size: 12.5px; font-weight: 650; }
.inbox-thread-row small { margin-top: 2px; color: var(--muted); font-size: 11px; }
.inbox-thread-row time { color: var(--faint); font-size: 10px; }
.inbox-room { display: flex; flex-direction: column; overflow: hidden; }
.inbox-peer { padding: 10px 16px; border-bottom: 1px solid var(--line); color: var(--muted); font-size: 12px; }
.inbox-peer b { color: var(--ink); }
.inbox-thread { display: flex; flex-direction: column; gap: 14px; min-height: 260px; max-height: calc(100dvh - 360px); overflow-y: auto; padding: 20px clamp(16px, 3vw, 32px) 12px; }
.inbox-thread:empty { display: none; }
.inbox-empty { padding: 44px 20px; color: var(--muted); font-size: 13px; text-align: center; }
.inbox-thread .msg-op { align-self: flex-start; align-items: flex-start; max-width: 76%; }
.inbox-thread .msg-op .msg-bubble { padding: 10px 14px; border-radius: 8px 8px 8px 2px; background: color-mix(in srgb, var(--surface-3) 92%, transparent); border: 1px solid var(--line); white-space: pre-wrap; overflow-wrap: anywhere; }
.inbox-thread .msg-meta time { color: var(--faint); }
.inbox-composer { border-radius: 0 0 var(--radius) var(--radius); }

/* THE PLANS VIEW (rulings R-26): the tiers as tiles, one per entry in
   plans.js. The current tile is marked from the account plane's own answer;
   the CTA is the caller's (checkout for a free account, Stripe's portal for
   a subscriber — pay-spec §3.4). */
/* CENTERED (Nick 2026-09-01: "plans need to be centered"). auto-fit + 1fr
   stretched the tracks to fill whatever width the page had, so three tiles on
   a wide screen became three billboards pinned to the edges. A max track width
   lets each tile keep its own size and `justify-content: center` centers the
   SET — so the grid reads as a group on a wide monitor and still collapses to
   one full-width column on a phone, where minmax's floor takes over. */
.plans-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 264px)); gap: 14px; justify-content: center; }
/* The billing-period toggle (Nick 2026-09-01), above the grid and centered on
   it. Segmented, in the shape the Appearance theme picker already uses. */
/* `width: max-content` + auto margins, not inline-flex: an inline box ignores
   `margin: auto` and the control would sit hard against the left edge. */
.plan-period { display: flex; width: max-content; max-width: 100%; gap: 4px; margin: 0 auto 16px; padding: 4px; border: 1px solid var(--line); border-radius: 999px; background: color-mix(in srgb, var(--surface) 92%, transparent); }
.plan-period-btn { display: inline-flex; align-items: baseline; gap: 6px; padding: 8px 16px; border: 0; border-radius: 999px; background: none; color: var(--muted); font: inherit; font-size: 13px; font-weight: 650; cursor: pointer; }
.plan-period-btn:hover { color: var(--ink); }
.plan-period-btn[aria-checked="true"] { background: color-mix(in srgb, var(--ember) 16%, transparent); color: var(--ember-soft); }
.plan-period-save { color: var(--win); font-size: 11px; font-weight: 700; }
.plan-period-btn[aria-checked="true"] .plan-period-save { color: var(--win); }
.plan-tile { display: flex; flex-direction: column; gap: 10px; padding: 20px; border: 1px solid var(--line); border-radius: var(--radius); background: color-mix(in srgb, var(--surface) 92%, transparent); }
.plan-tile.current { border-color: color-mix(in srgb, var(--ember) 55%, transparent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ember) 12%, transparent); }
.plan-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.plan-name { font-size: 13px; font-weight: 700; color: var(--muted); }
.plan-chip { padding: 3px 7px; border-radius: 999px; background: color-mix(in srgb, var(--ember) 16%, transparent); color: var(--ember-soft); font-size: 9px; font-weight: 740; letter-spacing: .08em; text-transform: uppercase; white-space: nowrap; }
.plan-price { display: flex; align-items: baseline; gap: 4px; }
.plan-price b { font: 760 32px/1 var(--font-display); letter-spacing: -.03em; }
.plan-price small { color: var(--muted); font-size: 12px; }
.plan-blurb { color: var(--ink); font-size: 13px; font-weight: 600; }
/* R11: the yearly offer — one quiet line under the price (words in plans.js) */
.plan-yearly { display: block; margin-top: 2px; color: var(--muted); font-size: 12px; }
.plan-lines { list-style: none; padding: 0; margin: 2px 0 6px; display: grid; gap: 7px; color: var(--muted); font-size: 12.5px; line-height: 1.4; }
.plan-lines li { position: relative; padding-left: 18px; }
.plan-lines li::before { content: "\2713"; position: absolute; left: 0; color: var(--win); font-weight: 700; }
.plan-foot { margin-top: auto; }
.plan-cta { width: 100%; min-height: 42px; font-size: 13px; }
.plan-yours { display: block; padding: 11px 0; color: var(--muted); font-size: 12px; text-align: center; }
/* the trial's terms under the door (R-39) — one quiet line, the tile's own muted voice */
.plan-note { display: block; margin-top: 8px; color: var(--muted); font-size: 12px; line-height: 1.4; text-align: center; }
#settings-panel { display: grid; gap: 0; }
/* B3's viewer (Nick 2026-08-18): the same utility-modal chrome as Settings,
   sized for a DOCUMENT. The card stops scrolling and the document scrolls
   inside it, so the head and the affordance line hold still while a long
   memory moves — and so the highlight popover has ONE scroller to close on
   rather than the whole page. */
.profile-dialog { width: min(680px, calc(100% - 24px)); height: min(760px, calc(100dvh - 24px)); display: flex; flex-direction: column; overflow: hidden; }
.profile-dialog #profile-note { margin: 0 0 14px; color: var(--muted); font-size: 12px; }
.profile-dialog .pf-scroll { flex: 1; min-height: 0; overflow-y: auto; padding: 16px; border: 1px solid var(--line); border-radius: 10px; background: color-mix(in srgb, var(--void) 40%, transparent); }
/* Bottom-left, Nick 2026-08-18 — the destructive verb in the corner the
   primary actions do not use. Outline on the modal's own ground; a filled
   red button is a thing you hit by accident. */
.profile-dialog #profile-foot { margin-top: 14px; color: var(--muted); font-size: 11px; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
#pf-foot-hint { color: var(--muted); }
.pf-delete { display: inline-flex; align-items: center; gap: 7px; padding: 7px 12px; border-radius: 7px; border: 1px solid var(--line); background: transparent; color: var(--loss); font: inherit; font-size: 12px; cursor: pointer; }
.pf-delete .icon { width: 15px; height: 15px; }
.pf-delete:hover { border-color: color-mix(in srgb, var(--loss) 55%, transparent); background: color-mix(in srgb, var(--loss) 10%, transparent); }
.pf-delete[disabled] { opacity: .6; cursor: default; }
/* inline confirm — a native confirm() over this frame is the I-01 family */
.pf-del-confirm { padding: 7px 12px; border-radius: 7px; font: inherit; font-size: 12px; cursor: pointer; border: 1px solid var(--line); }
.pf-del-yes { background: var(--loss); border-color: var(--loss); color: var(--ink); }
.pf-del-no { background: none; color: var(--muted); }
.pf-del-confirm[disabled] { opacity: .6; cursor: default; }
/* THE SETTINGS MODAL (Nick 2026-08-26, rulings R-27): two panes — tabs
   down the left, one page on the right — in a card wide enough to be a
   settings surface rather than a list of rows. The reference he pointed
   at: a quiet gradient card, an eyebrow + title + description grammar per
   section, fields on the sunken ground, one full-width button per action. */
/* R-40 (Nick 2026-08-27): ONE size. The box used to grow and shrink to each
   tab's content and jumped around as you clicked; now the frame is fixed and
   the page inside scrolls (#settings-panel). */
.settings-dialog { width: min(760px, calc(100% - 24px)); height: min(720px, calc(100dvh - 24px)); padding: 0; overflow: hidden; display: flex; flex-direction: column; }
.settings-head { margin: 0; padding: 24px 26px 16px; border-bottom: 1px solid var(--line); }
.settings-body { flex: 1; min-height: 0; display: grid; grid-template-columns: 172px minmax(0, 1fr); }
.set-tabs { display: flex; flex-direction: column; gap: 2px; padding: 14px 12px; border-right: 1px solid var(--line); background: color-mix(in srgb, var(--void) 35%, transparent); }
.set-tab { position: relative; padding: 9px 12px; border-radius: 8px; text-align: left; font-size: 13px; font-weight: 600; color: var(--muted); white-space: nowrap; transition: color .15s, background .15s; }
.set-tab:hover { color: var(--ink); }
.set-tab.active { color: var(--ink); background: color-mix(in srgb, var(--ember) 10%, transparent); }
.set-tab.active::before { content: ""; position: absolute; left: 0; top: 50%; width: 2px; height: 18px; transform: translateY(-50%); border-radius: 0 2px 2px 0; background: var(--ember); box-shadow: 0 0 6px color-mix(in srgb, var(--ember) 60%, transparent); }
.set-tab:focus-visible { outline: 2px solid var(--ember); outline-offset: -2px; }
#settings-panel { min-height: 0; overflow-y: auto; padding: 22px 26px 26px; }
.set-page { display: grid; gap: 10px; align-content: start; }
.set-page h3 { font-size: 15px; font-weight: 640; letter-spacing: -.01em; }
.set-eyebrow { display: flex; align-items: center; gap: 6px; color: color-mix(in srgb, var(--muted) 75%, transparent); font-size: 10px; font-weight: 740; letter-spacing: .14em; text-transform: uppercase; }
.set-eyebrow .icon { width: 13px; height: 13px; }
.set-desc { color: var(--muted); font-size: 12.5px; line-height: 1.55; }
.set-desc:empty { display: none; }
.set-fact { display: flex; align-items: center; gap: 10px; min-height: 22px; }
/* R-40 (Nick 2026-08-27): the Plan tab's card, off nextclip's plan card —
   kind as an eyebrow, the plan's name large, one line of terms, a meter with
   a thin bar, the doors as an uppercase footer. The look is a first cut. */
.plan-card { display: grid; gap: 6px; padding: 20px 22px 14px; border-radius: 12px; border: 1px solid color-mix(in srgb, var(--ember) 32%, var(--line)); background: linear-gradient(160deg, color-mix(in srgb, var(--ember) 9%, var(--surface)), var(--surface) 70%); }
.plan-card-kind { display: flex; align-items: center; gap: 6px; font-size: 10px; font-weight: 740; letter-spacing: .12em; text-transform: uppercase; color: color-mix(in srgb, var(--ember) 80%, var(--ink)); }
.plan-card-title { font-size: 22px; font-weight: 760; letter-spacing: -.02em; color: var(--ink); }
.plan-card-sub { margin: 0; color: var(--muted); font-size: 13px; line-height: 1.5; }
.plan-usage { display: grid; gap: 8px; margin-top: 8px; padding-top: 14px; border-top: 1px solid var(--line); }
.plan-usage-row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.plan-usage-row .plan-card-kind { color: var(--muted); }
.plan-usage-row b { font-size: 15px; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }
.plan-bar { height: 3px; border-radius: 2px; background: color-mix(in srgb, var(--ink) 12%, transparent); overflow: hidden; }
.plan-bar i { display: block; height: 100%; width: 0; border-radius: 2px; background: var(--ember); transition: width .3s; }
.plan-card-foot { display: flex; flex-wrap: wrap; gap: 4px 18px; margin-top: 10px; padding-top: 12px; border-top: 1px solid var(--line); }
.plan-card-link { padding: 4px 0; border: 0; background: none; font: inherit; font-size: 11px; font-weight: 740; letter-spacing: .1em; text-transform: uppercase; color: var(--ink); cursor: pointer; }
.plan-card-link:hover { color: var(--ember); }
.set-fact b { color: var(--ink); font-size: 13px; font-weight: 650; }
.set-fact select { margin-left: auto; padding: 5px 8px; border: 1px solid var(--line-strong); border-radius: 6px; background: var(--sunken); color: var(--ink); font: inherit; font-size: 12px; }
.set-label { margin-top: 4px; color: var(--muted); font-size: 12px; }
.set-input { width: 100%; min-height: 40px; padding: 0 12px; border: 1px solid var(--line-strong); border-radius: 8px; background: var(--sunken); color: var(--ink); font-size: 14px; outline: 0; }
.set-input:focus { border-color: color-mix(in srgb, var(--ember) 60%, transparent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ember) 9%, transparent); }
.set-btn { width: 100%; min-height: 40px; padding: 0 14px; border: 1px solid var(--line-strong); border-radius: 8px; background: var(--sunken); color: var(--ink); font-size: 13px; font-weight: 600; transition: border-color .15s, background .15s, filter .15s; }
.set-btn:hover { border-color: color-mix(in srgb, var(--ink) 30%, var(--line-strong)); background: var(--surface-2); }
.set-btn.primary { border-color: transparent; color: var(--ember-ink); background: linear-gradient(135deg, color-mix(in srgb, var(--ember) 86%, black), color-mix(in srgb, var(--ember) 72%, white)); }
.set-btn.primary:hover { filter: brightness(1.07); background: linear-gradient(135deg, color-mix(in srgb, var(--ember) 86%, black), color-mix(in srgb, var(--ember) 72%, white)); }
.set-btn[disabled] { opacity: .5; cursor: default; }
.set-hr { border: 0; border-top: 1px solid var(--line); margin: 8px 0 4px; }
.settings-dialog .set-row { padding: 11px 0; border-bottom: 1px solid var(--line); }
.settings-dialog .rail-intel { margin-top: 6px; }
.theme-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
.theme-card { display: grid; gap: 4px; padding: 10px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface); text-align: left; transition: border-color .15s, box-shadow .15s; }
.theme-card:hover { border-color: var(--line-strong); }
.theme-card[aria-checked="true"] { border-color: color-mix(in srgb, var(--ember) 60%, transparent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ember) 12%, transparent); }
.theme-card b { margin-top: 4px; font-size: 13px; font-weight: 650; }
.theme-card small { color: var(--muted); font-size: 11px; line-height: 1.4; }
.theme-swatch { height: 56px; border-radius: 7px; overflow: hidden; display: grid; grid-template-columns: 28% 1fr; background: var(--sw-bg); border: 1px solid var(--sw-line); }
.theme-swatch i:first-child { background: var(--sw-chrome); }
.theme-swatch i:last-child { margin: 9px; border-radius: 4px; background: var(--sw-surface); border: 1px solid var(--sw-line); }
.set-row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; padding: 3px 0; font-size: 11px; color: var(--muted); }
.set-row b { color: var(--ink); font-weight: 650; }
/* Nick 2026-08-18: the whole row is the target, so it IS the button. Bigger
   icon, no 'Open' word - a fully clickable row needs no label saying a part
   of it is clickable. */
.set-row-btn { width: 100%; text-align: left; font: inherit; cursor: pointer; align-items: center; border: 0; border-bottom: 1px solid var(--line); background: none; color: var(--muted); }
.set-row-btn:hover { color: var(--ink); }
.set-row-icon { width: 22px; height: 22px; color: var(--ember, currentColor); flex: none; }
.memory-sources { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 25px; }
.memory-sources span { padding: 6px 8px; border: 1px solid color-mix(in srgb, var(--ember) 20%, transparent); border-radius: 5px; color: #71809a; font-size: 9px; }
.memory-sources b { color: #a8b8d0; font-weight: 650; }

/* Connect-your-PC panel (onboarding spec §5): a plain working panel — the
   memory panel keeps the decoration, this carries the levers. (The redeem
   coaching banner retired 2026-08-24, R-23.) */
.pc-dialog { width: min(560px, calc(100% - 24px)); }
.pc-dialog h2 { font-size: 21px; letter-spacing: -.025em; }
.pc-dialog > div > p, .pc-modal-recency { margin-top: 8px; color: var(--muted); font-size: 12px; }
.pc-dialog .memory-sources { margin-top: 16px; }
.connect-actions { display: flex; flex-wrap: wrap; gap: 6px 18px; margin-top: 14px; }
.minted-token { display: flex; align-items: center; gap: 8px; margin-top: 12px; padding: 9px 11px; border: 1px solid var(--line-strong); border-radius: 7px; background: var(--sunken); }
.minted-token code { flex: 1; min-width: 0; overflow-x: auto; white-space: nowrap; color: var(--ink); font-size: 11px; letter-spacing: .03em; }
.minted-token button { color: var(--muted); font-size: 10px; font-weight: 640; }
.minted-token button:hover { color: var(--ink); }
.setup-note { margin-top: 8px; color: var(--faint); font-size: 10px; line-height: 1.4; }
#mint-err { margin-top: 10px; font-size: 11px; }
/* First-login highlight (2026-08-24, Nick's spec): the modal auto-opens on a
   fresh account's first PC visit and this ring says which action it wants.
   The class leaves on the first press — a highlight that survives its own
   answer is a nag. */
.mint-attn { position: relative; padding: 6px 10px; border-radius: 8px; color: var(--ink); animation: mint-pulse 1.6s ease-in-out infinite; }
@keyframes mint-pulse {
  0%, 100% { box-shadow: 0 0 0 1px color-mix(in srgb, var(--ember) 55%, transparent), 0 0 0 0 color-mix(in srgb, var(--ember) 34%, transparent); }
  50% { box-shadow: 0 0 0 1px color-mix(in srgb, var(--ember) 80%, transparent), 0 0 0 7px color-mix(in srgb, var(--ember) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .mint-attn { animation: none; box-shadow: 0 0 0 1px color-mix(in srgb, var(--ember) 70%, transparent); }
}

.page-heading { min-height: 120px; display: flex; align-items: flex-end; justify-content: space-between; gap: 30px; padding: 6px 0 27px; }
.page-heading h1 { margin-top: 10px; }
/* The record: a rate to read at a glance, the raw W–L as its caption. Both
   runs are nowrap — the string this replaced broke across lines on a phone. */
.page-record { flex: none; text-align: right; }
.page-record b { display: block; white-space: nowrap; font: 700 30px/1 var(--font-display); letter-spacing: -.03em; color: var(--ink); }
.page-record small { display: block; margin-top: 5px; white-space: nowrap; color: var(--muted); font-size: 11px; font-weight: 620; letter-spacing: .02em; }
.list-tools { padding: 8px; margin-bottom: 13px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); display: flex; align-items: center; gap: 10px; }
.search-field { min-width: 230px; flex: 1; height: 40px; padding: 0 13px; display: flex; align-items: center; gap: 9px; background: #080e19; border: 1px solid var(--line); border-radius: 6px; color: #65728a; }
.search-field input { width: 100%; border: 0; outline: 0; background: transparent; color: var(--ink); font-size: 12px; }
/* One segmented track rather than three loose pills — the group reads as a
   single control, and the counts ride inside each segment. */
.result-filters { flex: none; display: flex; gap: 2px; padding: 3px; border-radius: 7px; background: var(--sunken); }
.filter { min-width: 62px; height: 34px; padding: 0 11px; border-radius: 5px; color: #718099; font-size: 11px; display: inline-flex; align-items: center; justify-content: center; gap: 6px; white-space: nowrap; }
.filter i { font-style: normal; font-size: 10px; font-weight: 700; color: var(--faint); font-variant-numeric: tabular-nums; }
.filter.active { background: color-mix(in srgb, var(--ember) 18%, transparent); color: #e9e6ff; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ember) 20%, transparent); }
.filter.active i { color: var(--ember-soft); }
.list-nomatch { padding: 26px 4px; text-align: center; color: var(--muted); font-size: 12px; }
/* patch-window-spec §4.4: the door under the games list — a row-shaped word, not a row */
.games-older { display: block; width: 100%; margin-top: 10px; padding: 14px 16px; border: 1px dashed var(--line); border-radius: var(--radius); background: none; color: var(--muted); font: inherit; font-size: 12px; font-weight: 600; text-align: center; cursor: pointer; }
.games-older:hover { color: var(--ember); border-color: color-mix(in srgb, var(--ember) 45%, transparent); }
.list-nomatch button { color: var(--ember-soft); font-size: 12px; font-weight: 650; text-decoration: underline; text-underline-offset: 3px; }
.game-stack.full { border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); overflow: hidden; }
.game-stack.full .game-row { min-height: 77px; padding-inline: 18px; }
.empty { min-height: 330px; padding: 60px 24px; text-align: center; display: grid; place-items: center; align-content: center; }
.empty-mark { color: var(--ember); font-size: 38px; }
.empty h2 { margin-top: 13px; font-family: var(--font-display); font-size: 23px; font-weight: 720; letter-spacing: -.01em; }
.empty p { max-width: 450px; margin-top: 8px; color: var(--muted); }
/* B5: what a finished game leaves behind. Deliberately quiet — it is a way
   back, not an announcement. A starting point to react to, not a ruling. */
.live-last { margin-top: 12px; font-size: 12px; padding: 6px 12px; color: var(--muted);
  background: transparent; border: 1px solid var(--line); border-radius: 999px; }
.live-last:hover:not(:disabled) { color: var(--ink); border-color: var(--accent); }
.live-last:disabled { opacity: .75; cursor: default; }
.live-last-note { margin: 8px 0 0; font-size: 12px; color: var(--muted); }
.empty small { margin-top: 14px; color: #596473; }
/* Deck cards sized by their content, not by air (Nick 2026-08-13): name,
   the three facts in one row, and the deck code as a real action. */
.decks-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 12px; }
.deck-card { position: relative; padding: 15px 16px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); overflow: hidden; text-align: left; cursor: pointer; transition: border-color .16s, background .16s, transform .16s; }
/* Decoration must never eat a click: the ornament overlaps the corner the
   copy button lives in, and a pseudo-element paints AFTER the real children
   — without pointer-events:none it hit-tests first and the button goes dead
   wherever the triangle covers it (Nick caught it as an "iffy" click). */
.deck-card::after { content: ""; position: absolute; width: 140px; height: 140px; right: -84px; top: -84px; border: 1px solid color-mix(in srgb, var(--ember) 28%, transparent); transform: rotate(45deg); pointer-events: none; }
.deck-card:hover { border-color: color-mix(in srgb, var(--ember) 48%, transparent); background: var(--surface-2); transform: translateY(-2px); }
.deck-card:focus-visible { outline: 2px solid color-mix(in srgb, var(--ember) 60%, transparent); outline-offset: 2px; }
.deck-card h2 { max-width: calc(100% - 84px); font-size: 17px; font-weight: 720; line-height: 1.2; letter-spacing: -.025em; }
.deck-card .deck-stats { display: flex; gap: 16px; margin-top: 10px; }
.deck-card .deck-stats span { display: block; }
.deck-card .deck-stats b { display: block; font-size: 14px; font-weight: 680; }
.deck-card .deck-stats small { display: block; margin-top: 1px; color: #667181; font-size: 9px; text-transform: uppercase; letter-spacing: .07em; }
/* Top-right, over the corner ornament (solid bg so it reads), no row of
   its own — the tile is exactly name + the two facts tall. */
/* Nick's color trial 2026-08-13: the live copy action wears #EA923E. The
   dim (code-less) state keeps its gray — orange would read as clickable. */
.deck-card .deck-copy { position: absolute; top: 12px; right: 13px; z-index: 1; padding: 4px 9px; border: 1px solid var(--copy-trial); border-radius: 5px; background: var(--copy-trial); color: var(--ember-ink); font-size: 10px; font-weight: 700; }
.deck-card .deck-copy:hover { filter: brightness(1.08); }
.deck-card .deck-copy.dim { border-color: var(--line); background: var(--surface); color: var(--muted); font-weight: 640; }
/* No synced code yet: grayed, not gone — the title says what to do about it */
.deck-card .deck-copy.dim { opacity: .45; cursor: default; }
.deck-card .deck-copy.dim:hover { color: var(--muted); border-color: var(--line); }
.deck-tools { min-height: 0; }
.decks-note { margin-top: 13px; color: var(--faint); font-size: 11px; }
/* The deck's own hero paints the tile (2026-08-13, machinery — treatment is
   Nick's to restyle): 512x square art, faces sit in the upper third so the
   focal crop rides ~22% from the top (research heuristic, tune per taste);
   the scrim is baked into the same background stack in app.js. */
/* 112% not cover: some SKIN squares ship a flat slab baked into an edge
   (HERO_07ak_Guldan: right columns 488-508 of 512, measured 2026-08-13 —
   Nick saw it as a "white cutoff"). cover at this aspect crops only
   vertically, so every source column showed, slab included; oversizing
   ~6% per side drops edge slabs out of frame for every hero. */
.deck-card.has-art { min-height: 122px; background-size: 112% auto; background-position: center 22%; }
.deck-card.has-art h2, .deck-card.has-art .deck-stats b { text-shadow: 0 1px 3px color-mix(in srgb, black 75%, transparent); }
.deck-card.has-art .deck-stats small { color: color-mix(in srgb, var(--ink) 65%, var(--muted)); }
/* Nick 2026-08-13: the hero-power circle owns the bottom-left, so the stats
   row starts at the tile's horizontal middle instead of under the circle. */
.deck-card.has-art .deck-stats { margin-left: 50%; }
/* The hero power, drawn the way the game draws it: a circle, bottom-left.
   The square art has edge slivers — the img runs 110% so they fall outside
   the mask. Decoration never hit-tests (today's ornament lesson). */
.deck-card .deck-hp { position: absolute; left: 13px; bottom: 11px; width: 44px; height: 44px; border-radius: 50%; overflow: hidden; border: 2px solid color-mix(in srgb, var(--ember) 55%, transparent); box-shadow: 0 2px 9px color-mix(in srgb, black 55%, transparent); background: black; pointer-events: none; }
.deck-card .deck-hp img { width: 110%; height: 110%; object-fit: cover; margin: -5%; display: block; }

.back-link { margin: 2px 0 22px; color: #798493; font-size: 11px; }
.back-link:hover { color: var(--ink); }
.game-detail-head { min-height: 116px; display: grid; grid-template-columns: 1fr auto; gap: 18px; align-items: center; }
.detail-texthead { display: grid; grid-template-columns: 54px 1fr; gap: 18px; align-items: center; }
.detail-result { display: grid; place-items: center; width: 52px; height: 52px; border-radius: 8px; color: var(--win); background: var(--win-tint); font-size: 22px; font-weight: 750; }
.detail-result.L { color: var(--loss); background: var(--loss-tint); }
.game-detail-head h1 { margin: 4px 0 4px; font-size: 34px; }
.game-detail-head p { color: #687382; font-size: 10px; }
.game-detail-head #game-overline { color: var(--ember-soft); font-size: 8px; font-weight: 750; letter-spacing: .14em; }

/* the matchup head (md.js:matchupHeadEl) — the web half of the twin; the
   desktop's lives in theme.css. Same builder, same class names, each
   surface's own identity tokens. The plate FRAME is CSS and the art is a
   CDN request from the visitor's browser: a hosted frame asset would need
   its own art-posture provenance row before it could ship. */
.mu-head { min-width: 0; display: grid; gap: 8px; }
.mu-row { display: flex; align-items: center; justify-content: center; gap: 14px; flex-wrap: wrap; }
.mu-plate { position: relative; display: flex; align-items: flex-end; width: 178px; min-height: 60px; padding: 9px 11px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); background-size: cover; background-position: center 26%; overflow: hidden; }
.mu-plate.has-art { border-color: color-mix(in srgb, var(--ember) 42%, transparent); }
.mu-name { font-size: 13px; font-weight: 720; line-height: 1.15; letter-spacing: -.02em; overflow-wrap: anywhere; }
.mu-plate.has-art .mu-name { text-shadow: 0 1px 3px color-mix(in srgb, black 78%, transparent); }
.mu-vs { color: var(--muted); font-size: 12px; font-weight: 750; letter-spacing: .04em; }
.mu-result { font-size: 15px; font-weight: 760; letter-spacing: -.01em; }
.mu-result.W { color: var(--win); }
.mu-result.L { color: var(--loss); }
.mu-result.T, .mu-result.U { color: var(--unknown); }
.mu-meta { color: var(--muted); font-size: 10px; }
.detail-tabs { align-self: end; display: flex; gap: 4px; padding-bottom: 15px; }
.tab { min-width: 74px; height: 35px; border-radius: 5px; color: #74829a; font-size: 11px; }
.tab.active { background: color-mix(in srgb, var(--ember) 17%, transparent); color: #ece9ff; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ember) 16%, transparent); }
.game-workspace { min-height: calc(100dvh - 270px); border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); overflow: hidden; }
.chat-pane { min-height: calc(100dvh - 272px); display: flex; flex-direction: column; }
#thread { flex: 1; min-height: 300px; max-height: calc(100dvh - 390px); overflow-y: auto; padding: 24px clamp(18px, 5vw, 70px) 12px; display: flex; flex-direction: column; gap: 15px; }
/* Message anatomy = the desktop's: meta line, body, quiet action row. */
.msg { display: flex; flex-direction: column; gap: 4px; }
.msg-user { align-self: flex-end; align-items: flex-end; max-width: 76%; }
.msg-coach { align-self: stretch; max-width: 790px; }
/* The web half of the same amendment (Nick 2026-08-21, 2x speaker face).
   Centred rather than stretched so the taller face does not stretch the time
   and the unprompted mark to its own height; sized in `em` off this row's
   font-size, exactly 2x faceSvg()'s 1.39em. Twin: client/ui/theme.css. */
.msg-meta { display: flex; gap: 7px; align-items: center; color: #5f6a7c; font-size: 10px; }
.msg-meta .op-face { display: inline-flex; align-items: center; }
.msg-meta .op-face-svg { display: block; width: 2.78em; height: 2.78em; }
.msg-meta .msg-who { color: var(--ember-soft); font-weight: 700; }
/* bump-spec B3: the mark that says the coach spoke first. A starting point to
   react to, not a design ruling — the words are the honesty rule, the look is
   Nick's. */
.msg-meta .who-pushed { color: var(--muted); font-weight: 500; letter-spacing: .03em; }
.msg-pushed .msg-body { border-left: 2px solid color-mix(in srgb, var(--accent) 55%, transparent); padding-left: 8px; }
.msg-user .msg-bubble { padding: 10px 14px; border-radius: 8px 8px 2px 8px; background: linear-gradient(135deg, color-mix(in srgb, var(--ember) 22%, transparent), color-mix(in srgb, var(--surface-3) 92%, transparent)); border: 1px solid color-mix(in srgb, var(--ember) 15%, transparent); white-space: pre-wrap; overflow-wrap: anywhere; }
.msg-coach .msg-body { overflow-wrap: anywhere; color: #d9dce1; }
.msg-acts { display: flex; gap: 8px; opacity: 0; transition: opacity .15s ease; }
.msg:hover .msg-acts, .msg:focus-within .msg-acts { opacity: 1; }
.msg-acts button { color: #6d7a8e; font-size: 10px; }
.msg-acts button:hover { color: var(--ink); }
@media (hover: none) { .msg-acts { opacity: .55; } }
/* Card sauce (shared md.js markup). Rarity stays metadata, never text paint. */
.card {
  color: var(--ink);
  font-weight: 700;
  text-shadow: 0 1px 0 #040610;
  border-radius: 2px;
  cursor: help;
}
.card:hover {
  color: var(--ink);
  background: var(--surface-3);
  outline: 1px solid #040610;
  outline-offset: 2px;
}
/* The honest-failure surface (app.js `toast`) — failures only, never a
   confirmation (rulings I-06): above the modals, because the refusals raised
   from inside one (Delete memory, the share switch) would otherwise speak
   from behind their own backdrop. Twin of client/ui/theme.css #toast, in this
   surface's palette. */
#toast {
  position: fixed; left: 50%; bottom: calc(28px + env(safe-area-inset-bottom));
  transform: translateX(-50%) translateY(8px);
  max-width: min(70%, 460px); padding: 10px 16px;
  background: var(--surface-3); color: var(--ink);
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  box-shadow: 0 18px 38px rgba(0,0,0,.5);
  font-size: 13px; opacity: 0; pointer-events: none;
  transition: opacity .2s ease, transform .2s ease; z-index: 120;
}
#toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
#card-peek {
  display: none;
  position: fixed;
  z-index: 80;
  pointer-events: none;
  filter: drop-shadow(0 18px 38px rgba(0,0,0,.72));
}
#card-peek.open { display: block; }
#card-peek img { display: block; width: 230px; max-width: 60vw; }
.msg-coach pre { margin: 8px 0; padding: 12px; overflow-x: auto; border: 1px solid var(--line); border-radius: 9px; background: #0d1118; font-size: 12px; }
.msg-coach code { padding: 1px 5px; border-radius: 4px; background: var(--surface-3); font-size: 13px; }
.msg-coach ul, .msg-coach ol { padding-left: 22px; margin: 7px 0; }
.msg-coach h2, .msg-coach h3, .msg-coach h4 { margin: 14px 0 5px; font-size: 17px; font-weight: 720; }
.msg-coach p { margin: 7px 0; }
/* Prose forms the shared renderer can emit (tables, code-copy, quotes, links)
   — the phone renders every shape the desktop can, or the fork is back. */
.msg-coach .codeblock { position: relative; margin: 8px 0; }
.msg-coach .codeblock pre { margin: 0; }
.msg-coach .copycode { position: absolute; top: 6px; right: 8px; color: #6d7a8e; font-size: 10px; opacity: 0; transition: opacity .15s ease; }
.msg-coach .codeblock:hover .copycode { opacity: 1; }
@media (hover: none) { .msg-coach .copycode { opacity: .6; } }
.msg-coach .tablewrap { overflow-x: auto; margin: 8px 0; }
.msg-coach table { border-collapse: collapse; font-size: 12.5px; }
.msg-coach th, .msg-coach td { padding: 6px 10px; border: 1px solid var(--line); text-align: left; }
.msg-coach th { color: var(--ink); background: color-mix(in srgb, var(--surface-2) 60%, transparent); font-weight: 680; }
.msg-coach blockquote { margin: 8px 0; padding: 4px 12px; border-left: 2px solid var(--line-strong); color: #a7b1c2; }
.msg-coach hr { border: 0; border-top: 1px solid var(--line); margin: 12px 0; }
.msg-coach a.ext { color: var(--ember-soft); cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
.msg-coach a.ext:hover { color: var(--ink); }
#thread .empty { min-height: 280px; }
/* The zero-wait beats: typing dots, then the working checklist — the
   desktop's own shapes (.tdots/.wstep), the shared WORK_STEPS copy. */
.tdots { display: inline-flex; gap: 4px; padding: 4px 0; }
.tdots i { width: 6px; height: 6px; border-radius: 50%; background: #8d99ad; animation: tblink 1.2s infinite; }
.tdots i:nth-child(2) { animation-delay: .18s; }
.tdots i:nth-child(3) { animation-delay: .36s; }
@keyframes tblink { 0%, 60%, 100% { opacity: .25; } 30% { opacity: 1; transform: translateY(-3px); } }
#working { margin: 2px clamp(18px,5vw,70px) 8px; padding: 2px 0 2px 12px; border-left: 2px solid var(--line); color: #748090; font-size: 11px; }
#working .wstep { display: flex; align-items: baseline; gap: 8px; padding: 2px 0; }
#working .wstep.done { color: #5a6577; }
#working .wstep.active { color: #97a3b6; }
#working .wmark { width: 12px; flex: none; text-align: center; }
#working .wstep.active .wmark::after { content: ""; display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--ember); animation: pulse 1.2s ease-in-out infinite; box-shadow: 0 0 10px var(--ember); }
.dots::after { content: ""; animation: dots 1.4s steps(4, end) infinite; }
@keyframes dots { 0% { content: ""; } 25% { content: "."; } 50% { content: ".."; } 75% { content: "..."; } }
@keyframes pulse { 50% { opacity: .25; transform: scale(.7); } }
.chat-error { margin: 4px clamp(18px,5vw,70px); }
.composer { display: flex; gap: 9px; padding: 11px clamp(14px,4vw,52px); border-top: 1px solid var(--line); background: #0b1321; }
.chat-input { flex: 1; max-height: 160px; resize: none; padding: 11px 13px; border: 1px solid var(--line); border-radius: 7px; outline: 0; background: var(--surface-2); color: var(--ink); }
.chat-input:focus { border-color: color-mix(in srgb, var(--ember) 60%, transparent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ember) 9%, transparent); }
/* The round arrow — the ONE send affordance, twin of theme.css .chat-send
   (Nick's image spec 2026-08-15). Same derivation as the desktop's: the
   disc takes this surface's darkest ground, the arrow takes the ink, and
   the border carries the edge when the two grounds agree. The ember
   gradient it replaced was the only place the accent shouted on a screen
   whose whole point is the board art. */
.send-button { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; align-self: flex-end; padding: 0; border: 1px solid var(--line-strong); border-radius: 50%; background: var(--void); color: var(--ink); font-size: 17px; }
.send-button .icon { width: 1.15em; height: 1.15em; vertical-align: 0; }
.send-button:disabled { opacity: .45; }
.budget { padding: 0 54px 9px; color: #606d83; font-size: 9px; text-align: right; background: #0b1321; }
.replay-pane { min-height: calc(100dvh - 272px); overflow: auto; background: #0d1118; }
.replay-label { position: sticky; top: 0; z-index: 3; padding: 10px 15px; border-bottom: 1px solid var(--line); background: rgba(13,17,24,.95); display: flex; align-items: center; justify-content: space-between; gap: 12px; color: #667486; font-size: 8px; font-weight: 760; letter-spacing: .14em; }
.replay-label small { color: #566274; font-size: 8px; font-weight: 550; letter-spacing: 0; text-transform: none; }
.replay-close { display: none; margin-left: auto; color: #aab5c7; font-size: 10px; }
.replay-visual { padding: 8px; }
#replay-frame { display: block; width: 100%; min-height: 430px; border: 0; border-radius: 7px; background: #080d17; }
#compact-text { padding: 20px; color: #b7bec8; font: 12px/1.6 var(--mono); white-space: pre-wrap; overflow-wrap: anywhere; }
/* skeleton-spec: the board frame's wait — ONE block wearing #replay-frame's
   box (its inset and its floor), never a board */
.replay-sk { margin: 8px; min-height: 430px; border-radius: 7px; }
.mobile-dock { display: none; }

/* Responsive ------------------------------------------------------------- */
/* Desktop scale (R-55, R-56). Above 980px the words stand at headline scale
   on one line and the CTA sits ON the centre line - it is the axis the eye
   walks down; the row's own centring puts it there since the one-word "Free"
   note beside it went (Nick, 2026-09-06: "no need"). No
   height bands: the document scrolls, so nothing here is budgeted to a
   screen (the pitch's own first-screen rule is above). */
@media (min-width: 981px) {
  .public-pitch h1 { max-width: none; font-size: clamp(44px, 5.2vw, 76px); }
  .hero-lede { font-size: clamp(16px, 1.35vw, 20px); }
}

@media (max-width: 980px) {
  .public-view { --nav-h: 62px; }
  .public-nav { height: 62px; }
  .public-stage { width: min(760px, calc(100% - 32px)); }
  .public-pitch h1 { max-width: 18ch; font-size: clamp(34px, 5.6vw, 50px); }
  .hero-lede { max-width: 56ch; }
}

@media (max-width: 760px) {
  .public-view { --nav-h: 56px; }
  .public-nav { width: calc(100% - 28px); height: 56px; }
  .public-nav .brand > .brand-name { font-size: 17px; }
  .nav-auth { min-height: 32px; padding: 0 10px; font-size: 10px; }
  .public-stage { width: calc(100% - 28px); }
  .public-pitch h1 { margin: 0 auto clamp(9px, 1.7vh, 15px); font-size: clamp(28px, 8.4vw, 40px); line-height: 1.02; }
  .hero-lede { font-size: 13px; line-height: 1.45; }
  .hero-get { margin-top: clamp(10px, 1.8vh, 16px); gap: 9px; }
  .hero-get-btn { min-height: 38px; padding: 0 16px; font-size: 14px; }
  .public-tiles { gap: 8px; }
  .tiles-frame { border-radius: 9px; }
  .tiles-tabs { gap: 6px; }
  .tiles-tab { padding: 8px 9px 9px; gap: 3px; }
  .tiles-tab b { font-size: 11.5px; }
  .tiles-tab span { font-size: 9.5px; line-height: 1.38; }
  .public-footer { width: calc(100% - 28px); padding-bottom: 9px; }
  .modal-card { width: calc(100% - 20px); max-height: calc(100dvh - 20px); padding: 18px; }
  .modal-head { margin-bottom: 15px; }
  .settings-dialog { padding: 0; }
  .settings-head { padding: 18px 16px 12px; margin: 0; }
  .settings-body { grid-template-columns: 1fr; grid-template-rows: auto minmax(0, 1fr); }
  .set-tabs { flex-direction: row; overflow-x: auto; gap: 4px; padding: 10px 12px; border-right: 0; border-bottom: 1px solid var(--line); }
  .set-tab { flex: none; padding: 7px 10px; font-size: 12px; }
  .set-tab.active::before { display: none; }
  #settings-panel { padding: 16px; }
  .capture-note { padding-top: 9px; font-size: 8px; }

  .member-shell { display: block; }
  .app-rail { display: none; }
  .rail-toggle, .app-topbar::after { display: none; }
  .inbox-layout.operator { grid-template-columns: 1fr; }
  .inbox-thread { max-height: calc(100dvh - 330px); }
  .app-stage { min-height: 100dvh; padding-bottom: 67px; }
  .app-topbar { height: 62px; padding: 0 15px; }
  .mobile-brand { display: inline-flex; align-items: center; gap: 8px; }
  .mobile-brand > .brand-name { font-size: 16px; font-weight: 760; }
  .topbar-context { display: none; }
  .mobile-newchat { display: flex; align-items: center; gap: 9px; width: 100%; margin: 0 0 11px; padding: 11px 13px; border: 1px dashed color-mix(in srgb, var(--ember) 45%, transparent); border-radius: 9px; background: color-mix(in srgb, var(--ember) 7%, transparent); color: var(--ember); font: inherit; font-size: 13px; font-weight: 650; text-align: left; }
  .mobile-newchat .icon { width: 16px; height: 16px; }
  .mobile-newchat:active { background: color-mix(in srgb, var(--ember) 13%, transparent); }
  .mobile-signout { display: block; margin: 24px auto 0; padding: 10px 22px; color: var(--faint); font-size: 12px; font-weight: 640; }
  .mobile-signout:active { color: var(--ink); }
  .workspace-main { padding: 23px 14px 40px; }
  /* THE ROW TIGHTENING, RE-AIMED 2026-08-21. These rules named .game-row,
     .result-chip, .game-opp and .game-when — the hand-rolled row this list
     used to mint. It moved to md.js's shared .panel-row builder and the rules
     went on matching NOTHING, so the phone silently got the desktop's row
     metrics back. A selector aimed at a class the markup no longer mints is
     invisible to every check that reads the stylesheet; the rig measures the
     rendered row now, at both widths.

     The base .game-* and .panel-head rules stay where they are: the untracked
     local lab page (mobile/palette-lab.html) still mints them.

     TWO of the five are NOT re-aimed, because they have no live target:
     .result-chip is gone by ruling (R4, 2026-08-16 — the result is a WORD in
     the subtitle now), and nothing on this surface mints a .panel-head.

     Nor is `.game-when { display: none }`. That hid the date from a FOUR
     column row — chip, avatar, opponent, date — at 390px. The row is three
     columns now, and measured at 390 the subtitle still renders without
     ellipsis beside the date, so re-imposing the hide would be restoring a
     decision about markup that no longer exists. The invariant was no dead
     air in the list, never "the date must not show". */
  .panel-row { min-height: 70px; padding: 11px 13px; grid-template-columns: 30px minmax(0,1fr) auto; gap: 10px; }
  .panel-row .avatar { width: 30px; height: 30px; font-size: 12px; }
  .panel-title { font-size: 12px; }
  /* The phone's top area is a header, not a title page: the heading and the
     record sit on one line, the tools card carries everything else. Every
     row of dead air here is a game the player has to scroll for. */
  .page-heading { min-height: 0; align-items: center; gap: 14px; padding: 2px 0 16px; }
  .page-heading h1 { margin: 0 0 5px; font-size: 30px; }
  .page-heading > div > p:last-child { font-size: 12px; line-height: 1.35; }
  .page-record b { font-size: 25px; }
  .page-record small { margin-top: 3px; font-size: 10px; }
  .list-tools { align-items: stretch; flex-direction: column; padding: 7px; gap: 7px; }
  .search-field { min-width: 0; }
  .result-filters { align-self: stretch; }
  .filter { flex: 1; min-width: 0; padding: 0 6px; }
  .decks-grid { grid-template-columns: 1fr; }
  .game-detail-head { grid-template-columns: 1fr; gap: 12px; }
  .detail-texthead { grid-template-columns: 43px 1fr; gap: 12px; }
  .detail-result { width: 43px; height: 43px; border-radius: 7px; }
  .game-detail-head h1 { font-size: 24px; }
  .detail-tabs { grid-column: 1 / -1; justify-self: stretch; align-self: auto; padding: 5px; background: var(--surface); border: 1px solid var(--line); border-radius: 7px; }
  .detail-tabs .tab { flex: 1; }
  /* The plates shrink rather than wrap to two rows: a matchup that stacks
     stops reading as "this deck against that one". */
  .mu-row { gap: 9px; flex-wrap: nowrap; }
  .mu-plate { width: auto; flex: 1 1 0; min-width: 0; min-height: 52px; padding: 7px 9px; }
  .mu-name { font-size: 11px; }
  .mu-result { font-size: 13px; }
  /* One scrolling surface in the game room. The view is a fixed 100dvh
     column — header, then the chat pane; ONLY #thread scrolls. Before this,
     the document and the thread both scrolled and the chat "slid around"
     like a desktop site on a phone (Nick's report, parity pass Tier 2.7). */
  body:has(#view-game:not([hidden])) { overflow: hidden; }
  body:has(#view-game:not([hidden])) .app-stage { padding-bottom: 0; }
  body:has(#view-game:not([hidden])) .workspace-main { padding: 0 14px; }
  #view-game { height: calc(100dvh - 62px); display: flex; flex-direction: column; overscroll-behavior: none; }
  #view-game .back-link { flex: none; margin: 10px 0 8px; }
  #view-game .game-detail-head { flex: none; min-height: 0; }
  .game-workspace { flex: 1; min-height: 0; display: flex; flex-direction: column; }
  .chat-pane { flex: 1; min-height: 0; }
  #thread { flex: 1; min-height: 0; max-height: none; padding-inline: 15px; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
  .replay-pane { flex: 1; min-height: 0; }
  .msg-user { max-width: 88%; }
  /* phone keeps the shorter composer cap; desktop widths match the desktop's 160 */
  .chat-input { max-height: 120px; }
  .composer { padding: 9px 9px calc(9px + env(safe-area-inset-bottom)); }
  .budget { padding-right: 14px; }
  /* auto-flow columns: the dock divides by however many entries EXIST —
     the hardcoded repeat(4,1fr) kept a ghost slot for the removed Home
     entry and snapped the three real buttons left (Nick's phone catch,
     2026-08-13). */
  .mobile-dock { position: fixed; z-index: 30; left: 0; right: 0; bottom: 0; height: calc(60px + env(safe-area-inset-bottom)); padding-bottom: env(safe-area-inset-bottom); border-top: 1px solid var(--line); background: rgba(6,10,19,.96); backdrop-filter: blur(18px); display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; }
  .mobile-dock button { color: #667181; font-size: 9px; display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 2px; }
  .mobile-dock button span { font-size: 15px; }
  .mobile-dock button.active { color: var(--ember-soft); }
  .game-view + .mobile-dock { display: none; }
}

/* Short landscape windows keep the same decision, but drop decorative proof. */
@media (max-height: 650px) and (min-width: 761px) {
  .public-view { --nav-h: 52px; }
  .public-nav { height: 52px; }
  .public-pitch h1 { margin: 0 auto 12px; font-size: clamp(32px, 3.9vw, 49px); }
  .hero-lede { font-size: 14px; }
  .hero-get { margin-top: 12px; }
  .hero-get-btn { min-height: 40px; font-size: 14px; }
  .login-card { gap: 6px; }
  .token-wrap, .login-card .button { height: 38px; min-height: 38px; }
  .modal-card { max-height: calc(100dvh - 12px); padding-block: 14px; }
  .modal-head { margin-bottom: 10px; }
  .capture-note { margin-top: 10px; padding-top: 8px; }
}

@media (max-height: 520px) and (max-width: 760px) {
  .public-view { --nav-h: 46px; }
  .public-nav { height: 46px; }
  .public-pitch h1 { margin: 0 auto 8px; font-size: clamp(24px, 4.9vw, 35px); }
  .hero-lede { font-size: 11px; }
  .access-copy > p:last-child { display: none; }
  .access-copy h2 { margin: 0; font-size: 18px; }
  .login-card { grid-template-columns: 1fr; gap: 6px; }
  .token-wrap, .login-card .button { height: 36px; min-height: 36px; }
  .modal-card { max-height: calc(100dvh - 10px); padding: 10px 14px; }
  .modal-head { margin-bottom: 7px; }
  .capture-note { display: none; }
}

/* A phone turned sideways becomes the replay surface, not a squeezed desktop
   shell. The close control returns to the normal game room without trapping. */
@media (max-height: 600px) and (max-width: 980px) {
  .member-shell:has(#pane-replay:not([hidden])) { display: block; }
  .member-shell:has(#pane-replay:not([hidden])) .app-rail,
  .member-shell:has(#pane-replay:not([hidden])) .app-topbar,
  .member-shell:has(#pane-replay:not([hidden])) .game-view > .back-link,
  .member-shell:has(#pane-replay:not([hidden])) .game-detail-head,
  .member-shell:has(#pane-replay:not([hidden])) .mobile-dock { display: none; }
  .member-shell:has(#pane-replay:not([hidden])) .app-stage { min-height: 100dvh; padding: 0; }
  .member-shell:has(#pane-replay:not([hidden])) .workspace-main { max-width: none; padding: 0; }
  .member-shell:has(#pane-replay:not([hidden])) .game-workspace,
  .member-shell:has(#pane-replay:not([hidden])) .replay-pane { height: 100dvh; min-height: 0; overflow: hidden; border: 0; border-radius: 0; }
  .member-shell:has(#pane-replay:not([hidden])) .replay-label {
    position: fixed; top: 7px; left: 7px; z-index: 30; width: auto; height: auto;
    padding: 0; border: 0; background: transparent; pointer-events: none;
  }
  .member-shell:has(#pane-replay:not([hidden])) .replay-label > span,
  .member-shell:has(#pane-replay:not([hidden])) .replay-label small { display: none; }
  .member-shell:has(#pane-replay:not([hidden])) .replay-close {
    display: block; height: 28px; padding: 0 9px; border: 1px solid var(--line);
    border-radius: 5px; background: rgba(8,13,23,.9); pointer-events: auto;
  }
  .member-shell:has(#pane-replay:not([hidden])) .replay-visual { height: 100dvh; padding: 0; }
  .member-shell:has(#pane-replay:not([hidden])) #replay-frame {
    height: 100dvh !important; min-height: 0; border-radius: 0;
  }
}

/* A SHORT phone drops the blocks' sentences and keeps their names (the
   picture still changes under them); a NARROW one keeps the sentences. The
   old rule hid copy on width alone, which cost the narrow-but-tall phone
   (360x640) text it had room for — measured, not assumed. */
@media (max-height: 620px) and (max-width: 760px) {
}

@media (max-width: 360px), (max-height: 610px) and (max-width: 760px) {
  .public-pitch h1 { font-size: clamp(26px, 8.5vw, 31px); }
  .access-copy > p:last-child { display: none; }
  /* The tightest shapes the entrance serves: the account card keeps every
     field, and pays for them in millimetres (measured, not guessed — a
     360x640 phone clipped the sign-in card by 5px before this). */
  .login-card { gap: 5px; }
  .token-wrap, .login-card .button { height: 36px; min-height: 36px; }
  .auth-switch { gap: 2px 12px; }
  .auth-switch button { font-size: 10px; }
}

/* ------------------------------- live coach room ------------------------ */

.live-pill {
  align-self: center; padding: 4px 11px; border-radius: 999px;
  font-size: 11px; font-weight: 700; letter-spacing: .04em;
  color: var(--win); border: 1px solid color-mix(in srgb, var(--win) 45%, transparent);
  background: color-mix(in srgb, var(--win) 12%, transparent);
  animation: livepulse 2.4s ease-in-out infinite;
}
@keyframes livepulse { 50% { opacity: .55; } }
.live-dot { color: var(--win); font-size: 8px; }
.live-pill.done { color: var(--muted); border-color: var(--line-strong); background: var(--surface-2); animation: none; }
.live-room { min-height: calc(100dvh - 250px); display: flex; flex-direction: column; overflow: hidden; }
.live-score {
  display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  gap: clamp(10px, 3vw, 30px); align-items: center;
  min-height: 102px; padding: 14px clamp(16px, 4vw, 38px);
  border-bottom: 1px solid var(--line); background: var(--surface-2);
}
.live-score.is-ended { background: color-mix(in srgb, var(--surface-2) 88%, var(--faint)); }
.live-side { min-width: 0; display: grid; grid-template-columns: auto 1fr; column-gap: 10px; align-items: baseline; }
.live-side.opp { grid-template-columns: 1fr auto; text-align: right; }
.live-side .live-side-label { grid-column: 1 / -1; color: var(--faint); font-size: 8px; font-weight: 760; letter-spacing: .14em; text-transform: uppercase; }
.live-side strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; }
.live-side.opp strong { grid-column: 1; grid-row: 2; }
.live-health { font-size: 29px; font-weight: 720; letter-spacing: -.04em; }
.live-health small { margin-left: 3px; color: var(--muted); font-size: 9px; font-weight: 650; letter-spacing: .04em; }
.live-side-facts { grid-column: 1 / -1; color: var(--muted); font-size: 10px; white-space: nowrap; }
.live-side-facts b { color: var(--ink); font-weight: 680; }
.live-center { min-width: 82px; display: flex; flex-direction: column; align-items: center; text-align: center; }
.live-center i { width: 7px; height: 7px; margin-bottom: 5px; border-radius: 50%; background: var(--win); box-shadow: 0 0 10px color-mix(in srgb, var(--win) 45%, transparent); }
.live-center.is-ended i { background: var(--faint); box-shadow: none; }
.live-center b { font-size: 12px; }
.live-center small { color: var(--muted); font-size: 9px; }
/* The stage (2026-08-31): the thread's flex box plus the one control that
   floats over it — the desktop #live-stage twin. It takes the thread's old
   sizing so the layout under it never moved. */
.live-stage { flex: 1; min-height: 280px; max-height: calc(100dvh - 390px); position: relative; display: flex; flex-direction: column; }
.live-thread { flex: 1; min-height: 0; overflow-y: auto; padding: 24px clamp(18px, 5vw, 70px) 12px; display: flex; flex-direction: column; gap: 15px; }
.live-thread .hintline { color: var(--muted); font-size: 12px; text-align: center; padding: 18px 8px; }
.live-finished { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 12px clamp(14px,4vw,52px); border-top: 1px solid var(--line); background: var(--surface-2); color: var(--muted); font-size: 11px; }
.live-finished b { display: block; color: var(--ink); font-size: 12px; }
.live-finished .button { min-height: 38px; flex: none; padding-inline: 16px; font-size: 11px; }
/* The wait bar (volley-spec §5): the lane's rhythm, drawn on the composer's
   own edge — chrome, never a message. The fill runs the MEASURED wait, so
   what the player learns to expect is what actually happens; this surface
   measures its own, which are longer than the desktop's by one poll. */
.wait-bar { height: 2px; margin: 0 9px 6px; border-radius: 2px;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  overflow: hidden; }
.wait-bar i { display: block; height: 100%; width: 100%;
  background: var(--accent); transform-origin: left center;
  animation: wait-fill linear forwards; }
@keyframes wait-fill { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@media (prefers-reduced-motion: reduce) {
  .wait-bar i { animation: none; transform: scaleX(.35); opacity: .5; }
}
.live-honesty { padding: 0 14px 8px; margin: 0; color: var(--muted); font-size: 10px; text-align: center; letter-spacing: .03em; }
.live-view .composer { border-top: 1px solid var(--line); }
@media (max-width: 760px) {
  body:has(#view-live:not([hidden])) { overflow: hidden; }
  body:has(#view-live:not([hidden])) .app-stage { padding-bottom: 0; }
  body:has(#view-live:not([hidden])) .workspace-main { padding: 0 14px; }
  .live-view { height: calc(100dvh - 122px); display: flex; flex-direction: column; overscroll-behavior: none; }
  .live-view .page-heading { flex: none; min-height: 76px; align-items: center; }
  .live-view .page-heading h1 { font-size: 26px; }
  #live-active { flex: 1; min-height: 0; display: flex; }
  .live-room { flex: 1; min-height: 0; }
  .live-score { min-height: 88px; gap: 7px; padding: 10px; }
  .live-center { min-width: 68px; }
  .live-side { display: block; }
  .live-side strong { display: block; font-size: 11px; }
  .live-health { font-size: 24px; }
  .live-side-facts { display: block; overflow: hidden; text-overflow: ellipsis; font-size: 9px; }
  .live-stage { flex: 1; min-height: 0; max-height: none; }
  .live-thread { padding: 16px 15px 10px; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
  .live-finished { align-items: stretch; flex-direction: column; gap: 9px; padding: 10px; }
  .live-finished .button { width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { scroll-behavior: auto !important; animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* messages-spec (2026-08-26): reply quotes, reactions, the emoji strip, the
   composer's reply bar, the optimistic row. inbox.js emits the classes;
   this is the paint, first cut. */
.inbox-thread .msg-pending .msg-bubble { opacity: .55; }
.inbox-quote { display: flex; gap: 6px; align-items: baseline; max-width: 100%; margin-bottom: 4px; padding: 5px 10px; border-left: 2px solid var(--ember); border-radius: 0 6px 6px 0; background: color-mix(in srgb, var(--surface-3) 70%, transparent); color: var(--muted); font-size: 11.5px; cursor: pointer; }
.inbox-quote b { color: var(--ember-soft); font-weight: 700; white-space: nowrap; }
.inbox-quote span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.inbox-quote-gone { font-style: italic; }
.inbox-reactions { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.inbox-react { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border: 1px solid var(--line); border-radius: 999px; background: color-mix(in srgb, var(--surface-3) 80%, transparent); color: var(--ink); font: inherit; font-size: 12px; line-height: 1.3; cursor: pointer; }
.inbox-react.mine { border-color: color-mix(in srgb, var(--ember) 55%, transparent); background: color-mix(in srgb, var(--ember) 16%, transparent); }
.inbox-react-n { color: var(--muted); font-size: 11px; }
.inbox-actions { display: flex; gap: 2px; margin-top: 3px; opacity: 0; transition: opacity .12s; }
.inbox-thread .msg:hover .inbox-actions, .inbox-thread .msg:focus-within .inbox-actions { opacity: 1; }
@media (hover: none) { .inbox-actions { opacity: .7; } }
.inbox-act { padding: 1px 7px; border: 1px solid var(--line); border-radius: 999px; background: transparent; color: var(--muted); font: inherit; font-size: 12px; line-height: 1.4; cursor: pointer; }
.inbox-act:hover { color: var(--ink); border-color: var(--ember); }
.inbox-picker { display: flex; gap: 2px; margin-top: 4px; padding: 4px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface); }
.inbox-pick { padding: 2px 6px; border: 0; border-radius: 999px; background: transparent; font: inherit; font-size: 16px; line-height: 1; cursor: pointer; }
.inbox-pick:hover { background: color-mix(in srgb, var(--ember) 16%, transparent); }
.inbox-replybar { display: flex; align-items: center; gap: 8px; margin: 0 clamp(16px, 3vw, 32px); padding: 6px 10px; border-left: 2px solid var(--ember); border-radius: 0 6px 6px 0; background: color-mix(in srgb, var(--surface-3) 70%, transparent); color: var(--muted); font-size: 12px; }
.inbox-replybar[hidden] { display: none; }   /* the [hidden]-vs-display trap, closed at the rule */
.inbox-replybar-lead { white-space: nowrap; }
.inbox-replybar-lead b { color: var(--ember-soft); }
.inbox-replybar-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); }
.inbox-replybar-cancel { border: 0; background: transparent; color: var(--muted); font: inherit; font-size: 16px; line-height: 1; cursor: pointer; }
/* R-35: the operator's face — his own photo, beside his rows and on the heading */
.inbox-op-face { width: 20px; height: 20px; border-radius: 50%; object-fit: cover; flex: none; }
.inbox-head-face { width: 44px; height: 44px; align-self: center; }
/* .page-heading is a title-left / actions-right row (space-between). With the
   face as its first child that shoved the title to the far edge (Nick's
   screenshots, 2026-08-26): this heading is face THEN title, packed left. */
#view-messages .page-heading { display: flex; align-items: center; justify-content: flex-start; gap: 14px; }


/* share-spec §4.5 (R-43): the Your-data row — the switch is the server's word
   (aria-checked mirrors the projection, never the press); the ask swaps over
   the Account page (one dialog at a time, the §16 decision shape) and the box
   keeps its one size (R-40) because the panel scrolls and the ask is the only
   child showing. #share-ask stays hidden by an explicit rule: .set-page's
   grid would otherwise beat the UA's [hidden] (the desktop's own R-40 trap). */
.share-row { display: grid; gap: 4px; padding: 6px 0 2px; }
.share-row-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.share-row-name { color: var(--ink); font-size: 13px; font-weight: 650; }
.switch { position: relative; flex: none; width: 40px; height: 22px; padding: 0; border: 1px solid var(--line-strong); border-radius: 11px; background: var(--sunken); cursor: pointer; transition: background .15s, border-color .15s; }
.switch-knob { position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: var(--muted); transition: transform .15s, background .15s; }
.switch.on { border-color: transparent; background: color-mix(in srgb, var(--ember) 80%, black); }
.switch.on .switch-knob { transform: translateX(18px); background: var(--ember-ink); }
.switch[disabled] { opacity: .55; cursor: default; }
.switch:focus-visible { outline: 2px solid var(--ember); outline-offset: 2px; }
#share-ask[hidden] { display: none; }
.set-page.asking > :not(#share-ask) { display: none; }
.share-ask { display: grid; gap: 10px; }
.share-terms { margin: 0; padding-left: 1.1em; display: grid; gap: 6px; color: var(--ink); font-size: 12.5px; line-height: 1.5; }
.share-terms li::marker { color: var(--faint); }
.share-ask-actions { display: grid; gap: 8px; grid-template-columns: 1fr 1fr; }
@media (prefers-reduced-motion: reduce) { .switch, .switch-knob { transition: none; } }

/* R-48, placement unified 2026-08-31 (Nick's mockups): the Ask press floats
   centred over the thread's foot, above the composer — the desktop
   #live-ask twin. "Ask" plus OP's blooming face; the face IS the OP, so the
   button never spells the letters. */
.live-ask { position: absolute; left: 50%; bottom: 20px; transform: translateX(-50%);
  z-index: 3; display: inline-flex; align-items: center; gap: 7px;
  width: auto; padding: 9px 18px; border: 1px solid var(--line-strong);
  border-radius: 12px; background: var(--surface-2); color: var(--ink);
  font: inherit; font-size: 13px; font-weight: 700; white-space: nowrap;
  box-shadow: 0 6px 18px color-mix(in srgb, black 35%, transparent); cursor: pointer; }
.live-ask:hover { border-color: color-mix(in srgb, var(--ember) 60%, transparent); }
.live-ask:disabled { opacity: .5; }
.live-ask .op-face { display: inline-flex; }
/* legibility-spec §5.7 (C5): the install page under the six groups — the same
   op-row grammar; a verdict is a headline row, its facts beside it; a dot is a
   check the machine could state (green / red / grey not read), a kv row is a
   fact with no dot (the page states, the verdict names). */
.op-install-form { display: flex; gap: 8px; margin: 18px 0 0; }
.op-install-form input { flex: 1; max-width: 360px; padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); color: var(--ink); font: inherit; }
.op-install { display: grid; gap: 14px; margin-top: 14px; }
.op-verdicts { display: grid; gap: 6px; padding: 16px 18px; border: 1px solid var(--line); border-radius: var(--radius); background: color-mix(in srgb, var(--surface) 92%, transparent); }
.op-verdicts h2 { margin: 0; font: 760 18px/1.2 var(--font-display); }
.op-verdict { display: grid; gap: 4px; padding: 8px 0; border-top: 1px solid var(--line); }
.op-verdict:first-of-type { border-top: 0; }
.op-verdict b { font-weight: 650; }
.op-verdict.op-headline b { font-size: 15px; }
.op-verdict small { color: var(--muted); font-size: 12px; overflow-wrap: anywhere; }
.op-stale { color: var(--ember); font-weight: 650; }
.op-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--muted); margin-right: 8px; position: relative; top: -1px; }
.op-dot.ok { background: var(--win); }
.op-dot.bad { background: var(--loss); }
