/* Ported from the original React app's index.css + App.css, plus a WCAG/mobile pass:
   AA-contrast chrome text, visible focus rings, prefers-reduced-motion, and the touch
   D-pad/action-bar for coarse-pointer (touch) devices. See the bottom of this file.

   Chrome colors below read from CSS custom properties (--rouge-*), set inline on .app by
   GameShell from the user's chosen theme (ThemeResolver-resolved, so --rouge-highlight is
   always guaranteed visible against --rouge-bg/--rouge-bg-panel). The literal hex fallback
   after each var(...) is the untouched default theme and is also what renders before the
   settings service finishes loading.

   Game-semantic colors (HP/hunger status, gold, drop/warning/error/notice, win/death outcome)
   keep their meaning across themes — red is always red — but read their value from --rouge-good
   /-caution/-warn/-danger/-gold/-magic, which hold that same hue at a lightness resolved against
   the current panel. Fixed literals were unreadable once light themes existed. Monster and item
   glyphs on the map are the exception: those are per-entity identity, so they keep their own
   color and are only nudged if they would vanish into the floor (see GameMapView). */

*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html, body {
  background: var(--rouge-bg, #000);
  color: var(--rouge-fg, #ccc);
  font-family: 'Courier New', Courier, monospace;
  overflow: hidden;
  height: 100%;
}

/* ===== Layout ===== */
.app {
  width: 100vw;
  height: 100vh;
  background: var(--rouge-bg, #000);
  display: flex;
  font-family: 'Courier New', Courier, monospace;
  overflow: auto;
  /* The Android WebView is laid out edge to edge behind the system status and navigation bars
     and does report real insets here (35px/48px on the test device), so without this the top of
     the status bar sits under the clock and the bottom row of buttons under the nav bar.
     box-sizing: border-box (set globally above) keeps this inside the 100vh. */
  padding-top: env(safe-area-inset-top, 0px);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  padding-left: env(safe-area-inset-left, 0px);
  padding-right: env(safe-area-inset-right, 0px);
}

/* Centered via auto margins on the child rather than justify-content/align-items on .app.
   Flex centering distributes overflow to *both* sides, and overflow past the start edge is
   unreachable by scrolling — on a phone that permanently hid the left of the map and the HP
   readout. Auto margins center the same way but collapse to 0 on the overflowing axis, so
   whatever doesn't fit stays scrollable. */
.app > .title-screen,
.app > .game-container,
.app > .gameover-screen {
  margin: auto;
}

.game-container {
  display: flex;
  flex-direction: column;
  gap: 0;
  border: 1px solid var(--rouge-border, #444);
  background: var(--rouge-bg-panel, #0a0a0a);
}

/* ===== Map ===== */
/* .map-wrapper is the fixed frame (the inventory overlay positions against it); .map-viewport
   inside it is what actually scrolls. Keeping them separate means the overlay covers the
   visible window rather than the full width of the scrolled-away map. */
.map-wrapper {
  position: relative;
}

.map-viewport {
  overflow: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  /* Drag with a finger to look around explored ground; the browser's own panning handles it,
     so there is no custom drag handling to conflict with taps on the map. */
  touch-action: pan-x pan-y;
}

/* One knob for the whole grid. Cells are sized in em off this, so the map scales by changing
   this single value; the narrow-viewport override below is what makes 80 columns fit a phone. */
.game-map {
  display: block;
  line-height: 1;
  background: var(--rouge-bg, #000);
  padding: 2px 0;
  user-select: none;
  font-size: 14px;
}

.map-row {
  display: flex;
  white-space: pre;
}

.map-cell {
  /* flex: 0 0 auto is load-bearing. As flex items these cells default to flex-shrink: 1, so
     once the container is narrower than the grid (i.e. on any phone) all 80 columns squeeze
     down to fit — which both clipped every glyph against its box and stopped the map ever
     overflowing, so it could not be panned. The grid must keep its natural size and scroll. */
  flex: 0 0 auto;
  display: inline-block;
  /* 1ch is exactly one advance width of the rendered monospace font, so a centred glyph fills
     its cell without being cut off — 0.62em was an assumption about Courier New's metrics that
     doesn't hold for whatever monospace face the platform actually substitutes. */
  width: 1ch;
  height: 1.25em;
  text-align: center;
  font-size: inherit;
  line-height: 1.25;
  font-family: 'Courier New', Courier, monospace;
}

/* ===== Pixel Art mode (pixel-art-phase.md) =====
   --rouge-cell is the single sizing knob for square cells, defined on .map-wrapper so both the
   width cap below and .map-cell's own size (inherited down through .map-viewport/.game-map) read
   the same value. Session 1 keeps drawing the ASCII glyphs — only the geometry changes; the
   sprite atlas and the rule that hides the glyph behind it land in a later session. Only integer
   scale ratios of the 32x32 source art are permitted (pixel-art-phase.md §4), so this value must
   stay a multiple that divides evenly into 32 if it's ever changed. */
.map-wrapper.pixel-mode {
  --rouge-cell: 16px;
}

.map-wrapper.pixel-mode .map-cell {
  width: var(--rouge-cell);
  height: var(--rouge-cell);
  line-height: var(--rouge-cell);
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}

/* Desktop-only width cap. Without it, 80 columns at 16px is 1,280px — wider than most desktop
   windows — and .game-container (sized by its content) drags the status bar and log wide with
   it: the exact failure the narrow-viewport block exists to prevent, resurfacing outside it.
   Scoped to the complement of that block's own query so the two can never fight over width. */
@media (pointer: fine) and (min-width: 761px) {
  .map-wrapper.pixel-mode {
    max-width: calc(80 * var(--rouge-cell));
    max-height: calc(22 * var(--rouge-cell));
  }
}

/* ===== Status Bar ===== */
.status-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  padding: 4px 8px;
  background: var(--rouge-bg-panel, #111);
  border-bottom: 1px solid var(--rouge-border, #333);
  font-size: 13px;
  color: var(--rouge-fg, #ccc);
}

/* The same stat spans are rendered in two places — the bar here, and the columns either side of
   the touch D-pad — so their colours are keyed off both containers. */
.status-bar .label,
.touch-stats .label {
  color: var(--rouge-fg-muted, #888);
}

.status-bar .value,
.touch-stats .value {
  color: var(--rouge-fg, #eee);
}

/* Not a box of its own: the stat spans inside it stay direct flex items of .status-bar, so they
   wrap across the bar exactly as they did before they were grouped. The group exists only so the
   touch layout can hide all of them in one rule. */
.status-stats {
  display: contents;
}

.status-equip {
  display: flex;
  gap: 12px;
  color: var(--rouge-fg-muted, #aaa);
  font-size: 0.85em;
}

/* Only in the DOM while a monster trait is actually on the player (frozen, held, confused), so
   it is styled to be noticed rather than to blend into the bar it shares. Its own full-width row:
   it appears and disappears mid-run, and letting it reflow the equipment line beside it would
   shift the whole bar under the player's eye at the worst possible moment. */
.status-afflictions {
  flex-basis: 100%;
  display: flex;
  gap: 8px;
  font-size: 0.85em;
}

.status-affliction {
  /* --rouge-warn is the theme's "this is going wrong but you are not dead" hue, resolved against
     the panel these sit on, exactly as the hunger and HP labels do it. */
  color: var(--rouge-warn, #ff8844);
  font-weight: bold;
}

/* ===== Message Log ===== */
/* A fixed-height scroll window over the full retained log, so it can be dragged through with a
   finger and — unlike a panel that grows with its contents — never changes the height of the
   layout around it. */
.message-log {
  padding: 4px 8px;
  background: var(--rouge-bg-panel, #0d0d0d);
  border-top: 1px solid var(--rouge-border, #333);
  height: 5.5em;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  font-size: 13px;
}

.message {
  color: var(--rouge-fg, #ccc);
  line-height: 1.4;
}

/* ===== Action bar ===== */
.action-bar {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 4px 8px;
  background: var(--rouge-bg-panel, #0a0a0a);
  border-top: 1px solid var(--rouge-border, #222);
}

/* ===== Inventory Overlay ===== */
.inventory-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.85);
  z-index: 10;
}

.inventory-box {
  background: var(--rouge-bg-panel, #111);
  border: 1px solid var(--rouge-border, #666);
  padding: 16px 20px;
  min-width: 400px;
  max-width: 600px;
  max-height: 80%;
  overflow-y: auto;
  font-size: 13px;
}

.inventory-title {
  color: var(--rouge-highlight, #ffdd88);
  text-align: center;
  margin-bottom: 12px;
  font-size: 15px;
}

.inventory-empty {
  color: var(--rouge-fg-muted, #888);
  text-align: center;
  padding: 12px 0;
}

.inventory-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.inventory-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 2px 0;
}

.inv-letter {
  color: var(--rouge-fg-muted, #888);
  min-width: 24px;
}

.inv-actions {
  margin-left: auto;
  display: flex;
  gap: 6px;
}

.inv-btn {
  background: var(--rouge-bg-panel, #222);
  border: 1px solid var(--rouge-border, #555);
  color: var(--rouge-fg, #ccc);
  padding: 2px 8px;
  font-size: 11px;
  cursor: pointer;
  font-family: 'Courier New', Courier, monospace;
}

.inv-btn:hover {
  background: var(--rouge-border, #333);
  border-color: var(--rouge-highlight, #888);
}

/* Always the warning red, never the chosen accent — "drop" is destructive. The hue is fixed;
   only its lightness follows the theme, so it stays readable on a light one. */
.inv-drop {
  border-color: var(--rouge-border, #664444);
  color: var(--rouge-danger, #cc8888);
}

.inv-drop:hover {
  background: #2a1111;
}

/* ===== Merchant ===== */
.shop-purse {
  text-align: center;
  color: var(--rouge-fg-muted, #888);
  font-size: 12px;
  margin-bottom: 10px;
}

/* The price is the button, so what it costs and how to buy it are one thing to look at — and one
   thing to hit with a thumb. */
.shop-buy {
  min-width: 5.5em;
  text-align: center;
}

.shop-buy:disabled {
  opacity: 0.45;
  cursor: default;
}

.shop-buy:disabled:hover {
  background: var(--rouge-bg-panel, #222);
  border-color: var(--rouge-border, #555);
}

/* The sell list, only rendered when Settings -> Gameplay -> Sell to merchants is on. Its own
   titled block with a rule above it: the two lists in this dialog carry near-identical rows whose
   buttons move gold in opposite directions, and telling them apart at a glance matters more than
   the few pixels the separation costs. */
.shop-sell {
  margin-top: 14px;
  padding-top: 10px;
  border-top: 1px solid var(--rouge-border, #333);
}

.shop-sell .inventory-title {
  font-size: 12px;
  color: var(--rouge-fg-muted, #888);
}

.shop-sell-btn {
  min-width: 5.5em;
  text-align: center;
}

.inventory-hint {
  margin-top: 12px;
  text-align: center;
  color: var(--rouge-fg-muted, #999);
  font-size: 11px;
}

/* ===== Save / Load Menu ===== */
.saveload-overlay {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.85);
  z-index: 20;
}

.saveload-box {
  background: var(--rouge-bg-panel, #111);
  border: 1px solid var(--rouge-border, #666);
  padding: 16px 20px;
  min-width: 440px;
  max-width: 640px;
  max-height: 85vh;
  overflow-y: auto;
  font-size: 13px;
  color: var(--rouge-fg, #ccc);
}

.saveload-title {
  color: var(--rouge-highlight, #ffdd88);
  text-align: center;
  margin-bottom: 14px;
  font-size: 15px;
}

/* The settings dialog pairs its title with a version line; the wrapper carries the title's
   usual bottom margin so the two read as one heading block. */
.settings-header {
  margin-bottom: 14px;
}

.settings-header .saveload-title {
  margin-bottom: 2px;
}

.settings-version {
  color: var(--rouge-fg-muted, #666);
  text-align: center;
  font-size: 11px;
  letter-spacing: 1px;
}

.saveload-form {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 16px;
}

.saveload-label {
  color: var(--rouge-fg-muted, #888);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1px;
}

.saveload-row {
  display: flex;
  gap: 8px;
}

.saveload-input {
  flex: 1;
  background: var(--rouge-bg, #000);
  border: 1px solid var(--rouge-border, #555);
  color: var(--rouge-fg, #eee);
  padding: 6px 8px;
  font-family: 'Courier New', Courier, monospace;
  font-size: 13px;
}

.saveload-input:focus {
  outline: none;
  border-color: var(--rouge-highlight, #ffdd88);
}

.saveload-confirm {
  padding: 2px 14px;
}

.saveload-listtitle {
  color: var(--rouge-fg-muted, #999);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1px;
  margin-bottom: 6px;
}

.saveload-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.saveload-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 6px 0;
  border-bottom: 1px solid #1e1e1e;
}

.saveload-item-main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.saveload-name {
  color: var(--rouge-fg, #eee);
  overflow-wrap: anywhere;
}

.saveload-meta {
  color: var(--rouge-fg-muted, #888);
  font-size: 11px;
}

.saveload-date {
  color: var(--rouge-fg-muted, #999);
  font-size: 11px;
}

/* Warning/error/notice keep their universally recognizable hues rather than following the
   accent — the theme only decides how light or dark that hue has to be to stay readable. */
.saveload-warn {
  color: var(--rouge-warn, #ffbb55);
  font-size: 12px;
}

.saveload-error {
  color: var(--rouge-danger, #ff6666);
  font-size: 12px;
  margin-top: 10px;
}

.saveload-notice {
  color: var(--rouge-good, #88aa88);
  font-size: 12px;
  margin-top: 10px;
}

.saveload-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 16px;
}

.saveload-footer .inventory-hint {
  margin-top: 0;
}

/* Two panels in one dialog — the play screen has a single "Save / Load" button. */
.saveload-tabs {
  display: flex;
  gap: 6px;
  margin-bottom: 14px;
}

.saveload-tab[aria-selected="true"] {
  border-color: var(--rouge-highlight, #ffdd88);
  color: var(--rouge-highlight, #ffdd88);
  background: var(--rouge-highlight-hover, #1a1a00);
}

/* ===== Settings menu ===== */
.settings-section {
  margin-bottom: 16px;
}

.settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 0;
}

/* A scrap of dungeon inside the theme preview — the half of a theme that is hidden behind this
   dialog while it is open. Monospace so the cells line up like the real map's do. */
.settings-preview-map {
  margin-top: 6px;
  padding: 4px 6px;
  font-family: 'Courier New', Courier, monospace;
  font-size: 14px;
  letter-spacing: 0.15em;
  align-self: flex-start;
}

.settings-preview {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 12px;
  margin-top: 8px;
  border-radius: 2px;
}

.settings-preview-title {
  color: var(--preview-highlight, var(--rouge-highlight, #ffdd88));
  font-size: 14px;
}

.settings-preview-body {
  font-size: 13px;
}

/* ===== Monster dictionary, potion library, scroll library =====
   One set of rules for all three: the bestiary and the two item libraries are the
   same disclosure list over different data, and styling them apart would only mean
   they could drift apart. */
.monster-dictionary,
.item-library {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 8px;
}

.monster-dict-summary,
.item-dict-summary {
  color: var(--rouge-fg-muted, #888);
  font-size: 12px;
  padding-bottom: 4px;
}

/* Why the list is longer than any one run's cast, and what "tier" on each row means. Sits above
   the rows rather than in a tooltip: it is the thing that stops the sealed rows reading as a
   checklist the player is failing to complete. */
.monster-dict-note,
.item-dict-note {
  color: var(--rouge-fg-muted, #777);
  font-size: 11px;
  line-height: 1.45;
  padding-bottom: 8px;
}

.monster-dict-entry,
.item-dict-entry {
  border: 1px solid var(--rouge-border, #333);
  background: var(--rouge-bg, #0a0a0a);
}

.monster-dict-entry > summary,
.monster-dict-unknown,
.item-dict-entry > summary,
.item-dict-unknown {
  display: flex;
  align-items: center;
  gap: 10px;
  /* 44px of touch target, the same floor the D-pad buttons hold to. */
  min-height: 44px;
  padding: 6px 10px;
  cursor: pointer;
}

.monster-dict-unknown,
.item-dict-unknown {
  cursor: default;
  color: var(--rouge-fg-muted, #666);
  font-style: italic;
}

.monster-dict-glyph,
.item-dict-glyph {
  font-family: 'Courier New', Courier, monospace;
  font-size: 16px;
  min-width: 1.5em;
  text-align: center;
}

.monster-dict-name,
.item-dict-name {
  color: var(--rouge-fg, #eee);
}

.monster-dict-unknown .monster-dict-name,
.item-dict-unknown .item-dict-glyph,
.item-dict-unknown .item-dict-name {
  color: inherit;
}

.monster-dict-depth,
.item-dict-depth {
  margin-left: auto;
  color: var(--rouge-fg-muted, #888);
  font-size: 11px;
}

.monster-dict-body,
.item-dict-body {
  padding: 4px 10px 10px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  line-height: 1.45;
}

.monster-dict-desc,
.item-dict-desc {
  color: var(--rouge-fg, #ccc);
}

/* Stat pairs wrap rather than sitting in fixed columns, so the block reflows to a phone instead
   of forcing the dialog wider than the screen. */
.monster-dict-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
}

.monster-dict-stats > div {
  display: flex;
  gap: 6px;
}

.monster-dict-stats dt {
  color: var(--rouge-fg-muted, #888);
}

.monster-dict-heading,
.item-dict-heading {
  color: var(--rouge-highlight, #ffdd88);
  font-size: 12px;
}

.monster-dict-abilities,
.item-dict-effects {
  margin: 0;
  padding-left: 18px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.monster-dict-lore,
.item-dict-lore {
  color: var(--rouge-fg-muted, #aaa);
  font-style: italic;
}

.settings-actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 10px;
}

/* ===== Title Screen ===== */
.title-screen {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  padding: 40px 20px;
  color: var(--rouge-fg, #ccc);
  max-width: 700px;
}

.title-logo {
  color: var(--rouge-highlight, #ff8800);
  font-size: 13px;
  line-height: 1.15;
  text-shadow: 0 0 20px var(--rouge-highlight-glow, rgba(255, 136, 0, 0.5));
}

.title-subtitle {
  color: var(--rouge-fg-muted, #888);
  font-size: 14px;
  letter-spacing: 3px;
  text-transform: uppercase;
}

.title-flavor {
  text-align: center;
  color: var(--rouge-fg-muted, #aaa);
  font-size: 14px;
  line-height: 1.6;
}

.title-btn {
  background: none;
  border: 1px solid var(--rouge-border, #666);
  color: var(--rouge-highlight, #ffdd88);
  font-family: 'Courier New', Courier, monospace;
  font-size: 16px;
  padding: 10px 24px;
  cursor: pointer;
  transition: all 0.2s;
  /* These labels are bracketed as a single ASCII unit — "[ Press ENTER or click to begin ]" —
     so a wrap leaves the closing bracket stranded on a line of its own. See the coarse-pointer
     rules for how the longest of them is made to fit a phone. */
  white-space: nowrap;
}

.title-btn:hover {
  background: var(--rouge-highlight-hover, #1a1a00);
  border-color: var(--rouge-highlight, #ffdd88);
}

.title-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
}

.title-controls {
  border: 1px solid var(--rouge-border, #333);
  padding: 16px 24px;
  width: 100%;
}

.controls-title {
  color: var(--rouge-fg-muted, #888);
  text-align: center;
  margin-bottom: 10px;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 2px;
}

.title-version {
  color: var(--rouge-fg-muted, #666);
  font-size: 11px;
  letter-spacing: 1px;
}

.controls-grid {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 4px 24px;
  font-size: 13px;
}

.controls-grid span:nth-child(odd) {
  color: var(--rouge-fg-muted, #aaa);
}

.controls-grid span:nth-child(even) {
  color: var(--rouge-highlight, #ffdd88);
  text-align: right;
}

/* ===== Game Over ===== */
.gameover-screen {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  padding: 40px 20px;
  max-width: 600px;
  color: var(--rouge-fg, #ccc);
}

.gameover-title {
  font-size: 28px;
  font-weight: bold;
}

/* Death and victory are outcomes, not chrome accents, so their hues are fixed — but they still
   have to be legible on whichever background the theme puts behind them. */
.gameover-title.dead {
  color: var(--rouge-danger, #ff4444);
  text-shadow: 0 0 20px rgba(255, 68, 68, 0.4);
}

.gameover-title.won {
  color: var(--rouge-gold, #ffdd00);
  text-shadow: 0 0 20px rgba(255, 221, 0, 0.5);
}

.gameover-msg {
  text-align: center;
  color: var(--rouge-fg-muted, #aaa);
  line-height: 1.6;
}

.gameover-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px 24px;
  border: 1px solid var(--rouge-border, #333);
  padding: 16px 24px;
  width: 100%;
  font-size: 14px;
  color: var(--rouge-fg-muted, #888);
}

.stat-val {
  color: var(--rouge-fg, #eee);
}

.gameover-log {
  width: 100%;
  border: 1px solid var(--rouge-border, #222);
  padding: 12px 16px;
  font-size: 12px;
  color: var(--rouge-fg-muted, #888);
}

.log-title {
  color: var(--rouge-fg-muted, #999);
  margin-bottom: 6px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1px;
}

/* ===== Accessibility: focus, motion, screen-reader-only content ===== */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* A visible focus ring on every interactive element — the game's dark chrome has no default
   browser focus affordance strong enough to rely on outline:none anywhere. */
button:focus-visible,
input:focus-visible,
a:focus-visible {
  outline: 2px solid var(--rouge-highlight, #ffdd88);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  * {
    transition-duration: 0.001ms !important;
    animation-duration: 0.001ms !important;
  }
}

/* ===== Touch controls (mobile-friendly on-screen D-pad + actions) ===== */
/* Hidden by default; shown automatically on coarse-pointer (touch) devices, or when the user
   turns them on manually via the "Touch controls" toggle regardless of pointer type. */
.touch-controls {
  display: none;
  flex-direction: column;
  gap: 8px;
  padding: 8px;
  background: var(--rouge-bg-panel, #0a0a0a);
  border-top: 1px solid var(--rouge-border, #222);
}

@media (pointer: coarse) {
  .touch-controls {
    display: flex;
  }
}

.touch-controls-forced .touch-controls {
  display: flex;
}

/* Save / Load and Settings live in the touch action row whenever it is on screen, so the
   separate action bar (their home on pointer devices) stands down to avoid showing them twice
   — and the row it frees up goes to the message log. */
@media (pointer: coarse) {
  .action-bar {
    display: none;
  }

  /* The Settings menu's whole Controls section goes too: on a touch device the D-pad is already
     on screen, so the toggle that forces it has nothing to do, and the keyboard reference beside
     it lists keys the device does not have. Keyed off the pointer type rather than the forced
     class — a laptop with a touchscreen that has switched the D-pad on still needs the toggle to
     switch it back off. */
  .settings-controls {
    display: none;
  }
}

.touch-controls-forced .action-bar {
  display: none;
}

.touch-btn:disabled {
  opacity: 0.4;
  cursor: default;
}

.touch-btn:disabled:hover {
  background: var(--rouge-bg-panel, #1a1a1a);
  border-color: var(--rouge-border, #555);
}

.touch-dpad {
  display: grid;
  grid-template-columns: repeat(3, minmax(44px, 1fr));
  grid-template-rows: repeat(3, 44px);
  gap: 4px;
  max-width: 220px;
  margin: 0 auto;
}

/* ===== Stats beside the D-pad (touch layouts only) =====
   The D-pad is only ~220px wide, so on a phone the columns to its left and right are dead space.
   The numeric stats move into them and off the top of the screen, which buys the map a row.
   Whenever this row is on screen the status bar drops the same stats — see .status-stats below —
   so they are never shown twice. */
.touch-stats-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

/* The D-pad keeps its own size and stays centred; the two stat columns share what is left. */
.touch-stats-row .touch-dpad {
  flex: 0 0 auto;
  margin: 0;
}

/* Two tracks - labels, then values - rather than a stack of "label: value" lines. The labels are
   different lengths (HP, Str, Def, Lvl, XP; Gold, Floor, Turn), so lines that simply start at the
   same place staggered every value along the column, which is the one thing a player reads these
   for. Each .stat wrapper is display: contents (below), so its label and its value become grid
   items in their own right and drop into the two tracks: every value in a column now begins on the
   same vertical line, on both sides of the D-pad.

   The value track has a floor of 5ch instead of being sized purely by its contents. The right-hand
   column is packed against the outer edge, so a track that grew only with what was in it would drag
   the whole column sideways the first time gold or the turn counter reached another digit. Five
   characters covers the values a run actually reaches - five-figure gold and turn counts, "12/12"
   HP - so the column holds still through all of them, and anything past that widens the track
   rather than clipping it. Not more than five: the row also has to hold a 140px D-pad and a second
   stat column inside a 320px phone, and the reserved width is taken from the same budget. The font
   here is Courier New, so ch is an exact character width. */
.touch-stats {
  display: grid;
  grid-template-columns: max-content minmax(5ch, max-content);
  align-content: center;
  flex: 1 1 0;
  min-width: 0;
  gap: 2px 6px;
  /* Scales with the viewport like the status bar did, so "Very Hungry" and five-digit turn
     counts still fit the column on a narrow phone instead of wrapping mid-value. */
  font-size: min(13px, 3vw);
  line-height: 1.35;
  color: var(--rouge-fg, #ccc);
  white-space: nowrap;
}

/* The wrapper exists for the status bar, where each stat is one flex item of the row. Here it
   would be a box between the grid and the label and value inside it, and only the wrapper would
   land in a track. Dropping it lets the two halves align independently. */
.touch-stats > .stat {
  display: contents;
}

/* Hunger is a single word with no label of its own, so it has no business in the value track:
   it takes the whole row and starts where the labels start. */
.touch-stats > .stat-wide {
  grid-column: 1 / -1;
}

/* Which edge the pair of tracks is packed against - the only difference between the two columns.
   The rows read the same way in both; the block as a whole just hangs off the opposite side, so
   the stats sit against the screen edges with the D-pad free in the middle. */
.touch-stats-left {
  justify-content: start;
}

.touch-stats-right {
  justify-content: end;
}

/* The throw button, under the run stats beside the D-pad that aims it. It spans both of the
   column's tracks so it can hang off the same outer edge the stats do, and keeps the full 44px
   touch target: this column has height to spare beside the D-pad, the action row has no width. */
.touch-throw {
  grid-column: 1 / -1;
  justify-self: end;
  margin-top: 6px;
  padding: 0;
  width: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Lit while a throw is in progress, so it reads as "tap again to cancel" and the player can tell
   the D-pad is aiming rather than walking. */
.touch-btn-active {
  border-color: var(--rouge-highlight, #888);
  color: var(--rouge-highlight, #fff);
}

@media (pointer: coarse) {
  .status-stats {
    display: none;
  }
}

.touch-controls-forced .status-stats {
  display: none;
}

/* Stairs / Inventory / Pick up / Save / Load / cog, always across one line however narrow the
   phone is. nowrap rather than wrap: a second row costs a row of map on the device that has the
   least of it, and the buttons themselves have somewhere to give — see .touch-action, which
   scales its type and padding with the viewport and shrinks before the row ever overflows. The
   gap shrinks with it, so the space between buttons is not the thing that pushes one off. */
.touch-actions {
  display: flex;
  flex-wrap: nowrap;
  gap: min(4px, 1vw);
  justify-content: center;
  width: 100%;
}

.touch-btn {
  min-width: 44px;
  min-height: 44px;
  background: var(--rouge-bg-panel, #1a1a1a);
  border: 1px solid var(--rouge-border, #555);
  color: var(--rouge-fg, #eee);
  font-family: 'Courier New', Courier, monospace;
  font-size: 16px;
  cursor: pointer;
  touch-action: manipulation;
  /* The D-pad is meant to be held down, and a long press on a WebView otherwise starts a text
     selection or pops the callout menu — either of which cancels the pointer and stops the
     repeat mid-walk. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}

.touch-btn:hover {
  background: var(--rouge-border, #262626);
  border-color: var(--rouge-highlight, #888);
}

.touch-btn:active {
  background: var(--rouge-border, #333);
}

/* The row's per-button half of "always one line". Three things happen as the viewport narrows:
   the type and the horizontal padding scale down with it, min-width drops to 0 so flex-shrink is
   allowed to take width off a button that still will not fit, and the label is clipped with an
   ellipsis rather than wrapping to a second line inside the button. min-height stays at
   .touch-btn's 44px throughout, so the touch target keeps its full height even where the widest
   label has had to give up width. */
.touch-action {
  flex: 0 1 auto;
  min-width: 0;
  font-size: min(12px, 3.1vw);
  padding: 8px min(6px, 1.4vw);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The cog: square, so it costs only the minimum touch target instead of a word's width. It is the
   one button in the row with no label to shrink, so it scales with the viewport directly and does
   not flex — leaving the width it gives up to the buttons that still have words in them. */
.touch-action-icon {
  padding: 0;
  flex: 0 0 auto;
  width: min(44px, 11vw);
  min-width: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.touch-action-icon svg {
  width: min(22px, 5.5vw);
  height: auto;
}

.touch-controls-toggle {
  font-size: 11px;
}

/* ===== Narrow / portrait layout =====
   The mobile client is locked to portrait (MainActivity/Info.plist), so this is the layout the
   phone always gets. The governing rule is that nothing but the map may be wider than the
   viewport: the map is a pannable camera, everything else wraps or shrinks to fit. */
@media (pointer: coarse), (max-width: 760px) {
  /* Without this the container is sized by the 80-column map, which dragged the status bar,
     message log and controls off-screen with it. */
  /* 100% of .app's content box, i.e. the viewport minus the safe-area insets — not 100vh,
     which would include the strips behind the system bars. */
  .game-container {
    width: 100%;
    max-width: 100%;
    max-height: 100%;
  }

  /* The map takes whatever vertical room is left over and scrolls inside it, so the controls
     below it can never be pushed off the bottom of a short screen.
     min-width/min-height: 0 are load-bearing, not tidying: a flex item's automatic minimum size
     is its content, so without them .map-viewport refuses to shrink below the full 80-column
     map, overflows instead of scrolling, and the camera can neither be panned nor re-centred. */
  .map-wrapper {
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
    min-width: 0;
  }

  .map-viewport {
    flex: 1 1 auto;
    min-height: 0;
    min-width: 0;
  }

  /* Sized off the viewport so a row fits across it; longer values (five-digit turn counts,
     "Very Hungry") wrap onto an extra row rather than running off the edge. On a touch layout
     the numeric stats have left for the D-pad row and only the equipment line remains. */
  .status-bar {
    font-size: min(13px, 2.3vw);
    gap: 2px 4px;
    padding: 4px 6px;
  }

  /* Its own full-width row, with the weapon pinned to the left edge and the armour to the
     right — the two are far apart in length, so a gap between them reads better than a pair of
     items butted together mid-bar. */
  .status-equip {
    flex-basis: 100%;
    justify-content: space-between;
    /* Matched to .touch-stats rather than kept at the 0.85em the desktop bar uses. Once the
       other stats moved down beside the D-pad, these two were the only things left up here,
       and at 0.85em of an already-shrunken bar they read as a footnote to a row that isn't
       there any more. The longest pair the item tables can produce
       is longer than it was before enchantment and the Rogue armour ladder ("Wpn: +3 two-handed
       sword (+11)", "Arm: +3 studded leather armor (+6)"), so on the narrowest screens the two
       wrap onto their own rows rather than being clipped. */
    font-size: min(13px, 3vw);
  }

  /* Every tap target on the play screen gets a 44px touch box, including the inventory's
     use/equip/drop buttons and the action bar. */
  .action-bar .inv-btn,
  .inventory-item .inv-btn {
    min-height: 44px;
    padding: 6px 12px;
    font-size: 13px;
  }

  /* Takes over the room the action bar gave up, so more of the log is readable without
     scrolling it. flex: none keeps it at that size when the column is over budget — the map
     is the one that gives way, since it scrolls and pans anyway. */
  .message-log {
    height: 8em;
    flex: none;
  }

  /* Keyboard-only reference — meaningless without a keyboard, and it was the widest thing on
     the title screen. Still reachable from Settings on every platform. */
  .title-controls {
    display: none;
  }

  .title-screen {
    padding: 24px 12px;
    gap: 14px;
  }

  .title-logo {
    font-size: min(13px, 2.9vw);
  }

  /* The longest label, "[ Press ENTER or click to begin ]", is 33 monospace characters — about
     20em wide — which at 16px overruns a phone. Since the labels must not wrap, the type gives
     way instead, and only on the screens that can't afford it: min() holds 16px everywhere
     wider than ~400px. Trimming the side padding buys back the rest of what's needed. All the
     title buttons scale together so a row of them stays one size. */
  .title-btn {
    font-size: min(16px, 4vw);
    padding: 10px 12px;
  }

  /* min-width on these was wider than a phone screen, so the dialogs overflowed it. */
  .inventory-box,
  .saveload-box {
    min-width: 0;
    width: 100%;
  }

  .inventory-overlay,
  .saveload-overlay {
    padding: 12px;
  }

  .saveload-box {
    max-height: 80vh;
  }
}
