/* CRT Roguelike Styling */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

:root {
  --fg-wall: #555;
  --fg-floor: #2a7a2a;
  --fg-stairs: #ffff00;
  --fg-player: #ffffff;
  --fg-enemy: #ff4444;
  --fg-item: #44aaff;
  --fg-default: #33ff33;
  --fg-memory: #1a5c1a;
  --fg-message: #88cc88;
  --fg-border: #1a5c1a;
  --bg-game: #050505;
  --bg-body: #0a0a0a;
}

/* Biome palettes — bold colors for clear differentiation */
:root[data-biome="1"] {
  --fg-floor: #5a7a4a;
  --fg-wall: #3a5a2a;
  --fg-enemy: #ff4444;
}
:root[data-biome="2"] {
  --fg-floor: #2a7ab0;
  --fg-wall: #1a5a8a;
  --fg-enemy: #cc6600;
}
:root[data-biome="3"] {
  --fg-floor: #6a4a8a;
  --fg-wall: #4a2a6a;
  --fg-enemy: #aa44ff;
}
:root[data-biome="4"] {
  --fg-floor: #8a7a3a;
  --fg-wall: #6a5a1a;
  --fg-enemy: #ff8800;
}
:root[data-biome="5"] {
  --fg-floor: #d03030;
  --fg-wall: #9a2020;
  --fg-enemy: #ff2222;
}

/* Gate 76: per-area palette presets — each area gets distinct floor color */
:root[data-area="dungeon"] {
  --fg-floor: #5a7a4a;
  --fg-wall: #3a5a2a;
}
:root[data-area="forest"] {
  --fg-floor: #2a8a3a;
  --fg-wall: #1a6a2a;
}
:root[data-area="village"] {
  --fg-floor: #c4a45a;
  --fg-wall: #8a7a3a;
}
:root[data-area="mountain"] {
  --fg-floor: #8a8a9a;
  --fg-wall: #5a5a6a;
}
:root[data-area="meadow"] {
  --fg-floor: #b0c84a;
  --fg-wall: #6a7a2a;
}
:root[data-area="field"] {
  --fg-floor: #d4a830;
  --fg-wall: #9a7a20;
}

/* Combined area+biome selectors — higher specificity, override individual rules */
:root[data-area="dungeon"][data-biome="1"] { --fg-floor: rgb(90,122,74); }
:root[data-area="dungeon"][data-biome="2"] { --fg-floor: rgb(42,122,176); }
:root[data-area="dungeon"][data-biome="3"] { --fg-floor: rgb(106,74,138); }
:root[data-area="dungeon"][data-biome="4"] { --fg-floor: rgb(138,122,58); }
:root[data-area="dungeon"][data-biome="5"] { --fg-floor: rgb(208,48,48); }
:root[data-area="forest"][data-biome="1"] { --fg-floor: rgb(30,130,40); }
:root[data-area="forest"][data-biome="2"] { --fg-floor: rgb(42,170,90); }
:root[data-area="forest"][data-biome="3"] { --fg-floor: rgb(80,50,160); }
:root[data-area="forest"][data-biome="4"] { --fg-floor: rgb(30,160,130); }
:root[data-area="forest"][data-biome="5"] { --fg-floor: rgb(70,100,190); }
:root[data-area="village"][data-biome="1"] { --fg-floor: rgb(180,150,50); }
:root[data-area="village"][data-biome="2"] { --fg-floor: rgb(140,100,60); }
:root[data-area="village"][data-biome="3"] { --fg-floor: rgb(200,80,120); }
:root[data-area="village"][data-biome="4"] { --fg-floor: rgb(120,150,100); }
:root[data-area="village"][data-biome="5"] { --fg-floor: rgb(50,60,160); }
:root[data-area="mountain"][data-biome="1"] { --fg-floor: rgb(130,130,140); }
:root[data-area="mountain"][data-biome="2"] { --fg-floor: rgb(90,90,120); }
:root[data-area="mountain"][data-biome="3"] { --fg-floor: rgb(160,100,170); }
:root[data-area="mountain"][data-biome="4"] { --fg-floor: rgb(100,150,80); }
:root[data-area="mountain"][data-biome="5"] { --fg-floor: rgb(40,80,170); }
:root[data-area="meadow"][data-biome="1"] { --fg-floor: rgb(170,200,60); }
:root[data-area="meadow"][data-biome="2"] { --fg-floor: rgb(130,180,40); }
:root[data-area="meadow"][data-biome="3"] { --fg-floor: rgb(190,140,90); }
:root[data-area="meadow"][data-biome="4"] { --fg-floor: rgb(100,160,120); }
:root[data-area="meadow"][data-biome="5"] { --fg-floor: rgb(70,100,190); }
:root[data-area="field"][data-biome="1"] { --fg-floor: rgb(210,170,40); }
:root[data-area="field"][data-biome="2"] { --fg-floor: rgb(180,130,60); }
:root[data-area="field"][data-biome="3"] { --fg-floor: rgb(160,50,100); }
:root[data-area="field"][data-biome="4"] { --fg-floor: rgb(60,170,170); }
:root[data-area="field"][data-biome="5"] { --fg-floor: rgb(60,80,210); }

/* Gate 77: per-tile CSS colors for new v11 tile types */
.cell[data-tile="tree"]    { color: #228b22; }
.cell[data-tile="grass"]   { color: #7cfc00; }
.cell[data-tile="flower"]  { color: #ff69b4; }
.cell[data-tile="rock"]    { color: #808080; }
.cell[data-tile="cliff"]   { color: #a0522d; }
.cell[data-tile="building"]{ color: #cd853f; }
.cell[data-tile="house"]   { color: #da70d6; }
.cell[data-tile="wall-house"] { color: #bc8f8f; }
.cell[data-tile="crop"]    { color: #ffe135; }
.cell[data-tile="grain"]   { color: #f4a460; }
.cell[data-tile="wheat"]   { color: #fffacd; }

@keyframes crt-flicker {
  0% { opacity: 0.97; }
  5% { opacity: 0.95; }
  10% { opacity: 0.98; }
  15% { opacity: 0.96; }
  20% { opacity: 0.99; }
  50% { opacity: 0.97; }
  80% { opacity: 0.98; }
  90% { opacity: 0.95; }
  100% { opacity: 0.97; }
}

@keyframes text-glow-pulse {
  0%, 100% { text-shadow: 0 0 5px rgba(51, 255, 51, 0.3); }
  50% { text-shadow: 0 0 10px rgba(51, 255, 51, 0.6); }
}

@keyframes stair-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}

@keyframes potion-throb {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.3); }
}

@keyframes sword-glint {
  0%, 100% { text-shadow: none; }
  50% { text-shadow: 0 0 6px rgba(68, 170, 255, 0.8); }
}

@keyframes float-up {
  0% { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(-30px); }
}

@keyframes reveal-flash {
  0% { background-color: rgba(255,255,255,0.4); }
  100% { background-color: transparent; }
}

@keyframes screen-shake {
  0%, 100% { transform: translate(0, 0); }
  25% { transform: translate(-2px, 1px); }
  50% { transform: translate(2px, -1px); }
  75% { transform: translate(-1px, 2px); }
}

@keyframes boss-reveal-anim {
  0% { box-shadow: none; }
  50% { box-shadow: 0 0 60px rgba(255, 0, 0, 0.8) inset, 0 0 30px rgba(255, 0, 0, 0.5); }
  100% { box-shadow: none; }
}

body {
  background: var(--bg-body);
  color: var(--fg-default);
  font-family: 'Courier New', Courier, monospace;
  font-size: 14px;
  line-height: 1.2;
  overflow: hidden;
  min-height: 100vh;
  animation: crt-flicker 4s infinite;
}

body::after {
  content: '';
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  background: repeating-linear-gradient(
    0deg,
    rgba(0, 0, 0, 0.15) 0px,
    rgba(0, 0, 0, 0.15) 1px,
    transparent 1px,
    transparent 3px
  );
  z-index: 9999;
}

body::before {
  content: '';
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  background: radial-gradient(ellipse at center, transparent 60%, rgba(0, 0, 0, 0.5) 100%);
  z-index: 9998;
}

#game {
  display: block;
  padding: 4px;
  border: 2px solid var(--fg-border);
  margin: 8px auto;
  background: var(--bg-game);
  box-shadow: 0 0 20px rgba(51, 255, 51, 0.1);
}

#game.shake {
  animation: screen-shake 0.3s ease-out;
}

#game.boss-reveal {
  animation: boss-reveal-anim 1s ease-out;
}

#game[data-hp-state="hurt"] {
  filter: saturate(0.7);
}

#game[data-hp-state="critical"] {
  filter: hue-rotate(-30deg) saturate(1.4);
}

.row {
  white-space: pre;
  line-height: 1.1;
}

.cell {
  display: inline-block;
  width: 1ch;
  text-align: center;
  color: var(--fg-default);
}

.cell[data-tile="wall"] {
  color: var(--fg-wall);
}

.cell[data-tile="floor"] {
  color: var(--fg-floor);
}

.cell[data-tile="stairs-down"] {
  color: var(--fg-stairs);
  animation: stair-pulse 1.5s ease-in-out infinite;
}

.cell[data-tile="player"] {
  color: var(--fg-player);
  font-weight: bold;
  text-shadow: 0 0 8px rgba(255, 255, 255, 0.5);
}

.cell[data-tile="enemy"] {
  color: color-mix(in oklab, var(--fg-enemy) calc((1 - var(--wound, 0)) * 100%), #f4e3a1);
}

.cell[data-tile="item"] {
  color: var(--fg-item);
  animation: potion-throb 2s ease-in-out infinite;
}

.cell[data-visible="memory"] {
  color: var(--fg-memory);
  opacity: 0.6;
}

.cell[data-visible="false"] {
  color: transparent;
}

.cell.just-revealed, .cell[data-just-revealed="true"] {
  animation: reveal-flash 0.5s ease-out forwards;
}

.floater {
  position: absolute;
  font-weight: bold;
  font-size: 16px;
  pointer-events: none;
  animation: float-up 0.8s ease-out forwards;
  z-index: 9999;
}

.floater[data-kind="physical"] { color: #ff4444; }
.floater[data-kind="heal"] { color: #33ff33; }
.floater[data-kind="poison"] { color: #88cc00; }
.floater[data-kind="fire"] { color: #ff8800; }

.particle {
  position: absolute;
  font-size: 12px;
  pointer-events: none;
  animation: float-up 1.2s ease-out forwards;
  z-index: 9998;
}

.particle[data-fx="spark"] { color: #ffff00; }
.particle[data-fx="blood"] { color: #ff0000; }
.particle[data-fx="dust"] { color: #888866; }

.damage-popup {
  position: absolute;
  color: #ff4444;
  font-weight: bold;
  font-size: 16px;
  pointer-events: none;
  animation: popup-float 0.8s ease-out forwards;
  z-index: 9999;
}

@keyframes popup-float {
  0% { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(-30px); }
}

.hit-flash {
  animation: hit-flash-anim 0.2s ease-out;
}

@keyframes hit-flash-anim {
  0%, 100% { box-shadow: none; }
  50% { box-shadow: 0 0 30px rgba(255, 0, 0, 0.6) inset; }
}

#tutorial-banner {
  text-align: center;
  padding: 4px;
  font-size: 12px;
  color: var(--fg-message);
  border-bottom: 1px solid var(--fg-border);
  animation: text-glow-pulse 3s ease-in-out infinite;
  transition: opacity 0.5s ease;
}

#tutorial-banner.faded {
  opacity: 0;
  pointer-events: none;
  transition: none;
}

#status {
  text-align: center;
  padding: 4px;
  font-size: 13px;
  color: var(--fg-default);
  border-bottom: 1px solid var(--fg-border);
}

#messages {
  height: 60px;
  overflow: hidden;
  padding: 4px;
  font-size: 12px;
  color: var(--fg-message);
  border-bottom: 1px solid var(--fg-border);
}

#messages .message {
  white-space: nowrap;
}

#messages .message[data-severity="hit"] { color: #ff6666; }
#messages .message[data-severity="kill"] { color: #ffcc66; }
#messages .message[data-severity="heal"] { color: #66ff99; }
#messages .message[data-severity="descend"] { color: #66ccff; }
#messages .message[data-severity="info"] { color: #88cc88; }

#inventory {
  padding: 4px;
  font-size: 12px;
  color: var(--fg-message);
  height: 30px;
}

#inventory .item {
  display: inline-block;
  margin-right: 16px;
}

#overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.85);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

#overlay h1 {
  color: #ff4444;
  font-size: 36px;
  text-shadow: 0 0 20px rgba(255, 68, 68, 0.5);
}

#help-overlay {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: rgba(5, 5, 5, 0.95);
  border: 2px solid var(--fg-default);
  padding: 16px 24px;
  max-width: 520px;
  width: 90%;
  z-index: 5000;
  color: var(--fg-default);
  font-family: 'Courier New', monospace;
  font-size: 13px;
  line-height: 1.5;
  box-shadow: 0 0 30px rgba(51, 255, 51, 0.3);
}

#help-overlay h2 {
  color: var(--fg-stairs);
  margin-bottom: 8px;
  font-size: 16px;
  text-shadow: 0 0 10px rgba(255, 255, 0, 0.4);
}

#help-overlay hr {
  border: none;
  border-top: 1px solid var(--fg-border);
  margin: 8px 0;
}

#help-overlay p {
  margin: 4px 0;
}

#descent-overlay {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 32px;
  color: #ffff00;
  text-shadow: 0 0 20px rgba(255, 255, 0, 0.6);
  z-index: 8000;
  opacity: 0;
  transition: opacity 0.3s ease;
  pointer-events: none;
}

#descent-overlay[data-active="true"] {
  opacity: 1;
}

#inventory-overlay {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: rgba(5, 5, 5, 0.95);
  border: 2px solid var(--fg-default);
  padding: 16px 24px;
  min-width: 300px;
  width: 80%;
  max-width: 500px;
  min-height: 150px;
  z-index: 5000;
  color: var(--fg-default);
  font-family: 'Courier New', monospace;
  font-size: 13px;
  line-height: 1.6;
  box-shadow: 0 0 30px rgba(51, 255, 51, 0.3);
  display: none;
  flex-direction: column;
}

#inventory-overlay h2 {
  color: var(--fg-stairs);
  margin-bottom: 8px;
  font-size: 16px;
  text-shadow: 0 0 10px rgba(255, 255, 0, 0.4);
}

#inventory-list {
  flex: 1;
}

.inv-item {
  padding: 2px 8px;
  cursor: default;
}

.inv-item.selected {
  background: rgba(51, 255, 51, 0.15);
  color: #ffffff;
  font-weight: bold;
}

.inventory-hint {
  margin-top: 8px;
  font-size: 11px;
  color: var(--fg-memory);
  text-align: center;
}

.empty-inventory {
  color: var(--fg-memory);
  font-style: italic;
  text-align: center;
  padding: 20px;
}

/* Marks-overlay: tiny chooser for player-droppable marks (m key). */
#marks-overlay {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: rgba(5, 5, 5, 0.95);
  border: 2px solid var(--fg-default);
  padding: 16px 24px;
  min-width: 240px;
  z-index: 5000;
  color: var(--fg-default);
  font-family: 'Courier New', monospace;
  font-size: 13px;
  line-height: 1.5;
  box-shadow: 0 0 30px rgba(51, 255, 51, 0.3);
}

#marks-overlay h2 {
  color: var(--fg-stairs);
  margin-bottom: 8px;
  font-size: 16px;
  text-shadow: 0 0 10px rgba(255, 255, 0, 0.4);
  text-align: center;
}

#marks-overlay .marks-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 10px 0;
}

#marks-overlay .mark-btn {
  background: rgba(51, 255, 51, 0.08);
  border: 1px solid var(--fg-border);
  color: var(--fg-default);
  font-family: 'Courier New', monospace;
  font-size: 13px;
  padding: 6px 10px;
  cursor: pointer;
  text-align: left;
}

#marks-overlay .mark-btn:hover,
#marks-overlay .mark-btn:focus {
  background: rgba(51, 255, 51, 0.18);
  color: #ffffff;
  outline: none;
}

#marks-overlay .mark-btn .glyph {
  display: inline-block;
  width: 1.5em;
  color: var(--fg-stairs);
  font-weight: bold;
}

#marks-overlay .marks-hint {
  margin-top: 8px;
  font-size: 11px;
  color: var(--fg-memory);
  text-align: center;
}

/* Mark glyph rendered on the map (cairn, sign, bones, blood).
   Combined into one selector — separate attribute-selector rules were
   triggering style-invalidation churn on every cell render and slowing
   the typewriter loop in tests/40_typed_messages.spec.js. */
.cell[data-tile="mark"] { color: var(--fg-stairs); opacity: 0.85; }
.mark-glyph-bones { color: #cccccc; }
.mark-glyph-blood { color: #cc3333; }
.mark-glyph-cairn { color: #c4a45a; }
.mark-glyph-sign  { color: #66ccff; }
/* -------- Touch / mobile mode -------- */

/* Help-overlay button that lets desktop users preview the touch UI */
.help-toggle-btn {
  display: inline-block;
  background: rgba(51, 255, 51, 0.1);
  color: var(--fg-default);
  border: 1px solid var(--fg-default);
  font-family: inherit;
  font-size: 12px;
  padding: 6px 14px;
  cursor: pointer;
  letter-spacing: 1px;
  text-transform: uppercase;
}
.help-toggle-btn:hover,
.help-toggle-btn:focus {
  background: rgba(51, 255, 51, 0.25);
  outline: 1px solid var(--fg-stairs);
}

/* Default: hide touch controls entirely. Both attributes act as gates so
   a desktop user never sees the d-pad unless they opt in. */
#touch-controls {
  display: none;
}

/* ─── Mobile UX redesign (v15) ───────────────────────────────────────────
   Concept B from docs/superpowers/specs/2026-05-26-mobile-ux-redesign.md:
   translucent d-pad + corner actions overlaid on the grid. The grid IS
   the screen; the four legacy info panels are hidden; messages and damage
   floaters bubble up over the grid; status is a thin pinned strip.

   All rules below are gated by either
     body[data-input-mode="touch"]    (the JS-set attribute)
     @media (max-width: 767px)        (small viewports — phones)
   so the desktop layout stays byte-identical. */

body[data-input-mode="touch"] #touch-controls {
  display: flex;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 8px calc(env(safe-area-inset-right, 0px) + 10px)
           calc(env(safe-area-inset-bottom, 0px) + 10px)
           calc(env(safe-area-inset-left, 0px) + 10px);
  z-index: 6000;
  pointer-events: none; /* let children opt back in */
  justify-content: space-between;
  align-items: flex-end;
  gap: 12px;
}

body[data-input-mode="touch"] #touch-dpad,
body[data-input-mode="touch"] #touch-actions {
  pointer-events: auto;
}

/* When any modal overlay is open, hide the touch controls so they don't
   visually clutter the overlay (and so users can't tap movement keys
   under a help dialog). Functionally `touchAction()` already blocks
   inputs when an overlay is open — this is the visual half of that.

   Detection: JS sets style.display to 'block' / 'flex' when opening an
   overlay; the initial markup has `style="display:none;"`. The :has()
   selector below matches the OPEN states (block / flex / grid) without
   touching the inline `display:none` formatting. Requires iOS Safari
   15.4+ — older devices fall back to the v14 behaviour where the d-pad
   is just visible on top of the overlay (a minor cosmetic issue, not
   functional, since touchAction already blocks input through the
   overlay). */
body[data-input-mode="touch"]:has(#help-overlay[style*="display: block"]) #touch-controls,
body[data-input-mode="touch"]:has(#inventory-overlay[style*="display: flex"]) #touch-controls,
body[data-input-mode="touch"]:has(#marks-overlay[style*="display: block"]) #touch-controls,
body[data-input-mode="touch"]:has(#overlay[style*="display: flex"]) #touch-controls {
  display: none;
}

/* Also hide the pinned status strip and tutorial banner under an open
   overlay — they'd otherwise float ABOVE the overlay since they're
   z-index 5500 / 5400 vs the overlay's 5000. Same :has() pattern. */
body[data-input-mode="touch"]:has(#help-overlay[style*="display: block"]) #status,
body[data-input-mode="touch"]:has(#inventory-overlay[style*="display: flex"]) #status,
body[data-input-mode="touch"]:has(#marks-overlay[style*="display: block"]) #status,
body[data-input-mode="touch"]:has(#overlay[style*="display: flex"]) #status,
body[data-input-mode="touch"]:has(#help-overlay[style*="display: block"]) #tutorial-banner,
body[data-input-mode="touch"]:has(#inventory-overlay[style*="display: flex"]) #tutorial-banner,
body[data-input-mode="touch"]:has(#marks-overlay[style*="display: block"]) #tutorial-banner,
body[data-input-mode="touch"]:has(#overlay[style*="display: flex"]) #tutorial-banner {
  display: none;
}

/* 3x3 grid d-pad with cross-shaped button placement.
   Center cell acts as the "wait" button. Diagonals stay empty for
   visual breathing room and to avoid mis-taps.

   v15 mobile redesign: translucent + smaller (48px cells, was 56px). The
   d-pad overlays the bottom-left of the grid — players can see the floor
   tiles through it. Opacity ramps up briefly on press so the press
   feedback is unambiguous. */
#touch-dpad {
  display: grid;
  grid-template-columns: repeat(3, 48px);
  grid-template-rows: repeat(3, 48px);
  gap: 3px;
  background: rgba(5, 5, 5, 0.40);
  border: 1px solid rgba(51, 255, 51, 0.35);
  border-radius: 10px;
  padding: 5px;
  box-shadow: 0 0 14px rgba(0, 0, 0, 0.5);
  /* Disable double-tap zoom on iOS */
  touch-action: manipulation;
  -webkit-user-select: none;
  user-select: none;
  opacity: 0.78;
  transition: opacity 0.15s ease-out;
}
#touch-dpad:active,
#touch-dpad:focus-within,
#touch-dpad:hover {
  opacity: 1;
}

.dpad-btn {
  min-width: 44px;
  min-height: 44px;
  width: 100%;
  height: 100%;
  background: rgba(20, 60, 20, 0.55);
  color: var(--fg-default);
  border: 1px solid var(--fg-border);
  font-family: 'Courier New', Courier, monospace;
  font-size: 22px;
  font-weight: bold;
  cursor: pointer;
  padding: 0;
  border-radius: 6px;
  touch-action: manipulation;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  text-shadow: 0 0 6px rgba(51, 255, 51, 0.4);
}
.dpad-btn:active,
.dpad-btn[data-pressed="true"] {
  background: rgba(80, 200, 80, 0.65);
  color: #ffffff;
  box-shadow: 0 0 12px rgba(80, 200, 80, 0.6) inset;
}

.dpad-up    { grid-column: 2; grid-row: 1; }
.dpad-left  { grid-column: 1; grid-row: 2; }
.dpad-wait  { grid-column: 2; grid-row: 2; opacity: 0.85; }
.dpad-right { grid-column: 3; grid-row: 2; }
.dpad-down  { grid-column: 2; grid-row: 3; }
.dpad-wait  { font-size: 30px; line-height: 0.5; }

/* v15: actions stack vertically in the bottom-right corner. They use
   the same translucent-on-grid pattern as the d-pad — players see the
   floor through them, opacity ramps up on press/hover. */
#touch-actions {
  display: flex;
  flex-direction: column-reverse;
  align-items: flex-end;
  gap: 8px;
  opacity: 0.78;
  transition: opacity 0.15s ease-out;
}
#touch-actions:active,
#touch-actions:focus-within,
#touch-actions:hover {
  opacity: 1;
}

.touch-action {
  min-width: 48px;
  min-height: 48px;
  width: 48px;
  height: 48px;
  padding: 0;
  background: rgba(5, 5, 5, 0.50);
  color: var(--fg-default);
  border: 1px solid rgba(51, 255, 51, 0.45);
  font-family: 'Courier New', Courier, monospace;
  font-size: 22px;
  font-weight: bold;
  cursor: pointer;
  border-radius: 50%;
  touch-action: manipulation;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  text-shadow: 0 0 6px currentColor;
  display: flex;
  align-items: center;
  justify-content: center;
}
.touch-action:active {
  background: rgba(80, 200, 80, 0.65);
  color: #ffffff;
  box-shadow: 0 0 12px rgba(80, 200, 80, 0.6) inset;
}
.touch-action[hidden] { display: none; }

/* Potion stands out — it's the most-tapped utility action. */
.touch-action-potion {
  background: rgba(60, 30, 30, 0.65);
  color: var(--fg-item);
  border-color: var(--fg-item);
  min-width: 56px;
  min-height: 56px;
  width: 56px;
  height: 56px;
  font-size: 28px;
}
.touch-action-descend {
  color: var(--fg-stairs);
  border-color: var(--fg-stairs);
}

/* ─── Touch-mode layout: maximise the grid ─────────────────────────────
   The grid renders edge-to-edge. The d-pad and action buttons float on
   top. The four legacy info panels (messages/inventory/players) are
   hidden — their content surfaces via the existing overlay system (i,
   ?, m) and via floater popups inside the grid.

   Status remains visible as a thin top-pinned strip; the renderStatus
   call site is untouched (Bug-1 agent owns it) — only the visual
   container changes. */

body[data-input-mode="touch"] {
  /* Use 100dvh so iOS Safari's URL-bar collapse doesn't jiggle the
     layout. Fall back to 100vh on older browsers — graceful clip. */
  min-height: 100dvh;
  padding:
    env(safe-area-inset-top, 0px)
    env(safe-area-inset-right, 0px)
    env(safe-area-inset-bottom, 0px)
    env(safe-area-inset-left, 0px);
  overflow: hidden;
  /* Prevent the iOS "look up / share" callout on long-press over the
     ASCII glyphs. */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

/* Pin the status strip to the top of the viewport. The DOM content
   inside (HP / Level / Turn etc.) is rendered by src/ui.js renderStatus
   which we do not touch — Bug-1 agent owns that path. */
body[data-input-mode="touch"] #status {
  position: fixed;
  top: env(safe-area-inset-top, 0px);
  left: 0;
  right: 0;
  z-index: 5500;
  padding: 6px 10px;
  font-size: 12px;
  background: rgba(5, 5, 5, 0.78);
  border-bottom: 1px solid var(--fg-border);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Tutorial banner floats over the top status strip on first boot, then
   fades out (existing JS adds `.faded` after the first turn). */
body[data-input-mode="touch"] #tutorial-banner {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 30px);
  left: 6px;
  right: 6px;
  z-index: 5400;
  font-size: 10px;
  padding: 4px 6px;
  background: rgba(5, 5, 5, 0.7);
  border: 1px solid var(--fg-border);
  border-radius: 4px;
  pointer-events: none;
}

/* Hide the legacy info panels. Their render code keeps running (so we
   don't disturb the data flow), but the DOM is visually removed. The
   `#messages` typewriter, `#inventory` flow-row, and `#players` peer
   list are surfaced elsewhere:
     - messages: in-grid floaters + the existing addMessage path
     - inventory: full-screen overlay opened via the (i) action button
     - players: visible as `&` glyphs on the grid + count in status (TBD) */
body[data-input-mode="touch"] #messages,
body[data-input-mode="touch"] #inventory,
body[data-input-mode="touch"] #players {
  display: none;
}

/* Game grid: full-bleed, vertically centered, position:relative so the
   damage-popup / floater absolute-positioning still anchors correctly. */
body[data-input-mode="touch"] #game {
  margin: 0;
  padding: 6px;
  border: none;
  box-shadow: none;
  background: var(--bg-game);
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 30px);
  left: env(safe-area-inset-left, 0px);
  right: env(safe-area-inset-right, 0px);
  bottom: env(safe-area-inset-bottom, 0px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  overflow: hidden;
  cursor: default;
  z-index: 100;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
body[data-input-mode="touch"] .cell {
  font-weight: bold;
}
body[data-input-mode="touch"] .cell[data-tile="player"] {
  text-shadow: 0 0 10px rgba(255, 255, 255, 0.85);
}

/* Phone-portrait layout overrides: scale the grid font to fit 40 cols in
   the viewport width with a small margin. clamp() so it doesn't go too
   small on narrow devices or too large on tablets. */
@media (max-width: 767px) {
  body {
    font-size: 12px;
  }
  #game {
    font-size: clamp(11px, 2.3vw, 18px);
    line-height: 1;
    margin: 4px auto;
    padding: 2px;
    max-width: 100vw;
    overflow: hidden;
  }
  .row {
    line-height: 1;
    font-size: inherit;
  }
  .cell {
    width: 1ch;
    font-size: inherit;
  }
  #tutorial-banner { font-size: 10px; padding: 2px; }
  #status         { font-size: 12px; padding: 4px; }
  #messages       { font-size: 11px; height: 48px; padding: 2px; }
  #inventory      { font-size: 11px; padding: 2px; height: 24px; }
  #players        { font-size: 11px; padding: 2px; }
}

/* Stack overlays full-screen on mobile: inventory, help, marks. Their
   desktop styles use a small centered modal; on mobile we want them to
   fill the screen so they're usable with a thumb. */
@media (max-width: 767px) {
  #help-overlay {
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    transform: none;
    width: auto;
    max-width: none;
    height: 100dvh;
    border-width: 0;
    padding: calc(env(safe-area-inset-top, 0px) + 16px) 16px
             calc(env(safe-area-inset-bottom, 0px) + 16px) 16px;
    overflow-y: auto;
    font-size: 13px;
    line-height: 1.5;
  }
  #inventory-overlay {
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    transform: none;
    width: auto;
    max-width: none;
    height: 100dvh;
    border-width: 0;
    padding: calc(env(safe-area-inset-top, 0px) + 24px) 16px
             calc(env(safe-area-inset-bottom, 0px) + 24px) 16px;
    font-size: 14px;
  }
  #marks-overlay {
    top: 50%;
    left: 50%;
    width: auto;
    max-width: 280px;
    transform: translate(-50%, -50%);
  }
}

/* Landscape phone: rotate the d-pad to the left edge, actions to the
   right edge, sandwich the grid between them. The grid takes the full
   remaining horizontal slice. */
@media (max-width: 900px) and (orientation: landscape) {
  body[data-input-mode="touch"] #touch-controls {
    align-items: center;
    padding-bottom: env(safe-area-inset-bottom, 8px);
    padding-top: env(safe-area-inset-top, 8px);
  }
  body[data-input-mode="touch"] #status {
    /* Move status to top-left corner only; don't span full width so it
       doesn't fight the landscape d-pad. */
    width: auto;
    right: auto;
    padding: 4px 8px;
    border-bottom-right-radius: 6px;
    border-right: 1px solid var(--fg-border);
  }
}
