:root {
  color-scheme: dark;
  --bg: #080b10;
  --panel: #101722;
  --line: #263445;
  --green: #7ee787;
  --gold: #e3b341;
  --red: #ff7b72;
  --muted: #8b949e;
}
* {
  box-sizing: border-box;
}
/* Issue #169: every form control reads at 1rem or better. Unstyled inputs
   otherwise sit at the UA default (~13.3px), and iOS auto-zooms when a
   sub-16px control takes focus. More specific rules below still win where a
   control carries its own size — they are all ≥1rem too. */
input,
select,
textarea {
  font-size: 1rem;
}
header p,
.hint,
.stats,
.reward-note {
  color: var(--muted);
}
/* Radial D-pad (issue #81): a circle divided into 8 wedge buttons with wait
   in the middle. Replaces the analog stick (PR #76) — discrete wedges make
   diagonals as easy to hit as cardinals.
   In-flow below the board (issue #71 follow-up) so it never covers dungeon
   tiles; shown on touch devices, hidden on desktop. */
#dpad {
  display: none;
  align-items: center;
  gap: 18px;
  margin: 10px 0 0;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}
#dpad.open {
  display: flex;
}
@media (pointer: coarse) {
  #dpad {
    display: flex;
  }
}
#radial {
  position: relative;
  width: 208px;
  height: 208px;
  flex: none;
  touch-action: none;
  cursor: pointer;
}
/* The pad is the canvas DPad component (rogueai/dpad.js), which sets its own
   touch-action/user-select/callout opt-outs and handles every touch itself. */
#radial .dpad-canvas {
  cursor: pointer;
}
/* Tutorial banner (issue #69): floats over the top of the dungeon, blocks
   nothing except via its own skip button. */
#tutorialOverlay {
  /* Slim in-flow banner under the board (issue #71): it must never cover
     the dungeon itself. */
  margin: 8px auto 0;
  max-width: 460px;
  display: flex;
  gap: 10px;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  text-align: center;
  font-size: 0.85rem;
  background: rgba(4, 7, 12, 0.88);
  border: 1px solid var(--gold);
  border-radius: 8px;
  padding: 6px 10px;
}
#tutorialOverlay[hidden] {
  display: none;
}
.bar,
.actions,
.training,
.brain-switch {
  display: flex;
  align-items: center;
  gap: 10px;
}
.brain-switch {
  flex-wrap: wrap;
}
.bar {
  justify-content: space-between;
  min-height: 34px;
}
.actions,
.training {
  flex-wrap: wrap;
  margin-top: 10px;
}
.training label {
  color: var(--muted);
  font-size: 1rem;
}
.training label:not(.toggle) {
  display: grid;
  grid-template-columns: auto auto;
  gap: 5px;
}
.training input[type="range"] {
  grid-column: 1/-1;
}
.enemy-stepper {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  color: var(--muted);
  font-size: 1rem;
}
.enemy-stepper input[type="range"] {
  width: 150px;
  accent-color: var(--gold);
}
.enemy-stepper output {
  color: var(--gold);
  min-width: 1.4em;
  text-align: center;
}
.reward-note {
  font-size: 0.78rem;
  line-height: 1.4;
}
.stats-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  font-size: 0.8rem;
}
.stats-grid span {
  padding: 8px;
  background: #080c12;
  border-radius: 5px;
}
.stats-grid b {
  display: block;
  color: var(--gold);
  margin-top: 4px;
}
#menuBtn,
#trainBtn {
  position: relative;
}
/* The Training Room's status (rogueai.js syncTrainBadge): a run training in the
   background pulses teal; a run that finished while you were away is a still gold dot. */
#trainBtn[data-training]::after {
  content: "";
  position: absolute;
  top: 5px;
  right: 5px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #6be6d8;
}
#trainBtn[data-training="running"]::after {
  animation: train-pulse 1.4s ease-in-out infinite;
}
#trainBtn[data-training="done"]::after {
  background: var(--gold);
}
@keyframes train-pulse {
  50% {
    opacity: 0.25;
  }
}
@media (prefers-reduced-motion: reduce) {
  #trainBtn[data-training="running"]::after {
    animation: none;
  }
}
/* Unread events: the log lives behind the menu, so the menu button carries the dot. */
#menuBtn.unread::after {
  content: "";
  position: absolute;
  top: 5px;
  right: 5px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--gold);
}
/* Event log (issue #67): the per-turn messages that used to thrash the header.
   Fixed max-height with internal scroll, so new entries never reflow the page. */
#eventLogList {
  margin: 0;
  padding: 0;
  list-style: none;
  font: 0.8rem "Courier New", monospace;
  color: #9fb2c8;
}
#eventLogList li {
  padding: 1px 0;
}
#eventLogList .t {
  color: #6b7a8f;
}
#dungeon {
  overflow: auto;
  background: #030507;
  border: 1px solid var(--line);
  border-radius: 7px;
  padding: 14px;
  line-height: 1.15;
  font-size: clamp(0.66rem, 1.55vw, 0.95rem);
  color: var(--green);
  touch-action: none;
  user-select: none;
  min-height: 300px;
}
/* Torch/isometric canvas renderer (issue #73): same footprint as the pre. */
#dungeonCanvas {
  display: block;
  width: 100%;
  height: auto;
  background: #020204;
  border: 1px solid var(--line);
  border-radius: 7px;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}
#dungeonCanvas[hidden],
#dungeonGL[hidden] {
  display: none;
}
#dungeonGL {
  position: absolute;
  inset: 0;
  pointer-events: none;
  display: block;
  width: 100%;
  height: 100%;
  background: #020204;
  border-radius: 7px;
}
#dungeonCanvas.gl-overlay {
  background: transparent;
  position: relative;
  z-index: 1;
}
#fpsGraph {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 40;
  pointer-events: none;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: rgba(9, 10, 13, 0.84);
}
#artModalClose {
  border: 1px solid var(--line);
  background: none;
  color: var(--fg);
  border-radius: 6px;
  width: 30px;
  height: 30px;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}
#artStyles {
  display: grid;
  gap: 8px;
}
#artStyles [data-art] {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  text-align: left;
  border: 1px solid var(--line);
  background: #060a10;
  color: var(--fg);
  border-radius: 8px;
  padding: 10px 12px;
  cursor: pointer;
}
#artStyles [data-art] span {
  color: #9fb2c8;
  font-size: 1rem;
}
#artStyles [data-art].sel {
  border-color: var(--gold);
}
button {
  font: inherit;
  background: #182331;
  color: #d4ddd8;
  border: 1px solid #3b4e64;
  border-radius: 6px;
  padding: 9px 12px;
  cursor: pointer;
}
button:hover:not(:disabled),
button.active {
  border-color: var(--gold);
  color: var(--gold);
}
button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.good {
  color: var(--green);
}
.bad {
  color: var(--red);
}
#network {
  position: relative;
  width: 100%;
  height: 430px;
  background: #080c12;
  border-radius: 7px;
  margin: 8px 0;
  overflow: hidden;
}
#network canvas {
  display: none;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
#network canvas.active {
  display: block;
}
#networkOverlay {
  pointer-events: none;
}
.network-crumb {
  font: 12px "Courier New", monospace;
  color: #8fa1b8;
  margin: 8px 0 0;
  min-height: 20px;
}
.network-crumb button {
  background: none;
  border: 1px solid #33506e;
  border-radius: 4px;
  color: #cfe3ff;
  font: inherit;
  font-size: 1rem;
  cursor: pointer;
  padding: 1px 8px;
}
.network-crumb button:hover {
  background: #16283d;
}
#networkGraph:focus {
  outline: 1px solid #33506e;
  outline-offset: -1px;
}
#networkGraph {
  touch-action: none;
}
/* Module-graph navigation toolbar (issue #65): overlaid top-right of the
   #network stack, visible only on the graph level. */
.graph-toolbar {
  position: absolute;
  top: 8px;
  right: 8px;
  display: flex;
  gap: 6px;
  z-index: 5;
}
.graph-toolbar[hidden] {
  display: none;
}
.graph-toolbar button {
  padding: 4px 10px;
  font-size: 1rem;
  line-height: 1.2;
  background: #101722ee;
}
/* iOS fallback for graph fullscreen (mirrors the game's focus-mode): when the
   Fullscreen API is unavailable, pin #network to the viewport instead. */
body.graph-focus #network {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  z-index: 80;
  border-radius: 0;
  margin: 0;
}
.network-meta {
  display: flex;
  justify-content: flex-end;
  margin: 0 0 8px;
}
.renderer-label {
  font: 10px "Courier New";
  color: var(--muted);
}
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.controls {
  display: grid;
  grid-template-columns: 1fr 1fr auto;
  gap: 12px;
  align-items: end;
}
.controls label {
  display: grid;
  gap: 5px;
  color: var(--muted);
  font-size: 1rem;
}
.controls output {
  color: var(--gold);
}
input[type="range"] {
  width: 100%;
}
.reward-flash {
  animation: flash 0.4s ease;
}
@keyframes flash {
  50% {
    box-shadow: 0 0 35px #e3b341aa;
  }
}
.tabbar {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin: 4px 0 12px;
}
.tab.active {
  border-color: var(--gold);
  color: var(--gold);
}
.tabpanel {
  display: none;
}
.tabpanel.active {
  display: block;
}
.game .bar strong,
.game .bar button,
#hp {
  white-space: nowrap;
  flex: none;
}
@media (max-width: 520px) {
  .game .bar {
    gap: 6px;
  }
  .game .bar button {
    padding: 6px 8px;
  }
  .brain-switch {
    gap: 8px;
  }
  .brain-switch button {
    padding: 8px 10px;
    flex: 1 1 auto;
  }
}
#autoplay.on {
  background: #2c4a35;
  border-color: #7dff9a;
}
#dungeon .tp {
  color: #ffe45e;
  text-shadow: 0 0 6px #ffe45e73; /* subtle glow */
  font-weight: 700;
}
#dungeon .te {
  color: #ff5a5a;
  text-shadow: 0 0 6px #ff5a5a73; /* subtle glow */
  font-weight: 700;
}
#dungeon .tb {
  color: #ff9a3c;
  text-shadow: 0 0 6px #ff9a3c73; /* subtle glow */
  font-weight: 700;
}
#dungeon .to {
  color: #7dff9a;
  text-shadow: 0 0 6px #7dff9a73; /* subtle glow */
  font-weight: 700;
}
#dungeon .ts {
  color: #5ecfff;
  text-shadow: 0 0 6px #5ecfff73; /* subtle glow */
  font-weight: 700;
}
/* a non-hostile NPC (issue #189): its own colour comes inline */
#dungeon .tn {
  font-weight: 700;
}
#dungeon .tw {
  color: #5a6b7d;
}
#dungeon .tf {
  color: #2c3a47;
}
.rewards {
  margin: 10px 0 4px;
}
.rewards summary {
  cursor: pointer;
  font-weight: 600;
}
.reward-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
  gap: 2px 14px;
  margin: 8px 0;
}
.reward-head {
  grid-column: 1/-1;
  opacity: 0.6;
  font-size: 0.85em;
  margin-top: 6px;
}
.reward-grid label {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}
.reward-grid input {
  width: 78px;
}
.ckpt-status{opacity:.75;font-size:12px;margin-left:6px;align-self:center}
.nn-prof {
  flex-basis: 100%;
  margin: 8px 0 0;
  padding: 8px;
  background: #080c12;
  border-radius: 6px;
  color: var(--muted);
  font: 12px/1.4 ui-monospace, monospace;
  white-space: pre;
  overflow-x: auto;
}

/* Coaching: which side your moves teach (@ player or one E). A two-state
   toggle behind the menu (docs/hud.md): one pill, the coached side's glyph lit,
   with what your moves teach beside it. */
.pcoach {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  margin: 2px 0 10px;
}
.pcoach .coach-label {
  font: 500 0.72rem/1 var(--f-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.pcoach .coach-note {
  flex: 1 1 100%;
  white-space: normal;
}
.coach {
  display: inline-flex;
  flex: none;
  border: 1px solid var(--edge);
  border-radius: 999px;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.35);
}
.coach button {
  padding: 3px 11px;
  font: 600 0.85rem/1.25 var(--f-mono);
  background: transparent;
  border: 0;
  border-radius: 0;
  color: var(--ash);
  cursor: pointer;
  transition:
    background 0.15s,
    color 0.15s;
}
.coach button.active {
  background: var(--torch);
  color: #14100a;
}
.coach-note {
  flex: 1 1 14em;
  min-width: 0;
  font-size: 0.75rem;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Aim phase (issue #115): the selected target and the shot's line, and the other candidates. */
#dungeon .ta {
  color: #ffd25a;
  font-weight: 700;
}
#dungeon .tc {
  color: #ffb347;
  text-decoration: underline;
}
/* Lock-on in text styles (issue #149): the target in inverse gold, marching-ant dots on the
   lane (ordered from @ by --i), enemies the bow reaches but cannot hit dimmed. */
#dungeon .tsel {
  background: #ffd25a;
  color: #140c02;
  text-shadow: none;
  border-radius: 2px;
}
/* Loomwork: the part whose card is open (just placed, or tapped), outlined in lilac. */
#dungeon .tfocus {
  outline: 1px solid #c58cff;
  outline-offset: -1px;
  border-radius: 2px;
}
/* Loomwork: building with something in hand, the floor you can reach is lit amber. */
#dungeon .treach {
  color: #ffb257;
  background: rgba(255, 178, 87, 0.08);
}
#dungeon .tant {
  color: #9fd3ff;
  font-weight: 700;
  animation: tant 630ms linear infinite;
  animation-delay: calc(var(--i, 0) * 70ms - 630ms);
}
/* The Ember fuse (issue #150): the firebolt's aim, the same marks in warm orange. */
#dungeon .tsel.tember {
  background: #ff9a3c;
}
#dungeon .tant.tember {
  color: #ffb257;
  text-shadow: 0 0 6px rgba(255, 140, 50, 0.7);
}
@keyframes tant {
  0%,
  100% {
    opacity: 0.35;
  }
  30% {
    opacity: 1;
  }
}
#dungeon .tblk {
  color: #6e3434;
  text-shadow: none;
}
/* Combat effects snapped to cells (issue #149): colour comes inline from the effect. */
#dungeon .tfx {
  font-weight: 700;
}
@media (prefers-reduced-motion: reduce) {
  #dungeon .tant {
    animation: none;
    opacity: 0.8;
  }
}
#dungeon .ty {
  color: #c792ff;
  text-shadow: 0 0 8px #c792ffaa;
  font-weight: 700;
  outline: 1px solid #c792ff;
  outline-offset: -1px;
}

/* Brain's-eye view (phase 2): Q-value compass + the brain's scalar inputs. */
#sensesPanel {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 12px;
  align-items: flex-start;
  margin-top: 8px;
  font-size: 0.78rem;
  color: var(--muted);
}
#sensesPanel[hidden] {
  display: none;
}
#qGrid {
  display: grid;
  grid-template-columns: repeat(3, 5.2em);
  gap: 2px;
  font-variant-numeric: tabular-nums;
}
#qGrid span {
  --q: 0;
  padding: 3px 4px;
  text-align: center;
  border-radius: 3px;
  border: 1px solid transparent;
  color: #d8e2dc;
  background: rgba(94, 207, 255, calc(0.08 + 0.4 * var(--q)));
}
#qGrid span.off {
  background: #0b0f15;
  color: #4a5560;
}
#qGrid span.best {
  border-color: #ffd166;
  color: #ffd166;
  font-weight: 700;
}
#senseInfo {
  flex: 1 1 14em;
  margin: 0;
  color: var(--text, #d8e2dc);
}
/* Brain layers modal (reuses the art picker's box and head). */
.layers-intro {
  margin: 0 0 10px;
  font-size: 0.78rem;
  color: var(--muted);
}
#layerList {
  display: grid;
  gap: 8px;
}
#layerList label {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 2px 8px;
  align-items: baseline;
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: 6px;
  cursor: pointer;
}
#layerList input {
  accent-color: var(--gold);
}
#layerList span {
  grid-column: 2;
  font-size: 0.75rem;
  color: var(--muted);
}
#layerList .g {
  color: #7dff9a;
  font-style: normal;
}
#layerList .r {
  color: #ff5a5a;
  font-style: normal;
}
#layersBtn.on {
  background: #2c4a35;
  border-color: #7dff9a;
}
/* Upgrade shop (phase 3): same modal box as the art and layers pickers. */
#shopList {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 8px;
}
#shopList li {
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: 6px;
}
#shopList p {
  margin: 4px 0 0;
  font-size: 0.75rem;
  color: var(--muted);
}
#shopList .shop-lesson {
  color: #c792ff;
}
.shop-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
#shopBtn {
  padding: 4px 10px;
  color: var(--gold);
}

/* Issue #81 follow-up: kill iOS double-tap-to-zoom on every tap target in the
   game UI. The pad/dungeon/canvas already use touch-action: none; this catches
   the surrounding chrome (buttons, hints, headers). manipulation keeps
   pinch-zoom and panning working. */
html, body {
  touch-action: manipulation;
}
/* touch-action is not inherited: WebKit resolves it only up to the nearest
   scrollable ancestor, which resets it to auto. So the html/body opt-out never
   reaches controls inside the shell's scrolling panels (issue #83 overlays,
   lab, event log, phone caught screen). Opt those containers and the controls
   themselves out too; the touch-action: none rules above still win. */
.ov > .panel,
.lab-body,
#eventLogList,
#caughtScreen .cgrid,
button,
[role="button"],
input,
select,
label {
  touch-action: manipulation;
}

/* ==== Menu-driven shell (issue #83) ====
   One fixed game window: every menu is a screen or an overlay, nothing scrolls
   during play. Palette custom properties (--void, --torch, ...) are set on
   :root by GlyphFX.applyCss from fx.json; the values below are the fallbacks. */
:root {
  --void: #07080b;
  --bone: #e9e1cd;
  --torch: #ffb257;
  --torch-deep: #a8601f;
  --ember: #ff5b4d;
  --potion: #7dff9a;
  --stairs: #5ecfff;
  --stone: #0e1116;
  --slab: #161a21;
  --edge: #2a2f38;
  --ash: #a19d92;
  --smoke: #6b6d6a;
  --f-mono: "IBM Plex Mono", ui-monospace, Menlo, Consolas, monospace;
  --f-disp: "Big Shoulders Display", "Arial Narrow", Impact, sans-serif;
  --fg: #d4ddd8;
}
html,
body.app {
  height: 100%;
}
body.app {
  margin: 0;
  padding: 0;
  min-height: 0;
  overflow: hidden;
  background: var(--void);
  color: var(--bone);
  font-family: var(--f-mono);
  overscroll-behavior: none;
}
[hidden] {
  display: none !important;
}
.game-root {
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bot: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  position: fixed;
  inset: 0;
  overflow: hidden;
  isolation: isolate;
  background: var(--void);
  user-select: none;
  -webkit-user-select: none;
}
.game-root kbd {
  font: 500 0.72em/1.2 var(--f-mono);
  color: var(--ash);
  border: 1px solid var(--edge);
  border-bottom-width: 2px;
  border-radius: 3px;
  padding: 0.08em 0.42em;
  background: rgba(0, 0, 0, 0.3);
}
#fxCanvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 60;
  pointer-events: none;
}
.toast {
  position: absolute;
  left: 50%;
  bottom: calc(1.2em + var(--safe-bot));
  z-index: 55;
  transform: translateX(-50%);
  padding: 0.55em 1em;
  border: 1px solid var(--torch);
  background: rgba(10, 12, 16, 0.94);
  font: 500 0.8rem var(--f-mono);
  color: var(--bone);
  /* lines wrap (Basil's toasts run to a sentence or two); never cut off with "…" */
  white-space: normal;
  width: max-content;
  max-width: min(30rem, calc(100vw - 32px));
  text-align: center;
  opacity: 0;
  transition: opacity 0.25s;
  pointer-events: none;
  max-width: 90%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.toast.on {
  opacity: 1;
}

/* Play screen */
.game.card {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  background: var(--void);
  padding: calc(8px + var(--safe-top)) calc(12px + var(--safe-r)) calc(8px + var(--safe-bot)) calc(12px + var(--safe-l));
}
.game.card > * {
  flex: none;
}
.game .bar {
  gap: 8px;
  flex-wrap: wrap;
}
.vitals,
.bar-btns {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.vitals {
  flex-wrap: nowrap;
}
.vitals strong {
  font: 800 1.4rem/1 var(--f-disp);
  letter-spacing: 0.06em;
  color: var(--bone);
  text-transform: uppercase;
}
#hp {
  color: #ffe45e;
  letter-spacing: 0.1em;
  font-size: 1.2em;
}
/* The collapse clock is a ticking-down ring: the arc is the fraction of the
   floor's collapse window left (--frac, set by render() each turn) and the
   number inside is the exact count. */
.clock {
  position: relative;
  width: 28px;
  height: 28px;
  flex: none;
  white-space: nowrap;
}
.clock .ring {
  display: block;
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
}
.clock .ring circle {
  fill: none;
  stroke-width: 2.5;
}
.clock .ring-bg {
  stroke: #2a3342;
}
.clock .ring-fg {
  stroke: var(--torch);
  stroke-linecap: round;
  stroke-dasharray: 56.55;
  stroke-dashoffset: calc(56.55 * (1 - var(--frac, 1)));
  transition: stroke-dashoffset 0.25s linear;
}
.clock.low .ring-fg {
  stroke: var(--ember);
}
.clock b {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 0.58rem;
  color: var(--torch);
  font-variant-numeric: tabular-nums;
}
.clock.low b {
  color: var(--ember);
}
.view {
  flex: 1 1 auto !important;
  min-height: 0;
  position: relative;
}
/* A Training Room run training while you play: named on the board's bottom-left corner. */
.train-chip {
  position: absolute;
  left: 8px;
  bottom: 8px;
  z-index: 4;
  max-width: calc(100% - 16px);
  padding: 4px 10px;
  font: 600 0.75rem var(--f-mono);
  color: var(--torch);
  background: rgba(7, 8, 11, 0.85);
  border: 1px solid var(--torch-deep);
  border-radius: 999px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.viewport {
  position: absolute;
  inset: 0;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #030507;
  border: 1px solid var(--edge);
  border-radius: 6px;
}
.viewport #dungeon {
  flex: none;
  overflow: visible;
  border: 0;
  padding: 0;
  min-height: 0;
  margin: 0;
  background: transparent;
  line-height: 1.15;
  width: max-content;
  will-change: transform;
}
.viewport #dungeonCanvas {
  flex: none;
  border: 0;
  border-radius: 0;
  will-change: transform;
}
.viewport.follow #dungeon,
.viewport.follow #dungeonCanvas {
  transition: transform var(--fx-glide-ms, 130ms) cubic-bezier(0.215, 0.61, 0.355, 1);
}
/* While a move glide runs, the render loop drives the camera from the glide
   itself, frame by frame: no CSS tween on top. */
.viewport.follow.gliding #dungeonCanvas {
  transition: none;
}
/* Build mode's zoom tween (viewport.zoomTo) steps the camera every frame itself: no glide on top. */
.viewport.zooming #dungeon,
.viewport.zooming #dungeonCanvas {
  transition: none;
}
.viewport #dungeonCanvas:not(.cam) {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
/* HUD panels over the map */
.hud-panel {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 3;
  margin: 0 !important;
  padding: 8px 10px;
  max-width: min(22rem, 46%);
  background: rgba(8, 10, 14, 0.86);
  border: 1px solid var(--edge);
  border-radius: 4px;
  flex-direction: column;
  align-items: stretch;
}
.hud-panel #senseInfo {
  font-size: 0.7rem;
  line-height: 1.35;
  color: var(--ash);
}
.hud-log {
  position: absolute;
  left: 10px;
  bottom: 8px;
  z-index: 3;
  margin: 0;
  padding: 0;
  list-style: none;
  width: min(34rem, 60%);
  font: 0.72rem/1.6 var(--f-mono);
  pointer-events: none;
}
.hud-log li {
  color: var(--ash);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-shadow: 0 0 4px #000, 0 0 8px #000;
}
.hud-log li:nth-last-child(2) {
  opacity: 0.7;
}
.hud-log li:nth-last-child(3) {
  opacity: 0.45;
}
.hud-log li:nth-last-child(n + 4) {
  opacity: 0.25;
}
.hud-log .t {
  color: var(--smoke);
}
.hud-reward {
  position: absolute;
  left: 10px;
  bottom: 14px;
  z-index: 3;
  margin: 0;
  font: 600 0.85rem/1.6 var(--f-mono);
  white-space: nowrap;
  pointer-events: none;
  text-shadow: 0 0 4px #000, 0 0 8px #000;
}
.hud-reward[hidden] {
  display: none;
}
/* Entries are placed by the shell (bottom offsets in px): each one rises on
   its own float/fade clock, and newer entries nudge it up only by the space
   they actually still need. */
.hud-reward-item {
  position: absolute;
  left: 0;
  bottom: 0;
}
/* The ticker's green/red, desaturated about halfway toward grey (issue #162). */
.hud-reward-item.pos {
  color: #7fac7f;
}
.hud-reward-item.neg {
  color: #b07878;
}
/* Streamer-alert motion (issue #162): rise in, hold, then float up and fade.
   Each stack entry runs it on its own clock and is removed when it ends. */
.hud-reward-item.anim {
  animation: hud-reward-float 2.4s ease-out forwards;
}
@keyframes hud-reward-float {
  0% {
    opacity: 0;
    transform: translateY(6px);
  }
  12% {
    opacity: 1;
    transform: translateY(0);
  }
  55% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: translateY(-16px);
  }
}
@keyframes hud-reward-fade {
  0% {
    opacity: 0;
  }
  12% {
    opacity: 1;
  }
  55% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  .hud-reward-item.anim {
    animation-name: hud-reward-fade;
  }
}
.hud-mode {
  position: absolute;
  right: 10px;
  bottom: 8px;
  z-index: 3;
  margin: 0;
  font: 0.7rem/1.5 var(--f-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ash);
  text-align: right;
  pointer-events: none;
  text-shadow: 0 0 4px #000, 0 0 8px #000;
}
.hud-mode b {
  color: var(--torch);
  font-weight: 500;
}
.banner {
  position: absolute;
  left: 0;
  right: 0;
  top: 36%;
  z-index: 4;
  text-align: center;
  font: 800 clamp(2rem, 8vw, 4.4rem) / 1 var(--f-disp);
  letter-spacing: 0.22em;
  color: var(--bone);
  text-shadow: 0 0 0.45em rgba(255, 178, 87, 0.55);
  opacity: 0;
  transition: opacity 0.35s;
  pointer-events: none;
}
.banner.on {
  opacity: 1;
}
.game.card .hint {
  margin: 0;
  font-size: 0.72rem;
}
#hurtFlash {
  display: none;
}
.hurt .viewport {
  animation: hurtFlash var(--fx-hurt-ms, 450ms) ease-out;
}
@keyframes hurtFlash {
  from {
    box-shadow: inset 0 0 9em var(--fx-hurt-color, rgba(255, 70, 55, 0.55));
  }
  to {
    box-shadow: inset 0 0 9em transparent;
  }
}
/* Pause: the world desaturates and takes the configured tint. */
.paused .viewport {
  filter: grayscale(var(--fx-pause-gray, 1)) brightness(0.8);
}
.paused .view::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  background: var(--fx-pause-tint, #5a78a8);
  mix-blend-mode: color;
  opacity: 0.55;
  pointer-events: none;
}
#tutorialOverlay {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(3.4rem + var(--safe-bot));
  z-index: 6;
  margin: 0;
}
/* Buttons */
.game-root button {
  border-radius: 4px;
}
.tacts {
  display: none;
}
#dpad {
  justify-content: space-between;
}
#dpad #radial {
  width: 168px;
  height: 168px;
}

#brainEditorFrame { width: 100%; height: 100%; min-height: 0; border: 0; background: var(--void); }
#brainEditorScreen { background: var(--void); }
body.phone #brainEditorScreen .lab { inset: var(--safe-top) 0 var(--safe-bot); padding: 12px; gap: 8px; }
/* Screens: title, caught, lab, editor */
.scr {
  position: absolute;
  inset: 0;
  z-index: 5;
}
#app[data-screen="title"] .game.card .bar,
#app[data-screen="title"] .game.card #dpad,
#app[data-screen="title"] #tutorialOverlay,
#app[data-screen="title"] .game.card .hint,
#app[data-screen="title"] .hud-log,
#app[data-screen="title"] .hud-mode,
#app[data-screen="title"] .hud-panel {
  visibility: hidden;
}
#titleScreen {
  background: linear-gradient(90deg, rgba(7, 8, 11, 0.97) 0 30%, rgba(7, 8, 11, 0.62) 58%, rgba(7, 8, 11, 0.18)),
    radial-gradient(ellipse at 70% 55%, transparent 30%, rgba(7, 8, 11, 0.85) 100%);
}
#titleScreen .tcol {
  position: absolute;
  left: 5.5%;
  top: calc(6% + var(--safe-top));
  bottom: calc(6% + var(--safe-bot));
  width: min(44%, 38rem);
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
#titleLogo,
#caughtLogo {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 1;
  max-width: 100%;
}
#caughtLogo {
  aspect-ratio: 3.4 / 1;
}
.sub {
  margin: -0.8rem 0 0;
  font: 600 1rem var(--f-mono);
  letter-spacing: 0.72em;
  color: var(--bone);
  white-space: nowrap;
}
.tag {
  margin: 0;
  font: 400 0.95rem/1.5 system-ui, sans-serif;
  color: var(--ash);
  max-width: 32em;
}
.menuwrap {
  margin-top: auto;
  display: grid;
  gap: 0.6rem;
  max-width: 30rem;
}
.menu {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.1rem;
}
.menu button {
  all: unset;
  box-sizing: border-box;
  position: relative;
  display: grid;
  grid-template-columns: 1.4em minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.7em;
  width: 100%;
  padding: 0.42em 0.8em 0.42em 0.5em;
  cursor: pointer;
  font: 500 1.1rem/1.3 var(--f-mono);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ash);
}
.menu button > * {
  position: relative;
}
.menu button::before {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0;
  background: linear-gradient(90deg, rgba(255, 178, 87, 0.17), transparent 80%);
  border-left: 2px solid var(--torch);
  transition: opacity 0.15s;
}
.menu button.locked {
  opacity: 0.5;
  cursor: not-allowed;
}
.menu button.sel {
  color: var(--bone);
}
.menu button.sel::before {
  opacity: 1;
}
.menu button:focus-visible {
  outline: 1px dashed var(--torch);
  outline-offset: 2px;
}
.menu .cur {
  color: var(--smoke);
  text-align: center;
}
.menu .sel .cur {
  color: var(--torch);
  text-shadow: 0 0 0.7em var(--torch);
}
.menu .aux {
  font-size: 0.7em;
  letter-spacing: 0.1em;
  color: var(--smoke);
}
.menu .sel .aux {
  color: var(--torch);
}
.mdesc {
  min-height: 3em;
  margin: 0;
  padding-left: 2.6em;
  font: 400 0.85rem/1.5 system-ui, sans-serif;
  color: var(--ash);
}
.thint {
  position: absolute;
  left: 5.5%;
  bottom: calc(2% + var(--safe-bot));
  font: 0.7rem var(--f-mono);
  color: var(--smoke);
  display: flex;
  gap: 1.2em;
}
.tfoot {
  position: absolute;
  right: 2rem;
  bottom: calc(1.6rem + var(--safe-bot));
  display: grid;
  gap: 0.3em;
  justify-items: end;
  font: 0.72rem var(--f-mono);
  color: var(--smoke);
  letter-spacing: 0.06em;
  text-align: right;
}
.tfoot b {
  color: var(--ash);
  font-weight: 500;
}

/* Framed panels */
.panel {
  position: relative;
  background: linear-gradient(180deg, rgba(22, 26, 33, 0.97), rgba(11, 13, 17, 0.98));
  border: 1px solid var(--edge);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.55), 0 1.6em 4em rgba(0, 0, 0, 0.6);
}
.ptitle {
  position: absolute;
  top: 0.7rem;
  left: 1.4em;
  padding: 0;
  font: 600 0.75rem/1.3 var(--f-mono);
  letter-spacing: 0.2em;
  color: var(--torch);
  white-space: nowrap;
}
.phead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1em;
}
.phead h2 {
  white-space: nowrap;
}
.phead h2,
.art-modal-head h2,
.lab-head h2 {
  margin: 0;
  font: 800 1.8rem/1 var(--f-disp);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bone);
}
.art-modal-head h2 {
  font-size: 1.4rem;
}
.pfoot {
  display: flex;
  gap: 1.2em;
  font: 0.72rem var(--f-mono);
  color: var(--smoke);
}
.meta {
  font-size: 0.75rem;
  color: var(--ash);
}

/* Overlays */
.ov {
  position: absolute;
  inset: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(1.6rem + var(--safe-top)) 1.6rem calc(1.6rem + var(--safe-bot));
  background: rgba(5, 6, 9, 0.5);
}
.ov.side {
  justify-content: flex-end;
  background: linear-gradient(90deg, transparent 45%, rgba(5, 6, 9, 0.7));
  pointer-events: none;
}
.ov.side .panel {
  pointer-events: auto;
}
.ov > .panel {
  width: min(28rem, 100%);
  max-height: 100%;
  overflow: auto;
  padding: 2rem 1.5rem 1.3rem;
  display: grid;
  /* one column that may shrink to the panel: an auto track grows to its widest child's
     min-content (iOS Safari counts more than Chromium here), which let the sheet scroll
     sideways onto empty space */
  grid-template-columns: minmax(0, 1fr);
  gap: 0.9rem;
  align-content: start;
}
/* Brain panel (issue #83 follow-up): wide enough for the network views. */
#layersModal > .panel.brain-ov {
  width: min(46rem, 100%);
}
.brain-tabs {
  margin: 0;
}
.btab.active {
  border-color: var(--gold);
  color: var(--gold);
}
.brain-ov #network {
  height: min(430px, 55vh);
}
.brain-ov .brain-switch {
  flex-wrap: wrap;
  gap: 6px;
}
.brain-ov .brain-switch button,
body.phone .brain-ov .brain-switch button {
  flex: none;
  padding: 4px 10px;
  font-size: 1rem;
}
.brain-ov #params {
  font-size: 0.75rem;
  margin: 0;
}
.brain-notes summary {
  cursor: pointer;
  color: var(--ash);
  font-size: 0.8rem;
}
#pauseModal > .panel {
  width: min(24rem, 100%);
}
.ov > .panel.wide {
  width: min(58rem, 100%);
}
.ov:not([hidden]) > .panel {
  animation: frameIn var(--fx-panel-ms, 300ms) steps(var(--fx-panel-steps, 9), end);
}
@keyframes frameIn {
  from {
    clip-path: inset(48% 0 48% 0);
  }
  to {
    clip-path: inset(0 0 0 0);
  }
}
.art-modal-box {
  display: grid;
  /* one column that may shrink: an auto track grows to its widest child's min-content,
     and iOS Safari sizes a <select> to its longest option (the composed-brain picker). */
  grid-template-columns: minmax(0, 1fr);
  gap: 0.6rem;
}
.art-modal-box > *,
.brain-modules > * {
  min-width: 0;
}
.art-modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
#artModalClose,
#layersModalClose,
#shopModalClose,
#logClose {
  border: 1px solid var(--edge);
  background: none;
  color: var(--fg);
  width: 30px;
  height: 30px;
  padding: 0;
  font-size: 1.1rem;
  line-height: 1;
}
.set-h {
  margin: 0.4rem 0 0;
  font: 600 0.72rem var(--f-mono);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ash);
}
#artStyles {
  display: grid;
  gap: 8px;
}
#artStyles [data-art] {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  text-align: left;
  border: 1px solid var(--edge);
  background: #060a10;
  color: var(--fg);
  border-radius: 6px;
  padding: 8px 12px;
}
#artStyles [data-art] span {
  color: #9fb2c8;
  font-size: 1rem;
}
#artStyles [data-art].sel {
  border-color: var(--torch);
}
.set-rows {
  display: grid;
  gap: 10px;
  font-size: 1rem;
  color: var(--muted);
}
.set-rows label:not(.toggle) {
  display: grid;
  gap: 4px;
}
.set-rows output {
  color: var(--torch);
}
.layers-intro {
  margin: 0 0 6px;
  font-size: 0.78rem;
  color: var(--muted);
}
#layerList {
  display: grid;
  gap: 8px;
}
#layerList label {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 2px 8px;
  align-items: baseline;
  padding: 8px;
  border: 1px solid var(--edge);
  border-radius: 6px;
  cursor: pointer;
}
#layerList input {
  accent-color: var(--torch);
}
#layerList span {
  grid-column: 2;
  font-size: 0.75rem;
  color: var(--muted);
}
#layerList .g {
  color: #7dff9a;
  font-style: normal;
}
#layerList .r {
  color: #ff5a5a;
  font-style: normal;
}
#layersBtn.on,
#autoplay.on {
  background: #2c4a35;
  border-color: #7dff9a;
}
/* Upgrade shop */
.sidesw {
  display: inline-flex;
  gap: 0;
}
.sidesw button {
  padding: 4px 10px;
  border-radius: 0;
}
.sidesw button[aria-pressed="true"] {
  border-color: var(--torch);
  color: var(--torch);
}
.shopgrid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: 1.2rem;
}
#shopList {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 8px;
  align-content: start;
}
#shopList li {
  padding: 8px 10px;
  border: 1px solid var(--edge);
  border-radius: 6px;
  cursor: pointer;
}
#shopList li.sel {
  border-color: var(--torch);
}
#shopList li.owned .cost {
  color: var(--potion);
}
#shopList p {
  margin: 4px 0 0;
  font-size: 0.75rem;
  color: var(--muted);
}
#shopList .shop-lesson {
  color: #c792ff;
}
.shop-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.shop-head .cost {
  color: var(--torch);
  font-size: 0.85rem;
  white-space: nowrap;
}
.netbox {
  display: grid;
  gap: 0.6rem;
  align-content: start;
}
#shopNet {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 10;
  border: 1px solid var(--edge);
  background: rgba(0, 0, 0, 0.35);
}
.lesson {
  margin: 0;
  min-height: 3em;
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--ash);
}
.buy {
  position: relative;
  overflow: hidden;
  text-align: center;
  padding: 0.75em 1em;
  border-color: var(--torch);
  color: var(--torch);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  touch-action: none;
}
.buy .fill {
  position: absolute;
  inset: 0;
  width: 0;
  background: repeating-linear-gradient(90deg, rgba(255, 178, 87, 0.45) 0 0.5em, rgba(255, 178, 87, 0.2) 0.5em 0.62em);
}
.buy span {
  position: relative;
}
/* Event log overlay */
#eventLogPanel > .panel {
  width: min(36rem, 100%);
}
#eventLogList {
  margin: 0;
  padding: 0;
  list-style: none;
  font: 0.8rem/1.55 var(--f-mono);
  color: #9fb2c8;
  max-height: 22rem;
  overflow: auto;
}
#eventLogList li {
  padding: 1px 0;
}
#eventLogList .t {
  color: #6b7a8f;
}

/* Caught screen */
#caughtScreen {
  background: linear-gradient(180deg, rgba(40, 6, 6, 0.97), rgba(7, 8, 11, 0.98));
}
#caughtScreen .cgrid {
  position: absolute;
  inset: calc(2rem + var(--safe-top)) 3rem calc(2rem + var(--safe-bot));
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 2.4rem;
  align-items: center;
}
.cl {
  display: grid;
  gap: 1rem;
  align-content: center;
}
.cause {
  margin: 0;
  font: 400 1rem/1.5 system-ui, sans-serif;
  color: var(--bone);
}
.cause b {
  color: var(--ember);
  font-weight: 600;
}
.cstats {
  margin: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.6rem;
}
.cstats div {
  border-top: 1px solid var(--edge);
  padding-top: 0.4em;
}
.cstats dt {
  font: 600 0.6rem var(--f-mono);
  letter-spacing: 0.2em;
  color: var(--ash);
  text-transform: uppercase;
}
.cstats dd {
  margin: 0;
  font: 800 1.8rem/1.1 var(--f-disp);
  font-variant-numeric: tabular-nums;
}
.teach {
  display: grid;
  gap: 0.4rem;
  padding: 0.8rem 1rem;
  border: 1px dashed var(--edge);
}
.teach .eyebrow {
  margin: 0;
  font: 600 0.68rem var(--f-mono);
  letter-spacing: 0.25em;
  color: var(--ash);
  text-transform: uppercase;
}
.teach .actions {
  margin-top: 0;
}
.cact {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}
.cta {
  padding: 0.7em 1.2em;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.cta.primary {
  border-color: var(--torch);
  color: var(--torch);
  background: rgba(255, 178, 87, 0.08);
}
.campanel {
  padding: 1.3rem 1rem 1rem;
  display: grid;
  gap: 0.6rem;
}
.camwrap {
  position: relative;
  aspect-ratio: 16 / 10;
  background: #050305;
}
.caught-cam {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  background: #050305;
  border-radius: 0;
}
/* The canvas's backing store is the box size times devicePixelRatio: without an
   explicit CSS size it shows at 2-3x on a phone and the box crops it. */
.caught-cam canvas {
  display: block;
  width: 100%;
  height: 100%;
}
.caught-cam button {
  position: absolute;
  right: 10px;
  bottom: 16px;
}
/* Centered at 30% height (the top 60% of the cam), where the replay's ceiling is
   empty, so the logo doesn't sit on the caught @ in the middle of the scene. */
.cam-logo {
  position: absolute;
  inset: 0 0 40% 0;
  z-index: 2;
  display: grid;
  place-items: center;
  padding: 0 4%;
  pointer-events: none;
  transition: opacity 250ms ease-out;
}
.cam-logo.hide {
  opacity: 0;
  transition: none;
}
.cam-idle {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  margin: 0;
  padding: 0.6rem 1rem;
  text-align: center;
  font-size: 0.8rem;
  color: var(--ash);
}
.cam-idle:empty {
  display: none;
}
.camcap {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1em;
  font: 0.72rem var(--f-mono);
  color: var(--ash);
}
.camcap button {
  padding: 3px 10px;
}

/* Training lab */
#labScreen,
#labsScreen,
#brainEditorScreen {
  background: rgba(7, 8, 11, 0.94);
}
#labScreen .lab,
#labsScreen .lab,
#brainEditorScreen .lab {
  position: absolute;
  inset: calc(1.4rem + var(--safe-top)) 2rem calc(1.4rem + var(--safe-bot));
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  gap: 1rem;
  padding: 1.4rem 1.6rem 1.2rem;
}
.lab-head {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}
.lab-head .tabbar {
  margin: 0 0 0 auto;
}
.lab-body {
  min-height: 0;
  overflow: auto;
  padding-right: 4px;
}
.lab-body .brain-switch {
  margin-bottom: 4px;
}
.stats-grid {
  grid-template-columns: repeat(4, 1fr);
}
#eventLogPanel .phead {
  align-items: center;
}

/* Phone layout: chosen by JS from ui.layout.phoneBreakpoint (body.phone). */
body.phone .game.card {
  padding-top: calc(6px + var(--safe-top));
  gap: 4px;
}
body.phone .game.card .hint,
body.phone #fullscreen,
body.phone .hud-mode {
  display: none;
}
/* The phone's top bar keeps its buttons compact: icons (and the shop's ⚡ count), no words. */
body.phone #trainBtn .lbl,
body.phone #shopBtn .lbl {
  display: none;
}
/* The coach toggle lives in the phone top bar as a compact pill; its long
   note is dropped there (the map's hint line says what your moves teach). */

body.phone .vitals {
  gap: 6px;
}
body.phone .vitals strong {
  font-size: 1.15rem;
}
body.phone .game .bar {
  flex-wrap: nowrap;
}
body.phone .game .bar button {
  padding: 5px 8px;
}
body.phone #dpad {
  display: flex;
  margin: 2px 0 0;
  padding: 0 4px;
}
body.phone #dpad #radial {
  width: 150px;
  height: 150px;
}
/* In-game actions (fire, and later others) live left of the pad, apart from the
   menu buttons on the right; the column shows only when one of them is available,
   and the pad shrinks (down to 100px on narrow phones) to make room. */
.acts {
  display: none;
}
body.phone .acts:has(.tact:not([hidden])) {
  position: relative;
  display: grid;
  grid-template-columns: var(--touch-action-size);
  grid-auto-rows: var(--touch-action-size);
  align-self: center;
  flex-shrink: 0;
  width: calc(var(--touch-action-size) + 38px);
  min-height: calc(var(--touch-action-size) + 25px);
  gap: 6px;
}
body.phone #dpad:has(.acts .tact:not([hidden])) {
  gap: 8px;
}
@media (max-width: 360px) {
  body.phone #dpad:has(.acts .tact:not([hidden])) { gap: 2px; }
}
body.phone #dpad:has(.acts .tact:not([hidden])) #radial {
  flex: 0 1 132px;
  width: auto;
  min-width: 100px;
  height: auto;
  aspect-ratio: 1;
}
body.phone .tacts {
  position: relative;
  display: grid;
  grid-template-columns: repeat(2, var(--touch-action-size));
  gap: 6px;
}
body.phone #dpad { --touch-action-size: clamp(44px, 14vw, 56px); }
body.phone #dpad .tact { width: var(--touch-action-size); height: var(--touch-action-size); }
.tact {
  width: 4.1rem;
  height: 4.1rem;
  padding: 0;
  border-radius: 50% !important;
  font: 600 0.62rem/1.1 var(--f-mono);
  letter-spacing: 0.06em;
  text-align: center;
  display: grid;
  place-items: center;
  align-content: center;
  background: rgba(10, 12, 16, 0.85);
}
.tact[hidden] {
  display: none;
}
/* A floor with no loom (the movement tutorial): Build stays on screen but wears the
   no-glyph, so it reads as unavailable here, not broken. */
#loomBuildBtn.loom-off {
  position: relative;
  opacity: 0.55;
}
#loomBuildBtn.loom-off::after {
  content: "🚫";
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 1.6rem;
  pointer-events: none;
}
.tact big {
  display: block;
  font-size: 1.6rem;
  line-height: 1;
  color: var(--torch);
}
/* Reserve a staggered slot beside Fire, between the first two weapon rows.
   The smaller red face keeps a 44px circular hit target without moving controls. */
body.phone #dpad #tCancel {
  position: absolute;
  left: calc(var(--touch-action-size) - 6px);
  top: calc(var(--touch-action-size) - 19px);
  width: 44px; height: 44px;
  padding: 0; border: 0; background: transparent;
  clip-path: circle(50%);
}
body.phone #tCancel::before {
  content: "";
  position: absolute; inset: 6px;
  border: 1px solid var(--red);
  border-radius: 50%;
  background: rgba(80, 15, 23, 0.96);
}
body.phone #tCancel big { position: relative; font-size: 1.05rem; color: var(--red); }
body.phone #tCancel:focus-visible::before { outline: 2px solid var(--red); outline-offset: 2px; }
body.phone .hud-panel {
  max-width: 58%;
  padding: 4px 6px;
}
body.phone #qGrid {
  grid-template-columns: repeat(3, 3.9em);
  font-size: 0.66rem;
}
body.phone .hud-log {
  width: 92%;
  font-size: 0.66rem;
}
body.phone .hud-panel #senseInfo {
  display: none;
}
body.phone #tutorialOverlay {
  bottom: calc(11.4rem + var(--safe-bot));
  width: 92%;
}
body.phone #titleScreen {
  background: linear-gradient(180deg, rgba(7, 8, 11, 0.95) 0 34%, rgba(7, 8, 11, 0.55) 60%, rgba(7, 8, 11, 0.9));
}
body.phone #titleScreen .tcol {
  left: 1.2rem;
  right: 1.2rem;
  width: auto;
  top: calc(var(--safe-top) + 1.6rem);
  bottom: calc(var(--safe-bot) + 1rem);
  gap: 0.8rem;
}
body.phone .sub {
  font-size: 0.75rem;
  letter-spacing: 0.5em;
  margin-top: -0.5rem;
}
body.phone .tfoot,
body.phone .thint {
  display: none;
}
body.phone .menu button {
  padding-block: 0.7em;
  font-size: 1rem;
}
body.phone .ov {
  align-items: flex-end;
  padding: 0;
  background: rgba(5, 6, 9, 0.55);
}
body.phone .ov.side {
  background: rgba(5, 6, 9, 0.35);
}
body.phone .ov > .panel,
body.phone .ov > .panel.wide {
  width: 100%;
  max-height: 84%;
  /* a phone sheet scrolls up and down only; wide views (the graph) pan inside themselves */
  overflow-x: hidden;
  overscroll-behavior-x: none;
  border-width: 1px 0 0;
  padding: 1.6rem 1.1rem calc(1.2rem + var(--safe-bot));
}
body.phone .ov:not([hidden]) > .panel {
  animation: sheetIn var(--fx-sheet-ms, 320ms) steps(var(--fx-sheet-steps, 8), end);
}
@keyframes sheetIn {
  from {
    transform: translateY(40%);
    clip-path: inset(0 0 60% 0);
  }
  to {
    transform: none;
    clip-path: inset(0 0 0 0);
  }
}
body.phone .ptitle {
  top: 0.6rem;
  left: 1.1rem;
}
body.phone .pfoot {
  display: none;
}
body.phone .shopgrid {
  grid-template-columns: 1fr;
}
body.phone #caughtScreen .cgrid {
  inset: calc(var(--safe-top) + 0.6rem) 0.8rem calc(var(--safe-bot) + 0.6rem);
  grid-template-columns: 1fr;
  gap: 0.7rem;
  align-content: start;
  overflow: auto;
}
body.phone .campanel {
  order: -1;
  padding: 0.5rem;
  gap: 0.4rem;
}
/* Portrait cam: it takes the height the essentials leave (cause, stats and the
   Descend again row stay above the fold, the rating buttons go below it). svh is
   the viewport with the browser's toolbars shown. */
body.phone .camwrap {
  aspect-ratio: auto;
  height: clamp(200px, calc(100svh - var(--safe-top) - var(--safe-bot) - 16.5rem), 75svh);
}
body.phone .cl {
  gap: 0.7rem;
}
body.phone .cause {
  font-size: 0.95rem;
  line-height: 1.35;
}
body.phone .cstats dd {
  font-size: 1.4rem;
}
body.phone .cact {
  order: -1;
  flex-wrap: nowrap;
}
body.phone .cause,
body.phone .cstats {
  order: -2;
}
body.phone .cact .cta {
  flex: 1 1 0;
  padding-inline: 0.6em;
}
body.phone #labScreen .lab,
body.phone #labsScreen .lab {
  inset: calc(var(--safe-top) + 0.5rem) 0.5rem calc(var(--safe-bot) + 0.5rem);
  padding: 1rem 0.8rem 0.8rem;
}
body.phone .lab-head .tabbar {
  margin-left: 0;
}
body.phone .stats-grid {
  grid-template-columns: repeat(2, 1fr);
}
body.phone .controls {
  grid-template-columns: 1fr 1fr;
}
body.phone #network {
  height: 360px;
}
body.phone .brain-ov #network {
  height: min(360px, 44vh);
}

@media (prefers-reduced-motion: reduce) {
  .ov:not([hidden]) > .panel,
  body.phone .ov:not([hidden]) > .panel,
  .hurt .viewport {
    animation: none;
  }
}

/* Labs (issue #88): three screens — exercise, training room, review. Each is a content area
   sized to fit the panel, which scrolls (by touch too) only where the screen is too short,
   and a button bar that is always on screen, so the next step never needs a scroll. */
#labsScreen .lab {
  overflow: hidden;
}
.labs-steps {
  display: flex;
  gap: 1.4em;
  margin: 0 0 0 auto;
  padding: 0;
  list-style: none;
  font: 600 0.7rem var(--f-mono);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--smoke);
}
.labs-steps b {
  display: inline-grid;
  place-items: center;
  width: 1.7em;
  height: 1.7em;
  margin-right: 0.5em;
  border: 1px solid var(--edge);
  border-radius: 50%;
  font-weight: 600;
}
.labs-steps li.on {
  color: var(--torch);
}
.labs-steps li.on b {
  border-color: var(--torch);
  box-shadow: 0 0 0.8em rgba(255, 178, 87, 0.35);
}
.labs-stage {
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
}
.labs-scroll {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  gap: 1.6rem;
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-y;
}
.labs-scroll[data-more] {
  -webkit-mask-image: linear-gradient(#000 calc(100% - 2.5rem), transparent);
  mask-image: linear-gradient(#000 calc(100% - 2.5rem), transparent);
}
/* A column never shrinks below its content (that would overlap the next one, e.g. a long
   exercise list over the presets on a small phone): the area scrolls instead. */
.labs-scroll > * {
  min-height: auto !important;
}
.labs-foot {
  flex: none;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.6rem;
  padding-top: 0.7rem;
  border-top: 1px solid var(--edge);
}
/* ◆ Apply latest's confirm (rogueai/labs-ui.js drawRoomApply): a full-width row atop the foot */
.labs-apply-confirm {
  flex: 1 0 100%;
}
.labs-apply-confirm[hidden] {
  display: none;
}
.labs-revtools {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}
.labs .eyebrow {
  margin: 0;
  font: 600 0.66rem var(--f-mono);
  letter-spacing: 0.25em;
  color: var(--ash);
  text-transform: uppercase;
}

/* 1 · Exercise */
.labs-select {
  grid-template-columns: minmax(0, 1fr);
}
.labs-settings {
  grid-column: 1 / -1;
  padding-bottom: 0.7rem;
  border-bottom: 1px solid var(--edge);
}
/* Open settings take the room's place (the scenario is restarted from them anyway). */
.labs-room.settings-open > :not(.labs-settings) {
  display: none;
}
.labs-settings[hidden] {
  display: none;
}
#labsGear {
  margin-right: auto;
}
.labs-pickcol,
.labs-loadout {
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
}
.labs-pickcol .mdesc {
  padding-left: 0;
  min-height: 0;
}
/* Issues #178 + #135: the who-plays dropdown and the drill-in picker on the
   select stage. The who-plays row is the persistent header above both
   picker views, so it wears a band, not a form row's clothes. */
.labs-trainee {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5em;
  padding: 0.6em 0.8em;
  border: 1px solid var(--edge);
  border-left: 3px solid var(--torch);
}
.labs-trainee[hidden] {
  display: none;
}
.labs-trainee select {
  flex: 1 1 12em;
  min-width: 0;
}
.labs-trainee .meta {
  flex-basis: 100%;
}
/* Drill-in (#178): hub and section screen swap inside the select stage;
   neither reuses .menu — sections and activities get distinct shapes. */
#labsHub,
#labsSection {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  flex: 1 1 auto;
  min-height: 0;
}
#labsHub[hidden],
#labsSection[hidden] {
  display: none;
}
.labs-seccards {
  display: grid;
  gap: 0.6rem;
}
.labs-seccard {
  all: unset;
  box-sizing: border-box;
  display: grid;
  grid-template-columns: 2em minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.9em;
  width: 100%;
  padding: 1em 1.1em;
  border: 1px solid var(--edge);
  cursor: pointer;
  font-size: 1rem;
  color: var(--ash);
}
.labs-seccard:hover {
  border-color: var(--torch-deep);
}
.labs-seccard.sel {
  border-color: var(--torch);
  background: linear-gradient(90deg, rgba(255, 178, 87, 0.14), transparent 75%);
}
.labs-seccard:focus-visible,
.labs-actcard:focus-visible,
.labs-secback:focus-visible {
  outline: 1px dashed var(--torch);
  outline-offset: 2px;
}
.labs-seccard-glyph {
  font: 400 1.9rem/1 var(--f-mono);
  color: var(--torch);
  text-align: center;
  text-shadow: 0 0 0.6em rgba(255, 178, 87, 0.55);
}
.labs-seccard-main {
  display: grid;
  gap: 0.25em;
  min-width: 0;
}
.labs-seccard-name {
  font: 700 1.45rem/1.05 var(--f-disp);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--bone);
}
.labs-seccard-desc {
  font: 400 0.92rem/1.4 system-ui, sans-serif;
  color: var(--ash);
}
.labs-seccard-ready {
  font: 600 0.72rem var(--f-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--smoke);
  border: 1px solid var(--edge);
  padding: 0.45em 0.7em;
  white-space: nowrap;
}
.labs-seccard-ready.on {
  color: var(--torch);
  border-color: var(--torch-deep);
}
.labs-secback {
  all: unset;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 0.45em;
  align-self: flex-start;
  padding: 0.15em 0.2em;
  cursor: pointer;
  font: 700 1.5rem/1.1 var(--f-disp);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--bone);
}
.labs-secback-arrow {
  color: var(--torch);
  font-size: 1.25em;
  line-height: 1;
}
.labs-actcards {
  display: grid;
  gap: 0.55rem;
}
.labs-actcard {
  all: unset;
  box-sizing: border-box;
  display: grid;
  gap: 0.3em;
  width: 100%;
  padding: 0.85em 1em;
  border: 1px solid var(--edge);
  cursor: pointer;
  text-align: left;
  font-size: 1rem;
  color: var(--ash);
}
.labs-actcard:hover {
  border-color: var(--torch-deep);
}
.labs-actcard.sel {
  border-color: var(--torch);
  background: linear-gradient(90deg, rgba(255, 178, 87, 0.12), transparent 80%);
}
.labs-actcard.locked {
  opacity: 0.55;
  cursor: not-allowed;
}
.labs-actcard-title {
  font: 600 1.05rem/1.25 var(--f-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bone);
}
.labs-actcard-goal {
  font: 400 0.92rem/1.4 system-ui, sans-serif;
  color: var(--ash);
}
.labs-actcard-status {
  font: 400 0.85rem/1.4 system-ui, sans-serif;
  color: var(--smoke);
}
.labs-actcard-status.ready {
  font: 600 0.72rem var(--f-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--torch);
}
.labs-showunavail {
  display: flex;
  align-items: center;
  gap: 0.5em;
  font-size: 1rem;
  color: var(--ash);
  cursor: pointer;
}
.labs-showunavail input {
  width: 1.1em;
  height: 1.1em;
  accent-color: var(--torch);
}
.labs-recent {
  margin-top: auto;
  display: grid;
  gap: 0.4rem;
}
.labs-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 4px;
}
.labs-pick {
  width: 100%;
  padding: 0.35em 0.7em;
  text-align: left;
  font-size: 1rem;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.labs-job[data-status="complete"] .labs-pick {
  border-color: rgba(126, 231, 135, 0.45);
}
.labs-job[data-status="failed"] .labs-pick {
  border-color: rgba(255, 91, 77, 0.45);
}
.labs-row {
  display: grid;
  grid-template-columns: 5.5em minmax(0, 1fr);
  align-items: center;
  gap: 0.8em;
}
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}
.chip {
  padding: 0.3em 0.75em;
  font-size: 1rem;
  border-radius: 999px !important;
  color: var(--ash);
  background: transparent;
  border-color: var(--edge);
}
.chip[aria-checked="true"],
.chip[aria-pressed="true"] {
  color: var(--torch);
  border-color: var(--torch);
  background: rgba(255, 178, 87, 0.08);
}
/* A sense in the current brain's set carries the brain's own tint (issue #117). */
.chip[data-brain="true"] {
  border-style: dashed;
}
/* A grouped sense category in the room settings (the shop window levels, the
   targeting grid packings) takes its own line: the name, then its picks as
   radio chips. A pick the player has not bought is shown locked, not hidden. */
.labs-sense-row {
  flex-basis: 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
}
.labs-sense-name {
  font-size: 0.72rem;
  color: var(--ash);
  margin-right: 0.3em;
}
.chip:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.labs-params {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px 1.2em;
  padding-top: 0.5rem;
  border-top: 1px solid var(--edge);
}
.labs-param {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 1.9em 4.2em 1.9em;
  align-items: center;
  gap: 3px;
}
.labs-param .plbl {
  font-size: 0.72rem;
  color: var(--ash);
  line-height: 1.2;
}
.labs-param button {
  padding: 0;
  height: 1.9em;
  line-height: 1;
}
.labs-param input,
.labs-seed input {
  width: 100%;
  min-width: 0;
  padding: 0.25em 0.3em;
  font: 500 1rem var(--f-mono);
  text-align: center;
  color: var(--bone);
  background: rgba(0, 0, 0, 0.35);
  border: 1px solid var(--edge);
  border-radius: 4px;
  -moz-appearance: textfield;
  appearance: textfield;
}
.labs-param input::-webkit-inner-spin-button,
.labs-param input::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.labs-seed {
  display: grid;
  grid-template-columns: 5.5em 8em;
  align-items: center;
  gap: 0.8em;
}

/* 2 · Training room */
.labs-room {
  grid-template-columns: minmax(0, 1fr) minmax(15rem, 19rem);
}
.labs-roomcol {
  min-height: 0;
  display: grid;
  /* The arena takes the spare height; below 8rem the room scrolls instead of hiding it. */
  grid-template-rows: minmax(8rem, 1fr) auto auto; /* arena, goal + spark row, controls (pad, other actions) */
  justify-items: center;
  gap: 0.7rem;
}
/* One row under the board: the pad and its other actions. Who plays is NOT
   here anymore (issue #135 flow revision) — it is picked on the select
   stage, and the room obeys the pick. */
.labs-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.6rem 0.5rem;
}
/* The flow's go button (the select stage's Start, the room foot's Play):
   full width at the bottom of its foot, filled with the steel secondary —
   var(--line), the slate family the plain secondary buttons wear — while
   torch stays the accent for selection and state, never the big fill. */
.labs-play {
  flex: 1 1 100%;
  min-height: 48px;
  background: var(--line);
  border-color: var(--line);
  color: var(--bone);
  font-size: 1.02rem;
}
.labs-play .bic {
  margin-right: 0.5em;
}
/* Module checkpoints, demoted from the foot's big slot to a normal button
   (issue #135 flow revision); when the run is done it still draws the eye
   with the old primary treatment, at normal size. */
#labsToReview.primary {
  border-color: var(--torch);
  color: var(--torch);
  background: rgba(255, 178, 87, 0.08);
}
.labs-arena {
  position: relative;
  min-height: 0;
  width: 100%;
  display: grid;
  place-items: center;
  background: radial-gradient(ellipse at center, rgba(255, 178, 87, 0.06), transparent 70%), #050608;
  border: 1px solid var(--edge);
  overflow: hidden;
}
.labs-grid {
  margin: 0;
  font: 500 1.6rem / 1.05 var(--f-mono); /* fitted to the arena by labs-ui.js */
  letter-spacing: 0.35em;
  color: var(--ash);
  text-shadow: 0 0 0.5em rgba(255, 178, 87, 0.25);
}
/* The game-rendered lab view (issue #97): the "lab" art style draws the scenario
   black-and-white through the glyph pipeline. */
.labs-view {
  max-width: 100%;
  border-radius: 2px;
}
/* While the canvas is up, the text render stays in the DOM as the accessible
   fallback: visually hidden, still readable by assistive tech and tests. */
/* The drawn frame sits out of flow, centred: the arena's size comes from the layout and
   the frame is fitted to it, never the other way round (which kept the arena as large as
   the first frame and pushed the controls off short screens). */
#labsView {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
.labs-grid.gfx {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.labs-banner {
  position: absolute;
  left: 50%;
  bottom: 0.8rem;
  transform: translateX(-50%);
  margin: 0;
  padding: 0.4em 1em;
  font: 600 0.72rem var(--f-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--torch);
  background: rgba(7, 8, 11, 0.85);
  border: 1px solid var(--torch-deep);
  cursor: default;
}
#labsScreen[data-stage="room"] .labs-banner {
  cursor: pointer;
}
/* With the status line on the board's bottom edge, the banner stands just above it. */
.labs-arena:has(#labsCap:not([hidden])) .labs-banner {
  bottom: 2.6rem;
}
/* Success toast (#99): reuses the standard banner style (torch-orange on dark).
   It sits where the press-any-key banner sat (bottom of the screen, never over
   the arena) for ~1.5s, then the room auto-advances; taps pass through it.
   NOTE: the `lab-toast` class must stay namespaced — the main game's generic
   `.toast` class carries `opacity: 0` (shown via `.toast.on`) and would render
   this banner fully transparent. */
/* A room that cannot start (a skill not trained yet, a missing capability) says why in a
   sentence: it wraps inside the arena instead of running off a phone screen. */
.labs-banner.labs-error {
  width: calc(100% - 1.6rem);
  max-width: 40rem;
  box-sizing: border-box;
  white-space: normal;
  overflow-wrap: anywhere;
  text-align: center;
  text-transform: none;
  letter-spacing: 0.03em;
  line-height: 1.45;
}
.labs-banner.lab-toast {
  opacity: 1;
  pointer-events: none;
}
/* The room's status line rides on the board itself (issue #117): an overlay
   at the arena's bottom edge (the top-left corner is Basil's), never a text
   band between the board and the controls. It takes no layout space and
   ignores taps. */
.labs-cap {
  position: absolute;
  bottom: 0.55rem;
  left: 50%;
  transform: translateX(-50%);
  max-width: calc(100% - 1rem);
  margin: 0;
  padding: 0.25em 0.8em;
  font: 600 0.72rem var(--f-mono);
  letter-spacing: 0.06em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--ash);
  background: rgba(7, 8, 11, 0.72);
  border: 1px solid var(--edge);
  border-radius: 999px;
  pointer-events: none;
  z-index: 2;
}
/* The game's radial D-pad (dpad.js) draws into this square. */
.labs-pad {
  width: 9.5rem;
  height: 9.5rem;
}
.labs-hud {
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
  padding-left: 1.4rem;
  border-left: 1px solid var(--edge);
}
/* The player's goal line (issue #117): roomy, first-person, always in the room. */
.labs-goal-line {
  margin: 0;
  font-size: 1.05rem;
  line-height: 1.35;
  color: var(--text);
}
.labs-tiles {
  margin: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.7rem;
}
.labs-tiles div,
.labs .cstats div {
  border-top: 1px solid var(--edge);
  padding-top: 0.35em;
}
.labs-tiles dt {
  font: 600 0.58rem var(--f-mono);
  letter-spacing: 0.2em;
  color: var(--ash);
  text-transform: uppercase;
}
.labs-tiles dd {
  margin: 0;
  font: 800 1.6rem/1.1 var(--f-disp);
  font-variant-numeric: tabular-nums;
  color: var(--bone);
}
/* The competency history strip, in the room column under the objective so it
   renders in the first screen on a phone (issue #117): a slim band of pixels,
   the same greedy-success history the taller chart drew before. */
#labsSpark {
  display: block;
  width: 100%;
  height: 2rem;
}
/* The goal line and the competency strip share one row: the line on the left, the strip a
   third of the width on the right, so the board keeps the room's height. */
.labs-goalrow {
  width: 100%;
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  align-items: center;
  gap: 0.8rem;
}
body.phone .labs-goal-line {
  font-size: 0.9rem;
}
/* Basil's header row is for the exercise list only: in a room he stands in the board's
   corner, so the row would just be empty space. */
#labsScreen:has(#labsRoom:not([hidden])) .basil-dock {
  display: none;
}

/* 3 · Review */
.labs-review {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
}
.labs-revcol {
  display: grid;
  gap: 1rem;
  align-content: center;
}
.labs .cstats dd {
  font-size: 1.6rem;
}
.labs-curvebox {
  display: grid;
  gap: 0.4rem;
}
#labsCurve {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  max-height: 50vh;
  background: #050608;
}
.labs-legend {
  margin: 0;
  display: flex;
  gap: 1.4em;
  font: 0.68rem var(--f-mono);
  color: var(--smoke);
}
.labs-legend span::before {
  content: "";
  display: inline-block;
  width: 1.4em;
  height: 0;
  margin-right: 0.5em;
  vertical-align: middle;
  border-top: 2px solid;
}
.labs-legend .k-greedy::before {
  border-color: var(--torch);
}
.labs-legend .k-explore::before {
  border-top-width: 1px;
  border-color: var(--ash);
}
.labs-legend .k-target::before {
  border-top-style: dashed;
  border-color: var(--smoke);
}

/* Phone: each step stacks into one column and still fits the screen. */
body.phone .labs-steps {
  order: 5;
  width: 100%;
  margin: 0;
  justify-content: space-between;
  gap: 0.5em;
  font-size: 0.6rem;
  letter-spacing: 0.1em;
}
body.phone #labsScreen .lab-head {
  gap: 0.5rem 1rem;
}
body.phone #labsScreen .lab-head h2 {
  font-size: 1.4rem;
}
body.phone .labs-stage,
body.phone .labs-scroll {
  gap: 0.8rem;
}
body.phone .labs-foot {
  padding-top: 0.5rem;
}
body.phone .labs-select,
body.phone .labs-room,
body.phone .labs-review {
  grid-template-columns: minmax(0, 1fr);
}
body.phone .labs-select,
body.phone .labs-review {
  grid-template-rows: auto auto;
  align-content: start;
}
body.phone .labs-pickcol .mdesc {
  display: none;
}
body.phone .labs-recent {
  display: none;
}
body.phone .labs-row {
  grid-template-columns: minmax(0, 1fr);
  gap: 0.3em;
}
body.phone .labs-params {
  grid-template-columns: minmax(0, 1fr);
  gap: 4px;
}
/* The play column takes the spare height but never less than its controls need (the
   arena's own floor included): on a short screen the room scrolls rather than overlap. */
body.phone .labs-room {
  grid-template-rows: minmax(auto, 1fr) auto;
}
body.phone .labs-hud {
  padding: 0.6rem 0 0;
  border-left: 0;
  border-top: 1px solid var(--edge);
  gap: 0.5rem;
}
body.phone .labs-tiles {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.4rem;
}
body.phone .labs-tiles dd {
  font-size: 1.2rem;
}
body.phone .labs-pad {
  width: 8.5rem;
  height: 8.5rem;
}
body.phone #labsGoal {
  display: none;
}
body.phone .labs-review {
  align-content: start;
}
body.phone .labs .cstats {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
body.phone #labsCurve {
  max-height: 22vh;
}
/* The caught screen's phone rules reorder .cact/.cause/.cstats; Labs keeps document order. */
body.phone .labs .cact,
body.phone .labs .cause,
body.phone .labs .cstats {
  order: 0;
  flex-wrap: wrap;
}
body.phone .labs .cact .cta {
  flex: 1 1 auto;
}
body.phone .labs-review .cause {
  font-size: 0.85rem;
}
body.phone .labs-param button {
  height: 1.7em;
}
.labs-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  justify-content: center;
}

/* Composition view (issue #92): the Labs room borrows the Brain panel for its wiring. */
/* Composed brains (issue #115): what @ runs, install / uninstall, and the wiring editor. */
.brain-modules {
  min-width: 0;
  font: 12px "Courier New", monospace;
  color: #c9d6e6;
  margin: 6px 0 0;
  display: grid;
  gap: 4px;
}
.brain-modules[hidden] {
  display: none;
}
/* The module list itself is 12px display text, but its buttons are controls
   (install, train a router, remove a module) and stay ≥16px (issue #169). */
.brain-modules button {
  font-size: 1rem;
}
.installed-line {
  margin: 0;
}
.installed-tools {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  align-items: center;
}
.installed-tools select,
.wiring-row select {
  flex: 1 1 12em;
  max-width: 100%;
  min-width: 0;
  font: inherit;
  font-size: 1rem;
}
.wiring-rows {
  display: grid;
  gap: 2px;
  margin: 4px 0;
}
.wiring-row {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  align-items: center;
  justify-content: space-between;
}
.wiring-check {
  margin: 4px 0;
  color: #7fd39a;
}
.wiring-check.bad {
  color: #ff9a8a;
}
.bow-aim {
  color: #ffd25a;
}
.composition-note {
  font: 12px "Courier New", monospace;
  color: #9fb2c8;
  margin: 6px 0 0;
  white-space: pre-line;
}
#layersModal.composition .brain-switch,
#layersModal.composition .btab[data-btab="senses"] {
  display: none;
}
#layersModal.composition #params,
#layersModal.composition .brain-notes {
  display: none;
}
/* The Composition Lab's skill picker (issue #115): one row per skill slot. */
.labs-skills {
  display: grid;
  gap: 4px;
  min-width: 0;
  flex: 1;
}
.labs-skill {
  display: grid;
  grid-template-columns: minmax(6em, 9em) minmax(0, 1fr);
  align-items: center;
  gap: 6px;
}
.labs-skill .plbl {
  font-size: 0.72rem;
  color: var(--ash);
}
.labs-skill select {
  min-width: 0;
  max-width: 100%;
  font: 500 1rem var(--f-mono);
}
.labs-skill .meta {
  grid-column: 2;
  font-size: 0.68rem;
}

/* ---- Basil, the guide (rogueai/basil.js, docs/basil.md) ---- */
.basil {
  display: none;
  /* floats above the overlays (z 20) and the screen wipe (#fxCanvas, z 60): the screen
     changes under him while he stays; rogueai/basil.js place() sets the transform */
  position: fixed;
  left: 0;
  top: 0;
  z-index: 61;
  max-width: min(26rem, calc(100vw - 16px));
  gap: 8px;
  align-items: flex-start;
  pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
  .basil {
    transition: transform 420ms cubic-bezier(0.2, 0.9, 0.25, 1.15);
  }
}
body.basil-on .basil:not(.off) {
  display: flex;
}
/* during the greeting the full-size bot is him */
body.basil-on .basil.covered {
  display: none;
}
/* by a panel: no NEXT chip, and beside a side panel his words grow away from it */
.basil.docked .basil-next {
  display: none;
}
.basil.dock-left {
  flex-direction: row-reverse;
  max-width: min(22rem, 40vw);
}
.basil.docked .basil-face {
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.6);
}
.basil > * {
  pointer-events: auto;
}
.basil-face {
  all: unset;
  pointer-events: auto; /* all: unset would inherit .basil's none */
  cursor: pointer;
  display: grid;
  justify-items: center;
  gap: 2px;
  padding: 4px 6px;
  border: 1px solid var(--edge);
  background: rgba(8, 10, 14, 0.86);
  color: var(--potion);
  font: 600 11px/1.05 var(--f-mono);
}
.basil-face pre {
  margin: 0;
  font: inherit;
  text-shadow: 0 0 6px rgba(125, 255, 154, 0.45);
}
.basil-face:focus-visible {
  outline: 1px dashed var(--torch);
}
.basil-name {
  font: 600 9px var(--f-mono);
  letter-spacing: 0.2em;
  color: var(--ash);
  white-space: nowrap;
}
.basil[data-stage="2"] .basil-face {
  color: var(--stairs);
}
.basil[data-stage="3"] .basil-face,
.basil.glimpse .basil-face {
  color: var(--ember);
  border-color: var(--ember);
}
.basil.glimpse .basil-face pre {
  text-shadow: 0 0 10px rgba(255, 91, 77, 0.8);
}
/* when an NPC hails @ on the floor or escorts @ into a menu, the guide widget wears their icon and colour */
.basil[data-guide] .basil-face {
  color: var(--guide-color, var(--stairs));
  border-color: var(--guide-color, var(--stairs));
}
.basil[data-guide] .basil-face pre {
  text-shadow: 0 0 6px rgba(255, 178, 87, 0.35);
}
.basil[data-guide] .basil-name {
  color: var(--guide-color, var(--stairs));
}
.basil[data-guide] .basil-say {
  border-left-color: var(--guide-color, var(--stairs));
}
.basil[data-guide] .basil-next {
  display: none;
}
.basil-col {
  display: grid;
  gap: 6px;
  min-width: 0;
}
.basil-next {
  margin: 0;
  justify-self: start;
  padding: 3px 8px;
  border: 1px solid var(--torch-deep);
  background: rgba(8, 10, 14, 0.86);
  font: 600 11px/1.35 var(--f-mono);
  letter-spacing: 0.06em;
  color: var(--torch);
}
.basil-next:empty {
  display: none;
}
.basil-say {
  margin: 0;
  padding: 6px 9px;
  border: 1px solid var(--edge);
  border-left: 2px solid var(--potion);
  background: rgba(8, 10, 14, 0.92);
  font: 400 12px/1.45 var(--f-mono);
  color: var(--bone);
  opacity: 0;
  transition: opacity 0.2s;
}
.basil-say.on {
  opacity: 1;
}
.basil-inbox {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  width: min(24rem, 86vw);
  max-height: 14rem;
  overflow: auto;
  margin: 0;
  padding: 6px 10px 6px 26px;
  border: 1px solid var(--edge);
  background: rgba(8, 10, 14, 0.96);
  font: 400 11px/1.5 var(--f-mono);
  color: var(--ash);
}
.basil-inbox[hidden] {
  display: none;
}
/* the button Basil wants you to press next */
body.basil-on .basil-point {
  box-shadow: 0 0 0 1px var(--torch), 0 0 12px rgba(255, 178, 87, 0.55);
}
@media (prefers-reduced-motion: no-preference) {
  body.basil-on .basil-point {
    animation: basilPoint 1.6s ease-in-out infinite;
  }
}
@keyframes basilPoint {
  50% {
    box-shadow: 0 0 0 1px var(--torch), 0 0 2px rgba(255, 178, 87, 0.2);
  }
}
/* the Brain tour card */
.basil-tour {
  display: none;
  gap: 10px;
  align-items: flex-start;
  margin: 6px 0;
  padding: 8px 10px;
  border: 1px solid var(--edge);
  border-left: 2px solid var(--potion);
  background: rgba(8, 10, 14, 0.6);
}
body.basil-on .basil-tour {
  display: flex;
}
.basil-tour pre {
  margin: 0;
  font: 600 10px/1.05 var(--f-mono);
  color: var(--potion);
}
.basil-tour p {
  margin: 0;
  font: 400 12px/1.5 var(--f-mono);
  color: var(--bone);
}
/* guided, not advanced: hide tuning and internals. Access points and the
   room's human-facing goal/graph are NOT internals and never hide here:
   configuration lives behind the Settings gear, and the gear itself must
   stay reachable in every mode. */
body.basil-on.basil-simple #exportBrain,
body.basil-on.basil-simple #revertBrain,
body.basil-on.basil-simple #ckptStatus,
body.basil-on.basil-simple #wiringEditor,
body.basil-on.basil-simple #labsWiring,
body.basil-on.basil-simple #labsJobs,
body.basil-on.basil-simple .labs-recent,
body.basil-on.basil-simple #labsEval,
body.basil-on.basil-simple #labsRemove,
body.basil-on.basil-simple #labsCurve,
body.basil-on.basil-simple .labs-curvebox {
  display: none !important;
}
body.phone .basil {
  max-width: calc(100vw - 16px);
}
body.phone .basil-say,
body.phone .basil-next {
  font-size: 11px;
}

/* Bar buttons share one line box: with line-height "normal" an emoji's fallback font
   (iOS especially) makes 🎨/📜 taller than a text glyph like ≡. Icon-only buttons
   (🎨 🧠 📜 ⛶ ≡) are also one fixed width, centered, with the same border, so the menu
   icon in the top-right corner matches its neighbours on every platform. */
.game .bar button {
  line-height: 1.25;
}
.game .bar .glyph-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: content-box;
  width: 1.25em;
  height: 1.25em;
}
.game .bar .glyph-btn[hidden] {
  display: none;
}
/* Shop: an affordable card fills while held (hold-to-buy without the button). */
#shopList li {
  position: relative;
  overflow: hidden;
}
#shopList li.can-buy {
  touch-action: pan-y;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
#shopList li > :not(.card-fill) {
  position: relative;
}
#shopList .card-fill {
  position: absolute;
  inset: 0;
  width: 0;
  background: repeating-linear-gradient(90deg, rgba(255, 178, 87, 0.3) 0 0.5em, rgba(255, 178, 87, 0.12) 0.5em 0.62em);
  pointer-events: none;
}
#shopList li.can-buy .cost {
  border: 1px solid var(--torch);
  padding: 1px 6px;
}
/* Phone shop: no scrolling to the button. The short intro, a compact list, and the
   selected item's lesson + buy button pinned to the sheet's bottom. */
body.phone #shopModal .layers-intro span[data-msg="ui.shop.introRest"] {
  display: none;
}
body.phone #shopModal #shopList p:not(.shop-lesson) {
  display: none;
}
body.phone #shopModal #shopList .shop-lesson {
  display: none; /* the selected item's lesson is pinned under the list */
}
body.phone #shopModal #shopNet {
  display: none;
}
body.phone #shopModal .netbox {
  position: sticky;
  bottom: calc(-1.2rem - var(--safe-bot));
  margin-inline: -1.1rem;
  padding: 0.6rem 1.1rem calc(0.8rem + var(--safe-bot));
  background: linear-gradient(180deg, rgba(11, 13, 17, 0.85), rgba(11, 13, 17, 0.98) 30%);
  border-top: 1px solid var(--edge);
  gap: 0.4rem;
}
body.phone #shopModal .lesson {
  min-height: 0;
}

/* ---- Saves (shell.js renderSlots): one card per saved game, inline confirms ---- */
.slots-panel {
  width: min(34rem, 100%);
}
.slot-cards {
  list-style: none;
  margin: 0.6rem 0 0;
  padding: 0;
  display: grid;
  gap: 8px;
}
.slot-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 4px 12px;
  align-items: center;
  padding: 10px 12px;
  border: 1px solid var(--edge);
  border-radius: 6px;
  background: rgba(0, 0, 0, 0.25);
}
.slot-card.active {
  border-color: var(--torch);
  background: rgba(255, 178, 87, 0.06);
}
.slot-face {
  margin: 0;
  font: 600 13px/1 var(--f-mono);
  color: var(--potion);
}
.slot-card:not(.active) .slot-face {
  color: var(--ash);
}
.slot-info {
  min-width: 0;
}
.slot-name {
  display: flex;
  gap: 8px;
  align-items: baseline;
  font: 600 0.95rem var(--f-mono);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.slot-badge {
  font: 600 0.62rem var(--f-mono);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--torch);
}
.slot-sum,
.slot-when {
  margin: 2px 0 0;
  font-size: 0.75rem;
  color: var(--ash);
}
.slot-when {
  color: var(--smoke);
}
.slot-acts {
  display: flex;
  gap: 6px;
  align-items: center;
  flex-wrap: wrap;
}
.slot-acts .cta {
  min-height: 36px;
  padding: 0.35em 0.9em;
}
/* The ASCII throbber for work the player waits on (applying a checkpoint). */
.throbbing .throb {
  color: var(--torch);
  white-space: pre;
}
.slot-confirm,
.slot-menu {
  grid-column: 1 / -1;
  display: grid;
  gap: 8px;
  padding-top: 8px;
  border-top: 1px dashed var(--edge);
}
.slot-menu {
  display: flex;
}
.slot-confirm p {
  margin: 0;
  font-size: 0.85rem;
  color: var(--bone);
}
.slot-rename input {
  font: inherit;
  padding: 0.45em 0.6em;
  background: var(--void);
  color: var(--bone);
  border: 1px solid var(--edge);
  border-radius: 4px;
}
.cta.danger {
  border-color: var(--ember);
  color: var(--ember);
}
.slot-new {
  display: block;
  border-style: dashed;
  background: none;
}
.slot-new-btn {
  all: unset;
  box-sizing: border-box;
  display: block;
  width: 100%;
  padding: 6px 0;
  text-align: center;
  cursor: pointer;
  font: 600 1rem var(--f-mono);
  letter-spacing: 0.1em;
  color: var(--torch);
}
.slot-new-btn:focus-visible {
  outline: 1px dashed var(--torch);
}
.slots-meter {
  margin-top: 0.8rem;
  font-size: 0.75rem;
  color: var(--ash);
}
.slots-meter summary {
  cursor: pointer;
}

/* Basil's greeting (rogueai/basil.js showIntro): the friendly bot, his first lines, go. */
/* Conversations (issue #189, rogueai/dialog.js): scene mode, grown from Basil's greeting. The
   greeting keeps its .basil-intro-* classes (below) alongside these, and they win where they
   differ. The speaker's scene draws on .dlg-scene behind the card; the speaker's figure is live
   text (.dlg-figure), coloured by the speaker record. */
.dlg {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: grid;
  place-items: center;
  padding: calc(16px + var(--safe-top)) 16px calc(16px + var(--safe-bot));
  background: rgba(5, 6, 9, 0.9);
  overflow: auto;
}
.dlg-scene {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.dlg-card {
  position: relative;
  display: grid;
  justify-items: center;
  gap: 12px;
  width: min(34rem, 100%);
}
.dlg-head {
  justify-self: stretch;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-height: 1.5rem;
}
.dlg-tools {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
}
.dlg-tool {
  background: rgba(12, 15, 20, 0.9);
  border: 1px solid var(--edge);
  border-radius: 5px;
  padding: 4px 9px;
  font: 600 0.7rem var(--f-mono);
  letter-spacing: 0.06em;
  color: var(--bone);
  cursor: pointer;
  text-transform: uppercase;
}
.dlg-tool:hover {
  border-color: var(--torch);
  color: var(--torch);
}
.dlg-tool[aria-pressed="true"] {
  border-color: var(--stairs);
  color: var(--stairs);
  box-shadow: 0 0 8px rgba(94, 207, 255, 0.25);
}
.dlg-figure {
  margin: 0;
  font: 600 clamp(8px, 2.5vw, 13px) / 1.05 var(--f-mono);
  color: var(--bone);
  text-shadow: 0 0 8px rgba(94, 207, 255, 0.3);
}
.dlg-figure.has-companion {
  display: inline-flex;
  align-items: flex-end;
  justify-content: center;
  gap: clamp(4px, 1.5vw, 12px);
  max-width: 100%;
}
.dlg-fig-main {
  display: block;
  white-space: pre;
}
.dlg-fig-companion {
  display: block;
  white-space: pre;
  font-size: 0.56em;
  line-height: 1.05;
  color: #6be6d8;
  text-shadow: 0 0 8px rgba(107, 230, 216, 0.4);
}
.dlg[data-speaker="kit"] .dlg-figure {
  font-size: clamp(6.5px, 1.95vw, 11.5px);
  color: #6be6d8;
  text-shadow: 0 0 8px rgba(107, 230, 216, 0.38);
}
.dlg-doll {
  justify-self: stretch;
  display: grid;
  gap: 8px;
  width: 100%;
}
.dlg-doll-stage {
  position: relative;
  width: 100%;
  border: 1px solid var(--edge);
  border-radius: 8px;
  background: #07080b;
  overflow: hidden;
}
.dlg-doll-canvas {
  display: block;
  width: 100%;
}
.dlg-doll .dollslot {
  position: absolute;
  transform: translateY(-50%);
  width: 56px;
  padding: 3px 4px;
  border: 1px solid var(--edge);
  border-radius: 6px;
  background: rgba(11, 13, 17, 0.92);
  color: var(--ash);
  display: grid;
  justify-items: center;
  gap: 1px;
  cursor: pointer;
  font: 500 9px/1.1 var(--f-mono);
}
.dlg-doll .dollslot.l {
  left: 4px;
}
.dlg-doll .dollslot.r {
  right: 4px;
}
.dlg-doll .dollslot .ic {
  font-size: 13px;
  line-height: 1;
  color: var(--ash);
}
.dlg-doll .dollslot small {
  font-size: 8.5px;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dlg-doll .dollslot .sig {
  font-size: 8.5px;
  color: var(--c, var(--torch));
  letter-spacing: 1px;
}
.dlg-doll .dollslot.worn {
  border-color: color-mix(in srgb, var(--c, var(--torch)) 55%, var(--edge));
  color: var(--bone);
}
.dlg-doll .dollslot.worn .ic {
  color: var(--c, var(--torch));
}
.dlg-doll .dollslot:hover,
.dlg-doll .dollslot[aria-pressed="true"] {
  border-color: var(--c, var(--torch));
  box-shadow: 0 0 10px color-mix(in srgb, var(--c, var(--torch)) 35%, transparent);
}
.dlg[data-slots="off"] .dlg-doll .dollslot {
  display: none !important;
}
.dlg-doll-readout {
  margin: 0;
  padding: 6px 10px;
  border: 1px solid var(--edge);
  border-left: 2px solid var(--stairs);
  background: rgba(8, 10, 14, 0.92);
  font: 400 0.78rem/1.4 var(--f-mono);
  color: var(--bone);
  text-align: center;
}
/* Training provenance (issue #227): the badge row under the doll's readout —
   the checkpoint/training in use and its badges, in the readout's register. */
.dlg-doll-badges {
  margin: 0;
  padding: 4px 10px;
  border: 1px solid var(--edge);
  border-top: 0;
  background: rgba(8, 10, 14, 0.92);
  font: 400 0.72rem/1.4 var(--f-mono);
  color: var(--ash);
  text-align: center;
}
.dlg-name {
  margin: 0;
  font: 700 0.75rem var(--f-mono);
  letter-spacing: 0.14em;
  color: var(--ash);
}
.dlg-say {
  justify-self: stretch;
  min-height: 6em;
  padding: 10px 14px;
  border: 1px solid var(--edge);
  border-left: 2px solid var(--stairs);
  background: rgba(8, 10, 14, 0.92);
  font: 400 0.92rem/1.55 var(--f-mono);
  color: var(--bone);
}
.dlg-say p {
  margin: 0 0 4px;
}
.dlg-hint {
  margin: 0;
  font: 400 0.75rem var(--f-mono);
  color: var(--ash);
}
.dlg-responses {
  justify-self: stretch;
  display: grid;
  gap: 8px;
}
.dlg-response {
  opacity: 0.35;
  text-align: left;
}
.dlg.typed .dlg-response {
  opacity: 1;
}
/* the greeting: its one button sits in the card, centred, as it always has */
.basil-intro .dlg-responses {
  display: contents;
}
.basil-intro .dlg-response {
  text-align: center;
}
/* a line from someone other than Basil, in his inbox */
.basil-inbox-who {
  color: var(--stairs);
  font-weight: 700;
}
.basil-intro {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: grid;
  place-items: center;
  padding: calc(16px + var(--safe-top)) 16px calc(16px + var(--safe-bot));
  background: rgba(5, 6, 9, 0.9);
}
.basil-intro-card {
  display: grid;
  justify-items: center;
  gap: 14px;
  width: min(30rem, 100%);
}
.basil-intro-bot {
  margin: 0;
  font: 600 clamp(9px, 2.9vw, 14px) / 1.05 var(--f-mono);
  color: var(--potion);
  text-shadow: 0 0 8px rgba(125, 255, 154, 0.35);
}
.basil-intro-say {
  justify-self: stretch;
  min-height: 9.5em;
  padding: 10px 14px;
  border: 1px solid var(--edge);
  border-left: 2px solid var(--potion);
  background: rgba(8, 10, 14, 0.92);
  font: 400 0.92rem/1.55 var(--f-mono);
  color: var(--bone);
}
.basil-intro-say p {
  margin: 0 0 4px;
}
.basil-intro-controls {
  margin: 0;
  font: 400 0.75rem var(--f-mono);
  color: var(--ash);
}
.basil-intro-go {
  min-width: 10em;
  opacity: 0.35;
}
.basil-intro.typed .basil-intro-go {
  opacity: 1;
}
/* guided: Basil's greeting and NEXT chip replace the tutorial banner */
body.basil-on #tutorialOverlay {
  display: none !important;
}

/* iOS Safari enlarges text on pages it thinks are too wide; the layout is responsive. */
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

/* his words type into the bubble; the untyped rest holds the bubble's final size */
.basil-untyped {
  color: transparent;
}
.basil-say {
  cursor: pointer;
}
body.phone .basil.docked .basil-col {
  max-width: calc(100vw - 96px);
}

/* his row in the Training Room's exercise list header (rogueai/basil.js) */
.basil-dock {
  display: none;
  flex: 1 0 100%;
  min-height: 64px;
}
body.basil-on .basil-dock {
  display: block;
}
/* changing screens (rogueai/basil.js moveScreens) */
@media (prefers-reduced-motion: no-preference) {
  .basil.hop > * {
    animation: basilHop 460ms cubic-bezier(0.3, 0.7, 0.35, 1);
  }
  .basil.enter > * {
    animation: basilEnter 380ms ease-out;
  }
  .basil.exit > * {
    animation: basilExit 240ms ease-in forwards;
  }
}
@keyframes basilHop {
  0% { transform: translateY(0) scale(1, 1); }
  18% { transform: translateY(2px) scale(1.06, 0.92); }
  50% { transform: translateY(-18px) scale(0.95, 1.06); }
  82% { transform: translateY(1px) scale(1.04, 0.95); }
  100% { transform: translateY(0) scale(1, 1); }
}
@keyframes basilEnter {
  from { opacity: 0; transform: translateY(12px); }
  to { opacity: 1; transform: none; }
}
@keyframes basilExit {
  to { opacity: 0; transform: translateY(-8px); }
}


/* Keep the phone toolbar controls in one row as the vitals wrap. */
body.phone .bar-btns { flex-wrap: nowrap; flex-shrink: 0; }


/* Module run records share the review scroll region, including narrow phones. */
.labs-checkpoints { list-style: none; padding: 0; display: grid; gap: .6rem; }
.labs-checkpoints .slot-card { display: block; padding: .7rem; border: 1px solid var(--smoke); }
.labs-checkpoints .slot-acts { display: flex; flex-wrap: wrap; gap: .4rem; }
.labs-checkpoints button { min-height: 44px; }
.labs-curvebox { min-width: 0; }

/* The run home leads with the selected module; the curve and records share
   the same scroll surface instead of hiding checkpoints beneath all stats. */
.labs-review { grid-template-areas: "heading curve" "records detail"; align-items: start; align-content: start; }
.labs-checkpoint-heading { grid-area: heading; display: grid; gap: .65rem; }
.labs-checkpoint-heading h3, .labs-checkpoint-heading p { margin: 0; }
.labs-checkpoint-heading select { min-height: 44px; width: 100%; }
.labs-curvebox { grid-area: curve; }
.labs-checkpoints { grid-area: records; margin: 0; }
.labs-revcol { grid-area: detail; align-content: start; }
body.phone .labs-review { grid-template-areas: "heading" "curve" "records" "detail"; }
body.phone #labsReview .labs-foot .cta { flex: 1 1 calc(50% - .5rem); font-size: 16px; letter-spacing: .04em; padding: .65rem .4rem; min-height: 44px; }

/* Iconographic actions for the #135 run controls and checkpoint records: a
   glyph from the app's own vocabulary leads and the word rides along in a
   .blab span; the phone layout keeps the glyph alone — the autoplay button's
   pattern — with aria-label/title carrying the full word. Targets stay 44px. */
.labs .icon-btn { display: inline-flex; align-items: center; justify-content: center; gap: .45em; min-height: 44px; }
.labs .icon-btn .bic { font-size: 1.05rem; line-height: 1; }
.labs .icon-btn.danger { border-color: var(--ember); }
body.phone .labs .icon-btn { min-width: 44px; padding: .45rem .55rem; }
body.phone .labs .icon-btn .blab { display: none; }

/* Issue #212: an NPC's shadow training (the dungeon JIT mode): a sampled shadow floor and a
   success bar over the paused play screen. */
.npc-train {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: grid;
  place-items: center;
  padding: calc(16px + var(--safe-top, 0px)) 16px calc(16px + var(--safe-bot, 0px));
  background: rgba(5, 6, 9, 0.9);
  overflow: auto;
}
.npc-train-card {
  width: min(560px, 100%);
  display: grid;
  gap: 10px;
  padding: 16px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--bone);
}
.npc-train-card pre {
  margin: 0;
  overflow-x: auto;
  font-size: 0.85rem;
  line-height: 1.15;
  color: var(--bone);
}
.npc-train-bar {
  height: 10px;
  border: 1px solid var(--line);
  background: var(--void);
}
.npc-train-bar > span {
  display: block;
  height: 100%;
  width: 0;
  background: var(--potion);
  transition: width 0.2s;
}

/* The summon element (issue #237): one HUD control, a chooser of summonables. */
.summon-chooser {
  position: absolute;
  /* the 🐾 button is on the bottom panel (docs/hud.md): the chooser opens just above it */
  bottom: 8px;
  right: 8px;
  z-index: 4;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 10px;
  max-width: min(22rem, calc(100% - 16px));
  background: rgba(8, 10, 14, 0.92);
  border: 1px solid var(--edge);
  border-radius: 4px;
}
.summon-chooser[hidden] {
  display: none;
}
/* his row at the chooser's top (rogueai/basil.js place): open only while he stands in it */
body.basil-on .summon-chooser:not(.basil-here) #basilSummonDock {
  display: none;
}
.summon-title {
  margin: 0;
  font-size: 0.75rem;
  color: var(--muted);
}
.summon-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 2px 10px;
  text-align: left;
  padding: 6px 8px;
}
.summon-row .summon-prov {
  grid-column: 1 / -1;
  font-size: 0.7rem;
  color: var(--ash);
}
.summon-row .summon-state {
  font-style: normal;
  font-size: 0.75rem;
  color: var(--muted);
}
.summon-row[aria-pressed="true"] .summon-state {
  color: #6be6d8;
}
/* with you: call her back to your side, or talk to her (a bump on her swaps places) */
.summon-acts {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 0 8px 6px;
}
.summon-act {
  font-size: 0.75rem;
  padding: 4px 8px;
}
#app[data-screen="title"] .summon-chooser {
  visibility: hidden;
}

/* The shop's wallet: the unspent points of the brain the shop shows, in its header. */
.shop-wallet {
  margin-left: auto;
  padding: 2px 10px;
  border: 1px solid var(--gold);
  border-radius: 999px;
  color: var(--gold);
  white-space: nowrap;
}
.shop-wallet small {
  color: var(--muted);
}
.shop-wallet.empty {
  border-color: var(--edge);
  color: var(--muted);
}
.tact-count {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 1.3em;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--gold);
  color: #111;
  font-size: 0.7rem;
  line-height: 1.3em;
  text-align: center;
}
.tact-count[hidden] {
  display: none;
}
@media (max-width: 480px) {
  .shop-wallet {
    padding: 2px 6px;
  }
  .shop-wallet small {
    display: none;
  }
}

/* Coach points at a checkpoint's ◆ Apply (issue #229: the room trained a copy of Kit;
   the player's tap applies it). */
.coach-pointed {
  outline: 1px solid var(--gold);
}
.icon-btn.coach-point {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
  animation: coach-point 1.2s ease-in-out infinite;
}
@keyframes coach-point {
  50% {
    outline-color: transparent;
  }
}
@media (prefers-reduced-motion: reduce) {
  .icon-btn.coach-point {
    animation: none;
  }
}

/* HUD by category (docs/hud.md): the top bar goes elsewhere (Brain Editor, Training Room, shop, menu);
   the bottom panel acts in the dungeon (build, summon, auto). On a phone that panel is the pad's right
   column; on desktop, where the keys move and fire, it is a strip of pill buttons under the map. */
body:not(.phone) #dpad {
  display: flex;
  justify-content: flex-end;
  margin: 6px 0 0;
}
@media (pointer: fine) {
  body:not(.phone) #dpad:not(.open) #radial {
    display: none;
  }
}
body:not(.phone) .tacts {
  display: flex;
  gap: 8px;
}
body:not(.phone) .tacts .tact {
  width: auto;
  height: auto;
  border-radius: 4px !important;
  padding: 5px 12px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: 500 0.8rem/1.2 var(--f-mono);
  letter-spacing: 0;
}
body:not(.phone) .tacts .tact[hidden] {
  display: none;
}
body:not(.phone) .tacts .tact big {
  display: inline;
  font-size: 1rem;
}
#loomBuildBtn[aria-pressed="true"] {
  background: #2c4a35;
  border-color: #7dff9a;
}
/* The phone's top bar fits status + coach + 🧠 🎯 ⚡ ≡ down to 360px wide: tight gaps, square-ish buttons. */
body.phone .game .bar { gap: 4px; }
body.phone .bar-btns { gap: 4px; margin-left: auto; }
body.phone .game .bar .bar-btns button { padding: 4px 5px; min-width: 32px; }
body.phone .vitals { gap: 4px; }
@media (max-width: 380px) {
  body.phone .vitals strong { font-size: 1rem; }
  body.phone #hp { font-size: 1em; letter-spacing: 0.04em; }
}
@media (max-width: 380px) {
  body.phone .clock { width: 24px; height: 24px; }
  body.phone .game .bar .bar-btns button { padding: 4px 4px; min-width: 30px; }
}
/* Build mode (Loomwork's dock) takes over the bottom: the bottom panel hides while building
   (docs/hud.md); a tap on empty floor steps toward it instead of the pad. It keeps its space
   (visibility, not display), so the map never refits and the camera doesn't move; the dock
   reaches down over the panel's spot instead (loom-ui.js sets --dock-drop). */
body.loom-building .game.card #dpad {
  visibility: hidden;
  pointer-events: none;
}
/* One scrim from the game into the bottom (docs/hud.md): the bottom panel runs over the bottom of
   the map behind the title screen's fade (clear at the top, dark where the controls sit), the
   Loom Lab's. The overlap gives the map back a little height; the fade passes taps through to
   the map. Build mode's dock (loom-ui.js) uses the same scrim, so play and build read alike. */
:root {
  /* The fade's length (--scrim-len) is the whole ramp, clear to 95%; the bottom panel overlaps the
     map by all of it, so the map dims gradually in play exactly as it does under build's dock (an
     overlap shorter than the ramp showed only its steep start over the map: a hard edge). */
  --scrim-len: 96px;
  --scrim-bottom: linear-gradient(180deg, rgba(7, 8, 11, 0) 0, rgba(7, 8, 11, 0.72) calc(var(--scrim-len) * 0.46), rgba(7, 8, 11, 0.95) var(--scrim-len));
  --panel-overlap: var(--scrim-len);
}
body:not(.phone) {
  --scrim-len: 72px;
}
.game.card #dpad,
body.phone .game.card #dpad,
body:not(.phone) .game.card #dpad {
  position: relative;
  z-index: 5;
  margin-top: calc(-1 * var(--panel-overlap));
  padding-top: var(--panel-overlap);
  background: var(--scrim-bottom);
  pointer-events: none;
}
.game.card #dpad > * {
  pointer-events: auto;
}
/* The map dissolves into the scrim: no bottom edge where the panel (or build mode's dock) runs over it. */
.game.card .viewport {
  border-bottom-color: transparent;
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}
/* A frameless map with a fade on every edge (docs/hud.md): no border; the top bar casts the same fade
   down over the map's top as the bottom panel casts up over its bottom; and the map fades out toward
   its left and right edges, in a band as wide as a future icon column (--edge-col; the map still runs
   under it for now). */
:root {
  --scrim-top: linear-gradient(180deg, rgba(7, 8, 11, 0.95) 0, rgba(7, 8, 11, 0.6) 40%, rgba(7, 8, 11, 0) 100%);
  --scrim-top-h: 26px;
  --edge-col: 40px;
  --edge-row: 18px;
}
body.phone {
  --edge-col: 24px;
}
.game.card .viewport {
  border: 0;
  border-radius: 0;
  background: #020204; /* the torch canvas's own dark, so the map has no inner box; the fades carry it into the page */
  /* every edge fades out: sides over an icon column's width, top and bottom over --edge-row */
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 var(--edge-col), #000 calc(100% - var(--edge-col)), transparent 100%),
    linear-gradient(180deg, transparent 0, #000 var(--edge-row), #000 calc(100% - var(--edge-row)), transparent 100%);
  -webkit-mask-composite: source-in;
  mask-image: linear-gradient(90deg, transparent 0, #000 var(--edge-col), #000 calc(100% - var(--edge-col)), transparent 100%),
    linear-gradient(180deg, transparent 0, #000 var(--edge-row), #000 calc(100% - var(--edge-row)), transparent 100%);
  mask-composite: intersect;
}
.game.card .bar {
  position: relative;
  z-index: 5;
}
.game.card .bar::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: var(--scrim-top-h);
  background: var(--scrim-top);
  pointer-events: none;
}
/* A side sheet (the brain viewer) leaves the top bar reachable: it starts below the bar, so ≡ and
   the other top buttons still work while it's open (it's a sheet you play beside, not a modal). */
body:not(.phone) .ov.side {
  padding-top: calc(60px + var(--safe-top));
}
/* An inspect card open: reading, not moving; the bottom panel hides the same way (loom-ui.js). */
body.loom-reading .game.card #dpad {
  visibility: hidden;
  pointer-events: none;
}
/* Settings tabs (shell.js "Settings tabs"): Graphics, Sound, General; a section off its tab is hidden */
.set-tabs {
  margin: 6px 0 2px;
}
.set-tabs .btab {
  flex: 1 1 0;
  min-height: 40px;
}
#artModal .stab-off {
  display: none !important;
}
/* The phone's top bar also fits 🔊 (issue #306): HP and MP show as ♥ and 💧 alone, and the vitals,
   gaps and icon buttons tighten. tests/cues.spec.js holds it to one row, nothing cut off, 360-430px. */
body.phone .game .bar { gap: 3px; }
body.phone #shopBtn { padding: 4px 5px; }
body.phone .vitals, body.phone .bar-btns { gap: 3px; }
body.phone .vitals strong { font-size: 0.95rem; letter-spacing: 0.02em; }
body.phone #hp, body.phone #mp { letter-spacing: 0; font-size: 0.9em; }
body.phone .game .bar .bar-btns button { padding: 4px 4px; min-width: 0; }
body.phone .game .bar .bar-btns .glyph-btn { width: 1.1em; }
@media (max-width: 380px) {
  body.phone .vitals strong { font-size: 0.85rem; }
  body.phone .game .bar .bar-btns .glyph-btn { width: 1em; padding: 4px 3px; }
}
