:root {
  --ink-900: #171a24;
  --ink-800: #1f2330;
  --ink-700: #2a2f40;
  --ink-600: #3a4058;
  --paper: #e9e7de;
  --paper-dim: #a7a795;
  --gold: #e0a733;
  --gold-dim: #7a611f;
  --miss: #d8654f;
  --miss-dim: #4a2c26;
  --good: #7fae6f;
  --good-dim: #24331f;
  --font-ui: "Space Grotesk", "Avenir Next", "Segoe UI", sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", "Cascadia Code", Menlo, Consolas, monospace;
}

/* Light mode: every component already reads colors from these variables
   rather than hardcoding them, so re-pointing the same names is the whole
   theme swap — no per-component overrides needed. Applied via a data-theme
   attribute (set in app.js from a saved localStorage preference) rather
   than prefers-color-scheme, since this is an explicit user toggle, not
   an OS-level default the app should silently follow. */
:root[data-theme="light"] {
  --ink-900: #f6f5ee;
  --ink-800: #ffffff;
  --ink-700: #ddd9c8;
  --ink-600: #c7c2ab;
  --paper: #23262f;
  --paper-dim: #6b6b5c;
  --gold: #a6741a;
  --gold-dim: #7a611f;
  --miss: #b8402b;
  --miss-dim: #f7ddd6;
  --good: #3f7a30;
  --good-dim: #dff0d6;
}


* { box-sizing: border-box; }

/* The `hidden` attribute normally implies display:none via the browser's
   default stylesheet, but that's a *normal-priority* rule — any author CSS
   that sets an explicit `display` on the same element (like .app's
   display:grid or .join-gate's display:flex below) silently wins over it,
   so toggling `.hidden = true/false` in JS stops actually hiding anything.
   This forces hidden to always win, full stop. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--ink-900);
  color: var(--paper);
  font-family: var(--font-ui);
}

.app {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

/* ---- join gate ---- */
.join-gate {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--ink-900);
}
.join-card {
  width: 100%;
  max-width: 320px;
  padding: 32px 28px;
  background: var(--ink-800);
  border: 1px solid var(--ink-700);
  border-radius: 10px;
  text-align: center;
}
.mark.big { font-size: 30px; color: var(--gold); display: block; margin-bottom: 10px; }
.join-card h1 { font-size: 19px; margin: 0 0 22px; font-weight: 600; }
.join-card label {
  display: block;
  text-align: left;
  font-size: 12px;
  color: var(--paper-dim);
  margin-bottom: 14px;
}
.join-card input {
  display: block;
  width: 100%;
  margin-top: 6px;
  padding: 9px 10px;
  font-family: var(--font-mono);
  font-size: 15px;
  background: var(--ink-900);
  border: 1px solid var(--ink-600);
  border-radius: 6px;
  color: var(--paper);
}
.join-card input:focus { outline: none; border-color: var(--gold-dim); }
.join-hint { font-size: 11.5px; color: var(--paper-dim); line-height: 1.5; margin: 4px 0 16px; text-align: left; }
.join-error { font-size: 12.5px; color: var(--miss); margin: -6px 0 14px; text-align: left; }
.join-card .btn { width: 100%; }

@media (max-width: 900px) {
  .topnav-sub { display: none; }
}

/* ---- top nav (replaces the old fixed rail + always-visible log panel —
   both the lesson list and the attempt log are their own screens now) ---- */
.topnav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 24px;
  background: var(--ink-800);
  border-bottom: 1px solid var(--ink-700);
}
.topnav-brand { display: flex; align-items: center; gap: 10px; }
.mark { font-size: 22px; color: var(--gold); }
.topnav-title { font-weight: 600; font-size: 16px; letter-spacing: 0.01em; }
.topnav-sub { font-size: 12px; color: var(--paper-dim); }
.topnav-actions { display: flex; align-items: center; gap: 10px; }
.topnav-xp, .topnav-stat {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--gold);
  border: 1px solid var(--ink-700);
  border-radius: 999px;
  padding: 4px 12px;
  white-space: nowrap;
}
.topnav-stat { color: var(--paper-dim); }

.drill-exit { margin-bottom: 6px; padding: 4px 10px; font-size: 13px; }

.settings-group {
  border: 1px solid var(--ink-700);
  border-radius: 10px;
  padding: 16px;
  margin-bottom: 16px;
}
.settings-label { font-size: 12px; color: var(--paper-dim); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 10px; }
.settings-note { font-size: 13px; color: var(--paper-dim); line-height: 1.5; margin: 0 0 12px; }
.settings-actions { margin-top: 8px; }
.btn.danger { border-color: var(--miss); color: var(--miss); }
.btn.danger.danger-armed { background: var(--miss); color: var(--ink-900); }

.lesson-list { display: flex; flex-direction: column; gap: 4px; }
.lesson-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 13.5px;
  color: var(--paper);
  border-left: 2px solid transparent;
}
.lesson-item:hover { background: var(--ink-700); }
.lesson-item.active {
  background: var(--ink-700);
  border-left: 2px solid var(--gold);
}
.lesson-item .dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--ink-600);
  flex-shrink: 0;
}
.lesson-item.passed .dot { background: var(--gold); }
.lesson-item .name { flex: 1; }
.lesson-item .keys {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--paper-dim);
}

/* ---- levels (placeholder video-game-zone theming; art/monsters land
   here later — the grid and progress counts are the real, lasting part) ---- */
.levels-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 14px;
  max-width: 900px;
}
.level-card {
  background: var(--ink-800);
  border: 1px solid var(--ink-700);
  border-radius: 10px;
  padding: 18px 16px;
  cursor: pointer;
  text-align: left;
  transition: border-color 0.15s ease;
}
.level-card:hover { border-color: var(--gold-dim); }
.level-card .level-title { font-weight: 600; font-size: 15px; margin-bottom: 4px; }
.level-card .level-subtitle { font-size: 12px; color: var(--paper-dim); margin-bottom: 8px; }
.level-card .level-progress { font-size: 12px; color: var(--paper-dim); font-family: var(--font-mono); }
.level-card .level-progress.complete { color: var(--good); }

/* ---- stage ---- */
.stage {
  padding: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.screen { width: 100%; max-width: 620px; }

#screen-intro { text-align: center; }
.intro-key {
  display: flex;
  gap: 8px;
  justify-content: center;
  margin-bottom: 28px;
}
.intro-key span {
  font-family: var(--font-mono);
  width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--ink-600);
  border-radius: 5px;
  color: var(--paper-dim);
  font-size: 14px;
}
#screen-intro h1 { font-size: 22px; margin: 0 0 8px; font-weight: 600; }
.muted { color: var(--paper-dim); font-size: 14px; }

.drill-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  margin-bottom: 28px;
}
.drill-title { font-size: 19px; font-weight: 600; }
.drill-step { font-size: 12.5px; color: var(--paper-dim); margin-top: 2px; }
.live-stats { display: flex; gap: 22px; }
.stat { text-align: right; }
.stat-num { display: block; font-family: var(--font-mono); font-size: 22px; color: var(--gold); line-height: 1; }
.stat-label { font-size: 11px; color: var(--paper-dim); }

.type-target {
  font-family: var(--font-mono);
  font-size: 22px;
  line-height: 1.9;
  letter-spacing: 0.01em;
  padding: 22px 24px;
  background: var(--ink-800);
  border: 1px solid var(--ink-700);
  border-radius: 8px;
  outline: none;
  white-space: pre-wrap; /* real newlines/indentation must render, not collapse — needed for code */
}
.type-target:focus { border-color: var(--gold-dim); }
.type-target .ch { position: relative; }
.type-target .ch.correct { color: var(--paper); }
.type-target .ch.wrong { color: var(--miss); background: var(--miss-dim); border-radius: 2px; }
.type-target .ch.pending { color: var(--paper-dim); }
.type-target .ch.cursor::before {
  content: "";
  position: absolute;
  left: -1px; top: 2px; bottom: 2px;
  width: 2px;
  background: var(--gold);
}

.type-input-sink {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  width: 1px; height: 1px;
  resize: none;
  overflow: hidden;
}
.type-hint { font-size: 12.5px; color: var(--paper-dim); margin-top: 12px; }

/* ---- field drills (data entry grid / form entry) ---- */
.field-drill { display: flex; flex-direction: column; gap: 18px; }

.field-box {
  font-family: var(--font-mono);
  font-size: 15px;
  background: var(--ink-800);
  border: 1px solid var(--ink-700);
  border-radius: 6px;
  padding: 8px 10px;
  color: var(--paper);
  outline: none;
}
.field-box:focus { border-color: var(--gold-dim); }
.field-target {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--paper-dim);
  margin-bottom: 4px;
  user-select: none; /* it's a reference to copy, not something to accidentally select/drag */
}
/* Correctness only ever shows after a field is left (blur) or at final
   submit — never mid-keystroke. Real forms don't scream "wrong" at you
   while you're still typing, and neither should this. */
.field-box.correct { border-color: var(--good); background: var(--good-dim); }
.field-box.wrong { border-color: var(--miss); background: var(--miss-dim); }

/* data_entry_grid: a real table, header row is the column labels */
.field-surface.as-grid { overflow-x: auto; }
.field-grid-table { border-collapse: separate; border-spacing: 6px; }
.field-grid-table th {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--paper-dim); font-weight: 600; text-align: left; padding: 0 10px 4px;
}
.field-grid-table td { padding: 0; vertical-align: top; }
.field-grid-table .field-box { width: 100%; min-width: 90px; }

/* form_entry: stacked labeled rows, like a real form */
.field-surface.as-form { display: flex; flex-direction: column; gap: 14px; max-width: 420px; }
.field-form-row { display: flex; flex-direction: column; gap: 5px; }
.field-form-label { font-size: 12.5px; color: var(--paper-dim); }
.field-surface.as-form .field-box { width: 100%; }

.field-drill-actions { display: flex; justify-content: flex-end; }

/* ---- area map (branching path, proof of concept on home row) ---- */
/* Matches the SVG's own viewBox ratio (960×320 = 3:1) so an uploaded area
   background lines up with node/monster x,y coordinates instead of
   stretching or letterboxing oddly. Plain background-color fallback when
   an area has no background art yet (see AREA_CONFIG in app.js). */
#mapSurface {
  position: relative;
  width: 100%;
  aspect-ratio: 3 / 1;
  background-color: var(--ink-800);
  background-size: cover;
  background-position: center;
  border-radius: 10px;
  overflow: hidden;
}
.map-svg { width: 100%; height: 100%; display: block; }
.map-edge { stroke: var(--ink-700); stroke-width: 3; }

.map-node circle, .map-node polygon {
  stroke-width: 3;
  transition: filter 0.15s ease;
}
.map-node.locked circle, .map-node.locked polygon {
  fill: var(--ink-800); stroke: var(--ink-700);
}
.map-node.unlocked circle, .map-node.unlocked polygon {
  fill: var(--ink-800); stroke: var(--gold);
}
.map-node.passed circle, .map-node.passed polygon {
  fill: var(--good-dim); stroke: var(--good);
}
.map-node.boss circle, .map-node.boss polygon { stroke-width: 4; }
.map-node:not(.locked):hover circle, .map-node:not(.locked):hover polygon { filter: brightness(1.3); }

.map-node-label {
  fill: var(--paper-dim);
  font-family: var(--font-ui);
  font-size: 11px;
  text-anchor: middle;
}
.map-node.locked .map-node-label { opacity: 0.5; }

.map-token { font-size: 22px; text-anchor: middle; }

#btnViewMap { margin-top: 18px; }

.result-stars { font-size: 30px; letter-spacing: 4px; margin: 4px 0 2px; }
.result-stars .star.filled { color: var(--gold); }
.result-stars .star.empty { color: var(--ink-700); }
.result-xp {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  font-size: 13px;
  color: var(--paper-dim);
  margin: 6px 0 14px;
}
.result-xp-gained {
  font-family: var(--font-mono);
  color: var(--gold);
  font-weight: 600;
}

/* ---- result ---- */
#screen-result { text-align: center; }
.result-badge {
  display: inline-block;
  font-size: 11px;
  letter-spacing: 0.06em;
  padding: 5px 12px;
  border-radius: 999px;
  margin-bottom: 14px;
}
.result-badge.pass { background: var(--gold-dim); color: var(--gold); }
.result-badge.fail { background: var(--miss-dim); color: var(--miss); }
#resultHeadline { font-size: 22px; font-weight: 600; margin: 0 0 24px; }
.result-grid { display: flex; justify-content: center; gap: 40px; margin-bottom: 20px; }
.result-num { display: block; font-family: var(--font-mono); font-size: 34px; color: var(--paper); }
.result-label { font-size: 12px; color: var(--paper-dim); }
.miss-panel { margin-bottom: 24px; }
.miss-title { font-size: 12px; color: var(--paper-dim); margin-bottom: 8px; }
.miss-keys { display: flex; gap: 8px; justify-content: center; }
.miss-keys .k {
  font-family: var(--font-mono);
  border: 1px solid var(--miss);
  color: var(--miss);
  border-radius: 5px;
  padding: 5px 10px;
  font-size: 13px;
}
.result-actions { display: flex; gap: 10px; justify-content: center; }
.btn {
  font-family: var(--font-ui);
  font-size: 13.5px;
  font-weight: 600;
  padding: 10px 18px;
  border-radius: 6px;
  border: none;
  background: var(--gold);
  color: var(--ink-900);
  cursor: pointer;
}
.btn.ghost {
  background: transparent;
  border: 1px solid var(--ink-600);
  color: var(--paper);
}
.btn:hover { filter: brightness(1.08); }

/* ---- attempt log (its own screen now, not a fixed side panel) ---- */
.log-body { display: flex; flex-direction: column; gap: 8px; max-width: 480px; }
.log-row {
  border: 1px solid var(--ink-700);
  border-radius: 6px;
  padding: 8px 10px;
}
.log-row .l1 { display: flex; justify-content: space-between; font-size: 12.5px; }
.log-row .lesson { color: var(--paper); }
.log-row .when { color: var(--paper-dim); font-size: 11px; }
.log-row .l2 { font-family: var(--font-mono); font-size: 11.5px; margin-top: 4px; }
.log-row .l2.pass { color: var(--gold); }
.log-row .l2.fail { color: var(--miss); }
.log-empty { color: var(--paper-dim); font-size: 12.5px; }
.log-note { margin-top: 16px; font-size: 11px; color: var(--paper-dim); line-height: 1.5; }

/* --- Character sheet (Phase 5: player-built stat allocation) ----------- */
.char-level-block { margin-bottom: 8px; }
.char-level-label { font-family: var(--font-mono); font-size: 14px; margin-bottom: 6px; }
.char-xp-bar {
  height: 10px;
  border-radius: 5px;
  background: var(--ink-900);
  overflow: hidden;
}
.char-xp-fill { height: 100%; background: var(--good); transition: width 0.2s ease; }
.char-xp-label { font-size: 12px; color: var(--paper-dim); margin-top: 6px; font-family: var(--font-mono); }

.char-points-note { font-size: 13px; color: var(--paper-dim); margin: 16px 0; }

.char-stats { display: flex; flex-direction: column; gap: 12px; }
.char-stat-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px;
  border: 1px solid var(--ink-700, #333);
  border-radius: 8px;
}
.char-stat-name { font-size: 13px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--paper-dim); }
.char-stat-value { font-family: var(--font-mono); font-size: 20px; min-width: 2ch; text-align: right; }
.char-stat-add {
  width: 28px; height: 28px; border-radius: 50%;
  font-size: 16px; line-height: 1;
}
.char-stat-add:disabled { opacity: 0.35; cursor: default; }

/* Elemental affinity (Phase 6.5) — unlocks at level 10 on the Character
   screen, see renderAffinitySection() in app.js. */
.char-affinity { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--ink-700); }
.char-affinity-label { font-size: 13px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--paper-dim); margin-bottom: 8px; }
.char-affinity-note { font-size: 12.5px; color: var(--paper-dim); line-height: 1.5; margin: 0 0 12px; }
.char-affinity-picked { font-size: 20px; font-family: var(--font-mono); margin-bottom: 6px; }
.char-affinity-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.char-affinity-choice { font-size: 15px; padding: 12px; }

/* --- Battle screen (Phase 6) --------------------------------------------- */
.battle-monster-portrait {
  width: 250px;
  height: 250px;
  max-width: 100%;
  aspect-ratio: 1 / 1;
  margin: 0 auto 16px;
  border: 2px dashed var(--ink-700);
  border-radius: 12px;
  background: var(--ink-800);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 96px;
  overflow: hidden;
}
.battle-monster-portrait img { width: 100%; height: 100%; object-fit: contain; }

.battle-combatant { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 6px; }
.battle-name { font-weight: 600; }
.battle-hp-num { font-family: var(--font-mono); font-size: 13px; color: var(--paper-dim); }
.battle-hp-bar { height: 12px; border-radius: 6px; background: var(--ink-700); overflow: hidden; margin-bottom: 12px; }
.battle-hp-fill { height: 100%; transition: width 0.3s ease; }
.battle-hp-fill.player { background: var(--gold); }
.battle-hp-fill.monster { background: var(--miss); }

.battle-ap-row { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; }
.battle-ap-label { font-size: 12px; color: var(--paper-dim); width: 30px; }
.battle-ap-bar { flex: 1; height: 10px; border-radius: 5px; background: var(--ink-700); overflow: hidden; }
.battle-ap-fill { height: 100%; background: var(--good); transition: width 0.08s linear; }

.battle-menu { display: flex; gap: 8px; margin-bottom: 4px; }
.battle-menu .btn.focused { outline: 2px solid var(--gold); outline-offset: -2px; }
.battle-menu-hint { font-size: 11px; color: var(--paper-dim); margin: 6px 0 16px; }

.battle-attack-box { margin: 12px 0; }
.battle-timer-bar { height: 6px; border-radius: 3px; background: var(--ink-700); overflow: hidden; margin-bottom: 10px; }
.battle-timer-fill { height: 100%; background: var(--miss); transition: width 0.08s linear; }
.battle-word-list { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
.battle-word-chip {
  font-family: var(--font-mono); font-size: 15px; padding: 4px 10px;
  border-radius: 6px; background: var(--ink-900); border: 1px solid var(--ink-700); color: var(--paper);
}
.battle-word-chip.current { border-color: var(--gold); }
#battleAttackArea input {
  width: 100%; padding: 10px; font-family: var(--font-mono); font-size: 16px;
  background: var(--ink-900); border: 1px solid var(--ink-700); border-radius: 8px; color: var(--paper);
}

/* Spell picker (Phase 6.5) — shown by startBattleSpell() only once
   Intelligence >=1 gives more than one option; below that, Magic Missile
   casts straight away with no picker at all. */
.spell-picker { margin: 12px 0; }
.spell-picker-hint { font-size: 12px; color: var(--paper-dim); margin: 0 0 10px; }
.spell-picker-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; }
.spell-choice { font-size: 14px; padding: 10px; text-align: left; }
.spell-mult { font-family: var(--font-mono); font-size: 12px; }
.spell-mult.good { color: var(--good); }
.spell-mult.bad { color: var(--miss); }

.battle-log { max-height: 150px; overflow-y: auto; font-family: var(--font-mono); font-size: 12px; color: var(--paper-dim); margin-top: 16px; }
.battle-log div { padding: 4px 0; border-bottom: 1px solid var(--ink-700); }
.battle-log div:last-child { border-bottom: none; }

.battle-banner { text-align: center; font-size: 16px; font-weight: 600; padding: 10px; border-radius: 8px; margin-bottom: 12px; }
.battle-banner.win { background: var(--good-dim); color: var(--good); }
.battle-banner.lose { background: var(--miss-dim); color: var(--miss); }
.battle-xp-gained {
  text-align: center; font-family: var(--font-mono); font-size: 14px;
  color: var(--gold); margin: -4px 0 12px;
}

.map-monster-glyph { font-size: 30px; }
.map-monster:hover .map-monster-glyph { filter: brightness(1.15); }
/* A "random" marker (no fixed species — see resolveRandomAreaMonster in
   app.js) shows the same "?" glyph until it's fought, so it visibly reads
   as "something's here" rather than looking like a broken/missing icon. */
.map-monster.is-random .map-monster-glyph { opacity: 0.85; }

/* Monster's own attack clock — a brief warning pulse before the hit lands,
   so a monster attack (independent of anything the player does) is never a
   surprise, per the "generous, telegraphed" design call. */
@keyframes battle-windup-pulse {
  0%, 100% { filter: brightness(1) drop-shadow(0 0 0 var(--miss)); }
  50% { filter: brightness(1.3) drop-shadow(0 0 10px var(--miss)); }
}
.battle-monster-portrait.winding-up { animation: battle-windup-pulse 0.6s ease-in-out infinite; }

/* Monster reactions (Phase 6.5 polish) — hit-flash and attack-lunge are
   transient (added by pulseClass() in app.js, auto-removed a few hundred ms
   later); enraged is permanent once triggered, since HP never heals back up
   mid-fight. Note: a plain CSS class toggle can only run ONE `animation`
   shorthand per element at a time, so if two of these ever land on the
   portrait simultaneously, only one plays (the one declared later below
   wins on equal specificity) — acceptable for a first pass, worth revisiting
   with real keyframe composition if it looks janky in practice. */

/* Player lands a hit — quick red flash + shake, distinct from the
   monster's own "attack-lunge" below so hitting it vs. being hit by it
   read as two different things at a glance. */
@keyframes battle-hit-flash {
  0% { transform: translateX(0); filter: brightness(1) saturate(1); }
  20% { transform: translateX(-6px); filter: brightness(1.6) saturate(1.8) drop-shadow(0 0 14px var(--miss)); }
  40% { transform: translateX(6px); }
  60% { transform: translateX(-4px); }
  80% { transform: translateX(2px); }
  100% { transform: translateX(0); filter: brightness(1) saturate(1); }
}
.battle-monster-portrait.hit-flash { animation: battle-hit-flash 0.35s ease-out; }

/* The monster's own attack connecting — a forward lunge at the moment of
   impact, separate from the slower pre-attack "winding-up" glow (that's
   the tell; this is the swing). */
@keyframes battle-attack-lunge {
  0% { transform: scale(1) translateY(0); }
  35% { transform: scale(1.08) translateY(10px); }
  100% { transform: scale(1) translateY(0); }
}
.battle-monster-portrait.attack-lunge { animation: battle-attack-lunge 0.4s ease-out; }

/* Bloodied (≤30% HP, see ENRAGE_HP_RATIO in app.js) — a permanent size-up
   + slow red halo, so a fight visibly escalates near the kill instead of
   the monster looking identical at full HP and one hit from death. A
   gentle, slow pulse on purpose — kept visually distinct from the faster
   windup blink above. */
@keyframes battle-enrage-pulse {
  0%, 100% { box-shadow: 0 0 0 rgba(216, 101, 79, 0.35); }
  50% { box-shadow: 0 0 22px rgba(216, 101, 79, 0.55); }
}
.battle-monster-portrait.enraged {
  transform: scale(1.15);
  border-color: var(--miss);
  animation: battle-enrage-pulse 1.6s ease-in-out infinite;
}

/* Random-encounter interstitial — the Pokémon-swirl equivalent. Full-screen,
   sits above everything (outside the normal .screen toggling in <main>), a
   quick burst + flash rather than anything elaborate. */
.encounter-flash {
  position: fixed; inset: 0; z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  background: var(--ink-900);
  opacity: 0;
  pointer-events: none;
}
.encounter-flash.in { animation: encounter-flash-fade 1.1s ease-out forwards; }
@keyframes encounter-flash-fade {
  0% { opacity: 0; }
  15% { opacity: 1; }
  75% { opacity: 1; }
  100% { opacity: 0; }
}
.encounter-flash-burst {
  position: absolute; inset: 0;
  background: radial-gradient(circle, var(--gold-dim) 0%, transparent 65%);
  animation: encounter-flash-burst 1.1s ease-out;
}
@keyframes encounter-flash-burst {
  0% { transform: scale(0.2); opacity: 0; }
  30% { opacity: 1; }
  100% { transform: scale(2.2); opacity: 0; }
}
.encounter-flash-text {
  position: relative;
  font-family: var(--font-mono); font-size: 22px; font-weight: 700;
  color: var(--paper); text-align: center; padding: 0 24px;
  animation: encounter-flash-text-pop 1.1s ease-out;
}
@keyframes encounter-flash-text-pop {
  0%, 10% { transform: scale(0.7); opacity: 0; }
  30% { transform: scale(1.08); opacity: 1; }
  40% { transform: scale(1); }
  85% { opacity: 1; }
  100% { opacity: 0; }
}

/* Monster battle gating (session 11) — a locked marker (requiresNodeId not
   yet passed) dims and shows a lock glyph instead of its species/emoji;
   see monsterMarkerUnlocked() in app.js. Gating is purely a render/click
   concern here — it never touches the node/edge graph above. */
.map-monster.is-locked { opacity: 0.45; cursor: not-allowed; }
.map-monster.is-locked .map-monster-glyph { filter: grayscale(1); }
.map-monster.is-locked:hover .map-monster-glyph { filter: grayscale(1); }

/* Self-dismissing explanation shown when a locked monster marker is
   clicked — see showMapToast() in app.js. */
.map-toast {
  margin: 10px 0 0;
  padding: 8px 12px;
  background: var(--ink-800);
  border: 1px solid var(--gold-dim);
  color: var(--gold);
  border-radius: 6px;
  font-size: 12.5px;
  font-family: var(--font-mono);
}

/* --- Equipment (session 11) ----------------------------------------------- */
/* Equipped-weapon block on the Character screen — see renderEquipSection()
   in app.js. Sits below the existing affinity section, same bordered-
   section rhythm. */
.char-equip { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--ink-700); }
.char-equip-label { font-size: 13px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--paper-dim); margin-bottom: 8px; }
.char-equip-current { font-size: 18px; font-family: var(--font-mono); margin-bottom: 10px; }
.char-equip-picker { display: flex; gap: 8px; align-items: center; }
.char-equip-picker select {
  flex: 1;
  background: var(--ink-800);
  color: var(--paper);
  border: 1px solid var(--ink-600);
  border-radius: 6px;
  padding: 8px 10px;
  font-family: var(--font-ui);
  font-size: 13px;
}
/* Armor pass: the second (armor) equip-picker block reuses all of the
   above wholesale — just needs its own top margin so it doesn't crowd the
   weapon block right above it. */
.char-equip-picker + .char-equip-label { margin-top: 16px; }
.char-equip-defense { color: var(--paper-dim); font-size: 13px; font-family: var(--font-ui); }

/* --- Shop (session 11) ----------------------------------------------------- */
/* Currency is always a fresh server-side recomputation (never client
   state) — shopGold is just repainted from the /shop/items response each
   time the screen renders. */
.shop-gold {
  font-family: var(--font-mono);
  font-size: 16px;
  color: var(--gold);
  margin: -8px 0 20px;
}
.shop-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 14px;
}
.shop-card {
  background: var(--ink-800);
  border: 1px solid var(--ink-700);
  border-radius: 10px;
  padding: 16px 12px;
  text-align: center;
}
.shop-card-emoji { font-size: 34px; margin-bottom: 6px; }
.shop-card-name { font-weight: 600; font-size: 13.5px; margin-bottom: 4px; }
.shop-card-slot { font-size: 10.5px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--paper-dim); margin-bottom: 4px; }
.shop-card-price { font-family: var(--font-mono); font-size: 12px; color: var(--paper-dim); margin-bottom: 10px; }
.shop-card-buy { width: 100%; }
.shop-card-buy:disabled { opacity: 0.4; cursor: default; }
.shop-card-owned {
  display: inline-block;
  font-size: 11px;
  letter-spacing: 0.04em;
  padding: 5px 12px;
  border-radius: 999px;
  background: var(--good-dim);
  color: var(--good);
}