/* ============ DESIGN TOKENS (presentation B 2026-10-14) ============
   Two palettes, one rule (DECISIONS.md 2026-10-03, "System voice"):
   pre-System is a GROUNDED TERMINAL — earthy greens/ambers, monospace grit.
   Post-System, the System overlay slides OVER it — cold blues/whites,
   too-clean geometry, rounded corners that feel wrong. It never replaces the
   survival UI. Every colour below has one name; use the name, not the hex.
   Contrast: --ink/--dim/--muted pass 4.5:1 on --bg, --bg2 and --panel. */
:root {
  /* --- grounded (the survival UI: always there) --- */
  --bg: #0a0f0a;
  --bg2: #0e150e;
  --ink: #c9d6c0;
  --ink-hi: #ccc;
  --dim: #7d9273;          /* was #6b7f62 (4.46:1); now 5.7:1 on --bg */
  --muted: #aaa;
  --muted-2: #8f8f8f;      /* was #888 (5.0:1 on --panel) */
  --acc: #9ae66e;
  --ok: #7fd67f;
  --warn: #e6c66e;
  --amber: #e8b34a;
  --amber-hi: #ffd76a;
  --gold: #ffd54d;
  --bad: #e66e6e;
  --bad-soft: #e05c5c;
  --danger: #ff3b3b;
  --danger-soft: #ff8080;
  --danger-card: #ff5252;
  --line: #1e2b1e;
  --panel: #161616;
  --panel-line: #2c2c2c;
  --panel-edge: #3a3a3a;
  --surface-1: #141414;
  --surface-2: #1a1a1a;
  --surface-warm: #14140f;
  --chip: #222;
  --chip-hi: #333;
  --chip-edge: #444;
  --press: #2a2a2a;
  --white: #fff;
  --black: #000;
  --paper: #e8dcc0;        /* marginalia ink-on-paper warmth */
  --hand-ink: #e3c99a;     /* your handwriting: warm, human */
  --cell-grass: #1a2f1a;
  --cell-dirt: #2f241a;
  --font-mono: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  /* handwriting: system fonts only — offline PWA, zero downloads */
  --font-hand: 'Segoe Print', 'Bradley Hand', 'Comic Neue', 'Chalkboard SE', 'Comic Sans MS', cursive;
  /* --- telegraph voices (each monster speaks its own colour) --- */
  --tg-beam: #ff3b30;
  --tg-beam-hot: #fff3c4;
  --tg-burst: #ff6b35;
  --tg-charge: #ffd23f;
  --tg-charge-2: #ff9f1c;
  --tg-line: #e71d36;
  --tg-target-2: #ff8a80;
  --tg-lock: #9d4edd;
  --tg-doze: #8a5a2b;
  --tg-pep: #ff2d95;
  --tg-hum: #d8d8e4;
  --tg-croak: #39d353;
  --tg-flash: #e8f4ff;
  --tg-heron: #d5dde5;
  --tg-ambush: #5c5c5c;
  /* --- god paths (Dead Cells rule: one colour per path) --- */
  --path-brawler: #e5484d;
  --path-shieldbearer: #4d7cfe;
  --path-hunter: #46a758;
  --path-survivalist: #f5a524;
  --path-caregiver: #e93d82;
  --path-drifter: #8e4ec6;
  --path-explorer: #12a594;
  --path-socialite: #ffd60a;
  --path-miser: #8b8d98;
  --path-forager: #ad5700;
  /* --- the System (cold, too clean) --- */
  --sys: #6ec8e6;
  --sys-cyan: #4df3ff;
  --sys-cyan-rgb: 77, 243, 255;
  --sys-hot: #0ff;
  --sys-night: #001;
  --sys-ink: #eef6ff;
  --sys-edge: #bfe3ff;
  --sys-glow-rgb: 170, 215, 255;   /* was mint rgb(0,255,200): now cold blue-white */
  --sys-glass: rgba(14, 22, 36, .94);
  --sys-radius: 22px;              /* too perfect: every System corner, the same exact curve */
  --sys-font: -apple-system, 'Helvetica Neue', 'Segoe UI', Roboto, Arial, sans-serif;
  --focus: #ffd76a;
}
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { height: 100%; }
body {
  margin: 0; background: var(--bg); color: var(--ink);
  font-family: var(--font-mono);
  font-size: 16px; line-height: 1.5;
  overscroll-behavior: none;
  /* PINCH ZOOM ALLOWED (presentation B 2026-10-14, accessibility): the
     viewport no longer pins maximum-scale. manipulation keeps pinch zoom
     but drops double-tap zoom, so fast taps on buttons never zoom. */
  touch-action: manipulation;
  color-scheme: dark; /* the game is dark by design: native controls match */
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}
#app { max-width: 520px; margin: 0 auto; min-height: 100dvh; display: flex; flex-direction: column; }
#screen { flex: 1; padding: 16px 12px 120px; } /* 120px bottom: room for the fixed D-pad bar */

/* The same journal, read within one viewport. Back never scrolls away. */
body:has(.journal-reader) { height:100dvh; min-height:0; overflow:hidden; }
#app:has(.journal-reader) { height:100%; min-height:0; overflow:hidden; }
#screen:has(.journal-reader) { display:flex; padding:12px; min-height:0; overflow:hidden; }
.journal-reader { display:flex; flex-direction:column; flex:1; min-height:0; min-width:0; }
.journal-reader-head { display:flex; flex:none; align-items:center; justify-content:space-between; gap:12px; padding-bottom:8px; }
.journal-reader-head .title { font-size:20px; margin:0; }
.journal-reader-head .btn { width:auto; min-height:44px; padding:8px 12px; margin:0; }
.journal-reader-body { flex:1; min-height:0; overflow:auto; overscroll-behavior:contain; touch-action:pan-y; }
.hidden { display: none !important; }

/* terminal chrome */
.term-bar { color: var(--dim); font-size: 12px; border-bottom: 1px solid var(--line); padding-bottom: 8px; margin-bottom: 16px; display: flex; justify-content: space-between; }
/* dayticks: the action clock. Thin, subtle — time passing, made visible. */
.dayticks { position: relative; height: 14px; background: var(--surface-1); border-bottom: 1px solid var(--line); margin: -16px 0 12px; overflow: hidden; }
.dayticks-fill { position: absolute; left: 0; top: 0; bottom: 0; background: #3a3a22; transition: width 0.3s; }
.dayticks-lbl { position: absolute; right: 6px; top: 0; font-size: 12px; color: var(--dim); line-height: 14px; }
h1.title { color: var(--acc); font-size: 28px; letter-spacing: 2px; margin: 24px 0 4px; }
.subtitle { color: var(--dim); margin-bottom: 32px; }
.ascii { color: var(--dim); font-size: 12px; line-height: 1.3; white-space: pre; margin: 16px 0; }

/* buttons: big touch targets */
.btn {
  display: block; width: 100%; padding: 16px; margin: 10px 0;
  background: var(--bg2); color: var(--acc); border: 1px solid var(--acc);
  font: inherit; font-size: 17px; text-align: center; cursor: pointer; border-radius: 4px;
}
.btn:active { background: var(--acc); color: var(--bg); }
.btn.ghost { border-color: var(--dim); color: var(--dim); }
.btn:disabled { opacity: .35; }

/* stat bars */
.stat { margin: 8px 0; }
.stat .lbl { display: flex; justify-content: space-between; font-size: 13px; color: var(--dim); }
.bar { height: 10px; background: var(--line); border-radius: 5px; overflow: hidden; margin-top: 4px; }
.bar > i { display: block; height: 100%; background: var(--acc); transition: width .3s; }
.bar.low > i { background: var(--warn); }
.bar.crit > i { background: var(--bad); }
/* THE BANK: past the "fed" line the bar glows gold — the war chest, visible. */
.bar.banked > i { background: linear-gradient(90deg, var(--acc), #ffd34d); }

/* log */
#log { border: 1px solid var(--line); border-radius: 4px; padding: 12px; min-height: 120px; max-height: 32dvh; overflow-y: auto; font-size: 14px; }
#log p { margin: 6px 0; }
#log .sys { color: var(--sys); }
#log .bad { color: var(--bad); }
#log .good { color: var(--acc); }

#toast {
  position: fixed; left: 50%; bottom: 32px; transform: translateX(-50%);
  background: var(--bg2); border: 1px solid var(--acc); color: var(--ink);
  padding: 12px 20px; border-radius: 4px; font-size: 14px; z-index: 50;
}
.card { border: 1px solid var(--line); border-radius: 4px; padding: 14px; margin: 10px 0; background: var(--bg2); }
.card h3 { margin: 0 0 6px; color: var(--acc); font-size: 16px; }
.card p { margin: 4px 0; font-size: 14px; color: var(--dim); }
.card.warn { border-color: var(--warn); }
.card.warn h3 { color: var(--warn); }
.card.codex { border-left: 3px solid var(--warn); }
.card.codex p { color: var(--ink); font-style: italic; }
.row { display: flex; gap: 10px; }
.row .btn { margin: 5px 0; }
.small { font-size: 13px; color: var(--dim); }
.term-line { font-size: 14px; margin: 6px 0; color: var(--ink); }

/* map */
.map { margin: 12px 0; border: 1px solid var(--line); border-radius: 4px; padding: 8px; background: var(--bg2); width: 100%; }
.mrow { display: flex; width: 100%; }
.tile {
  flex: none; width: calc(100% / 7); aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
  font-size: 18px; color: var(--dim); border: 1px solid transparent; border-radius: 4px;
  cursor: pointer; user-select: none; min-height: 40px;
}
/* Detail grid is 9x9, world map is 9x9 (Steve 2026-10-07: was 7x7, tiles overflowed at 100%/7) */
.detail .tile { width: calc(100% / 9); }
.map .tile, .minimap .tile { width: calc(100% / 9); }
/* FOG (presentation B 2026-10-14): the unknown has texture, not content.
   A faint diagonal hatch + grain on near-black — reads as "unexplored" at a
   glance without hinting at what's there. The hatch phase varies per tile
   (fog-v0..3, set from x/y) so fog never reads as a grid of identical
   holes. "If you don't know, it doesn't show." */
.tile.fog, .fogtile {
  color: transparent; border-color: #141a14;
  background-color: var(--fog);
  background-image:
    repeating-linear-gradient(135deg, var(--fog-hatch) 0 1px, transparent 1px 7px),
    radial-gradient(circle at 30% 40%, var(--fog-grain) 0 1px, transparent 1.5px),
    radial-gradient(circle at 70% 75%, var(--fog-grain) 0 1px, transparent 1.5px);
  background-size: auto, 11px 11px, 13px 13px;
}
.fogtile { width: 100%; height: 100%; min-height: 40px; border-radius: 6px; border: 1px solid #141a14; }
.fogtile.fog-v1 { background-position: 3px 0, 4px 6px, 1px 2px; }
.fogtile.fog-v2 { background-position: 5px 2px, 8px 1px, 6px 9px; }
.fogtile.fog-v3 { background-position: 1px 4px, 2px 9px, 10px 3px; }
.tile.me { color: var(--bg); background: var(--acc); font-weight: bold; }
/* (world-map player outline removed Steve 2026-10-06: no "you are here"
   marker — you render as your villager sprite, like anyone else.) */
.tile.beast { color: var(--bad); border-color: var(--bad); animation: pulse 1.6s infinite; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .45; } }
.tile.move { border-color: var(--acc); color: var(--acc); }
.tile.dest { border-color: var(--warn); color: var(--warn); animation: pulse 1.2s infinite; cursor: pointer; }
.tile.spent { opacity: .45; }
/* LIVING WORLD: depletion states — the land shows what you've taken. */
.tile.depleted-lush { /* full stock: normal, no extra styling */ }
.tile.depleted-thinning { opacity: .85; filter: sepia(.25) saturate(.85); }
.tile.depleted-picked { opacity: .7; filter: sepia(.4) saturate(.7); }
.tile.depleted-barren { opacity: .45; filter: grayscale(.6) sepia(.3); border: 1px dashed #4a3f30; }
.tile.worn-path { border-bottom: 2px solid #6b5b3e; }
.tile:active { background: var(--line); }
.cost { font-size: 12px; opacity: .7; font-weight: normal; }
.closeup .tile { width: 44px; height: 44px; font-size: 22px; }
.minimap .tlabel { display: none; }
/* COMPASS (Steve 2026-10-05): one line, not 49 tiles. Tap to expand. */
.ord-compass { margin: 4px 0; }
.compass {
  font-size: 13px; color: var(--muted); padding: 4px 8px;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.compass:active { background: rgba(255,255,255,.05); }
.mapoverlay.hidden { display: none; }
.mapoverlay { position: fixed; inset: 0; z-index: 50; display: flex; align-items: center; justify-content: center; }
.mapoverlay-back { position: absolute; inset: 0; background: rgba(0,0,0,.7); }
.mapoverlay-box { position: relative; background: var(--bg, #0a0c10); border: 1px solid var(--line); border-radius: 12px; padding: 12px; max-width: 90vw; }
.mapoverlay-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; font-size: 14px; }
.detail { margin: 8px 0; width: 100%; }
.drow { display: flex; width: 100%; }
/* ============ D-PAD (walk pad) ============
   Primary movement: 8 directions, one press = one animated step. The pad
   floats over the grid's bottom-right (the thumb zone), never pushing layout
   — the one-screen rule holds. Collapsible to 🧭 for a clear view. */
/* the grid and its overlays are one positioned unit (all screens, not just phone) */
.ord-gridwrap { position: relative; }
.dpad {
  /* DOCKED (Steve 2026-10-05): lives in the fixed bottom bar — no floating,
     no absolute positioning, never edges off-screen. */
  display: grid; grid-template-columns: repeat(3, 38px); grid-auto-rows: 38px; gap: 3px;
  padding: 6px; border-radius: 12px;
  background: rgba(10, 12, 18, .72); border: 1px solid rgba(255, 255, 255, .14);
  touch-action: none; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
  position: relative;
}

.ord-dpad {
  /* IN FLOW (Steve 2026-10-05): below the grid, not fixed. You only need it
     when looking at the map. Scrolls naturally with content. */
  display: flex; justify-content: center; padding: 8px 0;
}
/* LAYOUT STABILITY (dialog-layout 2026-10-11, Steve: "spawns in paragraphs,
   pushes the screen around" — no layout shift): the narration region has a
   FIXED height at 390×844. Dialogue, contest, person card and narration lines
   all render inside it; new lines scroll WITHIN the region, never push the
   grid, d-pad or status bars. One screen, no shift. */
.ord-narration {
  margin: 8px 0;
  height: 184px;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: auto; /* Let a swipe reach Pack when this pane cannot scroll. */
  -webkit-overflow-scrolling: touch;
  flex-shrink: 0;
  /* TALK-CARD-CRUSH-FIX 2026-10-12: this region has a FIXED height by design
     (layout stability — it must never push the grid). As a scroll container
     its automatic flex minimum is 0, so inside the desktop side column
     (max-height: calc(100vh - 32px), overflow-y: auto) flex-shrink crushed it
     to ~0px whenever the column overflowed — the person card
     (max-height: 100%) collapsed with it, painted zero pixels, and its Talk
     button failed hit-testing as "obscured". flex-shrink: 0 holds the fixed
     height; the column scrolls instead. */
}
/* The box fills its region exactly — swapping dialogue ↔ contest ↔ person
   card never changes the region's footprint. Speaker stays pinned; the
   current line scrolls inside; choices and the ▼ stay visible at the bottom. */
.ord-narration .dialogue-box {
  height: 100%;
  margin: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.ord-narration .dialogue-box.narr-box { margin: 0; }
.ord-narration .dialogue-box .dlg-speaker,
.ord-narration .dialogue-box .dlg-head,
.ord-narration .dialogue-box .dlg-next,
.ord-narration .dialogue-box .inline-btns { flex: 0 0 auto; }
/* CHOICE-LIST CLIP FIX (Steve phone 2026-10-17: 3rd chat option cut off mid-
   button): the box is height:100% of the fixed 184px region with
   overflow:hidden, and flex:none on .dlg-choices meant a tall option list
   overflowed the box instead of shrinking — the box clipped it. Let the list
   shrink (flex 0 1 auto, min-height:0); its own overflow-y:auto + max-height
   then scroll the options INSIDE the box. Header/next never shrink. */
.ord-narration .dialogue-box .dlg-choices { flex: 0 1 auto; min-height: 0; }
/* Keep the board and replies fixed, but let the text itself scroll when a
   beat exceeds its space (phone playtest, Steve 2026-10-19). Character-count
   pagination cannot guarantee a fit across fonts, zoom and small viewports. */
.ord-narration .dialogue-box .dlg-line {
  flex: 1 1 auto;
  min-height: 0;
  max-height: none;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
.ord-narration .dialogue-box .dlg-history {
  flex: 1 1 auto;
  min-height: 0;
  max-height: none;
  overflow-y: auto;
}
/* Person cards are the tallest tenant — they scroll inside the region. */
/* PERSON-CARD CONTAINMENT (Steve phone 2026-10-14: card bottom "partially
   covered" by the info bar): definite height, not max-height % — the scroll
   container must always form inside the fixed 184px region. Drop the vertical
   margin (it pushed the card past the region's bounds). Sticky header/buttons
   keep Close and actions reachable while the middle scrolls. */
.ord-narration .inlinecard {
  height: 100%; margin: 0; overflow-y: auto; box-sizing: border-box;
}
/* MUSTER-DIALOG-FIX (2026-10-12): the card's action buttons ("Ask for help")
   sit at the bottom of a ~400px card inside a 184px region — they were clipped
   below the fold ("not_visible"/"obscured" in browser automation), blocking
   titan mustering. The header (Close) and the action buttons are now sticky
   within the card's scroll: Close is always tappable at the top, Ask-for-help
   and the other actions are always tappable at the bottom, no matter how far
   the card content scrolls. */
.ord-narration .inlinecard .inline-head {
  position: sticky; top: 0; z-index: 2;
  background: var(--surface-warm); /* match .inlinecard so content scrolls under, not through */
  padding-top: 2px; padding-bottom: 6px;
}
.ord-narration .inlinecard .inline-btns {
  position: sticky; bottom: 0; z-index: 2;
  background: var(--surface-warm);
  padding-top: 8px; padding-bottom: 4px;
  margin-bottom: 0;
}
body.system-live .ord-narration .inlinecard .inline-head,
body.system-live .ord-narration .inlinecard .inline-btns {
  background: #10140f; /* keep the System-live tint consistent */
}
.ord-combathead { margin: 4px 0; }
.ord-actions { margin: 8px 0; }
/* .ord-bottombar removed (Steve 2026-10-05): D-pad lives in flow below grid */
.dpbtn {
  font-size: 17px; line-height: 1; color: var(--white);
  background: rgba(255, 255, 255, .08); border: 1px solid rgba(255, 255, 255, .16);
  border-radius: 9px; min-height: 38px; padding: 0; cursor: pointer;
  touch-action: none; user-select: none; -webkit-user-select: none;
}
.dpbtn:active, .dpbtn.held { background: rgba(255, 255, 255, .24); transform: scale(.93); }
.dpbtn.dpstop { font-size: 14px; color: #ff9a8a; }
.dpmin {
  position: absolute; top: -10px; right: -6px; width: 26px; height: 26px;
  border-radius: 50%; font-size: 16px; line-height: 1; color: var(--white);
  background: rgba(10, 12, 18, .85); border: 1px solid rgba(255, 255, 255, .25);
  cursor: pointer; padding: 0;
}
.dpshow {
  position: absolute; right: 8px; bottom: 8px; z-index: 20;
  width: 52px; height: 52px; border-radius: 50%; font-size: 24px;
  background: rgba(10, 12, 18, .72); border: 1px solid rgba(255, 255, 255, .2);
  cursor: pointer; touch-action: none;
}
/* ENTITY GLIDE: entities opt into FLIP animation via data-ent. They must be
   transformable (inline-block) — the step animator slides them tile-to-tile
   instead of letting re-renders teleport them. */
.detail .cell [data-ent] { display: inline-block; will-change: transform; }
.detail .cell .vent { display: inline-block; }
@media (prefers-reduced-motion: reduce) {
  .detail .cell [data-ent] { will-change: auto; }
}
/* 9x9 detail grid: FLUID — fills the column on any screen. Cells scale with
   the viewport; emoji scale with the cell (em-based children below). */
.cell {
  flex: none; width: calc(100% / 9); aspect-ratio: 1;
  display: flex; align-items: center; justify-content: center;
  font-size: clamp(16px, 5.2vw, 44px);
  border: 1px solid transparent; border-radius: 4px;
  cursor: pointer; user-select: none;
}
.cell.grass { background: var(--cell-grass); }
/* SEALED FIGHT (Steve 2026-10-11): fleeing is prevented — the edge reads as
   a red barrier. Shown ONLY when sealed; normal fights get no edge marking. */
.cell.barrierSealed { outline: 2px solid rgba(255, 70, 60, .9); outline-offset: -2px; }
.cell.dirt { background: var(--cell-dirt); }
/* SCORCHED EARTH: ASH, not shadow. The beam ruins what it crosses — pale,
   desaturated, unmistakable even at night. The morning after tells the story.
   Scorched cells give no forage and recover in ~3 days. */
.cell.scorched { background-color: rgba(190, 190, 178, .38); filter: grayscale(.85) brightness(1.3); box-shadow: inset 0 0 14px rgba(30, 25, 20, .55); }
/* BEAM LANE: windup telegraph (codex-gated) — the warning. Amber, pulsing. */
.cell.beamLane { outline: 2px solid var(--tg-beam); outline-offset: -2px; background-color: rgba(255, 60, 40, .28); animation: beamPulse 0.8s infinite alternate; }
@keyframes beamPulse { from { background-color: rgba(255, 60, 40, .18); } to { background-color: rgba(255, 60, 40, .38); } }
/* BEAM LIVE: the kamehameha. NOT a highlight — a column of energy. White-hot
   core, orange bloom, punches through the night dimming (the .detail night
   filter multiplies to ~0.42, so the core overdrives past 1.0 to stay blinding). */
.cell.beamLane.beamLive {
  background: linear-gradient(180deg, rgba(255, 255, 244, .96), rgba(255, 196, 80, .88));
  outline: 2px solid var(--tg-beam-hot); outline-offset: -2px;
  box-shadow: 0 0 22px 9px rgba(255, 170, 60, .9), inset 0 0 16px rgba(255, 255, 255, .95);
  filter: brightness(2.3);
  animation: beamLivePulse 0.22s infinite alternate;
  position: relative; z-index: 2;
}
@keyframes beamLivePulse { from { filter: brightness(2.0); } to { filter: brightness(2.6); } }
/* BEAM SOURCE: the deer's tile while charging/firing. The beam EMANATES from
   the beast — eyes blazing, the origin point of the column. */
.cell.beamSource {
  box-shadow: 0 0 26px 10px rgba(255, 224, 130, .95), inset 0 0 20px rgba(255, 255, 224, .95);
  filter: brightness(1.9);
  animation: beamSourcePulse 0.4s infinite alternate;
  position: relative; z-index: 3;
}
@keyframes beamSourcePulse {
  from { box-shadow: 0 0 16px 6px rgba(255, 224, 130, .65), inset 0 0 14px rgba(255, 255, 224, .65); }
  to { box-shadow: 0 0 34px 14px rgba(255, 224, 130, 1), inset 0 0 26px rgba(255, 255, 224, 1); }
}
/* BEAM LIGHT: halo — the beam lights up the night around it. */
.cell.beamLight { filter: brightness(1.45); background-color: rgba(255, 190, 90, .20); }
/* BEAM GHOST: where the beam just was — the sweep arc reads as motion. */
.cell.beamGhost { outline: 2px solid rgba(255, 130, 40, .55); outline-offset: -2px; background-color: rgba(255, 130, 40, .22); }
/* ATTACK VISUALS (Steve 2026-10-05): the grid IS the telegraph. Pattern-specific. */
/* BURST: radius circle — pulsing red-orange, danger zone */
.cell.burstRadius { outline: 2px solid var(--tg-burst); outline-offset: -2px; background-color: rgba(255, 107, 53, .25); animation: burstPulse 0.9s infinite alternate; }
@keyframes burstPulse { from { background-color: rgba(255, 107, 53, .18); } to { background-color: rgba(255, 107, 53, .35); } }
/* CHARGE: lane — directional stripes, the beast is coming through */
.cell.chargeLane { outline: 2px solid var(--tg-charge); outline-offset: -2px; background: repeating-linear-gradient(45deg, rgba(255, 210, 63, .25), rgba(255, 210, 63, .25) 4px, rgba(255, 210, 63, .1) 4px, rgba(255, 210, 63, .1) 8px); animation: chargePulse 0.6s infinite alternate; }
@keyframes chargePulse { from { outline-color: var(--tg-charge); } to { outline-color: var(--tg-charge-2); } }
/* LINE: straight line — clean red line */
.cell.lineCells { outline: 2px solid var(--tg-line); outline-offset: -2px; background-color: rgba(231, 29, 54, .22); }
/* SINGLE: target tile — crosshair, one tile */
.cell.targetTile { outline: 3px solid var(--tg-beam); outline-offset: -3px; background-color: rgba(255, 59, 48, .3); animation: targetBlink 0.5s infinite alternate; }
@keyframes targetBlink { from { outline-color: var(--tg-beam); } to { outline-color: var(--tg-target-2); } }
/* DIRECT: lock-on — corner brackets, the beast has you */
.cell.lockOn { outline: 2px solid var(--tg-lock); outline-offset: -2px; background-color: rgba(157, 78, 221, .2); box-shadow: inset 0 0 12px rgba(157, 78, 221, .4); }
/* WAVE 1 STYLE VOICES (Steve 2026-10-06): burstStyle/chargeStyle finally
   consumed — each monster's attack reads in its own visual voice. All
   knowledge-gated upstream in tbAllTelegraphCells, like every bucket. */
/* DOZE LANE: the bulldozer's charge — a wall of dust and debris, not the
   deer's clean yellow hazard stripes. Brown, churning; ➤ arrows (inline
   span, dozeAngle) ride the charge direction. */
.cell.dozeLane { position: relative; outline: 2px solid var(--tg-doze); outline-offset: -2px;
  background: repeating-linear-gradient(45deg, rgba(150,100,50,.38), rgba(150,100,50,.38) 6px, rgba(110,72,35,.16) 6px, rgba(110,72,35,.16) 12px);
  animation: dozeChurn 0.7s infinite alternate; }
@keyframes dozeChurn { from { filter: brightness(1.0); } to { filter: brightness(1.3); } }
/* PEP BURST: the hype horn's pep-burst — hot magenta/pink concentric sound
   rings, not the orange burstRadius. ♪ glyphs via ::after; rings via
   repeating-radial-gradient. Distinct from orange burstRadius, white biHot,
   and the swarm's white strobe. */
.cell.pepBurst { position: relative; outline: 2px solid var(--tg-pep); outline-offset: -2px;
  background: repeating-radial-gradient(circle at center, rgba(255,45,149,.38) 0 5px, rgba(255,45,149,.10) 5px 11px);
  animation: pepRing 0.8s infinite alternate; }
.cell.pepBurst::after { content: '♪'; position: absolute; inset: 0; display: flex;
  align-items: center; justify-content: center; font-size: 14px; line-height: 1;
  color: rgba(255,140,200,.9); text-shadow: 0 0 4px rgba(0,0,0,.8); pointer-events: none; }
@keyframes pepRing { from { filter: brightness(1.0); } to { filter: brightness(1.45); } }
/* SWARM HUM: hummice — dotted "many small bodies" texture on the burst
   cells; each declaring mouse gets an expanding ◎ hum ring (inline span,
   .swarmRing) — the hum's many throats. Muted slate + bright dots: nothing
   else on the grid dots. */
.cell.swarmHum { position: relative; outline: 2px dotted var(--tg-hum); outline-offset: -2px;
  background-color: rgba(140,140,160,.28);
  background-image: radial-gradient(rgba(240,240,250,.6) 1.6px, transparent 2.4px);
  background-size: 9px 9px;
  animation: humSwell 1.1s infinite alternate; }
@keyframes humSwell { from { filter: brightness(1.0); } to { filter: brightness(1.35); } }
.swarmRing { animation: humRing 1.1s ease-out infinite; }
@keyframes humRing { from { transform: scale(.7); opacity: .95; } to { transform: scale(1.3); opacity: .2; } }
/* RESONANT BURST: the belltoad's croak — green-tinged concentric rings with
   a slow throat-pulse. No other telegraph is green. */
.cell.resonantBurst { position: relative; outline: 2px solid var(--tg-croak); outline-offset: -2px;
  background: repeating-radial-gradient(circle at center, rgba(57,211,83,.32) 0 5px, rgba(57,211,83,.08) 5px 12px);
  animation: croakPulse 1.2s infinite alternate; }
@keyframes croakPulse { from { filter: brightness(1.0); } to { filter: brightness(1.5); } }
/* FLASH BURST: the mirrormoth's mirror-flash — hard silver blink, cool
   white. Distinct from the camera swarm's smooth white strobe (slower,
   harder on/off, blue-silver tint). */
.cell.flashBurst { position: relative; outline: 2px solid var(--tg-flash); outline-offset: -2px;
  background-color: rgba(232,244,255,.35);
  /* mirror glint: a sheen slash across each cell — "catching light wrong".
     Static-visible, so the flash reads even between blinks. */
  background-image: linear-gradient(135deg, transparent 42%, rgba(255,255,255,.65) 50%, transparent 58%);
  animation: flashBlink 0.55s steps(2, jump-none) infinite; }
@keyframes flashBlink { 0%, 49% { filter: brightness(1.7); } 50%, 100% { filter: brightness(0.85); } }
/* HERON STRIKE (Steve 2026-10-06): the white noise heron's Spearfish Strike —
   a needle out of TV static. Pale gray-white, dashed outline, snow-grain
   fill that flickers. Distinct from the deer's harsh red beamLane and the
   moth's hard silver flashBurst: this is soft, wrong, staticky. */
.cell.heronStrike { position: relative; outline: 2px dashed var(--tg-heron); outline-offset: -2px;
  background-color: rgba(205,215,225,.16);
  background-image: radial-gradient(rgba(255,255,255,.8) 1px, transparent 1.7px),
    radial-gradient(rgba(150,160,175,.55) 1px, transparent 1.7px);
  background-size: 7px 7px, 11px 11px;
  animation: staticFlicker .3s infinite alternate; }
@keyframes staticFlicker { from { filter: brightness(1); } to { filter: brightness(1.45); } }
/* AMBUSH: ambush zone — dark, lurking */
.cell.ambushZone { outline: 2px dashed var(--tg-ambush); outline-offset: -2px; background-color: rgba(40, 40, 40, .4); }
/* VAGUE: pattern not learned yet — dimmer, you see danger but not the shape */
.cell.vague { opacity: 0.5; filter: saturate(0.5); }
/* IGNITE FLASH: full-screen flinch on discharge. */
#beamflash { position: fixed; inset: 0; pointer-events: none; z-index: 90; opacity: 0;
  background: radial-gradient(circle, rgba(255, 242, 205, .9), rgba(255, 165, 60, .38) 55%, transparent 80%); }
#beamflash.go { animation: beamFlashAnim 0.38s ease-out; }
@keyframes beamFlashAnim { 0% { opacity: 0; } 7% { opacity: 1; } 100% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  #beamflash.go { animation-duration: 0.15s; }
  .cell.beamLane.beamLive, .cell.beamSource { animation-duration: 1.2s; }
  .cell.dozeLane, .cell.pepBurst, .cell.swarmHum, .cell.resonantBurst, .cell.flashBurst, .cell.heronStrike, .swarmRing { animation: none; }
}
/* BUILDING VISUAL LANGUAGE: walls are dark, doors glow, halls are plain floor. */
/* A wall should never look like rubble. A door should never look like a hallway. */
.cell.c-wall { background: #0d0d0f; }
.cell.c-door { background: #14301a; outline: 2px solid var(--ok); outline-offset: -2px; }
.cell.c-hall { background: #26262b; }
.cell.c-bunk { background: #23232a; }
.cell.c-lodge { background: #2a2118; outline: 2px solid #c9a86a; outline-offset: -2px; }
.cell.c-gym, .cell.c-class, .cell.c-office, .cell.c-apt, .cell.c-cube,
.cell.c-break, .cell.c-conf, .cell.c-lobby, .cell.c-bay, .cell.c-sanct,
.cell.c-base, .cell.c-dock { background: #202027; }
.cell.c-fire { background: #3a1a0d; }
.cell.c-tent { background: #1f2a1f; }
.cell.c-rubble { background: #2b2b26; }
/* (player marker removed Steve 2026-10-06: you render as your villager
   sprite, like anyone else. .cell.me remains as a telegraph hook only.) */
.cell.me .vent.diveTarget, .cell.me .vent.sbLockTarget { position: relative; }
.minimap .tile .mface {
  display: inline-block; font-size: 1.05em; color: var(--sys-cyan);
  text-shadow: 0 0 3px var(--black); line-height: 1;
}
/* PLAYER ON THE WORLD MAP (Steve 2026-10-06): your villager sprite, no marker.
   Same size language as villager tokens on the grid. */
.minimap .tile .mface.msprite { display: inline-block; width: 3em; height: 3em; vertical-align: middle; } /* Steve 2026-10-06: bigger player */
.minimap .tile .mface.msprite .vsprite { display: inline-block; width: 100%; height: 100%; }
.minimap .tile .mface.msprite .vsprite svg.sprite { width: 100%; height: 100%; display: block; }
/* SHARED TILES (Steve 2026-10-06): ground someone showed you, not ground
   you've walked. Slightly muted — secondhand knowledge looks secondhand. */
.minimap .tile.shared { filter: saturate(.55) brightness(.82); }
/* inline tile panel: context actions without leaving the world */
.tilepanel {
  border: 1px solid var(--line); border-left: 3px solid var(--sys-cyan);
  border-radius: 6px; padding: 8px 10px; margin: 8px 0;
  background: var(--bg2); position: relative;
}
/* header: title left, ✕ right, proper flex — the ✕ can NEVER overlap text.
   (was: position:absolute + width:100% from .btn = full-width bar over the title.) */
.tp-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.tp-head b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0; }
.tp-x {
  position: static; width: auto; flex-shrink: 0; margin: 0;
  padding: 3px 8px !important; font-size: 13px; line-height: 1.2;
}
/* villagers: emoji on top, TINY name underneath. never swallows the tile. */
.cell.villager, .cell.me { flex-direction: column; line-height: 1.1; }
.cell.villager .vtoken, .cell.me .vtoken { font-size: 1.2em; } /* Steve 2026-10-06: bigger */
/* VILLAGER SPRITES (Steve 2026-10-06): generated portraits sit in the token. */
/* PLAYER SIZE - ROOT CAUSE FIX (Steve 2026-10-06):
   The cell is `width: calc(100%/9)` with `font-size: clamp(16px, 5.2vw, 44px)`.
   The sprite was `width: 2em` — em scales with font-size (vw-based), cell scales
   with container width. Different rates = sprite breaks layout on resize.
   FIX: size sprite as % of CELL (not em). Structurally tied, cannot regress.
   Cell is flex with align/justify center; sprite is 90% of cell, overflow visible
   for slight overlap. The border shows the standing tile. */
.cell.me { position: relative; }
/* PLAYER PROMINENCE (Steve 2026-10-07): player must be easy to spot on the grid.
   The vtoken fills the cell; the sprite scales with cell size (not a fixed px cap).
   Name sits below in the column flow. */
.cell.me .vtoken { display: flex; align-items: center; justify-content: center;
  width: 100%; flex: 1 1 auto; min-height: 0; }
.cell.me .vtoken .vsprite { display: block; width: 100%; height: 100%;
  max-width: 56px; max-height: 56px;
  overflow: visible; pointer-events: none; }
.cell.me .vtoken .vsprite svg.sprite { width: 100%; height: 100%; display: block;
  overflow: visible; }
.cell.villager .vtoken .vsprite { display: inline-block; width: 2em; height: 2em; vertical-align: -0.2em; }
/* STOOD-ON GLYPH (gap-playtest9 2026-11-02): the ground under the player's
   feet stays visible as a small corner chip — the player token no longer
   hides whether the stood-on tile is a plant, bush or tree. Taps still land
   on the cell (pointer-events:none); the chip is a marker, not a button. */
.cell.me .stood-glyph { position: absolute; right: 1px; bottom: 1px; z-index: 3;
  width: 36%; height: 36%; min-width: 14px; min-height: 14px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(10, 12, 18, .72); border: 1px solid rgba(255, 255, 255, .25);
  border-radius: 6px; font-size: 12px; line-height: 1; pointer-events: none; overflow: hidden; }
.cell.me .stood-glyph svg { width: 100% !important; height: 100% !important; display: block; }
.cell.me .stood-glyph .plantsprite, .cell.me .stood-glyph .treesprite { width: 100%; height: 100%; display: flex; }
.cell.me .stood-glyph.depleted { opacity: .55; filter: grayscale(.7); }
/* HARD CONSTRAINT (Steve 2026-10-06): sprites must not break the grid layout.
   Villager/monster sprites capped at 40px. Player has its own larger cap above. */
.cell .vsprite, .detail .vsprite { max-width: 100% !important; max-height: 100% !important; }
/* PLAYER EXCLUDED (Steve phone 2026-10-17: player rendered tiny): the generic
   40px auto-size cap below used to catch the player's svg too, defeating the
   player-specific width:100%/height:100% rule above via !important. The player
   keeps cell-relative sizing; everyone else keeps the 40px cap. */
.cell.villager .vsprite svg, .detail .cell:not(.me) .vsprite svg { max-width: 40px !important; max-height: 40px !important; width: auto !important; height: auto !important; }
.cell.villager .vtoken .vsprite svg.sprite { width: 100%; height: 100%; display: block; }
/* HUMANOID MONSTER SPRITES (Steve 2026-10-06): calm SVG on the grid. */
/* SPRITE SIZE (Steve 2026-10-06): sprites must FILL the cell, not hide in the corner.
   Emoji set the bar: font-size clamp(16px, 5.2vw, 44px). Sprites match it. */
/* ACTING HIGHLIGHT (Steve 2026-10-06): the currently-acting monster pulses.
   Combat has cadence — you see WHO is acting, not just the aftermath. */
.cell.acting {
  animation: acting-pulse 0.6s ease-in-out infinite;
  z-index: 10;
}
@keyframes acting-pulse {
  0%, 100% { box-shadow: 0 0 0 2px rgba(255,200,80,.9), 0 0 12px 4px rgba(255,180,60,.5); }
  50% { box-shadow: 0 0 0 3px rgba(255,220,100,1), 0 0 20px 8px rgba(255,180,60,.7); }
}
.cell.creature .csprite { display: inline-block; width: 2.2em; height: 2.2em; vertical-align: middle; }
.cell.creature .csprite svg.sprite { width: 100%; height: 100%; display: block; }
.cell .msprite { display: inline-block; width: 2.2em; height: 2.2em; vertical-align: middle; }
.cell .msprite svg.sprite { width: 100%; height: 100%; display: block; }
/* MULTI-TILE SPAN (Steve 2026-10-12): a size-N monster draws ONE sprite on
   its anchor (top-left) tile; the sprite overflows to cover the whole N×N
   footprint. .cell is position:relative so the span anchors to the tile.
   pointer-events:none — taps land on the cell (tap handling is cell-level),
   and the z-index keeps the body above neighboring tiles. */
.cell { position: relative; }
.cell .csprite.multitile, .cell .msprite.multitile {
  position: absolute; left: 0; top: 0; z-index: 2; pointer-events: none;
  display: block; width: calc(var(--body-size, 2) * 100%); height: calc(var(--body-size, 2) * 100%);
}
.cell .csprite.multitile.mts3, .cell .msprite.multitile.mts3 { width: 300%; height: 300%; }
.cell .csprite.multitile svg.sprite, .cell .msprite.multitile svg.sprite {
  width: 100%; height: 100%; display: block;
}
.cell .plantsprite { display: inline-block; width: 2em; height: 2em; vertical-align: middle; }
.cell .plantsprite svg.sprite { width: 100%; height: 100%; display: block; }
.cell .treesprite { display: inline-block; width: 2.4em; height: 2.4em; vertical-align: middle; }
.cell .treesprite svg.sprite { width: 100%; height: 100%; display: block; }
/* VILLAGER PORTRAIT (Steve 2026-10-06): generated portrait in the person card. */
.vportrait { display: inline-block; width: 44px; height: 44px; vertical-align: middle; margin-right: 8px; }
.vportrait svg.sprite { width: 100%; height: 100%; display: block; }
.cell.villager .vname, .cell.me .vname {
  font-size: .39em; color: #9a9a9a;
  /* max-width must be relative to the TILE, not the tiny font: 1.6em of a
     .39em font is ~1 character wide, which hid whole names. */
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  text-shadow: 0 1px 2px var(--black);
}
/* world map: fluid tiles, capped width so it doesn't get absurd on desktop */
.minimap { max-width: 480px; }
.minimap .tile { font-size: clamp(12px, 3.6vw, 22px); }
.minimap .tile.dest { outline: 2px solid var(--warn); }
/* MAP INFO (Steve 2026-10-06): tap-through tile info inside the map overlay. */
.map-info {
  margin: 8px 4px 0; padding: 8px 12px;
  background: rgba(255,255,255,.06); border-radius: 8px;
  font-size: 14px; line-height: 1.4; min-height: 20px;
}
.map-info .dim { opacity: .6; }
.minimap .tile:not(.dest):not(.me) { opacity: .75; }
.cell.plantcell { cursor: default; }

/* close-up: the node you're on, zoomed — 5x5 neighborhood, you at the center */
.closeup { margin: 12px 0; border: 1px solid var(--line); border-radius: 6px; padding: 10px; background: var(--bg2); }
.closeup .tile { font-size: 30px; min-height: 56px; cursor: default; }
.closeup .tile.here { border: 2px solid var(--acc); border-radius: 8px; }
.closeup .tile.offmap { visibility: hidden; }
.closeup .tile { flex-direction: column; }
.tlabel { font-size: 12px; line-height: 1.2; color: var(--warn); margin-top: 2px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* action rows */
.actions { display: flex; gap: 8px; margin: 8px 0; }
.actions .btn.sm { margin: 0; padding: 14px 8px; font-size: 15px; flex: 1; }
.linklike { background: none; border: none; color: var(--acc); font: inherit; font-size: 14px; cursor: pointer; text-decoration: underline; padding: 4px; }

/* log: 3 lines max. It's a feed, not a novel. */
.log { border: 1px solid var(--line); border-radius: 4px; padding: 8px 10px; margin-top: 10px; max-height: 84px; overflow-y: auto; background: var(--bg); }
.itempick { cursor: pointer; }
.itempick.sel { border-color: var(--ok); background: var(--cell-grass); }

/* SYSTEM ARRIVAL: the sky splits open. Animated. */
.system-arrival-overlay {
  position: fixed; top: 0; left: 0; right: 0; bottom: 0;
  background: var(--black); z-index: 1000;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
  animation: skySplit 3s ease-out forwards;
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding-top: calc(env(safe-area-inset-top, 0px) + 18px);
  padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 90px);
  box-sizing: border-box;
}
@keyframes skySplit {
  0% { background: var(--black); }
  30% { background: linear-gradient(to bottom, #000 45%, #0ff 50%, #000 55%); }
  60% { background: linear-gradient(to bottom, #001 40%, #0ff 50%, #001 60%); }
  100% { background: var(--sys-night); }
}
.system-crack {
  position: absolute; top: 0; bottom: 0; left: 50%;
  width: 2px; background: var(--sys-hot);
  box-shadow: 0 0 20px var(--sys-hot), 0 0 40px var(--sys-hot);
  animation: crackGrow 3s ease-out forwards;
  pointer-events: none;
}
@keyframes crackGrow {
  0% { transform: scaleY(0); opacity: 0; }
  35% { transform: scaleY(1); opacity: 1; }
  100% { transform: scaleY(1); opacity: 0.08; }
}
.system-text {
  color: var(--sys-hot); font-family: monospace; text-align: center;
  font-size: 1.2em; margin: 10px; max-width: 80%;
  animation: textFlicker 0.5s infinite alternate;
  text-shadow: 0 0 10px var(--sys-hot);
}
@keyframes textFlicker {
  0% { opacity: 0.8; }
  100% { opacity: 1; }
}
.system-window {
  border: 1px solid var(--sys-hot); padding: 10px; margin: 5px;
  background: rgba(0, 255, 255, 0.05);
  animation: windowSlide 1s ease-out;
  max-width: 80%;
  /* the System's first windows already have its too-perfect corners */
  border-radius: var(--sys-radius);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .05);
}
@keyframes windowSlide {
  0% { transform: translateY(-20px); opacity: 0; }
  100% { transform: translateY(0); opacity: 1; }
}
.system-typewriter {
  overflow: hidden; white-space: pre-wrap;
  animation: typewriter 4s steps(100) forwards;
}
/* DAY 7 CINEMATIC: staged beats, tap to continue. Kicker labels the beat;
   narration renders in a warm voice distinct from the System's cyan. */
.system-beat-kicker {
  color: var(--amber-hi); font-family: monospace; font-size: 12px;
  letter-spacing: 4px; text-transform: uppercase; opacity: .85; margin-bottom: 6px;
}
.system-narr { color: #ffe9c9; font-style: italic; }

.emptybush { opacity: 0.4; filter: grayscale(50%); }

/* DEPLETED: a foraged bush/tree, picked clean but recovering. Dimmed and
   wilted — you can SEE what you've taken. Clears when it regrows. */
.cell.depleted { opacity: 0.45; filter: grayscale(55%) sepia(.25); }
.cell.depleted::after {
  content: '·'; position: absolute; bottom: 1px; right: 3px;
  font-size: 12px; color: var(--dim); opacity: .8;
}

/* TERRAFORM (Steve 2026-10-06): monster-reshaped ground. The tint is the
   tell — wreckage, paper, craters and scorched earth are physically there,
   so they render ungated. The mechanical effect is learned by touch. */
.cell[class*="terr-"] { position: relative; }
.cell.terr-trample { background-color: rgba(139, 90, 43, .38); }
.cell.terr-paper { background-color: rgba(225, 225, 215, .22); }
.cell.terr-crater { background-color: rgba(15, 15, 15, .55); box-shadow: inset 0 0 8px var(--black); }
.cell.terr-scorch { background-color: rgba(80, 25, 10, .5); }
.cell[class*="terr-"]::after {
  position: absolute; bottom: 1px; right: 3px;
  font-size: 12px; opacity: .75;
}
.cell.terr-trample::after { content: '≋'; color: #c08a3e; }
.cell.terr-paper::after { content: '▤'; color: #e8e8e0; }
.cell.terr-crater::after { content: '○'; color: #777; }
.cell.terr-scorch::after { content: '▲'; color: #e05545; }
.cell.terr-claimed { background-color: rgba(120, 90, 20, .35); }
.cell.terr-claimed::after { content: '⚖'; color: #d4a017; }
/* THE MODERATOR (Steve 2026-10-06): suppression field — bruised purple, the
   color of a rule. Shadowban tiles go near-black: a dead channel. */
.cell.terr-suppressed { background-color: rgba(150, 40, 180, .28); box-shadow: inset 0 0 6px rgba(150, 40, 180, .5); }
.cell.terr-suppressed::after { content: '⊘'; color: #c77dff; }
.cell.terr-shadowed { background-color: rgba(8, 4, 14, .72); box-shadow: inset 0 0 10px var(--black); }
.cell.terr-shadowed::after { content: '◾'; color: #3a2b52; }

/* PWA update banner: subtle top strip, appears only when a newer build is ready. */
#update-banner {
  position: fixed; top: 0; left: 0; right: 0; z-index: 60;
  background: var(--bg2); border-bottom: 1px solid var(--acc);
  color: var(--dim); font-size: 13px; text-align: center; cursor: pointer;
  padding: calc(8px + env(safe-area-inset-top)) 16px 8px;
}
#update-banner b { color: var(--acc); }
#update-banner.hidden { display: none; }

/* contextual action strip: quiet, persistent, updates as you move.
   SINGLE ROW, horizontal scroll — the action zone never grows vertically. */
.contextbar {
  display: flex; flex-wrap: nowrap; gap: 6px; margin: 8px 6px 0;
  padding: 4px 2px 8px; overflow-x: auto;
  border-top: 1px solid var(--line);
  -webkit-overflow-scrolling: touch;
}
.contextbar:empty { display: none; }
.contextbar .ctx-btn { flex: 0 0 auto; }
.ctx-label { font-size: 12px; color: var(--muted-2); align-self: center; margin-right: 2px; }
/* COMPACT STATUS: stat chips. Same info as the old tall rows at ~1/3 the
   height — vitals stay above the fold. Low states glow red. */
.statgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 5px 12px; margin: 8px 6px 0; }
.statc { font-size: 12px; min-width: 0; }
.statc > span { display: flex; justify-content: space-between; white-space: nowrap; }
.sbar { height: 5px; background: #232323; border-radius: 3px; margin-top: 2px; overflow: hidden; }
.sbar > i { display: block; height: 100%; background: var(--ok); border-radius: 3px; }
.statc.low .sbar > i { background: var(--bad-soft); }
.statc.low > span { color: var(--bad-soft); font-weight: bold; }

/* SURROUNDINGS STRIP: glanceable "where am I / what's here". Tap for details. */
.surround-strip {
  display: block; width: calc(100% - 12px); text-align: left;
  background: var(--bg2); border: 1px solid var(--line); border-radius: 10px;
  padding: 8px 10px; margin: 8px 6px 0; color: inherit; cursor: pointer;
  font-size: 13px; line-height: 1.4;
}
.surround-strip:active { background: var(--press); }
.ss-main { font-weight: bold; }
.ss-biome { opacity: .6; font-weight: normal; }
.ss-here { display: block; font-size: 12px; color: var(--ok); margin-top: 2px; }
.surround-strip .perceiveline { margin: 4px 0 0; }
.ss-expand { display: block; font-size: 12px; opacity: .45; margin-top: 3px; }
.surround-detail { margin: 6px 6px 0; }

/* BETTER HUMAN stats bar — compact, quiet */
.statsbar {
  display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0;
  padding: 4px 8px; font-size: 12px; color: var(--muted);
  border-top: 1px solid var(--line);
}
.statsbar:empty { display: none; }
.stat { font-weight: 600; color: var(--ink-hi); }
.passive { color: #8a9; font-size: 12px; }
.ctx-btn {
  font-size: 12px; padding: 6px 10px; border-radius: 12px;
  border: 1px solid var(--line); background: var(--bg2); color: inherit;
  cursor: pointer;
}
.ctx-btn:active { background: var(--press); }

/* ============ SHEET SYSTEM: bottom sheets, queued by priority ============ */
/* Sheets live in #sheet-root, OUTSIDE #screen, so re-renders don't kill them. */
#sheet-root {
  position: fixed; inset: 0; z-index: 500;
  pointer-events: none; /* clicks pass through except on the sheet itself */
}
#sheet-root:empty { display: none; }
.sheet-backdrop {
  position: absolute; inset: 0;
  background: rgba(0,0,0,.55);
  pointer-events: auto;
  display: flex; align-items: flex-end; justify-content: center;
}
.sheet {
  pointer-events: auto;
  background: var(--panel);
  border-top: 2px solid var(--panel-edge);
  border-radius: 14px 14px 0 0;
  width: 100%; max-width: 520px;
  max-height: 82vh;
  display: flex; flex-direction: column;
  animation: sheetup .22s ease-out;
  box-shadow: 0 -8px 32px rgba(0,0,0,.6);
}
@keyframes sheetup {
  from { transform: translateY(40px); opacity: .4; }
  to { transform: translateY(0); opacity: 1; }
}
.sheet-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 12px 16px;
  border-bottom: 1px solid var(--panel-line);
  position: sticky; top: 0; background: var(--panel);
  border-radius: 14px 14px 0 0;
  flex-shrink: 0;
}
.sheet-head b {
  font-size: 15px; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sheet-x {
  flex-shrink: 0;
  width: 32px; height: 32px; border-radius: 50%;
  border: 1px solid var(--chip-edge); background: var(--chip); color: var(--muted);
  font-size: 15px; cursor: pointer; line-height: 1;
}
.sheet-x:active { background: var(--chip-hi); color: var(--white); }
.sheet-body {
  padding: 12px 16px;
  overflow-y: auto;
  /* SCROLL-FIX 2026-10-12 (Steve: bottom sheet item cut off on mobile):
     the body must shrink inside the 82vh flex column and scroll internally.
     min-height:0 is belt-and-suspenders — scroll containers already get an
     automatic zero minimum, but this survives any future overflow change. */
  flex: 0 1 auto;
  min-height: 0;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  font-size: 14px; line-height: 1.55;
}
.sheet-body p { margin: 8px 0; }
.sheet-btns {
  padding: 4px 16px 16px;
  display: flex; flex-wrap: wrap; gap: 8px;
  /* SCROLL-FIX 2026-10-12 (Steve: bottom sheet item cut off on mobile):
     the old flex-shrink:0 DEFEATED overflow-y:auto — a shrink-proof box
     never shrinks, so it never scrolled; it just overflowed the 82vh sheet
     and the bottom buttons rendered cut off below the fold. flex:0 1 auto
     lets the button area shrink and scroll internally instead; max-height
     keeps a huge choice list from starving the head + body entirely. */
  flex: 0 1 auto;
  min-height: 0;
  overflow-y: auto;
  max-height: 62vh;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}
.sheet-btns .btn { flex: 1 1 auto; min-width: 120px; }
/* non-modal sheets: no backdrop, game stays interactive */
#sheet-root.nomodal .sheet-backdrop {
  background: transparent;
  pointer-events: none;
  align-items: flex-end;
}
#sheet-root.nomodal .sheet { pointer-events: auto; }

/* ============ TARGETING MODE: pick who/what ============ */
.cell.targetable {
  outline: 2px solid var(--sys-cyan) !important;
  outline-offset: -2px;
  animation: targetpulse 1.1s ease-in-out infinite;
  cursor: pointer;
}
@keyframes targetpulse {
  0%, 100% { box-shadow: inset 0 0 0 2px rgba(var(--sys-cyan-rgb), .25); }
  50% { box-shadow: inset 0 0 0 4px rgba(var(--sys-cyan-rgb), .6); }
}
.targetbar {
  display: flex; flex-direction: column; gap: 8px; margin: 6px 0; padding: 8px 10px;
  border: 1px solid var(--sys-cyan); border-radius: 10px;
  background: rgba(var(--sys-cyan-rgb), .07);
  font-size: 13px;
}
.target-prompt { font-size: 14px; }
.target-list { display: flex; flex-wrap: wrap; gap: 6px; }
.target-pick { font-size: 13px; padding: 6px 10px; }
.targetbar .t-cancel {
  align-self: flex-end; padding: 6px 12px; border-radius: 8px;
  border: 1px solid #555; background: var(--chip); color: var(--ink-hi);
  font-size: 12px; cursor: pointer;
}

/* ============ ABILITY BAR: your powers, always in reach ============ */
.abilitybar {
  display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0;
  padding: 6px 8px; border-top: 1px dashed var(--chip-hi);
}
.abilitybar:empty { display: none; }
.ab-btn {
  font-size: 12px; padding: 6px 10px; border-radius: 12px;
  border: 1px solid #6a5a2a; background: #1d1a10; color: #e8d98a;
  cursor: pointer;
}
.ab-btn:disabled { opacity: .4; border-color: var(--chip-edge); color: var(--muted-2); background: var(--surface-2); }
.ab-btn:active:not(:disabled) { background: #2a2415; }
/* PATH COLORS (Steve 2026-10-14 ability coherency): Dead Cells pattern —
   one color per path, on buttons, synergy banners, codex. Feel the build
   before understanding it. */
.ab-btn[data-path="brawler"] { border-color: var(--path-brawler); }
.ab-btn[data-path="shieldbearer"] { border-color: var(--path-shieldbearer); }
.ab-btn[data-path="hunter"] { border-color: var(--path-hunter); }
.ab-btn[data-path="survivalist"] { border-color: var(--path-survivalist); }
.ab-btn[data-path="caregiver"] { border-color: var(--path-caregiver); }
.ab-btn[data-path="drifter"] { border-color: var(--path-drifter); }
.ab-btn[data-path="explorer"] { border-color: var(--path-explorer); }
.ab-btn[data-path="socialite"] { border-color: var(--path-socialite); }
.ab-btn[data-path="miser"] { border-color: var(--path-miser); }
.ab-btn[data-path="forager"] { border-color: var(--path-forager); }
.ab-btn .ab-role { font-size: 12px; opacity: .7; margin-left: 4px; }
.synergy-banner { border-left: 4px solid; padding: 8px 12px; margin: 8px 0;
  background: var(--surface-2); border-radius: 0 8px 8px 0; font-weight: bold; }

/* ============ TELEGRAPHED DANGER: combat is dodge-first ============ */
.cell.danger {
  outline: 2px solid var(--danger) !important;
  outline-offset: -2px;
  animation: dangerpulse .5s ease-in-out infinite;
  background: rgba(255, 59, 59, .15) !important;
}
@keyframes dangerpulse {
  0%, 100% { box-shadow: inset 0 0 0 2px rgba(255,59,59,.3); }
  50% { box-shadow: inset 0 0 0 6px rgba(255,59,59,.75); }
}
.dangerbar {
  margin: 6px 0; padding: 10px 12px;
  border: 2px solid var(--danger); border-radius: 10px;
  background: rgba(255,59,59,.12);
  font-size: 14px; font-weight: bold; color: var(--danger-soft);
  text-align: center;
  animation: dangerpulse .5s ease-in-out infinite;
}
.dangerbar:empty { display: none; }

/* ============ COMBAT MODE SHIFT: the game changes ============ */
body.in-combat #screen::after {
  content: ''; position: fixed; inset: 0; pointer-events: none; z-index: 60;
  background: radial-gradient(ellipse at center, transparent 52%, rgba(140, 10, 10, 0.5) 100%);
  animation: combatBreathe 2.2s ease-in-out infinite;
}
@keyframes combatBreathe {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.65; }
}
body.in-combat .detail {
  filter: saturate(0.75) contrast(1.1);
}
body.in-combat .dangerbar {
  animation: dangerPulse 0.9s ease-in-out infinite;
  border-color: var(--danger);
}
@keyframes dangerPulse {
  /* CLIP FIX (Steve 2026-10-06): the old scale(1.015) peak pushed the
     centered banner text ~3px past its overflow:hidden ancestor
     (.ord-actions, mobile), shaving edge glyphs — the bulldozer cue's
     final letter rendered "anythin[g]" live. The glow pulse keeps the
     attention-drawing with zero layout/visual clipping. */
  0%, 100% { box-shadow: 0 0 0 rgba(255,59,59,0); }
  50% { box-shadow: 0 0 18px rgba(255,59,59,.45); }
}

/* ============ INLINE INTERACTION SLOT: one screen, no overlays ============ */
#inlineslot { margin: 10px 0; }
#inlineslot:empty { display: none; }
.inlinecard {
  border: 1px solid #4a4a3a; border-radius: 10px; background: var(--surface-warm);
  padding: 10px 12px; margin: 8px 0;
  animation: inlinein .18s ease-out;
}
@keyframes inlinein {
  from { transform: translateY(8px); opacity: .5; }
  to { transform: translateY(0); opacity: 1; }
}
.inline-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 4px;
}
.inline-head b { font-size: 15px; }
.inline-body p { margin: 6px 0; }
.donation-picker { padding:8px; border:1px solid var(--line); border-radius:10px; font-size:14px; }
.donation-picker label { display:flex; align-items:center; justify-content:space-between; gap:8px; }
.donation-picker input { width:90px; min-height:44px; box-sizing:border-box; }
.donation-actions { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:6px; }
.donation-actions .btn { width:100%; min-width:0; min-height:44px; margin:0; padding:6px; font-size:13px; white-space:normal; }
.inline-btns { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
/* conversation transcript: reads like dialogue, not a log. Speaker names
   always shown; narration (non-quoted) is italic and dimmed. */
.convo .tline { font-size: 14px; line-height: 1.55; margin: 9px 0 0; }
.convo .tline.you { margin-left: 14px; }
.convo .tline.you b { color: #9fd8ff; font-weight: 600; }
.convo .tline.them b { color: #b8e986; font-weight: 600; }
.convo .tline .narr { font-style: italic; opacity: .72; }
/* FOREIGN SPEECH: real words in a tongue you may not understand.
   Italic amber, tagged with the language — visible, distinct, honest. */
.convo .tline .fsp { font-style: italic; color: #ffd98a; }
.convo .tline .flang { font-size: 0.75em; opacity: .6; margin-left: 5px; font-style: normal; white-space: nowrap; }
/* HESITATION: the "..." while someone thinks before responding. */
.thinking-dots { display: inline-block; letter-spacing: 2px; }
.thinking-dots span { display: inline-block; animation: tblink 1.1s infinite; opacity: .25; }
.thinking-dots span:nth-child(2) { animation-delay: .18s; }
.thinking-dots span:nth-child(3) { animation-delay: .36s; }
/* person card buttons: 2-column compact grid, not full-width stack */
.inlinecard .inline-btns .btn.sm {
  flex: 1 1 calc(50% - 3px); margin: 0; padding: 10px 6px; font-size: 14px;
  width: auto;
}
.inlinecard .inline-btns .btn.sm:active { background: var(--acc); color: var(--bg); }
/* collapsible person details: one line, expand for more */
.person-details summary { list-style: none; }
.person-details summary::-webkit-details-marker { display: none; }
.person-details summary::before { content: '▸ '; }
.person-details[open] summary::before { content: '▾ '; }
/* action feedback: obvious confirmation, no wondering "did that work?" */
.inline-result {
  color: var(--ok); font-weight: bold; font-size: 14px; margin: 6px 0;
  border-left: 3px solid var(--ok); padding-left: 8px;
  animation: resultflash .3s ease-out;
}
@keyframes resultflash {
  from { background: rgba(127,214,127,.25); }
  to { background: transparent; }
}

/* ============ SYSTEM-LIVE: the overlay creeps into your perception ============ */
/* Post-day-7, the interface gains System styling. Glowing borders, overlay
   accents, game-like chrome. You should FEEL the System integrating. */
body.system-live .inlinecard {
  border-color: rgba(var(--sys-glow-rgb), .45);
  box-shadow: 0 0 18px rgba(var(--sys-glow-rgb), .10), inset 0 0 30px rgba(var(--sys-glow-rgb), .03);
}
body.system-live .card {
  border-color: rgba(var(--sys-glow-rgb), .25);
}
body.system-live .sheet {
  border-top: 2px solid rgba(var(--sys-glow-rgb), .6);
  box-shadow: 0 -8px 32px rgba(var(--sys-glow-rgb), .15);
}
body.system-live #announce {
  border-bottom: 1px solid rgba(var(--sys-glow-rgb), .4);
  text-shadow: 0 0 8px rgba(var(--sys-glow-rgb), .25);
}
body.system-live .btn:not(.ghost) {
  border-color: rgba(var(--sys-glow-rgb), .5);
  box-shadow: 0 0 10px rgba(var(--sys-glow-rgb), .12);
}

/* ============ GAME SCREEN COLUMNS: one column on phones, two when wide ============ */
/* The expedition screen splits into main (grid) + side (status/panels) columns.
   On narrow screens the column wrappers dissolve (display:contents) and the
   children are ordered to match the classic single-column flow. On wide
   screens the grid goes left and the panels dock right. */
.game-cols { display: flex; flex-direction: column; min-width: 0; }
.game-col-main, .game-col-side { display: contents; }
/* INTERACTION STACK (talk-button-obscurer-fix 2026-10-11, Steve: "Don't let
   an element obscure crucial info"): wrapper around .ord-controls +
   .ord-narration. On narrow screens it is display:contents, so the two keep
   their own flex orders in the single-column flow (mobile layout untouched).
   On desktop it becomes the pinned sticky stack (see the min-width:900px
   block) — the person card lives inside the narration region and must never
   slide under the pinned header. */
.ord-topstack { display: contents; }
/* single-column order = the pre-columns flow: status, grid, interactions, panels */
.ord-status { order: 1; }
.ord-quest { order: 2; }
.ord-epithet { order: 3; }
.ord-grid { order: 4; }
/* perception line sits between grid and interactions — quiet, ignorable */
.ord-perceive { order: 4.5; }
.ord-inline { order: 5; }
/* COMBAT CARD: the fight panel sits directly under the grid, above the action
   bars -- combat is moment-to-moment, never below the fold. High-contrast
   treatment so the eye lands here the instant a fight starts. (highbeam) */
.ord-combatpanel { order: 5.2; }
.ord-combatpanel .card.warn {
  border: 2px solid var(--danger-card);
  box-shadow: 0 0 14px rgba(255, 82, 82, 0.35);
}
/* COMBAT CARD, COMPACT: arrives, doesn't intrude. Single breath of info —
   no stat blocks, no layout jump. Slides in instead of popping. */
.ord-combatpanel .card.combat-compact {
  border: 2px solid var(--danger-card);
  box-shadow: 0 0 14px rgba(255, 82, 82, 0.35);
  padding: 6px 8px;
  animation: combatArrive 0.25s ease-out;
}
@keyframes combatArrive {
  from { opacity: 0; transform: translateY(-6px); }
  to { opacity: 1; transform: translateY(0); }
}
.combat-compact .cc-head {
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: 13px; font-weight: bold; margin-bottom: 2px;
}
.combat-compact .cc-turn { font-weight: normal; opacity: .8; font-size: 12px; }
.combat-compact .cc-mon { margin: 2px 0; }
.combat-compact .cc-actions { display: flex; gap: 6px; margin-top: 6px; flex-wrap: wrap; }
.combat-compact .cc-actions .btn { flex: 1 1 0; min-width: 0; padding: 8px 4px; }
/* HP BAR: the card's unique info, no text senses. */
.combat-compact .cc-hpbar { display: inline-block; width: 64px; height: 8px; border: 1px solid rgba(255,255,255,.35); border-radius: 4px; vertical-align: middle; margin-left: 6px; background: rgba(0,0,0,.4); }
.combat-compact .cc-hpbar > span { display: block; height: 100%; border-radius: 3px; background: linear-gradient(90deg, #e5484d, #f5a524); }
/* FIGHT FEED: the last 3 fight events, inline under the grid — never buried
   in the bottom log box. The latest hits hardest. */
.combat-compact .cc-feed { margin-top: 4px; padding-top: 4px; border-top: 1px solid rgba(255, 255, 255, .12); }
.combat-compact .cc-feed .cc-evt { margin: 1px 0; font-size: 12px; line-height: 1.25; opacity: .85; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.combat-compact .cc-feed .cc-evt:last-child { opacity: 1; font-weight: 600; }
/* COMBAT STRIP (Steve 2026-10-05): threat + actions, nothing else. The strip
   is two rows max — the log below it is the ONE narrative channel. */
.ord-combatpanel .combat-strip {
  /* QUIET (Steve 2026-10-05): no red glow, no card border. Just a surface
     like the dialogue box — threat, narration, actions. Cohesive, not shouty. */
  padding: 6px 8px;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  margin: 6px 0;
}
.combat-strip .cs-line { display: flex; align-items: center; gap: 6px; font-size: 13px; flex-wrap: wrap; }
.combat-strip .cs-mon { white-space: nowrap; }
.combat-strip .cs-turn { margin-left: auto; opacity: .8; font-size: 12px; white-space: nowrap; }
.combat-strip .cs-narr { font-size: 13px; color: var(--ink-hi); margin: 4px 0; line-height: 1.4; }
.combat-strip .cs-actions { display: flex; gap: 6px; margin-top: 6px; }
.combat-strip .cs-actions .btn { flex: 1 1 0; min-width: 0; padding: 8px 4px; }
.combat-strip .cc-hpbar { display: inline-block; width: 48px; height: 7px; border: 1px solid rgba(255,255,255,.35); border-radius: 4px; vertical-align: middle; margin-left: 6px; background: rgba(0,0,0,.4); }
.combat-strip .cc-hpbar > span { display: block; height: 100%; border-radius: 3px; background: linear-gradient(90deg, #e5484d, #f5a524); }
/* MULTI-MOVE HUD STAGING (2026-10-12): stage 2 numbers, stage 3 previews. */
.combat-strip .cs-hpnum { font-size: 12px; color: var(--gold); margin-left: 4px; font-variant-numeric: tabular-nums; }
.combat-strip .cs-dmgprev { font-size: 12px; color: var(--tg-target-2); margin-left: 4px; font-variant-numeric: tabular-nums; }
.combat-strip .cs-odds { font-size: 12px; color: #82b4ff; margin-left: 4px; }
.combat-strip .cs-intel { font-size: 12px; color: #c8e6c9; margin: 4px 0 0 22px; line-height: 1.35; }
/* PARTY PANEL (Steve 2026-10-09): the 👥 chip toggles a compact member-status
   popover — name, honest HP bar + number, condition icons from real tracked
   state. Toggle, never clutter: it only exists while open. */
.party-pop { position: absolute; top: 115%; right: 0; z-index: 60; min-width: 184px; max-width: 232px;
  background: rgba(14,12,20,.97); border: 1px solid rgba(255,255,255,.28); border-radius: 10px;
  padding: 8px 10px; box-shadow: 0 10px 28px rgba(0,0,0,.55); }
.pp-title { font-size: 12px; font-weight: 700; margin-bottom: 4px; display: flex;
  justify-content: space-between; align-items: center; }
.pp-x { cursor: pointer; opacity: .6; padding: 0 4px; }
.pp-row { display: flex; align-items: center; gap: 6px; font-size: 12px; padding: 3px 0; }
.pp-row.pp-crit .pp-name { color: #ff9a9a; font-weight: 700; }
.pp-name { flex: 0 1 auto; max-width: 84px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pp-hpbar { flex: 1 1 auto; height: 7px; background: rgba(0,0,0,.5); border: 1px solid rgba(255,255,255,.3);
  border-radius: 4px; min-width: 40px; }
.pp-hpbar > span { display: block; height: 100%; border-radius: 3px; background: linear-gradient(90deg, #57d977, #b7f36b); }
.pp-hpbar.low > span { background: linear-gradient(90deg, #e5484d, #f5a524); }
.pp-hp { font-size: 12px; opacity: .85; min-width: 26px; text-align: right; font-variant-numeric: tabular-nums; }
.pp-cond { font-size: 12px; white-space: nowrap; }
.pp-sick { font-size: 12px; opacity: .75; padding: 0 0 3px 2px; }
.pp-empty { font-size: 12px; opacity: .7; padding: 2px 0; }
/* IN-COMBAT ALLY STRIP: same visual language as the enemy line. Downed allies
   stay visible — greyed with ✖, never silently dropped. */
.combat-allies .cs-ally { white-space: nowrap; }
.combat-allies .cs-ally.down { opacity: .45; }
.combat-allies .cc-hpbar { display: inline-block; width: 48px; height: 7px; border: 1px solid rgba(255,255,255,.35);
  border-radius: 4px; vertical-align: middle; margin-left: 6px; background: rgba(0,0,0,.4); }
.combat-allies .cc-hpbar > span { display: block; height: 100%; border-radius: 3px; background: linear-gradient(90deg, #57d977, #b7f36b); }
/* COMBAT LOG: the log is the ONE channel — pulled directly under the strip
   during combat so the fight never requires scrolling to follow it. */
body.in-combat .ord-log { order: 5.3; }
/* DIALOGUE BOX (Steve 2026-10-05): Pokémon-style — sits under the grid in the
   normal flow, map stays visible above. Speaker tab, one line at a time. */
.ord-dialogue { order: 5.4; }
.dialogue-box {
  margin: 6px 4px 0; padding: 10px 12px 8px;
  border: 2px solid rgba(255, 255, 255, .35); border-radius: 10px;
  background: rgba(10, 12, 20, .92);
  box-shadow: 0 2px 12px rgba(0, 0, 0, .5);
  /* READABILITY 2026-10-12 (Steve: chat hard to read on mobile):
     14px/1.45 was cramped in the 184px region on a phone. 16px/1.6 matches
     the narr-box precedent (Steve 2026-10-06: narration must land, not
     whisper); beats are paginated at emission so the line always fits. */
  font-size: 16px; line-height: 1.6; cursor: pointer;
  animation: combatArrive 0.2s ease-out;
}
/* NARRATION IMPACT (Steve 2026-10-06): narration must land, not whisper.
   Larger, left-accented, unmissable. */
.dialogue-box.narr-box {
  font-size: 16px; line-height: 1.5;
  border-left: 4px solid var(--amber);
  background: rgba(20, 16, 8, .95);
  margin: 8px 4px;
  padding: 12px 14px 10px;
}
.dlg-speaker {
  display: flex; justify-content: space-between; align-items: center;
  font-weight: bold; font-size: 14px; margin: -10px -12px 6px;
  padding: 5px 12px; border-bottom: 1px solid rgba(255, 255, 255, .15);
  background: rgba(255, 255, 255, .06); border-radius: 8px 8px 0 0;
}
.dlg-x { background: none; border: none; color: inherit; opacity: .6; font-size: 14px; padding: 2px 6px; cursor: pointer; }
.dlg-hist { background: none; border: none; color: inherit; font: inherit; font-weight: bold; font-size: 13px; padding: 2px 4px; cursor: pointer; text-align: left; }
/* HISTORY (Steve 2026-10-05): the full conversation, scrollable inside the box.
   The box stays one screen; only the history pane scrolls, and only when open.
   Tall enough to actually read — 45vh, not a peephole. */
.dlg-history { max-height: 45vh; overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; }
.dlg-history .dlg-line { min-height: 0; margin-bottom: 8px; }
/* CURRENT BEAT (dialog-ui-fix 2026-10-11): never scrolls — long lines are
   paginated at emission (Game.convoSplitBeats). min-height keeps the box
   from collapsing on short beats. */
.dlg-line { min-height: 40px; overflow: hidden; }
.dlg-line.narr { opacity: .85; font-style: italic; }
.dlg-who { font-weight: bold; }
/* CHOICE LIST (rethought 2026-10-14, Steve: "unable to see all the chat
   options and it isn't scrollable — rethink approach"): the old two-per-row
   flex-wrap clipped long labels and stranded options past the fold with no
   way to reach them. Choices are now a vertical list, full-width, text
   wraps, and the list scrolls INSIDE the box (same discipline as
   .dlg-history: the box stays one screen, only the list pane scrolls).
   ~4 options visible, the rest one swipe away. */
.dlg-choices {
  display: flex; flex-direction: column; gap: 6px; margin-top: 8px;
  max-height: 30vh; overflow-y: auto;
  -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
}
.dlg-choice {
  flex: 0 0 auto; width: 100%;
  text-align: left; white-space: normal; overflow-wrap: break-word;
  padding: 10px 12px; font-size: 15px; line-height: 1.4;
  margin: 0;
}
.dlg-next {
  display: block; margin: 2px auto 0; background: none; border: none;
  color: inherit; font-size: 15px; opacity: .85; cursor: pointer;
  animation: dlgBlink 1.2s infinite;
}
@keyframes dlgBlink { 0%, 100% { opacity: .85; } 50% { opacity: .3; } }
.ord-ctx { order: 6; }
/* SELF BAR: your persistent body-actions (Eat, Sleep, Pack, Wait). Personal
   actions FIRST — they sit at the top of the action area, right below the
   status bars, with the environment/contextual actions beneath them.
   DOM order matches (status → self → ctx) so the sequence holds even if
   flex ordering fails. Badges are peripheral: a quiet dot, no animation,
   no nagging. */
.ord-self { order: 5.5; }
.selfbar { display: flex; gap: 8px; margin: 10px 6px 0; align-items: stretch; }
.selfbar:empty { display: none; }
.selfbar .ctx-label { align-self: center; }
.self-btn {
  position: relative; flex: 1; padding: 12px 6px; font-size: 14px;
  min-height: 48px;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--bg2); color: inherit; cursor: pointer;
  white-space: nowrap;
}
.self-btn:active { background: var(--press); }
.self-btn .dot {
  position: absolute; top: 4px; right: 6px; width: 8px; height: 8px;
  border-radius: 50%; background: var(--bad-soft);
}
.self-btn .dot.soft { background: var(--amber); }
.ctx-btn { position: relative; }
.ctx-btn .dot {
  position: absolute; top: 2px; right: 4px; width: 8px; height: 8px;
  border-radius: 50%; background: var(--sys-cyan);
}
/* PERCEPTION LINE: peripheral vision. Small, italic, dimmed — it should feel
   like noticing, not like UI. No animation, no color pops. Ignorable. */
.perceiveline {
  font-size: 12px;
  font-style: italic;
  opacity: 0.62;
  margin: 2px 6px 0;
  line-height: 1.55;
}
.ord-target { order: 7; }
.ord-danger { order: 8; }
.ord-ability { order: 9; }
/* ONE SURFACE (Steve 2026-10-12): the separate feedback card is retired —
   action feedback renders in the narration box (dialogue-box.narr-box),
   latest beat wins. The styles below are gone with it. */
/* SYNERGY TEASE: the in-the-moment shiver keeps its purple accent inside
   the one narration box. */
.dialogue-box.narr-box.narr-syntease { border-left-color: #b48cff; background: rgba(150,100,255,.09); }
.dialogue-box.narr-box.narr-syntease .dlg-line.narr { font-style: italic; opacity: 1; }
.ord-taphint { order: 10; }
.ord-minimap { order: 11; }
.ord-panel { order: 12; }
.ord-codex { order: 13; }
.ord-log { order: 14; }

/* WIDESCREEN: the game uses the room — grid left, panels right.
   Scoped behind body:has(.game-cols) so non-game screens (title, onboarding,
   codex, ending) keep their narrow centered column. If :has() is unsupported
   the whole block is dropped and the game stays single-column — safe. */
@media (min-width: 900px) {
  body:has(.game-cols) #app { max-width: 1280px; padding-left: 28px; padding-right: 28px; }
  body:has(.game-cols) .game-cols {
    display: grid;
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    gap: 28px; align-items: start;
  }
  body:has(.game-cols) .game-col-main,
  body:has(.game-cols) .game-col-side { display: block; min-width: 0; }
  /* DESKTOP: interactive elements stay visible without scrolling (Steve 2026-10-05).
     Side column scrolls, but the action menu (dpad + buttons) is sticky at
     the top — it never scrolls away. Partner feedback 2026-10-05:
     "action menu gets lost." Narration/status scroll normally (info, not
     the thing you tap). Haven menu/panel scrolls below. */
  body:has(.game-cols) .game-col-side {
    position: sticky;
    top: 16px;
    max-height: calc(100vh - 32px);
    overflow-y: auto;
    display: flex;
    flex-direction: column;
  }
  /* Interaction stack first, info below — even on desktop */
  body:has(.game-cols) .ord-topstack { order: -3; }
  body:has(.game-cols) .ord-status { order: -1; }
  /* THE INTERACTION STACK STICKS (talk-button-obscurer-fix 2026-10-11,
     Steve: "Don't let an element obscure crucial info"). The action menu
     AND the narration region pin together at the top of the side column.
     The person card, dialogue box and contest box all render inside the
     narration region — when only the action menu was sticky, the card slid
     UNDER the opaque pinned header and its buttons (Talk, Close) failed
     hit-testing: mouse clicks refused as "obscured" while keyboard worked.
     The card is inside the pinned stack now, so no element can ever cover
     it. Everything BELOW the stack (status, haven panel, compass, minimap)
     still scrolls under it — that's info/management, not the thing you tap. */
  body:has(.game-cols) .ord-topstack {
    /* TALK-CARD-CRUSH-FIX 2026-10-12: the 2026-10-11 talk-button-obscurer-fix
       set position:sticky / z-index / background here but never overrode the
       base `display: contents` — a display:contents element generates no box,
       so the entire pinned-stack mechanism was inert (verified: scrolling the
       side column never pinned the controls). display:block gives the stack a
       real box so sticky/top/z-index/background take effect as intended.
       flex-shrink:0 keeps the pinned stack at its natural height (controls +
       the fixed 184px narration region); the column scrolls instead of
       crushing it. */
    display: block;
    flex-shrink: 0;
    position: sticky;
    top: 0;
    z-index: 10;
    background: var(--bg, #141414);
    border-bottom: 1px solid var(--line, #333);
  }
  /* Breathing room between the action menu and the narration region,
     inside the pinned stack. */
  body:has(.game-cols) .ord-topstack .ord-controls { padding-bottom: 8px; }
  body:has(.game-cols) .sheet { max-width: 640px; }
  body:has(.game-cols) .minimap { max-width: 430px; }
  /* Node map (compass) stays with the grid in the main column */
  body:has(.game-cols) .ord-compass { margin-top: 12px; }
}

/* ============ SUN/MOON DIAL ============ */
/* The header's right side holds the dial next to the day label. */
.term-bar > span:last-child { display: inline-flex; align-items: center; gap: 8px; }
.sundial { display: inline-flex; vertical-align: middle; line-height: 0; }
/* pre-System: hand-drawn, rough, personal. Slightly crooked, like you drew it. */
.sundial.pre { transform: rotate(-5deg); filter: sepia(.35); }
/* post-System: cold precision, faint cyan glow. */
.sundial.post { filter: drop-shadow(0 0 5px rgba(var(--sys-cyan-rgb), .55)); }
/* DIAL UPGRADE GLITCH: the System replacing your time-sense. Plays once. */
@keyframes dialGlitch {
  0% { transform: translate(0) rotate(0); filter: none; opacity: 1; }
  15% { transform: translate(-3px, 2px); filter: hue-rotate(120deg) saturate(3); opacity: .35; }
  30% { transform: translate(3px, -2px); filter: none; opacity: 1; }
  45% { transform: translate(-2px, 0) scaleY(.6); filter: invert(.9); opacity: .5; }
  60% { transform: translate(2px, 1px); filter: hue-rotate(-90deg); opacity: .8; }
  75% { transform: translate(0); filter: none; opacity: 1; }
  100% { transform: none; filter: none; opacity: 1; }
}
.dial-glitch { animation: dialGlitch 1s steps(3) 2; }

/* ============ NIGHT ============ */
/* --sky-light (0..1) is set from JS every render, continuous. Dawn and dusk
   glide through it; night bottoms out at moonlight. The grid dims and cools;
   the UI chrome stays readable. */
:root { --sky-light: 1; }
.detail {
  filter: brightness(calc(.42 + .58 * var(--sky-light, 1)))
          saturate(calc(.62 + .38 * var(--sky-light, 1)));
}
/* moonlight tint over the GRID ONLY (Steve 2026-10-06): night darkens the
   play area, not the map, menus, or UI chrome. */
body.is-night .ord-gridwrap.detail::after {
  content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 5;
  background: linear-gradient(180deg, rgba(8,12,40,.45), rgba(10,16,48,.25));
  opacity: calc(1 - var(--sky-light, 1));
  border-radius: inherit;
}
/* fire matters more at night: warm glow against the dark */
body.is-night .cell.c-fire {
  box-shadow: 0 0 24px 9px rgba(255,150,40,.55);
  position: relative; z-index: 2;
}
body.is-night .cell.c-tent { box-shadow: 0 0 10px 3px rgba(255,190,90,.25); }

/* ============ ONE-SCREEN PHONE LAYOUT (Steve's law) ============
   Moment-to-moment play must need no scrolling on a phone (390x844):
   status, grid, encounter panel, self actions, contextual actions, and
   feedback all visible in one viewport. Management (minimap, panels,
   codex, log) may scroll — that's the scroll rule.
   Budget: header 48 + daytick 18 + status 60 + epithet 22 + grid 330 +
   perceive 24 + self 50 + ctx 44 + feedback 70 = ~666 < 844. */
@media (max-width: 899px) {
  /* MOBILE LAYOUT (Steve 2026-10-05): interactive first, info below.
     Grid at top. D-pad + actions side-by-side below grid.
     D-pad docks left or right (user setting). Status and narration below. */
  .ord-gridwrap { order: 1; position: relative; display: flex; flex-direction: column; }
  .ord-gridwrap .detail { order: 1; }
  /* CONTROLS ROW: D-pad + actions side by side (Steve 2026-10-05) */
  .ord-controls { order: 2; display: flex; gap: 8px; align-items: flex-start; margin: 8px 0; }
  .ord-dpad { flex: 0 0 auto; padding: 0; }
  .ord-dpad.dpad-left { order: 1; }
  .ord-dpad.dpad-right { order: 2; }
  .ord-controls .ord-actions { flex: 1 1 0; min-width: 0; margin: 0; }
  .ord-controls .ord-actions.dpad-left { order: 2; }
  .ord-controls .ord-actions.dpad-right { order: 1; }
  .ord-narration { order: 3; }
  .ord-status { order: 4; }
  .ord-epithet { order: 5; }
  /* COMBAT: narration must be compact — the grid and D-pad need the space.
     Max 3 lines, scroll internally if longer. (Steve 2026-10-05)
     (dialog-layout 2026-10-11): shrink the REGION, not the box — the slot
     stays fixed-height either way, just shorter in combat. */
  body.in-combat .ord-narration { height: 96px; }
  body.in-combat .ord-narration .dialogue-box.narr-box {
    font-size: 13px;
    padding: 6px 8px;
  }
  /* Combat names and sprites must not displace the board and thumb controls. */
  body.in-combat .combat-enemies { display: flex; align-items: center; gap: 4px; }
  body.in-combat .combat-enemies svg { width: 24px !important; height: 24px !important; }
  body.in-combat .combat-enemies .msprite,
  body.in-combat .combat-enemies .csprite { width: 24px; height: 24px; flex-shrink: 0; }
  body.in-combat .combat-enemy-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
  .combat-turn-status { font-size: 12px; line-height: 1.4; margin: 4px 0; color: var(--gold); }
  /* COMPACT STATUS: 2x2 mini-bars instead of 4 full rows */
  .ord-status { display: grid; grid-template-columns: 1fr 1fr; gap: 0 10px; }
  /* INFO-BAR CLASH FIX (Steve phone 2026-10-14): the 2-col grid squeezed the
     stat grid and the beat rows (wave ledger) side-by-side — the ledger's
     text collided with the chips and the chips crowded each other. Each child
     takes full width; .statgrid keeps its own internal 2-col layout. */
  .ord-status > .statgrid { grid-column: 1 / -1; }
  .ord-status > .statrow { grid-column: 1 / -1; min-width: 0; }
  /* Beat rows (wave ledger, contest, afflictions) had no styles at all —
     unstyled text colliding in the grid. Full-width, quiet, readable. */
  .ord-status > .statrow {
    font-size: 12px; line-height: 1.45; color: #bbb;
    padding: 4px 2px; border-top: 1px solid rgba(255,255,255,.06);
  }
  .ord-status .stat { margin: 3px 0; }
  .ord-status .stat .lbl { font-size: 12px; }
  .ord-status .bar { height: 7px; margin-top: 2px; }
  /* LOWER MENU (Steve 2026-10-05): Pack, Sleep, Wait, Map — the slow actions.
     Below status, out of the moment-to-moment flow. Frees space for combat. */
  .ord-lowermenu { order: 5; margin: 8px 0; }
  .lowermenu { display: flex; gap: 8px; }
  .lm-btn {
    flex: 1; padding: 10px 4px; font-size: 13px; min-height: 44px;
    border: 1px solid var(--line); border-radius: 10px;
    background: var(--bg2); color: inherit; cursor: pointer;
    position: relative;
  }
  .lm-btn:active { background: var(--press); }
  .lm-btn .dot {
    position: absolute; top: 4px; right: 6px; width: 8px; height: 8px;
    border-radius: 50%; background: var(--bad-soft);
  }
  .lm-btn .dot.soft { background: var(--amber); }
  /* GRID CAP: the world stays tappable (~36px cells) but stops eating
     the viewport */
  .ord-gridwrap .detail { max-width: 100%; margin-left: auto; margin-right: auto; }
  .ord-epithet { margin: 4px 0; font-size: 12px; }
  /* ENCOUNTER/PERSON PANELS: overlay the grid, never push layout.
     The panel is transient (examine -> act -> dismiss); the grid is
     persistent. Tapping a cell behind the panel isn't needed while it's
     open — the panel carries the actions. */
  #inlineslot { position: fixed; left: 8px; right: 8px; bottom: 8px; z-index: 100;
    max-height: 60%; overflow-y: auto; margin: 0; }
  #inlineslot:empty { display: none; }
  #inlineslot .card { margin: 0; box-shadow: 0 -6px 28px rgba(0,0,0,.65); }
  #inlineslot .tilepanel { background: rgba(10, 12, 20, .97); border: 1px solid rgba(255,255,255,.2); border-radius: 12px; padding: 12px; }
  /* tighter action bars */
  .selfbar { margin-top: 6px; flex-wrap: wrap; }
  .self-btn { min-height: 44px; padding: 10px 4px; font-size: 13px; flex: 1 1 40%; white-space: normal; }
  .contextbar { margin-top: 6px; flex-wrap: wrap; }
  .ctx-btn { padding: 8px 10px; font-size: 13px; }
  .perceiveline { margin-top: 4px; }
  /* MOBILE: action buttons must wrap, not overflow into D-pad (Steve 2026-10-05) */
  .ord-controls .ord-actions { overflow: hidden; }
  .ord-controls .ord-actions .selfbar { flex-wrap: wrap; }
}
/* (sheet-btns scroll containment lives on the main .sheet-btns rule above —
   flex:0 1 auto + min-height:0 + overflow-y:auto. A separate override rule
   here used to fight flex-shrink:0; the single rule is the source of truth.) */

/* FIRST-CONTACT MONSTER FLASH (Steve 2026-10-05): freaky pixelated horror.
   Fullscreen, brief, auto-dismisses. The canvas is low-res scaled up with
   pixelation; scanlines via CSS, slice/RGB glitch via JS. */
#mflash {
  position: fixed; inset: 0; z-index: 9999;
  background: rgba(0, 0, 0, 0.88);
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;
  transition: opacity 0.3s;
}
#mflash.mflash-out { opacity: 0; }
#mflash-cv {
  image-rendering: pixelated;
  filter: contrast(1.3) brightness(1.1);
}
.mflash-scan {
  position: absolute; inset: 0; pointer-events: none;
  background: repeating-linear-gradient(0deg, transparent 0 2px, rgba(0,0,0,0.35) 2px 4px);
}

/* ITEM SPRITES (Steve 2026-10-06): inline in list rows — pack, loot, trade. */
.itemsprite { display: inline-block; width: 26px; height: 26px; vertical-align: -7px; margin-right: 6px; }
.itemsprite svg.sprite { width: 100%; height: 100%; display: block; }

/* EXAMINED PLANTS (Steve 2026-10-06): looked-at-but-unnamed. A subtle ring —
   you've studied this one; it's not just green anymore. Not the species, just
   the memory of looking. */
.cell.examinedplant { box-shadow: inset 0 0 0 2px rgba(255,255,255,0.35); }
.cell.examinedbush { box-shadow: inset 0 0 0 2px rgba(255,255,255,0.35); }

/* COHESIVE NARRATION SYSTEM (Steve 2026-10-06):
   - Toasts: ambient events, top of screen, auto-fade
   - Speech bubbles: villager pass-by chatter, above their sprite
   - Narration box: important beats only, more prominent */
.toast-layer {
  position: fixed; top: 64px; left: 50%; transform: translateX(-50%);
  z-index: 500; pointer-events: none;
  display: flex; flex-direction: column; gap: 6px; align-items: center;
  max-width: 92vw;
}
.toast {
  background: rgba(20, 24, 32, .92);
  border: 1px solid rgba(255,255,255,.2); border-radius: 20px;
  padding: 8px 16px; font-size: 13px; line-height: 1.4;
  color: #e8e8e8; text-align: center;
  box-shadow: 0 4px 16px rgba(0,0,0,.4);
  animation: toastIn 0.25s ease-out;
  max-width: 100%;
}
.toast.fading { opacity: 0; transition: opacity 0.5s; }
.toast.important {
  border-color: var(--amber); background: rgba(32, 24, 12, .95);
  font-size: 14px;
}
@keyframes toastIn {
  from { opacity: 0; transform: translateY(-8px); }
  to { opacity: 1; transform: translateY(0); }
}
/* Speech bubbles on the grid */
.speech-bubble {
  position: absolute; z-index: 100;
  background: rgba(255, 252, 240, .96);
  color: var(--surface-2); border-radius: 12px;
  padding: 6px 10px; font-size: 12px; line-height: 1.35;
  max-width: 140px; text-align: center;
  box-shadow: 0 2px 8px rgba(0,0,0,.3);
  transform: translate(-50%, -100%);
  margin-top: -8px;
  animation: bubbleIn 0.2s ease-out;
  pointer-events: auto; cursor: pointer;
}
.speech-bubble:after {
  content: ''; position: absolute; bottom: -6px; left: 50%;
  transform: translateX(-50%);
  border: 6px solid transparent; border-top-color: rgba(255, 252, 240, .96);
  border-bottom: none;
}
.speech-bubble .who {
  font-weight: bold; font-size: 12px; color: #5a4a2a;
  display: block; margin-bottom: 2px;
}
.speech-bubble.fading { opacity: 0; transition: opacity 0.4s; }
@keyframes bubbleIn {
  from { opacity: 0; transform: translate(-50%, -90%); }
  to { opacity: 1; transform: translate(-50%, -100%); }
}
/* Grid cells need relative positioning for bubbles */
.cell { position: relative; }

/* ================= BROADCAST MODE (Steve 2026-10-09) ================= */
/* The television mechanism: while a show/contest-watch/summons airs, the
   play grid becomes a TV screen. Entry and exit are always explicit —
   the player never wonders whether they're playing or watching. */
body.broadcasting .ord-gridwrap.detail {
  border: 4px solid #0d0d16;
  border-radius: 16px;
  overflow: hidden;
  box-shadow: 0 0 0 2px rgba(var(--sys-cyan-rgb), 0.35), 0 0 34px rgba(var(--sys-cyan-rgb), 0.22);
}
/* vignette + scanlines in one layer (::after is the night tint) */
body.broadcasting .ord-gridwrap.detail::before {
  content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 6;
  background:
    radial-gradient(ellipse at center, transparent 52%, rgba(2,2,12,0.60) 100%),
    repeating-linear-gradient(0deg, rgba(0,0,0,0.13) 0 1px, transparent 1px 3px);
  border-radius: inherit;
  animation: bc-flicker 4s infinite;
}
@keyframes bc-flicker { 0%,100% { opacity: 1; } 50% { opacity: 0.93; } 54% { opacity: 0.98; } }

/* LIVE bug — top-right, persistent while broadcasting */
.broadcast-live-bug {
  position: fixed; top: 10px; right: 12px; z-index: 300;
  display: flex; align-items: center; gap: 6px;
  background: rgba(10,4,8,0.92); border: 1px solid var(--danger); border-radius: 6px;
  color: var(--white); font-weight: bold; font-size: 12px; letter-spacing: 2px;
  padding: 5px 10px; pointer-events: none;
}
.broadcast-live-bug .live-dot {
  width: 9px; height: 9px; border-radius: 50%; background: var(--danger);
  animation: bc-blink 1.1s infinite;
}
@keyframes bc-blink { 0%,100% { opacity: 1; } 50% { opacity: 0.25; } }

/* watching ribbon — unmistakable "you are watching" label */
.broadcast-watching-ribbon {
  font-size: 12px; color: var(--sys-cyan); letter-spacing: 1px; text-align: center;
  padding: 4px 6px; margin-bottom: 4px;
  border-bottom: 1px dashed rgba(var(--sys-cyan-rgb), 0.4);
}

/* commentary ticker bar — the alien commentators, always talking */
.broadcast-ticker {
  display: flex; align-items: stretch; gap: 6px;
  background: rgba(6,3,12,0.94); border-top: 2px solid var(--sys-cyan);
  border-radius: 0 0 8px 8px; margin-top: 6px; overflow: hidden;
  min-height: 30px;
}
.broadcast-ticker .ticker-live {
  flex: 0 0 auto; align-self: center;
  color: var(--danger); font-weight: bold; font-size: 12px; letter-spacing: 1px;
  padding: 0 4px 0 8px;
}
.broadcast-ticker .ticker-scroll { flex: 1; overflow: hidden; display: flex; align-items: center; }
.broadcast-ticker .ticker-inner {
  display: inline-block; white-space: nowrap;
  font-size: 12px; color: #dfe9f5; padding: 7px 0;
  animation: bc-ticker-scroll 22s linear infinite;
}
@keyframes bc-ticker-scroll { 0% { transform: translateX(0); } 100% { transform: translateX(-50%); } }
.broadcast-ticker .ticker-voice { color: #ffd54a; font-weight: bold; }
.broadcast-ticker .ticker-sep { color: var(--sys-cyan); margin: 0 8px; }
.broadcast-ticker .ticker-crowd { margin-left: 6px; }

/* COMPACT PORTRAIT (Steve 2026-10-11): no sprite ever renders at the
   browser's 300px default size. Any svg.sprite without an explicit
   container size (grid cells, vportrait, etc.) gets a compact portrait
   cap instead of a massive blob. Explicit sizes are unaffected. */
svg.sprite { max-width: 72px; max-height: 72px; }

/* Large battles fit the same screen. The real row count supplies density;
   small human figures face a titan whose sprite fills its actual footprint. */
body.titan-fight .detail .drow { display:grid; grid-auto-flow:column; grid-auto-columns:minmax(0,1fr); }
body.titan-fight .cell {
  width: 100%; min-width:0;
  font-size: clamp(8px, 2.6vw, 20px);
}
body.titan-fight .cell .msprite svg.sprite,
body.titan-fight .cell .vsprite svg.sprite {
  /* Ants: allied/villager fighters render small */
  max-width: 28px;
  max-height: 28px;
}
body.titan-fight .cell.titan-cell { z-index: 4; }
/* the titan's body: no 72px portrait cap, a shadow it casts on the arena */
body.titan-fight .cell.titan-cell .multitile svg.sprite {
  max-width: none; max-height: none;
  filter: drop-shadow(0 0 6px rgba(255, 213, 77, .35)) drop-shadow(0 6px 10px rgba(0, 0, 0, .7));
}
body.titan-fight .cell.player-spotlight {
  outline: 3px solid var(--gold);
  outline-offset: -3px;
  box-shadow: 0 0 12px rgba(255, 213, 77, 0.7), inset 0 0 8px rgba(255, 213, 77, 0.3);
  border-radius: 4px;
  z-index: 3;
}
body.titan-fight .detail .cell .vname { display:none; }
body.titan-fight .detail .cell .vent { display:flex; width:100%; height:100%; align-items:center; justify-content:center; }
body.titan-fight .detail .cell .vtoken { display:block; width:85%; height:85%; font-size:inherit; }
body.titan-fight .detail .cell .vtoken .vsprite { display:block; width:100%; height:100%; }
body.titan-fight .detail .cell .treesprite,
body.titan-fight .detail .cell .plantsprite { width:100%; height:100%; }
/* MULTI-TILE BODIES (presentation B 2026-10-14): the creature wrapper fills
   its anchor cell so a 200%/300% sprite sizes against the cell. The wrapper
   is a will-change:transform containing block (FLIP glide); left inline at
   0 width it collapsed every multi-tile body to 0x0. */
.detail .cell [data-ent].ent-multi { position: absolute; left: 0; top: 0; width: 100%; height: 100%; display: block; }
.cell .multitile svg.sprite { max-width: none; max-height: none; }

/* TITAN BAR: the marquee over the arena. Face, title, one long bar in three
   acts. Grounded colours — this is your fight, not the System's chrome. */
.titanbar {
  order: 0; display: flex; align-items: center; gap: 10px;
  margin: 0 0 6px; padding: 6px 10px;
  border: 1px solid #5a4423; border-radius: 6px;
  background: linear-gradient(180deg, #1c150c, #120e08);
  box-shadow: inset 0 0 0 1px rgba(255, 213, 77, .08);
}
.titanbar-face { flex: 0 0 auto; width: 44px; height: 44px; }
.titanbar-face svg.sprite { width: 100%; height: 100%; display: block; }
.titanbar-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.titanbar-name { display: flex; justify-content: space-between; align-items: baseline; gap: 8px;
  font-weight: bold; letter-spacing: 2px; color: var(--gold); font-size: 14px; }
.titanbar-act { font-size: 12px; letter-spacing: 1px; color: var(--amber); font-weight: normal; }
.titanbar-track { position: relative; height: 10px; border-radius: 3px; overflow: hidden;
  background: #2a1e10; border: 1px solid #5a4423; }
.titanbar-track > i { display: block; height: 100%; background: linear-gradient(90deg, #a3261f, #e5484d 60%, var(--gold)); transition: width .4s; }
.titanbar-track .tb-tick { position: absolute; top: 0; bottom: 0; width: 2px; background: rgba(10, 8, 4, .85); }
.titanbar-epi { font-size: 12px; color: var(--dim); font-style: italic; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ============ HUNGER IS THE BIGGEST THING ON SCREEN ============ */
/* DECISIONS.md 2026-10-03: the System overlay "works around the hunger meter
   (still the biggest element — a visual joke about priorities)". */
.statgrid .statc-food { grid-column: 1 / -1; font-size: 14px; font-weight: 600; }
.statgrid .statc-food .sbar { height: 9px; border-radius: 4px; }

/* ============ KEYBOARD FOCUS ============ */
/* Visible focus for keyboard and switch users; pointer taps stay clean. */
:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.btn:focus-visible, .self-btn:focus-visible, .ctx-btn:focus-visible, .lm-btn:focus-visible,
.dpbtn:focus-visible, .ab-btn:focus-visible, .sheet-x:focus-visible, .cell:focus-visible,
.lu-card:focus-visible, .tbl-setting:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
:focus:not(:focus-visible) { outline: none; }

/* ============ MARGINALIA: your hand, not theirs ============ */
/* DECISIONS.md 2026-10-03 (Codex overlay): "warm human handwriting-style
   notes, visually distinct from the cold System overlay: your memory vs
   their broadcast." System fonts only — the PWA works offline, no download. */
.hand, .card.codex .hand {
  font-family: var(--font-hand); color: var(--hand-ink);
  font-style: normal; font-size: 15px; line-height: 1.45; letter-spacing: .2px;
}
.hand.note { display: block; padding: 2px 0 2px 10px; border-left: 2px solid rgba(227, 201, 154, .35); transform: rotate(-.4deg); }
.hand .who { opacity: .75; }
/* the journal's own voice on the codex screen */
.journal-hand { font-family: var(--font-hand); color: var(--hand-ink); font-size: 16px; }
/* tile labels on learned ground: the Codex writing on the world (stage 1+) */
.tlabel { font-family: var(--font-hand); color: var(--hand-ink); }

/* ============ THE SYSTEM OVERLAY (post day 7) ============ */
/* It slides OVER the grounded UI and never replaces it: survival chrome
   (grid, status, hunger, buttons) stays earthy. System pieces — its sheets,
   its integration chip, its log lines, its dial — are cold blue-white glass
   with radii that are slightly too perfect: every corner the same exact
   --sys-radius, a hairline inner border, a sans face that doesn't belong in
   a terminal. Rounded corners that feel wrong. */
body.system-live .sheet {
  background: var(--sys-glass);
  border: 1px solid rgba(var(--sys-glow-rgb), .55);
  border-radius: var(--sys-radius);
  margin: 0 8px 8px; width: calc(100% - 16px);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .04) inset, 0 -10px 40px rgba(var(--sys-glow-rgb), .14), 0 18px 40px rgba(0, 0, 0, .6);
  color: var(--sys-ink);
}
body.system-live .sheet-head {
  background: transparent; border-bottom: 1px solid rgba(var(--sys-glow-rgb), .22);
  border-radius: var(--sys-radius) var(--sys-radius) 0 0;
}
body.system-live .sheet-head b { font-family: var(--sys-font); letter-spacing: .6px; font-weight: 600; color: var(--sys-ink); }
body.system-live .sheet .btn:not(.ghost) {
  border-radius: calc(var(--sys-radius) - 8px);
  border-color: rgba(var(--sys-glow-rgb), .6); color: var(--sys-ink);
  background: rgba(var(--sys-glow-rgb), .06);
}
body.system-live .sheet .btn:not(.ghost):active { background: rgba(var(--sys-glow-rgb), .3); color: var(--black); }
body.system-live .sheet-x { border-radius: 50%; border-color: rgba(var(--sys-glow-rgb), .5); background: transparent; color: var(--sys-ink); }
body.system-live .sys-ui {
  border-radius: 999px; padding: 2px 8px;
  background: rgba(var(--sys-glow-rgb), .07); box-shadow: inset 0 0 0 1px rgba(var(--sys-glow-rgb), .4);
  color: var(--sys-ink); font-family: var(--sys-font);
}
body.system-live .sys-ui .sbar { background: rgba(var(--sys-glow-rgb), .15); }
body.system-live .sys-ui .sbar > i { background: var(--sys-edge); }
body.system-live #log .sys { font-family: var(--sys-font); color: var(--sys-edge); }
body.system-live .ab-btn { border-radius: calc(var(--sys-radius) - 8px); }

/* ALIEN SCRIPT: for half a second the translation slips and the System's own
   writing shows through one of its panels, sliced like a bad signal. Zero
   assets: glyphs are generated (presentation.js alienScript). */
.alien-glitch {
  position: fixed; z-index: 2000; pointer-events: none; overflow: hidden;
  padding: 4px 8px; border-radius: var(--sys-radius);
  background: rgba(4, 10, 22, .82);
  color: var(--sys-edge); font-size: 14px; line-height: 1.35; letter-spacing: 2px;
  word-break: break-all; text-shadow: 2px 0 rgba(255, 60, 90, .55), -2px 0 rgba(var(--sys-cyan-rgb), .55);
  animation: alienSlip .45s steps(4) forwards;
}
@keyframes alienSlip {
  0%   { opacity: 0; clip-path: inset(0 0 100% 0); transform: translateX(0); }
  20%  { opacity: 1; clip-path: inset(10% 0 55% 0); transform: translateX(-4px); }
  45%  { opacity: 1; clip-path: inset(40% 0 20% 0); transform: translateX(5px); }
  70%  { opacity: .9; clip-path: inset(0 0 0 0); transform: translateX(-2px); }
  100% { opacity: 0; clip-path: inset(48% 0 48% 0); transform: translateX(0); }
}
@media (prefers-reduced-motion: reduce) { .alien-glitch { display: none; } }

/* ============ THE PRESENTATION LADDER (stage-0..4 on <body>) ============ */
/* DECISIONS.md 2026-10-03: "Start simple, but it would be cool if as you
   progress, the game graphics and interface keep getting better and
   better." Stages are earned on Codex depth + days survived
   (src/data/presentation.json, presentation.js). Each step is small and
   contained; phone legibility never drops.
   Stage 0 TERMINAL: earthy, flat, a whisper of scanline grit on the grid.
   Stage 1 ANNOTATED: the Codex writes on the world (handwritten tile labels).
   Stage 2 PIXEL ENTITIES: sprites get weight (a ground shadow).
   Stage 3 VIGNETTES: ground cells get texture; the grid gets a soft frame.
   Stage 4 TILESET: full colour, light falloff on the grid. */
:root {
  --fog: #0b100b; --fog-hatch: rgba(120, 150, 110, .07); --fog-grain: rgba(150, 170, 140, .10);
}
body.stage-0 .ord-gridwrap.detail::before,
body.stage-1 .ord-gridwrap.detail::before {
  content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 4; border-radius: inherit;
  background: repeating-linear-gradient(0deg, rgba(0, 0, 0, .10) 0 1px, transparent 1px 3px);
}
body.stage-0 .cell.grass { background: #1a2a18; }
body.stage-0 .cell.dirt  { background: #2a2219; }
body.stage-2 .cell .csprite svg.sprite, body.stage-3 .cell .csprite svg.sprite, body.stage-4 .cell .csprite svg.sprite,
body.stage-2 .cell .vsprite svg.sprite, body.stage-3 .cell .vsprite svg.sprite, body.stage-4 .cell .vsprite svg.sprite {
  filter: drop-shadow(0 2px 1px rgba(0, 0, 0, .55));
}
body.stage-3 .cell.grass, body.stage-4 .cell.grass {
  background: radial-gradient(circle at 30% 35%, rgba(120, 170, 90, .16) 0 2px, transparent 3px) 0 0 / 9px 9px, #1d3420;
}
body.stage-3 .cell.dirt, body.stage-4 .cell.dirt {
  background: radial-gradient(circle at 60% 60%, rgba(170, 130, 80, .16) 0 1.5px, transparent 2.5px) 0 0 / 8px 8px, #33281b;
}
body.stage-3 .ord-gridwrap .detail, body.stage-4 .ord-gridwrap .detail {
  border-radius: 6px; box-shadow: 0 0 0 1px #2b3a24, 0 6px 18px rgba(0, 0, 0, .45);
}
body.stage-4 .cell.grass { background-color: #234026; }
body.stage-4 .ord-gridwrap.detail::before {
  content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 4; border-radius: inherit;
  background: radial-gradient(ellipse at 50% 45%, transparent 55%, rgba(0, 0, 0, .28) 100%);
}

/* ============ CLIMAX SCREENS ============ */
/* LEVEL UP: three cards, one screen. Track icon, path tag, role, cost, and —
   when the slots are full — what it would replace. Big thumb targets. */
.lu-wrap { display: flex; flex-direction: column; gap: 10px; }
.lu-intro { margin: 0; font-family: var(--sys-font); }
.lu-full { margin: 0; font-size: 13px; color: var(--amber); }
.lu-cards { display: flex; flex-direction: column; gap: 8px; }
.lu-card {
  display: grid; grid-template-columns: 44px 1fr; gap: 4px 10px; align-items: start;
  width: 100%; text-align: left; cursor: pointer; font: inherit; color: var(--ink);
  padding: 12px; min-height: 64px; border-radius: 14px;
  background: rgba(var(--sys-glow-rgb), .05); border: 1px solid rgba(var(--sys-glow-rgb), .45);
}
.lu-card:active { background: rgba(var(--sys-glow-rgb), .22); }
.lu-icon { grid-row: span 3; width: 44px; height: 44px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  font-size: 22px; background: rgba(var(--sys-glow-rgb), .1); box-shadow: inset 0 0 0 1px rgba(var(--sys-glow-rgb), .4); }
.lu-name { font-weight: bold; font-size: 16px; color: var(--sys-ink); }
.lu-tags { display: flex; flex-wrap: wrap; gap: 4px 6px; }
.lu-tag { font-size: 12px; padding: 1px 8px; border-radius: 999px; border: 1px solid rgba(var(--sys-glow-rgb), .35); color: var(--sys-edge); white-space: nowrap; }
.lu-tag.path { border-color: var(--pathc, rgba(var(--sys-glow-rgb), .35)); color: var(--ink); box-shadow: inset 3px 0 0 var(--pathc, transparent); }
.lu-tag.cost { color: var(--amber-hi); border-color: rgba(232, 179, 74, .5); }
.lu-tag.swap { color: var(--danger-soft); border-color: rgba(255, 128, 128, .5); }
.lu-desc { grid-column: 2; font-size: 13px; line-height: 1.45; color: var(--ink-hi); }
.lu-flavor { grid-column: 2; font-size: 12px; font-style: italic; color: var(--dim); }

/* THE TABLE: not a popup — a place laid for you. Warm wood under cold System
   geometry: a long table, the ring of light above it, your path at the head,
   the stakes and the options as place settings you sit down at. */
.tbl { position: relative; display: flex; flex-direction: column; gap: 10px; }
.tbl-ring {
  height: 34px; margin: 0 18% -4px; border-radius: 50%;
  border: 1px solid rgba(var(--sys-glow-rgb), .55);
  box-shadow: 0 0 18px rgba(var(--sys-glow-rgb), .25), inset 0 0 12px rgba(var(--sys-glow-rgb), .2);
}
.tbl-prompt { font-size: 13px; line-height: 1.5; color: var(--sys-ink); max-height: 22vh; overflow-y: auto;
  padding: 8px 10px; border-radius: 12px; background: rgba(var(--sys-glow-rgb), .05); }
.tbl-prompt p { margin: 4px 0; }
.tbl-board {
  position: relative; padding: 12px 10px 14px; border-radius: 18px;
  background:
    repeating-linear-gradient(90deg, rgba(0, 0, 0, .10) 0 2px, transparent 2px 23px),
    linear-gradient(180deg, #5a3a1f, #3d2714 70%, #2c1c0e);
  border: 2px solid #22160a; box-shadow: inset 0 2px 0 rgba(255, 220, 160, .15), 0 10px 24px rgba(0, 0, 0, .55);
}
.tbl-head { text-align: center; margin: -2px 0 8px; font-size: 12px; letter-spacing: 3px; color: #f1d9a8; text-transform: uppercase; }
.tbl-head b { color: var(--gold); letter-spacing: 1px; }
.tbl-settings { display: flex; flex-direction: column; gap: 8px; }
.tbl-setting {
  display: flex; align-items: center; gap: 10px; width: 100%; min-height: 56px; cursor: pointer;
  padding: 10px 12px; border-radius: 14px; font: inherit; text-align: left;
  color: #2a1d10; background: #efe4cc; border: 1px solid #c7b48c;
  box-shadow: 0 2px 0 #a8916a, inset 0 0 0 4px rgba(255, 255, 255, .35);
}
.tbl-setting:active { transform: translateY(2px); box-shadow: 0 0 0 #a8916a; }
.tbl-plate { flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  background: radial-gradient(circle, #fffaf0 0 46%, #e2d6bb 48% 62%, #fffaf0 64%); box-shadow: 0 0 0 1px #c7b48c; font-size: 15px; }
.tbl-label { font-weight: bold; font-size: 15px; line-height: 1.3; }
.tbl-chair { text-align: center; font-size: 12px; color: var(--dim); margin-top: 2px; }
/* iOS zooms into any text field under 16px on focus; now that pinch zoom is
   allowed, keep typing from yanking the one screen sideways. */
input[type="text"], input:not([type]), input[type="search"], textarea { font-size: 16px; }
/* STAGE 1+ ANNOTATED: what the Codex knows about this ground is written on
   it in your hand (the surroundings strip's "here" line). */
body.stage-1 .ss-here, body.stage-2 .ss-here, body.stage-3 .ss-here, body.stage-4 .ss-here {
  font-family: var(--font-hand); color: var(--hand-ink); font-size: 14px;
}

/* The existing garden is visible on its own ground; badges preserve people. */
.garden-marker { position:absolute; inset:2px; border:1px dashed #98806a; border-radius:4px; color:#d8bc98; background:rgba(88,59,36,.25); font-size:12px; text-align:right; padding:2px; pointer-events:none; }
.garden-marker.empty { border-style:solid; background:repeating-linear-gradient(0deg,rgba(130,86,43,.4) 0 3px,transparent 3px 7px); }
.garden-marker.growing { border-style:solid; border-color:#779b62; }
.garden-marker.ripe { border:2px solid #dfc861; background:rgba(153,151,54,.2); }
.garden-marker.dead { opacity:.6; }
.garden-plots { list-style:none; padding:0; margin:6px 0; font-size:13px; }
.garden-plots li { padding:4px 6px; border-left:2px solid #779b62; margin:3px 0; background:rgba(92,116,73,.12); }
.garden-next,.garden-feedback { border-left:2px solid #b8a273; padding-left:8px; }
.garden-work [data-garden-seed] { display:block; width:100%; text-align:left; margin-top:6px; }
.garden-work [data-garden-seed] small { display:block; opacity:.8; }

.pack-feedback { white-space: pre-line; }

/* Phone play has a viewport budget. Only explicitly opened tools/history scroll. */
@media (max-width: 899px) {
  body:has(.field-layout) .drama-screen [data-anim="dfx-bar-top"],
  body:has(.field-layout) .drama-screen [data-anim="dfx-bar-bot"] { display:none; }
  body:has(.field-layout) { height:100dvh; overflow:hidden; }
  body:has(.field-layout) #app { height:100%; min-height:0; }
  #screen:has(.field-layout) { padding:6px 8px; display:flex; flex-direction:column; gap:3px; min-height:0; overflow:hidden; }
  #screen:has(.field-layout) #exphead { flex:0 0 28px; overflow:hidden; }
  #screen:has(.field-layout) .term-bar { margin:0; padding:0; border:0; height:28px; align-items:center; font-size:12px; }
  #screen:has(.field-layout) .term-bar > span:first-child { font-size:12px; }
  #screen:has(.field-layout) .term-bar > span:last-child { display:flex; align-items:center; gap:5px; }
  #screen:has(.field-layout) .term-bar br,
  #screen:has(.field-layout) .term-bar .whereline,
  #screen:has(.field-layout) .term-bar > span:last-child > span > span { display:none !important; }
  #screen:has(.field-layout) #daytickwrap { flex:0 0 8px; overflow:hidden; }
  #screen:has(.field-layout) .dayticks { margin:0; height:8px;  }
  .field-layout { flex:1; min-height:0; display:grid; gap:4px;
    grid-template-columns:minmax(0,1fr);
    grid-template-rows:40px minmax(0,1fr) 104px 144px 44px;
    grid-template-areas:"status" "board" "message" "controls" "menu"; }
  #screen:has(.field-layout) .dayticks-lbl { display:none; }
  .field-layout .game-col-main,.field-layout .game-col-side,.field-layout .ord-topstack { display:contents; }
  .field-layout .titanbar { display:none; } /* combat strip already carries this enemy */
  .field-layout .ord-gridwrap { grid-area:board; min-height:0; min-width:0; display:flex; align-items:center; justify-content:center; overflow:hidden; container-type:size; }
  .field-layout .ord-gridwrap .detail { width:min(100cqw,100cqh); height:min(100cqw,100cqh); max-width:none; margin:0; flex:none; display:flex; flex-direction:column; }
  .field-layout .detail .drow { flex:1 1 0; min-height:0; display:grid; grid-auto-flow:column; grid-auto-columns:minmax(0,1fr); grid-template-rows:minmax(0,1fr); }
  .field-layout .detail .cell { width:100%; height:100%; min-height:0; min-width:0; aspect-ratio:auto; font-size:clamp(14px,5cqh,32px); overflow:visible; }
  .field-layout .detail .cell .vname { font-size:12px; line-height:1; }
  .field-layout .ord-controls { grid-area:controls; margin:0; gap:8px; min-width:0; align-items:stretch; }
  .field-layout .ord-dpad { align-self:end; }
  .field-layout .dpad { display:grid !important; grid-template-columns:repeat(3,44px); grid-auto-rows:44px; gap:2px; padding:4px; }
  .field-layout .dpbtn { min-height:44px; }
  .field-layout .dpmin,.field-layout .dpshow { display:none !important; }
  .field-layout .ord-actions { overflow:visible; display:flex; flex-direction:column; gap:2px; height:144px; margin:0; min-height:0; }
  .field-layout .ord-actions .contextbar { display:none; }
  /* Its real buttons have moved into Actions; the empty lightning label
     must not consume space or paint over the bottom navigation. */
  .field-layout .ord-actions .ord-ability { display:none; }
  .field-layout .ord-target,.field-layout .ord-danger { display:none; }
  .field-layout .ord-actions .selfbar,.field-layout .ord-actions .contextbar { margin:0; }
  .field-layout .combat-enemies { font-size:12px !important; line-height:16px; margin:0 !important; }
  .field-layout .combat-turn-status { margin:0; line-height:16px; font-size:12px; }
  .field-layout .ord-actions .combat-enemies { height:16px; flex:0 0 16px; overflow:hidden; }
  .field-layout .ord-actions .combat-enemies .msprite,
  .field-layout .ord-actions .combat-enemies .csprite { width:14px; height:14px; }
  .field-layout .combat-turn-status { flex:0 0 16px; white-space:nowrap; overflow:hidden; }
  .phone-ally-summary { flex:0 0 12px; line-height:12px; font-size:12px; white-space:nowrap; overflow:hidden; }
  .field-layout .phone-primary-actions { flex-shrink:0; }
  .phone-primary-actions { margin-top:auto; display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); grid-auto-rows:44px; gap:4px; }
  .phone-primary-actions button { margin:0; min-height:44px; padding:4px; width:auto; font-size:13px; }
  .phone-primary-actions.reply-actions { grid-template-columns:minmax(0,1fr); gap:4px; }
  .phone-primary-actions.reply-actions button { white-space:normal; line-height:16px; }
  .phone-primary-actions.reply-actions .long-reply { grid-row:span 2; }
  .phone-primary-actions.reply-actions button { overflow-y:auto; overscroll-behavior:contain; }
  .field-layout .dialogue-box > .dlg-line .dlg-who[data-npc="true"] { display:none; }
  .field-layout .ord-narration { grid-area:message; margin:0; height:auto !important; min-height:0; display:flex; flex-direction:column; overflow:hidden; }
  .field-layout .ord-narration .dialogue-box { min-height:0; flex:1; max-height:100%; padding:6px 8px; font-size:13px; line-height:1.35; }
  .field-layout .ord-narration .inline-btns,.field-layout .ord-narration .dlg-choices { display:none; }
  .field-layout #dlg-end,.field-layout #dlg-hist { min-height:44px; }
  .field-layout #dlg-end { min-width:44px; }
  .field-notice { flex:0 0 auto; padding:3px 6px; font-size:12px; line-height:16px; max-height:38px; overflow:hidden; border-left:2px solid var(--amber); background:var(--bg2); }
  .field-notice.important { border-color:var(--bad); color:var(--warn); }
  .field-danger { flex:0 0 auto; padding:4px 8px; font-size:13px; line-height:16px; max-height:72px; overflow:hidden; color:var(--warn); border:1px solid var(--danger); border-radius:6px; background:var(--bg2); }
  .field-layout .ord-status { grid-area:status; display:block; margin:0; min-height:0; }
  .field-layout .statgrid { margin:0; display:grid; grid-template-columns:64px minmax(0,1fr) minmax(0,1fr); gap:1px 6px; font-size:12px; line-height:14px; }
  .field-layout .statgrid:has(> .sys-ui) { grid-template-columns:52px minmax(0,1fr) minmax(0,1fr) 52px; }
  .field-layout .statc { min-width:0; margin:0; overflow:hidden; white-space:nowrap; }
  .field-layout .statc-food { grid-column:1/-1; order:-1; font-size:13px; line-height:16px; }
  .field-layout .sbar { height:2px; margin:0; }
  .field-layout .statc-food .sbar { height:4px; }
  .field-layout .ord-lowermenu { grid-area:menu; margin:0; }
  .field-layout .lowermenu { display:grid; grid-template-columns:repeat(5,minmax(0,1fr)); gap:4px; }
  .field-layout .lowermenu .lm-btn { padding:4px 1px; min-height:44px; font-size:12px; margin:0; }
  .field-workspace { grid-area:board; position:relative; z-index:10; min-height:0; overflow:hidden; background:var(--bg2); border:1px solid var(--line); border-radius:8px; display:flex; flex-direction:column; }
  /* (gap-playtest9: workspace outranks the inline slot's z 8, so an open
     tool panel's buttons can never sit under a stale inline card again.) */
  .field-workspace[hidden],.field-tool-content[hidden],.field-notice[hidden] { display:none; }
  .field-workspace > header { display:flex; justify-content:space-between; align-items:center; padding:2px 8px; flex:none; border-bottom:1px solid var(--line); }
  .field-workspace > header button { width:44px; min-height:44px; margin:0; }
  .field-tool-body { overflow:auto; min-height:0; padding:8px; overscroll-behavior:contain; }
  .field-tool-content > button { display:block; width:100%; min-height:44px; margin:4px 0; }
  .field-tool-content .ord-panel,.field-tool-content .ord-codex,.field-tool-content .ord-compass { margin:0; }
  .field-layout #inlineslot { position:absolute; inset:0; max-height:none; overflow:auto; margin:0; z-index:8; background:var(--bg2); overscroll-behavior:contain; }
  .field-layout #inlineslot:empty { display:none; }
  .field-layout #inlineslot .inline-head { position:sticky; top:0; z-index:2; background:var(--bg2); padding:4px; }
  /* (gap-playtest9: the tile card's ✕ sticks too — a long card scrolled
     down used to carry its only close button out of reach.) */
  .field-layout #inlineslot .tp-head { position:sticky; top:0; z-index:2; background:var(--bg2); padding:4px; }
  .field-layout .danger-message { border-color:var(--danger); color:var(--warn); }
  .field-layout .ord-taphint,.field-layout .toast-layer { display:none; }
  body.in-combat .field-layout { grid-template-rows:40px minmax(0,1fr) 128px 144px 44px; }
  .field-layout:has(.dialogue-box:not(.narr-box):not(.contest-box)) { grid-template-rows:40px minmax(0,1fr) 140px 144px 44px; }
  .field-layout .ord-narration .inlinecard { height:100%; overflow:auto; margin:0; padding:4px 8px; }
  .field-layout .ord-narration .inlinecard .inline-btns:empty { display:none; }
  .field-layout:has(.contest-box) { grid-template-rows:40px minmax(0,1fr) 168px 144px 44px; }
}

@media (max-width:899px) {
  .field-layout .pantry-quantity { gap:6px; }
  .field-layout .pantry-quantity button { min-height:44px; min-width:44px; flex:1; margin:0; }
}

@media (max-width:899px) {
  .field-layout #inlineslot:has(.pantry-card) { overflow:hidden; }
  .field-layout #inlineslot .pantry-card { height:100%; display:flex; flex-direction:column; box-sizing:border-box; margin:0; }
  .field-layout #inlineslot .pantry-card > .inline-head { position:static; flex:none; }
  .field-layout #inlineslot .pantry-card > .inline-body { min-height:0; flex:1; overflow:auto; overscroll-behavior:contain; }
  .field-layout #inlineslot .pantry-card > .inline-btns { flex:none; margin:0; padding:4px 0; }
}

/* Garden management keeps its current decisions ahead of detailed lists. */
.garden-actions { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:6px; margin:6px 0; }
.garden-actions .btn { width:100%; min-width:0; min-height:44px; padding:7px; font-size:13px; white-space:normal; }
.garden-work summary { min-height:44px; display:flex; align-items:center; cursor:pointer; font-size:13px; }
.garden-work [data-garden-keeper] { display:block; width:100%; margin:5px 0; text-align:left; min-height:44px; }

@media (max-width:899px) {
  .field-layout #inlineslot:has(.garden-card) { overflow:hidden; }
  .field-layout #inlineslot .garden-card { height:100%; display:flex; flex-direction:column; box-sizing:border-box; margin:0; }
  .field-layout #inlineslot .garden-card > .inline-head { position:static; flex:none; }
  .field-layout #inlineslot .garden-card > .inline-body { min-height:0; flex:1; overflow:auto; overscroll-behavior:contain; }
  .garden-work [data-garden-keeper] { font-size:13px; line-height:1.3; }
}
