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

html,
body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  font-family: Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--fg);
}

/* --- theme tokens ---------------------------------------------------------
 * Order matters. :root carries light, the prefers-color-scheme block flips it
 * for the first paint before JS runs, and body[data-theme] wins over both once
 * the player has an explicit preference.
 * ------------------------------------------------------------------------ */

:root {
  --hud-h: 48px;
  /* 200ms was ambiguous with "the click didn't register" at click speed. */
  --settle-ms: 450ms;

  --bg: #f3f3f1;
  --fg: #111;
  --hud-bg: rgba(255, 255, 255, 0.92);
  --hud-border: #ddd;
  --tile-bg: #fff;
  --tile-border: #8a8a8a;
  --tile-fg: #111;
  --armed: #7c3aed;
  --dev: #ea580c;
  --done: #2563eb;
  --done-fill: #dbeafe;
  --btn-bg: #f5f5f5;
  --btn-border: #bbb;
  --mm-bg: #e8e8e8;
  --mm-border: #bbb;
  --tip-bg: rgba(255, 255, 255, 0.96);
  --tip-border: #ccc;
  --shadow: rgba(0, 0, 0, 0.18);
  /* Settle: ink, not a fourth hue. The Three Signals Rule holds. */
  --settle: rgba(17, 17, 17, 0.72);
  /* Stacked-paper edges: nearest edge darkest, receding edges lighter. */
  --stack-1: rgba(0, 0, 0, 0.3);
  --stack-2: rgba(0, 0, 0, 0.255);
  --stack-3: rgba(0, 0, 0, 0.215);
  --stack-4: rgba(0, 0, 0, 0.18);
  --stack-5: rgba(0, 0, 0, 0.15);
  --stack-6: rgba(0, 0, 0, 0.125);
  --stack-7: rgba(0, 0, 0, 0.105);
  --done-1: rgba(37, 99, 235, 0.6);
  --done-2: rgba(37, 99, 235, 0.52);
  --done-3: rgba(37, 99, 235, 0.45);
  --done-4: rgba(37, 99, 235, 0.38);
  --done-5: rgba(37, 99, 235, 0.31);
  --done-6: rgba(37, 99, 235, 0.25);
  --done-7: rgba(37, 99, 235, 0.19);
  --done-ambient: rgba(37, 99, 235, 0.24);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #121212;
    --fg: #eee;
    --hud-bg: rgba(20, 20, 20, 0.92);
    --hud-border: #333;
    --tile-bg: #333;
    --tile-border: #7a7a7a;
    --tile-fg: #eee;
    --armed: #a78bfa;
    --dev: #fb923c;
    --done: #3b82f6;
    --done-fill: #1e3a5f;
    --btn-bg: #2a2a2a;
    --btn-border: #555;
    --mm-bg: #111;
    --mm-border: #444;
    --tip-bg: rgba(20, 20, 20, 0.96);
    --tip-border: #555;
    --shadow: rgba(0, 0, 0, 0.4);
    --settle: rgba(238, 238, 238, 0.72);
    --stack-1: rgba(255, 255, 255, 0.24);
    --stack-2: rgba(255, 255, 255, 0.205);
    --stack-3: rgba(255, 255, 255, 0.175);
    --stack-4: rgba(255, 255, 255, 0.148);
    --stack-5: rgba(255, 255, 255, 0.125);
    --stack-6: rgba(255, 255, 255, 0.105);
    --stack-7: rgba(255, 255, 255, 0.088);
    --done-1: rgba(59, 130, 246, 0.58);
    --done-2: rgba(59, 130, 246, 0.5);
    --done-3: rgba(59, 130, 246, 0.43);
    --done-4: rgba(59, 130, 246, 0.36);
    --done-5: rgba(59, 130, 246, 0.3);
    --done-6: rgba(59, 130, 246, 0.24);
    --done-7: rgba(59, 130, 246, 0.19);
    --done-ambient: rgba(59, 130, 246, 0.3);
  }
}

body[data-theme='light'] {
  --bg: #f3f3f1;
  --fg: #111;
  --hud-bg: rgba(255, 255, 255, 0.92);
  --hud-border: #ddd;
  --tile-bg: #fff;
  --tile-border: #8a8a8a;
  --tile-fg: #111;
  --armed: #7c3aed;
  --dev: #ea580c;
  --done: #2563eb;
  --done-fill: #dbeafe;
  --btn-bg: #f5f5f5;
  --btn-border: #bbb;
  --mm-bg: #e8e8e8;
  --mm-border: #bbb;
  --tip-bg: rgba(255, 255, 255, 0.96);
  --tip-border: #ccc;
  --shadow: rgba(0, 0, 0, 0.18);
  --settle: rgba(17, 17, 17, 0.72);
  --stack-1: rgba(0, 0, 0, 0.3);
  --stack-2: rgba(0, 0, 0, 0.255);
  --stack-3: rgba(0, 0, 0, 0.215);
  --stack-4: rgba(0, 0, 0, 0.18);
  --stack-5: rgba(0, 0, 0, 0.15);
  --stack-6: rgba(0, 0, 0, 0.125);
  --stack-7: rgba(0, 0, 0, 0.105);
  --done-1: rgba(37, 99, 235, 0.6);
  --done-2: rgba(37, 99, 235, 0.52);
  --done-3: rgba(37, 99, 235, 0.45);
  --done-4: rgba(37, 99, 235, 0.38);
  --done-5: rgba(37, 99, 235, 0.31);
  --done-6: rgba(37, 99, 235, 0.25);
  --done-7: rgba(37, 99, 235, 0.19);
  --done-ambient: rgba(37, 99, 235, 0.24);
}

body[data-theme='dark'] {
  --bg: #121212;
  --fg: #eee;
  --hud-bg: rgba(20, 20, 20, 0.92);
  --hud-border: #333;
  --tile-bg: #333;
  --tile-border: #7a7a7a;
  --tile-fg: #eee;
  --armed: #a78bfa;
  --dev: #fb923c;
  --done: #3b82f6;
  --done-fill: #1e3a5f;
  --btn-bg: #2a2a2a;
  --btn-border: #555;
  --mm-bg: #111;
  --mm-border: #444;
  --tip-bg: rgba(20, 20, 20, 0.96);
  --tip-border: #555;
  --shadow: rgba(0, 0, 0, 0.4);
  --settle: rgba(238, 238, 238, 0.72);
  /* Dark: edges are card-material, so they lighten rather than darken. */
  --stack-1: rgba(255, 255, 255, 0.24);
  --stack-2: rgba(255, 255, 255, 0.205);
  --stack-3: rgba(255, 255, 255, 0.175);
  --stack-4: rgba(255, 255, 255, 0.148);
  --stack-5: rgba(255, 255, 255, 0.125);
  --stack-6: rgba(255, 255, 255, 0.105);
  --stack-7: rgba(255, 255, 255, 0.088);
  --done-1: rgba(59, 130, 246, 0.58);
  --done-2: rgba(59, 130, 246, 0.5);
  --done-3: rgba(59, 130, 246, 0.43);
  --done-4: rgba(59, 130, 246, 0.36);
  --done-5: rgba(59, 130, 246, 0.3);
  --done-6: rgba(59, 130, 246, 0.24);
  --done-7: rgba(59, 130, 246, 0.19);
  --done-ambient: rgba(59, 130, 246, 0.3);
}

/* --- a11y utilities ------------------------------------------------------ */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: fixed;
  top: 6px;
  left: 6px;
  z-index: 200;
  padding: 10px 14px;
  border: 1px solid var(--btn-border);
  border-radius: 4px;
  background: var(--btn-bg);
  color: var(--fg);
  font-size: 13px;
  text-decoration: none;
  transform: translateY(-200%);
}

.skip-link:focus {
  transform: translateY(0);
}

/* --- HUD ----------------------------------------------------------------- */

#hud {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 40;
  height: var(--hud-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 0 14px;
  background: var(--hud-bg);
  border-bottom: 1px solid var(--hud-border);
  backdrop-filter: blur(6px);
  user-select: none;
}

/* The value speaks in the label's old uppercase voice, so the readout still
   reads as status, not as a search field. (The "In hand:" prefix survives
   sr-only for screen readers.) */
.armed-readout {
  flex: 1;
  min-width: 0;
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: 14px;
}

.readout-value {
  min-width: 0;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Mismatch acknowledgement, at the element the driver is already watching.
   Ink at full strength — no new hue, no motion, nothing that scolds. */
.armed-readout.is-flash .readout-value {
  font-weight: 600;
  opacity: 1;
}

.armed-readout.is-idle .readout-value {
  font-weight: 400;
  /* 0.62 clears 4.5:1 against the chrome surface; 0.55 measured 4.16:1. */
  opacity: 0.62;
}

.hud-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}

.stats {
  font-size: 13px;
  white-space: nowrap;
}

.stats--short,
.btn-label--short {
  display: none;
}

/* --- filed disclosure ----------------------------------------------------
 * The count is a summary; the names are behind it. Styled as chrome, not as a
 * control — it must not read as more important than the board.
 * ------------------------------------------------------------------------ */

.filed {
  position: relative;
  flex-shrink: 0;
}

.filed__summary {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 4px;
  border-radius: 4px;
  cursor: pointer;
  list-style: none;
  user-select: none;
}

.filed__summary::-webkit-details-marker {
  display: none;
}

.filed__summary:focus-visible {
  outline: 2px solid var(--armed);
  outline-offset: 2px;
}

/* A caret rather than a glyph: the system has no icon set. */
.filed__caret {
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
  opacity: 0.62;
}

.filed[open] .filed__caret {
  transform: rotate(180deg);
}

.filed__panel {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 60;
  width: max-content;
  max-width: min(420px, calc(100vw - 24px));
  max-height: min(60vh, 420px);
  overflow: auto;
  overscroll-behavior: contain;
  padding: 8px;
  /* Card stock, opaque — not the translucent Chrome Surface. Twenty-two
     names have to be readable over whatever board sits behind them, and a
     drawer's index card is the right object for a list of filed drawers. */
  background: var(--tile-bg);
  border: 1px solid var(--tip-border);
  border-radius: 6px;
  box-shadow: 0 8px 24px var(--shadow);
}

/* A closed <details> still reports a layout box in Chrome; take it out. */
.filed:not([open]) .filed__panel {
  display: none;
}

.filed__empty {
  margin: 0;
  padding: 4px 6px;
  font-size: 13px;
  opacity: 0.62;
}

/* The mistake tally, with the word the compressed bar has no room for. */
.filed__misses {
  margin: 0 0 6px;
  padding: 4px 6px;
  border-bottom: 1px solid var(--hud-border);
  font-size: 13px;
  opacity: 0.62;
}

.filed__empty[hidden] {
  display: none;
}

.filed__list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 2px 8px;
}

.filed__item {
  display: block;
  width: 100%;
  padding: 5px 6px;
  border: 0;
  border-radius: 3px;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
}

.filed__item:hover {
  background: var(--btn-bg);
}

.filed__item:focus-visible {
  outline: 2px solid var(--armed);
  outline-offset: -2px;
}

.btn {
  font-family: inherit;
  font-size: 13px;
  padding: 6px 12px;
  border: 1px solid var(--btn-border);
  border-radius: 4px;
  background: var(--btn-bg);
  color: var(--fg);
  cursor: pointer;
}

.btn--quiet {
  font-size: 12px;
  padding: 4px 8px;
  background: transparent;
  opacity: 0.75;
}

/* 36, not 32: the `?` was the smallest fine-pointer target in the product and
   the only glyph-only control. 44 under a coarse pointer, below. */
.btn--icon {
  width: 36px;
  height: 36px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  font-weight: 600;
  line-height: 1;
}

/* `opacity: 1` alone only did anything for .btn--quiet; every other control
   had no hover state at all. The outline sharpens to Ink — no fill shift, no
   lift, no colour change, so The Flat Rest Rule is untouched. */
.btn:hover {
  opacity: 1;
  border-color: var(--fg);
}

/* Chrome controls had no focus rule at all and fell back to the UA ring —
   which is blue, the hue this system reserves for "filed". */
.btn:focus-visible,
.skip-link:focus-visible {
  outline: 2px solid var(--armed);
  outline-offset: 2px;
}

/* --- board --------------------------------------------------------------- */

/* The scroll position IS the camera. One mechanism, so keyboard focus,
   touch drag, wheel and the minimap can never disagree.
   The field runs to the bottom of the window; the minimap floats over it and
   the board carries the panel's footprint as scroll padding (see syncBoardSize)
   so anything it covers can always be scrolled clear. */
#viewport {
  position: fixed;
  inset: var(--hud-h) 0 0 0;
  overflow: auto;
  overscroll-behavior: contain;
  touch-action: pan-x pan-y;
  cursor: default;
}

#board {
  position: relative;
  outline: none;
}

.tile {
  position: absolute;
  /* Resolved once at boot from the content's longest unbroken token; app.js
     also writes it per tile, so this is the pre-script value. */
  width: var(--col-w, 124px);
  margin: 0;
  padding: 12px;
  border: 1.5px solid var(--tile-border);
  border-radius: 2px;
  background: var(--tile-bg);
  color: var(--tile-fg);
  font: inherit;
  font-size: 13px;
  line-height: 1.35;
  text-align: center;
  overflow: hidden;
  cursor: pointer;
  user-select: none;
  appearance: none;
  /* Short tiles top-align in taller row bands via absolute y = row top. */
  display: flex;
  align-items: center;
  justify-content: center;
  transition:
    box-shadow 200ms ease-out,
    border-color var(--settle-ms) ease-out,
    /* Vertical band collapse, played as a FLIP from app.js. Transform only:
       400 cards can move at once and none of them may touch layout. */
    transform 180ms ease-out;
}

/*
 * Stack depth reads as physical paper thickness: hard, unblurred, stepped
 * offsets on both axes, so a pile reads as a stack of cards fanned down and
 * right rather than as a rule beneath a single card. Buckets, not per-member
 * steps — the point is reading "roughly how deep" across the board.
 */
.tile--d2 {
  box-shadow: 2px 2px 0 var(--stack-1);
}

.tile--d3 {
  box-shadow:
    2px 2px 0 var(--stack-1),
    4px 4px 0 var(--stack-2);
}

.tile--d4 {
  box-shadow:
    2px 2px 0 var(--stack-1),
    4px 4px 0 var(--stack-2),
    6px 6px 0 var(--stack-3);
}

.tile--d5 {
  box-shadow:
    2px 2px 0 var(--stack-1),
    4px 4px 0 var(--stack-2),
    6px 6px 0 var(--stack-3),
    8px 8px 0 var(--stack-4);
}

.tile--d6 {
  box-shadow:
    2px 2px 0 var(--stack-1),
    4px 4px 0 var(--stack-2),
    6px 6px 0 var(--stack-3),
    8px 8px 0 var(--stack-4),
    10px 10px 0 var(--stack-5);
}

.tile--d7 {
  box-shadow:
    2px 2px 0 var(--stack-1),
    4px 4px 0 var(--stack-2),
    6px 6px 0 var(--stack-3),
    8px 8px 0 var(--stack-4),
    10px 10px 0 var(--stack-5),
    12px 12px 0 var(--stack-6);
}

/*
 * Pre-completion (20–21 of 22). The deepest stack the board can carry, plus
 * the one non-shadow cue: the card's own hairline thickens to 2px — the same
 * weight a filed card sits at, in the card's own edge colour rather than a
 * fourth hue. "One or two away" without printing a number.
 */
.tile--d8 {
  border-width: 2px;
  box-shadow:
    2px 2px 0 var(--stack-1),
    4px 4px 0 var(--stack-2),
    6px 6px 0 var(--stack-3),
    8px 8px 0 var(--stack-4),
    10px 10px 0 var(--stack-5),
    12px 12px 0 var(--stack-6),
    14px 14px 0 var(--stack-7);
}

.tile:focus-visible {
  outline: 2px solid var(--armed);
  outline-offset: 2px;
}

.tile__label {
  display: block;
  width: 100%;
  overflow: hidden;
  /* Wrap at spaces only — never split an unbroken token. */
  word-break: keep-all;
  overflow-wrap: normal;
  hyphens: none;
}

.tile--armed {
  border-color: var(--armed);
  border-width: 2.5px;
  outline: 1px solid var(--armed);
}

/* Registered, not a match. Neutral ink, no motion on the card, no new hue —
   this is response, never punishment. */
.tile--mismatch {
  border-color: var(--settle);
  border-width: 2.5px;
}

.tile--dev {
  border-color: var(--dev);
  border-width: 2.5px;
  outline: 1px solid var(--dev);
}

.tile--completed {
  background: var(--done-fill);
  border-color: var(--done);
  border-width: 2px;
  /* Terminal state: the deepest stack, tinted, plus the one soft shadow
     the board permits. Same seven-step ramp as .tile--d8, in blue. */
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.35),
    2px 2px 0 var(--done-1),
    4px 4px 0 var(--done-2),
    6px 6px 0 var(--done-3),
    8px 8px 0 var(--done-4),
    10px 10px 0 var(--done-5),
    12px 12px 0 var(--done-6),
    14px 14px 0 var(--done-7),
    8px 14px 20px var(--done-ambient);
  cursor: default;
  animation: done-in 280ms ease-out;
}

@keyframes done-in {
  from {
    transform: scale(0.96);
  }
  to {
    transform: scale(1);
  }
}

.tile--just-merged .tile__label {
  animation: merge-pop 180ms ease-out;
}

@keyframes merge-pop {
  from {
    opacity: 0.35;
    transform: scale(0.97);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

/* --- minimap ------------------------------------------------------------- */

/* One bounded Chrome Surface holding both the controls and the canvas, so the
   instrument is a single opaque object rather than loose labels over the field.
   It does float over live tiles; the board reserves its footprint as scroll
   padding so anything under it can be scrolled clear. */
#minimap-wrap {
  position: fixed;
  right: 12px;
  bottom: 12px;
  z-index: 30;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  padding: 6px;
  background: var(--hud-bg);
  border: 1px solid var(--hud-border);
  border-radius: 6px;
  backdrop-filter: blur(6px);
}

.minimap-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
}

#minimap {
  display: block;
  border: 1px solid var(--mm-border);
  border-radius: 4px;
  cursor: pointer;
  background: var(--mm-bg);
}

#minimap:focus-visible {
  outline: 2px solid var(--armed);
  outline-offset: 2px;
}

#minimap[hidden] {
  display: none;
}

/* --- pan hint ------------------------------------------------------------ */

/* Present before the first pan, gone the moment one lands. */
.pan-hint {
  position: fixed;
  left: 50%;
  bottom: 20px;
  z-index: 35;
  transform: translateX(-50%);
  padding: 8px 14px;
  max-width: calc(100vw - 24px);
  background: var(--hud-bg);
  border: 1px solid var(--hud-border);
  border-radius: 6px;
  backdrop-filter: blur(6px);
  font-size: 13px;
  white-space: nowrap;
  pointer-events: none;
}

.pan-hint[hidden] {
  display: none;
}

/* --- the finish ----------------------------------------------------------
 * The cascade surface. Sized and positioned from JS to cover exactly the
 * viewport (the HUD stays reachable above it), and transparent until a card
 * paints on it — the trails accumulate over the live board the way the
 * original accumulated over the desktop.
 * ------------------------------------------------------------------------ */

.cascade {
  position: fixed;
  z-index: 90;
  /* Takes the pointer so the dismissing click never also lands on a card. */
  cursor: default;
}

/* Instruction, above the trails that would otherwise bury it. */
.cascade-hint {
  z-index: 95;
}

/* The one piece of chrome that would be painted over: it floats on the field
   rather than in the bar, and the finish owns the field. */
body.is-cascading #minimap-wrap {
  display: none;
}

/* --- tooltip ------------------------------------------------------------- */

#tooltip {
  position: fixed;
  z-index: 80;
  max-width: 320px;
  padding: 8px 10px;
  border-radius: 6px;
  background: var(--tip-bg);
  color: var(--fg);
  border: 1px solid var(--tip-border);
  box-shadow: 0 8px 24px var(--shadow);
  font-size: 13px;
  line-height: 1.35;
  pointer-events: none;
  white-space: normal;
  word-break: break-word;
}

#tooltip[hidden] {
  display: none;
}

/* --- modals -------------------------------------------------------------- */

.modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.modal[hidden] {
  display: none;
}

.modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
}

.modal__panel {
  position: relative;
  z-index: 1;
  width: min(640px, 100%);
  max-height: min(90vh, 640px);
  display: flex;
  flex-direction: column;
  background: var(--hud-bg);
  color: var(--fg);
  border: 1px solid var(--hud-border);
  border-radius: 6px;
  box-shadow: 0 16px 48px var(--shadow);
}

.modal__panel--intro,
.modal__panel--confirm {
  width: min(420px, 100%);
}

.modal__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 16px 0;
  flex-shrink: 0;
}

.modal__header h2 {
  margin: 0;
  font-size: 18px;
  font-weight: 700;
}

.modal__body {
  padding: 12px 16px 8px;
  overflow: auto;
  font-size: 14px;
  line-height: 1.45;
}

.modal__body h2 {
  margin: 4px 0 10px;
  font-size: 18px;
  font-weight: 700;
}

.modal__body p {
  margin: 0 0 12px;
}

/* The one rule that unlocks everything. Set at Title size, not Body — it is
   the only thing on a cold arrival that has to be read. */
.intro-rule {
  font-size: 18px;
  font-weight: 700;
  line-height: 1.3;
}

.intro-sub {
  opacity: 0.7;
}

/* The scorecard. Ink and the card's own type — no medal, no hue, no bar
   chart. Every figure here is one the HUD already carried or one derived
   from it, so the finish reports the game rather than scoring it.

   Opaque card stock rather than the translucent Chrome Surface, for the same
   reason the filed list is: it sits over a screen filling with cards, and
   figures have to be readable against whatever the cascade has painted. */
.modal__panel--finish {
  background: var(--tile-bg);
  border-color: var(--tip-border);
}

.finish-stats {
  margin: 0;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0;
  font-size: 14px;
}

.finish-stats dt,
.finish-stats dd {
  margin: 0;
  padding: 9px 0;
  border-top: 1px solid var(--hud-border);
}

.finish-stats dt {
  opacity: 0.7;
}

.finish-stats dd {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* The headline figure. Weight, not size — The One Size Rule holds here too. */
.finish-stats dd:first-of-type {
  font-weight: 700;
}

.finish-dates {
  margin: 14px 0 0;
  font-size: 12px;
  opacity: 0.62;
}

.finish-dates:empty {
  display: none;
}

.modal__body h3 {
  margin: 14px 0 6px;
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  opacity: 0.7;
}

.modal__body ul {
  margin: 0;
  padding-left: 1.15em;
}

.modal__body li {
  margin: 0 0 6px;
}

.modal__body li:last-child {
  margin-bottom: 0;
}

.modal__body kbd {
  font-family: inherit;
  font-size: 0.92em;
  padding: 1px 5px;
  border: 1px solid var(--btn-border);
  border-radius: 3px;
  background: var(--btn-bg);
}

.modal__footer {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  padding: 8px 16px 14px;
  flex-shrink: 0;
}

body.is-panning #viewport {
  cursor: grabbing;
}

body.modal-open {
  overflow: hidden;
}

/* --- reach --------------------------------------------------------------- */

/* Coarse pointers and narrow windows get real hit areas. Nothing in the
   product previously reached 44×44 in either dimension. */
@media (pointer: coarse), (max-width: 720px) {
  :root {
    --hud-h: 56px;
  }

  .btn {
    min-height: 44px;
    min-width: 44px;
    padding: 6px 14px;
  }

  .btn--quiet {
    padding: 6px 10px;
  }

  .btn--icon {
    width: 44px;
    height: 44px;
  }

  .minimap-actions {
    gap: 8px;
  }

  .filed__summary,
  .filed__item {
    min-height: 44px;
  }

  .filed__summary {
    padding: 6px 8px;
  }

  .filed__item {
    display: flex;
    align-items: center;
  }
}

/* Narrow HUD: compress the labels, keep the readout, keep theme access. */
@media (max-width: 720px) {
  #hud {
    gap: 8px;
    padding: 0 10px;
  }

  .hud-actions {
    gap: 6px;
  }

  .stats--full {
    display: none;
  }

  .stats--short {
    display: inline;
  }

  .btn-label--full {
    display: none;
  }

  .btn-label--short {
    display: inline;
  }

  /* The summary sits mid-bar, so a right-anchored popover runs off the left
     edge below ~420px. Pin it to the window instead. */
  .filed__panel {
    position: fixed;
    top: calc(var(--hud-h) + 6px);
    left: 10px;
    right: 10px;
    width: auto;
    max-width: none;
  }

  .filed__list {
    grid-template-columns: minmax(0, 1fr);
  }

  /* The hint shares the chrome band with the minimap panel, which is
     right-aligned; centred, it landed on the panel's controls at 390px. */
  .pan-hint {
    left: 10px;
    right: auto;
    transform: none;
    max-width: calc(100vw - 170px);
    white-space: normal;
    text-align: left;
  }
}

/* Stack depth is information, so the shadows stay; only the motion goes.
   The mismatch settle also stays — it is acknowledgement, not decoration. */
@media (prefers-reduced-motion: reduce) {
  /* Depth and the settle survive; only the motion goes. `playShifts()` also
     bails on this query, so a row collapse lands instantly rather than
     part-way through a transform. */
  .tile {
    transition: none;
    transform: none;
  }

  .tile--completed {
    animation: none;
  }

  .tile--just-merged .tile__label {
    animation: none;
  }

  .skip-link {
    transition: none;
  }
}

@media (max-width: 380px) {
  .btn {
    padding: 6px 10px;
  }

  .btn--quiet {
    font-size: 12px;
    padding: 6px 8px;
  }
}
