/* ==========================================================================
   PokeSim — "Bench Instrument", live page.

   tokens.css loads first and carries every colour, size and space used here.
   Only index.html loads this sheet, so its reset and base cannot leak onto the
   pages that have not been converted yet.

   Contents
     1  reset and base
     2  primitives ............ panel, legend, readout, lamp, meter, key, tag, plate
     3  chrome ................ top rail, identity strip, footer plate
     4  live page ............. bezel, deck, gamepad, plan, party bays
     5  overlays .............. toast
     6  responsive ............ 1100px and 640px, plus container queries on a bay
     7  motion and contrast preferences

   Spacing rhythm: 8px inside a control, 16px inside a panel, 24px between
   panels, 32px between page regions. Nothing uses a value off the --s scale.
   ========================================================================== */


/* ==========================================================================
   1  RESET AND BASE
   ========================================================================== */

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

/* Reserve the scrollbar's track on every page, so a page that grows past the
   window once its data arrives, or a short page next to a long one, keeps the
   same width and nothing slides sideways. */
/* panel.js keeps --rail-h equal to the part of the rail that stays on screen,
   so a focused field or an anchor never lands under it. */
html { -webkit-text-size-adjust: 100%; scrollbar-gutter: stable; scroll-padding-top: calc(var(--rail-h, 57px) + var(--s3)); }

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--chassis);
  color: var(--ink);
  font: var(--f-body) / 1.55 var(--font-text);
  /* A fine machined stipple over the chassis. One repeating gradient on a
     3px cell, so it survives any zoom and costs nothing. */
  background-image: radial-gradient(var(--stipple) 0.5px, transparent 0.5px);
  background-size: 3px 3px;
}

h1, h2, h3, p, figure, ul, ol, dl, dd { margin: 0; padding: 0; }
ul, ol { list-style: none; }
img, svg { display: block; max-width: 100%; }
button, input, select { font: inherit; color: inherit; }
a { color: var(--link); text-decoration: underline; text-underline-offset: 3px; }
a:hover { text-decoration-thickness: 2px; }
[hidden] { display: none !important; }

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--focus-halo);
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: var(--s2);
  padding: var(--s2) var(--s4);
  background: var(--panel);
  border: var(--hair) solid var(--rule);
  z-index: 40;
}
.skip-link:focus { left: var(--s4); }

/* One measure for the rail, the page and the footer, so their edges line up. */
.shell {
  width: 100%;
  max-width: 1360px;
  margin: 0 auto;
  padding-inline: var(--s6);
}
/* At least a window tall, so the footer starts below the fold instead of
   painting at the bottom of the empty page and being pushed away by the data. */
main.shell { flex: 1 0 auto; min-height: 100vh; padding-bottom: var(--s6); }

.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}


/* ==========================================================================
   2  PRIMITIVES
   ========================================================================== */

.panel {
  background: var(--panel);
  border: var(--hair) solid var(--rule);
  box-shadow: var(--key-inset);
}

/* Silkscreen: the display face, letterspaced, in caps. */
.legend {
  font: var(--f-legend) / 1 var(--font-panel);
  letter-spacing: var(--track-legend);
  text-transform: uppercase;
  color: var(--ink-3);
}
.legend--ink { color: var(--ink); }

/* The small printed label that sits above every reading. */
.micro {
  font: var(--f-micro) / 1.4 var(--font-mono);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--ink-3);
}

.readout {
  font: var(--f-read) / 1 var(--font-panel);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  white-space: nowrap;
}
.unit { color: var(--ink-3); }

/* ---- lamp: a square indicator. Never round, never the only signal. ------ */
.lamp {
  display: inline-block;
  width: 8px;
  height: 8px;
  flex: none;
  background: var(--cell-off);
  box-shadow: inset 0 0 0 1px var(--rule);
}
.lamp[data-on="ok"] { background: var(--ok-lamp); box-shadow: inset 0 0 0 1px var(--rule), 0 0 6px -1px var(--ok-lamp); }
.lamp[data-on="signal"] { background: var(--signal-lamp); box-shadow: inset 0 0 0 1px var(--rule), 0 0 6px -1px var(--signal-lamp); }
.lamp[data-on="warn"] { background: var(--warn-lamp); box-shadow: inset 0 0 0 1px var(--rule), 0 0 6px -1px var(--warn-lamp); }
.lamp[data-on="crit"] { background: var(--crit-lamp); box-shadow: inset 0 0 0 1px var(--rule), 0 0 6px -1px var(--crit-lamp); }

/* The only self-starting motion in the system. It steps rather than fades,
   and the word beside it carries the same meaning for anyone who cannot see
   it blink. */
.lamp--blink { animation: blink 1.6s steps(1, end) infinite; }
@keyframes blink { 0%, 60% { opacity: 1; } 60.01%, 100% { opacity: 0.32; } }

/* ---- meter: discrete cells, never a smooth bar -------------------------- */
.meter {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 1px;
  height: 10px;
  padding: 1px;
  background: var(--rule);
  border: var(--hair) solid var(--rule);
}
.meter i { background: var(--cell-off); }
.meter[data-level="ok"] i.on { background: var(--ok-lamp); }
.meter[data-level="warn"] i.on { background: var(--warn-lamp); }
.meter[data-level="crit"] i.on { background: var(--crit-lamp); }
.meter[data-level="signal"] i.on { background: var(--signal-lamp); }

/* ---- key: a button, as a physical key on the panel ---------------------- */
.key {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-height: 40px;
  padding: 0 var(--s4);
  background: var(--panel-2);
  border: var(--hair) solid var(--rule);
  box-shadow: var(--key-inset);
  font: var(--f-legend) / 1 var(--font-panel);
  letter-spacing: var(--track-legend);
  text-transform: uppercase;
  color: var(--ink);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--t);
  text-decoration: none;
}
.key:hover { background: var(--panel); text-decoration: none; }
.key:active { box-shadow: var(--well-inset); background: var(--well); }
.key--primary {
  background: var(--signal);
  border-color: var(--signal);
  color: var(--focus-halo);
  box-shadow: inset 1px 1px 0 rgba(255, 255, 255, 0.22), inset -1px -1px 0 rgba(0, 0, 0, 0.30);
}
.key--primary:hover { background: var(--signal); filter: brightness(1.12); }
.key:disabled { opacity: 0.45; cursor: not-allowed; }
.key--danger { color: var(--crit); border-color: var(--crit); }

/* ---- tag: a type or state, stamped rather than pilled ------------------- */
.tag {
  display: inline-block;
  padding: 2px 6px 1px;
  border: var(--hair) solid var(--rule-faint);
  font: var(--f-micro) / 1.3 var(--font-mono);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--ink-2);
  white-space: nowrap;
}
.tag--warn { color: var(--warn); border-color: var(--warn); }
.tag--crit { color: var(--crit); border-color: var(--crit); }

/* ---- plate: the portrait well ------------------------------------------
   A backlit slide in a milled frame. Cartridge portraits are 2bpp and at most
   56x56; app.js scales each one by the largest whole number that fits, so a
   sprite is never resampled to a fractional multiple and blurred. */
.plate {
  position: relative;
  display: grid;
  /* One track the size of the plate, so a portrait bigger than it (a 96px
     pack canvas at 2x) overflows evenly and its art stays centred. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
  place-items: center;
  background: #f4f3e9;
  border: var(--hair) solid var(--rule);
  box-shadow: var(--well-inset);
  background-image:
    radial-gradient(ellipse at 50% 42%, rgba(255, 255, 245, 0.78) 0%,
      rgba(255, 255, 245, 0.28) 44%, transparent 74%),
    linear-gradient(145deg, rgba(var(--type-glow, 150, 163, 124), 0.28),
      rgba(var(--type-glow, 150, 163, 124), 0.68));
  overflow: hidden;
}
.plate img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  image-rendering: pixelated;
}
/* No portrait installed: the well keeps its size and states the number. */
.plate-num {
  font: var(--f-read) / 1 var(--font-panel);
  color: var(--plate-ink);
  letter-spacing: 0.04em;
}
/* The inside of the frame is exactly 112px: two screen pixels per sprite
   pixel for a cartridge portrait. */
.plate--bay { width: calc(var(--sprite-2x) + 2px); height: calc(var(--sprite-2x) + 2px); }


/* ==========================================================================
   3  CHROME
   ========================================================================== */

/* ---- top rail ----------------------------------------------------------- */
.rail {
  position: sticky;
  top: var(--rail-top, 0);
  z-index: 20;
  background: var(--panel-2);
  /* One hard bottom edge and no lip under it: the tabs, and the current tab's
     lit strip, sit flush on this hairline. Content passes behind it. */
  border-bottom: var(--hair) solid var(--rule);
}
.rail-inner {
  display: flex;
  align-items: stretch;
  gap: var(--s5);
  min-height: 56px;
}

.brandplate {
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex: none;
  color: var(--ink);
  text-decoration: none;
}
.brandplate:hover { text-decoration: none; }
.brandplate .mark {
  position: relative;
  width: 24px;
  height: 24px;
  flex: none;
  background: var(--signal);
  box-shadow: var(--key-inset);
}
/* The handheld mark, squared off: a screen and one button. */
.brandplate .mark::before {
  content: "";
  position: absolute;
  inset: 4px 4px 11px;
  background: var(--focus-halo);
}
.brandplate .mark::after {
  content: "";
  position: absolute;
  left: 9px;
  bottom: 4px;
  width: 6px;
  height: 4px;
  background: var(--focus-halo);
}
.brandplate .wordmark {
  display: block;
  font: var(--f-legend) / 1 var(--font-panel);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.brandplate .model {
  display: block;
  margin-top: var(--s1);
  font: var(--f-micro) / 1 var(--font-mono);
  letter-spacing: var(--track-micro);
  color: var(--ink-3);
}
.brandplate .model:empty { display: none; }

/* The adventure selector sits beside the brand, which returns to the Library.
   Absent in standalone mode. */
.breadcrumb {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--s1);
  flex: none;
  min-width: 0;
  padding-left: var(--s5);
  border-left: var(--hair) solid var(--rule-faint);
}
.breadcrumb > a {
  align-self: flex-start;
  font: var(--f-micro) / 1 var(--font-mono);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--ink-3);
  text-decoration: none;
}
.breadcrumb > a::before { content: "\2039\00a0"; }
.breadcrumb > a:hover { color: var(--ink); text-decoration: none; }
.breadcrumb-mark { display: none; }
.adventure-switch {
  position: relative;
  display: flex;
  align-items: center;
  min-width: 0;
  /* Pulled left so the name lines up with the text above while the select's
     hover box still has padding. The wrapper moves, so the select stays inside it. */
  margin-left: calc(-1 * var(--s2));
}
.adventure-switch select {
  appearance: none;
  min-width: 0;
  /* A fixed width: a select sizes to its longest option, which would widen it
     once routes.js lists the other adventures. */
  width: 13rem;
  height: 22px;
  padding: 0 var(--s5) 0 var(--s2);
  background: none;
  border: var(--hair) solid transparent;
  font: var(--f-legend) / 1 var(--font-panel);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink);
  cursor: pointer;
  transition: background var(--t), border-color var(--t);
}
.adventure-switch select:hover { background: var(--panel); border-color: var(--rule-faint); }
.adventure-switch select option { font: var(--f-label) / 1.4 var(--font-mono); text-transform: none; }
.adventure-switch::after {
  content: "\25BC";
  position: absolute;
  right: var(--s2);
  font: 8px / 1 var(--font-panel);
  color: var(--ink-3);
  pointer-events: none;
}

.rail nav:not(.breadcrumb) {
  display: flex;
  align-items: stretch;
  margin-left: auto;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
}
.rail nav:not(.breadcrumb)::-webkit-scrollbar { display: none; }
.rail nav:not(.breadcrumb) a {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  padding: 0 var(--s3);
  border-left: var(--hair) solid var(--rule-faint);
  font: var(--f-legend) / 1 var(--font-panel);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-2);
  text-decoration: none;
  transition: background var(--t), color var(--t);
}
.rail nav:not(.breadcrumb) a:last-child { border-right: var(--hair) solid var(--rule-faint); }
.rail nav:not(.breadcrumb) a:hover { background: var(--panel); color: var(--ink); }
/* The current tab is a depressed key with a lit strip under it: inset as well
   as coloured, and it carries aria-current. */
.rail nav:not(.breadcrumb) a[aria-current] {
  background: var(--well);
  color: var(--ink);
  box-shadow: inset 0 -3px 0 var(--signal), inset 1px 1px 0 var(--bevel-lo);
}

.connection {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex: none;
  font: var(--f-micro) / 1 var(--font-mono);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--ink-2);
  white-space: nowrap;
  /* One slot for every state, from Live to Reconnecting…, so the tabs beside it
     never move when the status changes. */
  width: 120px;
  justify-content: flex-end;
}
/* app.js only toggles a class; the lamp colour follows it. */
.connection .lamp { background: var(--ok-lamp); box-shadow: inset 0 0 0 1px var(--rule), 0 0 6px -1px var(--ok-lamp); }
.connection.is-paused .lamp { background: var(--warn-lamp); box-shadow: inset 0 0 0 1px var(--rule), 0 0 6px -1px var(--warn-lamp); animation: none; }
.connection.is-offline .lamp { background: var(--crit-lamp); box-shadow: inset 0 0 0 1px var(--rule), 0 0 6px -1px var(--crit-lamp); }

/* Copy view link: a small square key at the end of the rail, kept close to
   the status it sits beside. A "Copied" note hangs under it for a moment. */
.rail-key {
  position: relative;
  align-self: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 32px;
  height: 32px;
  margin-left: calc(var(--s2) - var(--s5));
  padding: 0;
  background: var(--panel-2);
  border: var(--hair) solid var(--rule);
  box-shadow: var(--key-inset);
  color: var(--ink-2);
  cursor: pointer;
  transition: background var(--t), color var(--t);
}
.rail-key:hover { background: var(--panel); color: var(--ink); }
.rail-key:active { box-shadow: var(--well-inset); background: var(--well); }
.rail-key-icon { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: square; }
.rail-key .glyph-done { display: none; }
.rail-key[data-copied] { color: var(--ok); }
.rail-key[data-copied] .glyph-link { display: none; }
.rail-key[data-copied] .glyph-done { display: inline; }
.rail-key-note {
  position: absolute;
  top: calc(100% + var(--s2));
  right: 0;
  padding: var(--s1) var(--s2);
  background: var(--panel);
  border: var(--hair) solid var(--rule);
  border-left: 3px solid var(--ok-lamp);
  box-shadow: var(--key-inset), 2px 2px 0 var(--bevel-lo);
  font: var(--f-micro) / 1.4 var(--font-mono);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--ink);
  white-space: nowrap;
  pointer-events: none;
}
.rail-key-note:empty { display: none; }

/* ---- identity strip ---------------------------------------------------
   Who, then the run's totals. Every stat is label over reading with the same
   two line boxes, so all six readings share one baseline whatever they hold. */
.identity {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: var(--s4) var(--s6);
  padding: var(--s6) 0 var(--s5);
}
.identity .who { min-width: 0; }
.identity h1 {
  margin-top: var(--s2);
  /* A touch of leading, so a title that wraps on a phone does not touch itself. */
  font: var(--f-title) / 1.1 var(--font-panel);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  overflow-wrap: anywhere;
}
.identity .stats {
  display: flex;
  margin-left: auto;
}
.stat {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  padding: 0 var(--s5);
  border-left: var(--hair) solid var(--rule-faint);
  min-width: 0;
}
.stat:first-child { padding-left: 0; border-left: 0; }
.stat:last-child { padding-right: 0; }
.stat .micro { white-space: nowrap; }
.stat .micro small { font-size: inherit; color: var(--ink-3); }

/* Eight gym badges as eight tall lamps, exactly the height of a reading. */
.badge-bank { display: flex; gap: var(--s1); height: 24px; }
.badge-bank .lamp { width: 10px; height: 24px; }

/* ---- footer plate ------------------------------------------------------- */
.plate-foot {
  margin-top: auto;
  background: var(--panel-2);
  border-top: var(--hair) solid var(--rule);
  box-shadow: inset 0 1px 0 var(--bevel-hi);
}
.plate-foot .shell {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s3) var(--s5);
  min-height: 56px;
  padding-block: var(--s3);
}
.plate-foot p { color: var(--ink-2); }
.plate-foot .links { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5); margin-left: auto; }
.plate-foot a,
.plate-foot .restart-link {
  font: var(--f-micro) / 1.4 var(--font-mono);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
}
/* Footer links leave the page (a feed or GitHub), so they carry the arrow they
   had before the restyle. It is drawn here and kept out of the accessible name. */
.plate-foot .links a::after { content: "\00A0\2197"; content: "\00A0\2197" / ""; }
.restart-link {
  padding: 0;
  background: none;
  border: 0;
  color: var(--crit);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

/* Theme control: three states, so "auto" is reachable. It is set once and
   forgotten, so it lives on the footer plate rather than on the rail. */
.theme {
  display: flex;
  background: var(--well);
  border: var(--hair) solid var(--rule);
  box-shadow: var(--well-inset);
}
.theme button {
  min-height: 32px;
  padding: 0 var(--s3);
  background: none;
  border: 0;
  border-left: var(--hair) solid var(--rule-faint);
  font: var(--f-micro) / 1 var(--font-mono);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--ink-2);
  cursor: pointer;
}
.theme button:first-child { border-left: 0; }
.theme button[aria-pressed="true"] { background: var(--panel); color: var(--signal); box-shadow: var(--key-inset); }


/* ==========================================================================
   4  LIVE PAGE
   ========================================================================== */

/* The screen is the thing you watch and the party is the thing you glance at,
   so the stage gets the wider column. The two columns start and end together:
   the ratio is set so six bays and the stage are the same height on a desktop,
   and whichever is shorter stretches the rest of the way. A short party spreads
   its bays; a short stage grows the dark well around the picture. */
.live-grid {
  display: grid;
  grid-template-columns: minmax(0, var(--stage-w)) minmax(0, 1fr);
  gap: var(--s5);
  align-items: stretch;
  /* Six 132px bays and their header make the party about 895px tall. The
     stage spends 268px on its header, bezel, deck and gamepad strip, which
     leaves 627px of 10:9 screen, 697px wide, plus 26px of bezel. */
  --stage-w: 723px;
}
.live-grid > * { min-width: 0; }
.watch-stage, .watch-companions, .party-section { display: flex; flex-direction: column; }
.watch-stage > .game-card, .watch-companions > .party-section { flex: 1; }

/* ---- the stage -------------------------------------------------------- */
.game-card { display: flex; flex-direction: column; min-width: 0; }
.game-card > .bezel { flex: 1; display: flex; flex-direction: column; }

.bezel {
  background: var(--bezel);
  border: var(--hair) solid var(--rule);
  padding: var(--s3);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06);
}
.bezel-head {
  display: flex;
  align-items: center;
  gap: var(--s3);
  min-height: 32px;
  padding: 0 var(--s1) var(--s3);
}
.bezel .on-air {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex: none;
  font: var(--f-micro) / 1 var(--font-mono);
  letter-spacing: var(--track-micro);
  color: var(--bezel-ink);
  text-transform: uppercase;
}
.bezel-head h2 {
  flex: 1;
  min-width: 0;
  font: var(--f-read) / 1 var(--font-panel);
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: #D6DCC6;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bezel-head .coords {
  flex: none;
  font: var(--f-label) / 1 var(--font-mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--bezel-ink);
  font-variant-numeric: tabular-nums;
}
/* The Gen II in-game clock stacks inside the place line, so it never adds a row and the place name
   keeps its room: day and time over the period on a wide screen, one per line on a phone. */
.bezel-head .game-clock {
  flex: none;
  display: grid;
  grid-template-columns: auto auto;
  column-gap: 0.5em;
  row-gap: 3px;
  justify-items: end;
  font: var(--f-micro) / 1 var(--font-mono);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--bezel-ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.bezel-head .game-clock[hidden] { display: none; }
.bezel-head #game-clock-period { grid-column: 1 / -1; }
.bezel-key {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  flex: none;
  padding: 0;
  background: none;
  border: var(--hair) solid #2B3222;
  color: var(--bezel-ink);
  font: var(--f-legend) / 1 var(--font-panel);
  cursor: pointer;
}
.bezel-key:hover { color: #D6DCC6; border-color: var(--bezel-ink); }

/* 160x144 is 10:9. The well holds that ratio at every width, so the
   connecting state and the live picture occupy the same rectangle. */
.screen-well {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 10 / 9;
  flex: none;
  width: 100%;
  background: #0A0C07;
  box-shadow: inset 0 0 0 2px #2B3222;
  overflow: hidden;
  container-type: inline-size;
}
.screen-well::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    repeating-linear-gradient(to right, rgba(142, 154, 107, 0.08) 0 1px, transparent 1px 4px),
    repeating-linear-gradient(to bottom, rgba(142, 154, 107, 0.08) 0 1px, transparent 1px 4px);
  pointer-events: none;
}
.screen-well img {
  position: relative;
  width: 100%;
  height: 100%;
  object-fit: contain;
  image-rendering: pixelated;
}
.screen-well img:not([src]) { visibility: hidden; }
/* Pixels stay square and sharp only at a whole multiple of 160x144, so from
   2x up the picture snaps down to one and the well's dark glass frames the
   rest. Stacked, the well itself snaps, so there is no frame to show. */
@supports (width: round(down, 1px, 1px)) {
  @container (min-width: 320px) {
    .screen-well img { width: round(down, 100cqi, 160px); height: auto; aspect-ratio: 10 / 9; }
  }
}
html:has(.screen-well:fullscreen) { scrollbar-gutter: auto }
.screen-well:fullscreen { aspect-ratio: auto }
.screen-well:fullscreen { display: flex }
.screen-well:fullscreen { align-items: center }
.screen-well:fullscreen { justify-content: center }
.screen-well:fullscreen { width: 100vw }
.screen-well:fullscreen { height: 100vh }
.screen-well:fullscreen { max-width: none }
.screen-well:fullscreen { max-height: none }
.screen-well:fullscreen { background: #000 }
.screen-well:fullscreen::before { display: none }
.screen-well:fullscreen img { min-width: 0 }
.screen-well:fullscreen img { min-height: 0 }
.screen-well:fullscreen { container-type: size }
@supports (width: round(down, 1px, 1px)) {
  .screen-well:fullscreen img { width: min(round(down, 100cqi, 160px), round(down, 100cqb * 10 / 9, 160px)) }
}
/* screen.js appends this to the well while the stream connects. */
.screen-status {
  position: absolute;
  inset: auto 0 50%;
  transform: translateY(50%);
  text-align: center;
  font: var(--f-legend) / 1.6 var(--font-panel);
  letter-spacing: var(--track-legend);
  text-transform: uppercase;
  color: var(--bezel-ink);
}

/* If the stage is ever the shorter column, the surplus is dark bezel under
   the caption; the picture itself always keeps its 10:9. */
.bezel-foot {
  flex: 1;
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--s1) var(--s4);
  padding: var(--s3) var(--s1) 0;
  font: var(--f-micro) / 1.4 var(--font-mono);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--bezel-ink);
}
.bezel-foot .activity { color: #B9C29F; }

/* In manual play the housing carries a signal ring, so there is no doubt at
   a glance about who is holding the controls. */
.game-card.is-manual .bezel { outline: 2px solid var(--signal-lamp); outline-offset: -1px; }
.game-card.is-manual .on-air .lamp { background: var(--signal-lamp); }

/* ---- the deck: operator controls below the housing -------------------- */
.deck {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s2);
  padding: var(--s3);
  background: var(--panel);
  border: var(--hair) solid var(--rule);
  border-top: 0;
  box-shadow: var(--key-inset);
}
.bezel-foot .mode {
  display: flex;
  flex: none;
  align-items: center;
  gap: var(--s2);
  margin-left: auto;
  font: var(--f-micro) / 1.4 var(--font-mono);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: #B9C29F;
}
.game-card.is-manual .bezel-foot .mode { color: var(--signal-lamp); }
.game-card.is-manual .bezel-foot .mode .lamp { background: var(--signal-lamp); }



.gamepad {
  background: var(--panel-2);
  border: var(--hair) solid var(--rule);
  border-top: 0;
}
.gamepad > summary {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-height: 40px;
  padding: 0 var(--s3);
  font: var(--f-micro) / 1.4 var(--font-mono);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--ink-2);
  cursor: pointer;
  list-style: none;
}
.gamepad > summary::-webkit-details-marker { display: none; }
.gamepad > summary::before { content: "\25B6"; font: 8px / 1 var(--font-panel); color: var(--ink-3); }
.gamepad[open] > summary::before { content: "\25BC"; }
.gamepad > summary:hover { color: var(--ink); }
.pad-body {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s5);
  padding: var(--s4);
  border-top: var(--hair) solid var(--rule-faint);
}
.dpad { display: grid; grid-template-columns: repeat(3, 48px); grid-auto-rows: 48px; gap: 2px; }
.abxy { display: grid; grid-template-columns: repeat(2, auto); gap: var(--s2); }
.dpad button,
.abxy button {
  display: grid;
  place-items: center;
  min-width: 40px;
  height: 40px;
  padding: 0 var(--s3);
  background: var(--panel);
  border: var(--hair) solid var(--rule);
  box-shadow: var(--key-inset);
  font: var(--f-legend) / 1 var(--font-panel);
  text-transform: uppercase;
  color: var(--ink);
  cursor: pointer;
}
.dpad button:active,
.abxy button:active { background: var(--well); box-shadow: var(--well-inset); }
.pad-help { flex: 1 1 200px; display: grid; gap: var(--s2); }
.keymap-title { font: var(--f-label) / 1.3 var(--font-mono); letter-spacing: var(--track-micro); text-transform: uppercase; color: var(--ink-2); }
.keymap { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--s2) var(--s4); margin: 0; }
.keymap > div { display: flex; align-items: center; justify-content: space-between; gap: var(--s2); }
.keymap dt { font: var(--f-legend) / 1 var(--font-panel); text-transform: uppercase; color: var(--ink); }
.keymap dd { display: flex; gap: 4px; margin: 0; }
.keymap kbd { display: inline-grid; place-items: center; min-width: 26px; height: 26px; padding: 0 6px; background: var(--panel); border: var(--hair) solid var(--rule); box-shadow: var(--key-inset); font: 12px / 1 var(--font-mono); color: var(--ink); }

/* ---- the plan ----------------------------------------------------------
   The machine's statement of intent, directly under the picture it
   describes. Headline across the top; state and next step side by side. */
.plan {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr);
  margin-top: var(--s5);
  background: var(--panel);
  border: var(--hair) solid var(--rule);
  border-left: 3px solid var(--signal);
  box-shadow: var(--key-inset);
}
.plan > div { display: grid; align-content: start; gap: var(--s2); padding: var(--s4); min-width: 0; }
.plan .plan-state, .plan .plan-next { border-left: var(--hair) solid var(--rule-faint); }
.plan .headline {
  font: var(--f-read) / 1.2 var(--font-panel);
  letter-spacing: 0.03em;
  text-transform: uppercase;
}
.plan .body {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  max-width: 64ch;
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink-2);
}
.plan .body:empty { display: none; }
.plan .value {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font: var(--f-legend) / 1.25 var(--font-panel);
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
.plan .note { font-size: 13px; line-height: 1.45; color: var(--ink-3); }

/* ---- the vision agent's working notes -------------------------------- */
.agent-panel {
  margin-top: var(--s4);
  background: var(--panel);
  border: var(--hair) solid var(--rule);
  border-left: 3px solid var(--signal);
  box-shadow: var(--key-inset);
}
.agent-panel > .column-head { padding: var(--s3) var(--s4) 0; }
.agent-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr) minmax(0, 1fr);
}
.agent-block { display: grid; align-content: start; gap: var(--s2); padding: var(--s4); min-width: 0; }
.agent-block + .agent-block { border-left: var(--hair) solid var(--rule-faint); }
.agent-block .body { font-size: 14px; line-height: 1.5; color: var(--ink-2); overflow-wrap: anywhere; }
.agent-plan { display: grid; gap: var(--s1); margin: 0; padding-left: 1.2em; font-size: 14px; line-height: 1.45; color: var(--ink-2); }
.agent-plan li::marker { color: var(--signal); }
.agent-meta { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4); padding: 0 var(--s4) var(--s4); }
.agent-error { margin: 0 var(--s4) var(--s4); color: var(--ink); }
@media (max-width: 860px) {
  .agent-grid { grid-template-columns: minmax(0, 1fr); }
  .agent-block + .agent-block { border-left: 0; border-top: var(--hair) solid var(--rule-faint); }
}
.stat .note { display: block; margin-top: var(--s1); font: var(--f-micro) / 1.3 var(--font-mono); letter-spacing: var(--track-micro); color: var(--ink-3); }
.stat .note[hidden] { display: none; }
.egg-plate { display: grid; place-items: center; width: 100%; height: 100%; color: var(--plate-ink); }
.egg-plate svg { width: 60%; height: auto; max-width: 64px; image-rendering: pixelated; }

/* ---- party ------------------------------------------------------------ */
.column-head {
  position: sticky;
  top: var(--rail-h);
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  min-height: 57px;
  margin-bottom: var(--s2);
  border-bottom: var(--hair) solid var(--rule);
  background: var(--chassis);
  background-image: radial-gradient(var(--stipple) 0.5px, transparent 0.5px);
  background-size: 3px 3px;
}
/* The stage's header is the same height but does not stick: the screen is
   always in view on its own. */
.game-card > .column-head { position: static; }
.column-head h2 { font-size: var(--f-legend); }

.party { flex: 1; display: grid; grid-auto-rows: 1fr; gap: var(--s2); }

/* ---- a bay -------------------------------------------------------------
   Portrait, then a body of three rows: who, what, how they are doing.
   The whole bay opens the partner's details; the arrow only marks it. */
.mon {
  position: relative;
  container: bay / inline-size;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  background: var(--panel);
  border: var(--hair) solid var(--rule);
  box-shadow: var(--key-inset);
  transition: background var(--t);
}
.mon:has(.mon-open):hover { background: color-mix(in srgb, var(--panel) 70%, var(--panel-2)); }
.mon-plate { padding: var(--s2); border-right: var(--hair) solid var(--rule-faint); }
.mon-body { display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: auto auto 1fr; min-width: 0; }

.mon-head {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-height: 40px;
  padding: 0 var(--s2) 0 var(--s3);
  border-bottom: var(--hair) solid var(--rule-faint);
}
.mon-head .slotno { font: var(--f-micro) / 1 var(--font-mono); letter-spacing: var(--track-micro); color: var(--ink-3); }
.mon-head .name {
  min-width: 0;
  font: var(--f-legend) / 1 var(--font-panel);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mon-head .spacer { flex: 1; }
.mon-head .lv {
  font: var(--f-read) / 1 var(--font-panel);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.mon-head .lv em {
  margin-right: 2px;
  font: var(--f-micro) / 1 var(--font-mono);
  font-style: normal;
  letter-spacing: var(--track-micro);
  color: var(--ink-3);
}
.dv { display: flex; gap: 3px; }
.dv .lamp { width: 7px; height: 7px; }

/* The key stretches over the whole bay, so the hit target is the bay. */
.mon-open {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  flex: none;
  padding: 0;
  background: none;
  border: var(--hair) solid transparent;
  font: var(--f-legend) / 1 var(--font-panel);
  color: var(--ink-3);
  cursor: pointer;
}
.mon-open::after { content: ""; position: absolute; inset: 0; }
.mon-open:focus-visible { box-shadow: none; outline: 0; }
.mon-open:focus-visible::after { outline: 2px solid var(--focus); outline-offset: 2px; }
.mon:hover .mon-open { color: var(--signal); border-color: var(--rule-faint); background: var(--panel-2); }

.mon-id {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-width: 0;
  flex-wrap: wrap;
  min-height: 32px;
  padding: var(--s1) var(--s3);
  border-bottom: var(--hair) solid var(--rule-faint);
}
.mon-id .micro { color: var(--ink-2); white-space: nowrap; }

.mon-lower { display: grid; grid-template-columns: minmax(0, 1fr); }
/* One grid for both rows, so the HP and XP meters start and end together
   whatever width their readings take. */
.mon-meters {
  display: grid;
  grid-template-columns: auto minmax(32px, 1fr) auto;
  align-content: center;
  align-items: center;
  gap: var(--s2);
  padding: var(--s2) var(--s3);
}
.meter-row { display: contents; }
.meter-row .micro { color: var(--ink-2); }
.meter-row .value {
  font: var(--f-legend) / 1 var(--font-panel);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}

/* Moves are hidden until the bay is wide enough to hold them whole; the
   container queries in section 6 decide how they sit. */
.mon-moves { display: none; align-content: center; }
.move {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  min-width: 0;
  padding: 3px var(--s3);
  font: var(--f-micro) / 1.4 var(--font-mono);
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.move .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.move .pp { margin-left: auto; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.move .pp.empty { color: var(--crit); }
.no-moves { padding: 3px var(--s3); font: var(--f-micro) / 1.4 var(--font-mono); color: var(--ink-3); }

.mon--fainted { background: var(--panel-2); }
.mon--fainted .name,
.mon--fainted .lv { color: var(--ink-3); }
.mon--fainted .plate img { filter: grayscale(1) opacity(0.6); }

/* An open slot is a recess with a dashed tray, not a card. */
.mon--empty { background: var(--panel-2); box-shadow: var(--well-inset); border-color: var(--rule-faint); }
.mon--empty .plate { background: transparent; border-style: dashed; box-shadow: none; }
.mon--empty .plate-num { color: var(--ink-3); }
.mon--empty .mon-body { display: flex; flex-direction: column; justify-content: center; gap: var(--s1); padding: 0 var(--s4); }
.mon--empty .mon-body p:last-child { font-size: 14px; color: var(--ink-2); }


/* ==========================================================================
   5  OVERLAYS
   ========================================================================== */

.confirm-sheet { overscroll-behavior: contain; }

.toast {
  position: fixed;
  left: 50%;
  bottom: calc(var(--s5) + env(safe-area-inset-bottom, 0px));
  z-index: 50;
  transform: translateX(-50%);
  max-width: calc(100% - 2 * var(--s4) - env(safe-area-inset-left, 0px) - env(safe-area-inset-right, 0px));
  padding: var(--s3) var(--s4);
  background: var(--panel);
  border: var(--hair) solid var(--rule);
  border-left: 3px solid var(--ok-lamp);
  box-shadow: var(--key-inset), 4px 4px 0 var(--bevel-lo);
  font-size: 14px;
}
/* A question that guards something hard to undo, such as a rewind. Cancel is
   the first key and takes focus, so Enter never confirms by accident. */
.confirm-sheet {
  width: min(480px, calc(100% - 2 * var(--s4)));
  max-height: calc(100dvh - 2 * var(--s5));
  padding: 0;
  background: var(--panel);
  color: var(--ink);
  border: var(--hair) solid var(--rule);
  box-shadow: var(--key-inset), 6px 6px 0 var(--bevel-lo);
}
.confirm-sheet::backdrop { background: rgba(10, 12, 10, 0.55); }
.confirm-sheet form { display: grid; gap: var(--s3); padding: var(--s5); }
.confirm-sheet h2 { font: var(--f-read) / 1.2 var(--font-panel); }
.confirm-sheet p { color: var(--ink-2); }
.confirm-sheet strong { color: var(--ink); font-variant-numeric: tabular-nums; }
.confirm-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--s2); margin-top: var(--s3); }
.confirm-actions .key { min-height: 44px; }
@media (max-width: 400px) {
  .confirm-actions .key { flex: 1 1 100%; white-space: normal; }
}
.toast.error { border-left-color: var(--crit-lamp); color: var(--crit); }


/* ==========================================================================
   6  RESPONSIVE
   Every stylesheet shares five breakpoints: 400, 640, 860, 1100 and 1280px.
   tools/stylelint/breakpoints.mjs lists them and the CSS lint rejects any
   other width. Sizes that depend on a box rather than the window use a
   container query instead.
     1279px  an adventure's tabs take a row of their own, and the stage and
             the party stack. The party goes two bays abreast so it does not
             become a column of very wide, empty strips.
     1100px  the page gutter narrows.
      860px  the Library's tabs take a row of their own.
      640px  the phone. The rail stops being sticky and hands that job to the
             tab strip alone; everything goes single file.
      400px  the smallest phones, which get the tightest type and spacing.
   A bay also answers to its own width, so it is right in either layout.
   ========================================================================== */

/* Moves join the bay only where they fit as a 2x2 beside the meters, so a
   bay is the same height at every width and six of them stack predictably. */
@container bay (min-width: 560px) {
  .mon-lower { grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); }
  .mon-meters { border-right: var(--hair) solid var(--rule-faint); }
  .mon-moves { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .move { padding-block: 3px; }
  .move:nth-child(odd) { border-right: var(--hair) solid var(--rule-faint); }
}
@container bay (max-width: 500px) {
  .mon-head .slotno, .mon-head .dv, .mon-id .dexno { display: none; }
  .mon-head .lv { font-size: var(--f-legend); }
}
/* A phone-width bay fits ten-letter names (Charmeleon, Feraligatr, SHRIMPDESK)
   once the name's tracking comes in, the type steps down and the level drops its prefix. */
@container bay (max-width: 400px) {
  .mon-head { gap: var(--s1); padding-left: var(--s2); }
  .mon-head .name { font-size: 13px; letter-spacing: 0; }
  .mon-head .lv { font-size: 13px; }
  .mon-head .lv em { display: none; }
}
/* On the narrowest phones the name gets its own line rather than two letters. */
@container bay (max-width: 300px) {
  .mon-head { flex-wrap: wrap; row-gap: var(--s1); }
  .mon-head .name { flex: 1 0 100%; }
  /* The readings (HP 123/123) step down so the meters keep a usable track. */
  .mon-meters { grid-template-columns: auto minmax(16px, 1fr) auto; gap: var(--s1); padding-inline: var(--s2); }
  .meter-row .value { font-size: 12px; }
}

/* An adventure's six tabs share one line with the brand, the switcher and the
   status only from 1280px. The Library's three fit down to 861px. Narrower,
   the tabs take their own full-width row instead of being clipped. */
@media (max-width: 1279px) {
  body:not(.library) .rail-inner { flex-wrap: wrap; column-gap: var(--s4); }
  body:not(.library) .rail nav:not(.breadcrumb) {
    order: 3;
    width: 100%;
    margin-left: 0;
    border-top: var(--hair) solid var(--rule-faint);
  }
  body:not(.library) .rail nav:not(.breadcrumb) a { flex: 1 0 auto; justify-content: center; min-height: 44px; }
  body:not(.library) .rail nav:not(.breadcrumb) a:first-child { border-left: 0; }
  body:not(.library) .rail nav:not(.breadcrumb) a:last-child { border-right: 0; }
  body:not(.library) .connection { margin-left: auto; }
  .rail-key { margin-left: calc(var(--s2) - var(--s4)); }

  body:not(.library) .brandplate { min-height: 56px; }
}
@media (max-width: 860px) {
  .library .rail-inner { flex-wrap: wrap; column-gap: var(--s4); }
  .library .rail nav:not(.breadcrumb) { order: 3; width: 100%; margin-left: 0; border-top: var(--hair) solid var(--rule-faint); }
  .library .rail nav:not(.breadcrumb) a { flex: 1 0 auto; justify-content: center; min-height: 44px; }
  .library .rail nav:not(.breadcrumb) a:first-child { border-left: 0; }
  .library .rail nav:not(.breadcrumb) a:last-child { border-right: 0; }
  .library .connection { margin-left: auto; }
  .library .brandplate { min-height: 56px; }
}

@media (max-width: 1100px) {
  .shell { padding-inline: var(--s5); }

  .identity .stats { margin-left: 0; width: 100%; }
}

/* Sixteen Gen II badges and five readings do not fit one row below 1100px,
   so between a phone and a desktop the badges take the top row and the five
   readings share the row beneath, each column as wide as its reading. Only the
   live page's summary has that shape; the PC's two readings stay one row. */
@media (min-width: 641px) and (max-width: 1100px) {
  #journey-progress .stats {
    display: grid;
    grid-template-columns: repeat(5, minmax(max-content, 1fr));
    row-gap: var(--s4);
  }
  #journey-progress .stat { padding: 0 var(--s3); }
  #journey-progress .stat:first-child { grid-column: 1 / -1; padding-left: 0; }
  #journey-progress .stat:nth-child(2) { padding-left: 0; border-left: 0; }
}
@media (min-width: 641px) and (max-width: 860px) {
  #journey-progress .stat .readout { font-size: var(--f-legend); }
}

/* Side by side needs the fixed stage plus a party column wide enough for a
   bay to stay one line per row, so below 1280px the stage and the party
   stack, and the party goes two abreast while it can. Every row still spans
   the full shell, so edges line up down the page. */
@media (max-width: 1279px) {
  .live-grid { grid-template-columns: minmax(0, 1fr); gap: var(--s6); }
  .game-card .bezel { align-items: center; }
  .bezel-head, .bezel-foot { width: 100%; }
  .screen-well { max-width: 640px; }
  @supports (width: round(down, 1px, 1px)) {
    .screen-well { width: max(min(100%, 320px), round(down, 100%, 160px)); }
  }
  .party { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .plan { margin-top: var(--s6); }
}
@media (max-width: 860px) {
  /* Narrow, the plan's headline takes the full row above state and next. */
  .plan { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .plan .plan-now { grid-column: 1 / -1; border-bottom: var(--hair) solid var(--rule-faint); }
  .plan .plan-state { border-left: 0; }
}

@media (max-width: 640px) {
  .party { grid-template-columns: minmax(0, 1fr); }
}

/* A phone on its side is wide but short. panel.js pins only the tab strip, as
   on a phone held upright, and the screen drops to 2x so all of it fits below. */
@media (max-width: 1279px) and (max-height: 500px) {
  .screen-well { max-width: 320px; }
}

@media (max-width: 640px) {
  .shell { padding-inline: var(--s4); }

  /* panel.js pins the rail at a negative offset here, so the brand and the
     adventure slot scroll away and only the tab strip stays on screen. */
  .rail-inner { min-height: 0; row-gap: 0; }
  .brandplate { min-height: 52px; }
  /* One line on a phone: the way back, then the switcher filling the rest as
     a full-size touch target. */
  .breadcrumb { order: 2; flex-direction: row; align-items: center; gap: var(--s3); width: 100%; padding: 0 0 var(--s2); border-left: 0; }
  .breadcrumb > a { align-self: center; padding: var(--s3) 0; }
  .adventure-switch { flex: 1; margin-left: 0; }
  /* The adventure's name is read in the text face, so a long one fits
     instead of being cut down to a few capitals and an ellipsis. */
  .adventure-switch select { width: 100%; height: 44px; font: 600 15px / 1.2 var(--font-text); letter-spacing: 0; text-transform: none; background: var(--well); border-color: var(--rule); box-shadow: var(--well-inset); }
  /* The tabs are a strip that scrolls sideways, inset by the page gutter, so
     each keeps its own width and none of them touch the screen edge. */
  .rail nav:not(.breadcrumb) {
    margin-inline: calc(-1 * var(--s4));
    width: calc(100% + 2 * var(--s4));
    padding-inline: var(--s4);
    gap: var(--s1);
    scroll-padding-inline: var(--s4);
    overscroll-behavior-x: contain;
  }
  .rail nav:not(.breadcrumb) a,
  .rail nav:not(.breadcrumb) a:first-child,
  .rail nav:not(.breadcrumb) a:last-child {
    flex: 0 0 auto;
    padding: 0 var(--s3);
    border: var(--hair) solid var(--rule-faint);
    border-bottom: 0;
    font: 500 14px / 1 var(--font-text);
    letter-spacing: 0;
    text-transform: none;
  }
  /* The Library has only three tabs, so on a 320px phone they tighten to fit
     on one line rather than scroll. */
  @media (max-width: 400px) {
    body.library .rail nav:not(.breadcrumb) a,
    body.library .rail nav:not(.breadcrumb) a:first-child,
    body.library .rail nav:not(.breadcrumb) a:last-child { padding: 0 var(--s2); font-size: 13px; }
  }

  .identity { padding: var(--s5) 0 var(--s4); gap: var(--s4); }
  .identity h1 { font-size: var(--f-read); }
  /* The totals become a milled bank of cells rather than a wrapping row. */
  .identity .stats {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    background: var(--panel);
    border: var(--hair) solid var(--rule);
    box-shadow: var(--key-inset);
  }
  .stat { padding: var(--s3); border-left: var(--hair) solid var(--rule-faint); border-top: var(--hair) solid var(--rule-faint); }
  .stat:first-child { grid-column: 1 / -1; border-top: 0; padding: var(--s3); }
  .stat:nth-child(even) { border-left: 0; }
  .stat:last-child { padding-right: var(--s3); }
  /* Badges span the top, so a fifth reading would sit alone in half a row. */
  #journey-progress .stat:last-child:nth-child(even) { grid-column: 1 / -1; }
  .stat .readout { font-size: var(--f-legend); }
  .badge-bank { height: 16px; }
  .badge-bank .lamp { flex: 1; max-width: 24px; height: 16px; }

  .live-grid { gap: var(--s5); }
  /* The picture runs to the bezel's edges, so a 360px phone still gets a
     sharp 2x screen. The head and the caption keep their inset. */
  .bezel { padding: var(--s2) 0; }
  .bezel-head { padding: 0 var(--s2) var(--s2); }
  .bezel-foot { padding: var(--s2) var(--s2) 0; }

  .plan { grid-template-columns: minmax(0, 1fr); }
  .plan .plan-next { border-left: 0; border-top: var(--hair) solid var(--rule-faint); }
  .plan .headline { font-size: var(--f-legend); line-height: 1.35; }

  .column-head { min-height: 48px; }

  .plate-foot .links { margin-left: 0; }

  /* Touch targets: every key a thumb can reach is at least 40px. The status
     gives up its fixed slot, since the tabs no longer sit beside it, so the
     copy link key stays on the brand's row. */
  .connection { width: auto; }
  .rail-key, .bezel-key { width: 40px; height: 40px; }
  .theme button { min-height: 40px; }
  .plate-foot a, .plate-foot .restart-link { display: inline-flex; align-items: center; min-height: 40px; }
}


/* ==========================================================================
   7  MOTION AND CONTRAST PREFERENCES
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0s !important;
    scroll-behavior: auto !important;
  }
  .lamp--blink { opacity: 1; }
}

@media (prefers-contrast: more) {
  :root,
  [data-theme] {
    --rule: var(--ink);
    --rule-faint: var(--ink-2);
    --ink-3: var(--ink-2);
  }
}

@media (forced-colors: active) {
  .panel, .key, .mon, .plan, .deck, .gamepad {
    border: 1px solid CanvasText;
    box-shadow: none;
  }
  .lamp { border: 1px solid CanvasText; background: Canvas; }
  .lamp[data-on]:not([data-on=""]) { background: Highlight; }
  .meter i.on { background: Highlight; }
  .meter i { background: Canvas; }
  .rail nav:not(.breadcrumb) a[aria-current] { box-shadow: none; outline: 2px solid Highlight; outline-offset: -2px; }
}


/* The live speed selector shares the compact control row. */
.live-speed { display: inline-flex }
.live-speed { align-items: center }
.live-speed { gap: var(--s2) }
.live-speed { font: var(--f-label) / 1.3 var(--font-mono) }
.live-speed select { width: auto }
.live-speed select { min-height: 36px }
@media (max-width: 640px) {
  .live-speed select { min-height: 44px }
}
.live-speed select { min-width: 80px }
.live-speed select { padding: 8px }
.live-speed select { background: var(--panel-2) }
.live-speed select { color: var(--ink) }
.live-speed select { border: var(--hair) solid var(--rule) }
.live-speed select { border-radius: 0 }
.live-speed select { font: var(--f-legend) / 1 var(--font-panel) }

/* Game controls own touch gestures and remain usable while held. */
.controller [data-b] { touch-action: none }
.controller [data-b] { user-select: none }
.controller [data-b] { -webkit-user-select: none }
.controller [data-b] { -webkit-touch-callout: none }
.controller [data-b] { min-width: 48px }
.controller [data-b] { min-height: 48px }
.controller [data-b].is-held { background: var(--signal) }
.controller [data-b].is-held { color: var(--panel) }

.shiny-badge { color: var(--signal) }
.shiny-badge { border: 1px solid currentColor }

/* ---- live controls: one even grid of identical keys --------------------
   Every control is the same height and type. Wide enough for each key to
   hold its label: one row of equal keys. Otherwise an even grid (3 columns,
   then 2 on phones) with Take control spanning so no row is ragged. A label
   that still does not fit wraps inside its key instead of clipping or
   pushing the key wider. Status text gets its own full-width line. */
.deck.controls {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-items: stretch;
  gap: var(--s2);
}
.deck.controls > * { min-width: 0; margin: 0; }
.deck.controls .key--primary { grid-column: span 2; }
.deck.controls:has(#export-save) .key--primary { grid-column: auto; }
.deck.controls .key,
.deck.controls .live-speed select {
  width: 100%;
  height: 44px;
  min-height: 44px;
  padding: 0 6px;
  font: var(--f-legend) / 1.15 var(--font-panel);
  font-size: 12px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  text-align: center;
  white-space: normal;
  overflow-wrap: anywhere;
}
.deck.controls .live-speed { display: block; width: 100%; }
.deck.controls .live-speed select {
  -webkit-appearance: none;
  appearance: none;
  min-width: 0;
  padding: 0 24px 0 6px;
  text-align-last: center;
  background: var(--panel-2) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0h10L5 6z' fill='%23555'/%3E%3C/svg%3E") no-repeat right 9px center;
  border: var(--hair) solid var(--rule);
  border-radius: 0;
  box-shadow: var(--key-inset);
  color: var(--ink);
  cursor: pointer;
}
.deck.controls #sound-status {
  grid-column: 1 / -1;
  text-align: center;
  font: var(--f-micro) / 1.4 var(--font-mono);
  color: var(--ink-2);
}
.deck.controls #sound-status[hidden] { display: none; }

/* One row of five equal keys once each key is about 130px wide. */
@media (min-width: 861px) {
  .deck.controls:not(:has(#export-save)) { grid-template-columns: repeat(5, minmax(0, 1fr)); }
  .deck.controls:not(:has(#export-save)) .key--primary { grid-column: auto; }
}
@media (max-width: 640px) {
  .deck.controls,
  .deck.controls:has(#export-save) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .deck.controls .key--primary,
  .deck.controls:has(#export-save) .key--primary,
  .deck.controls #export-save { grid-column: 1 / -1; }
}
/* Long Gen 2 places (Azalea Pokémon Center 1F) need the smaller type to read on a phone. */
@media (max-width: 640px) {
  .bezel-head { gap: var(--s2); }
  .bezel-head h2 { font-size: 12px; letter-spacing: 0; }
  /* The blinking lamp still says live, and the place name gets the word's room. */
  .bezel-head .on-air-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  .bezel-head .coords { font-size: var(--f-micro); letter-spacing: 0; }
  /* On a phone the clock uses the game's short period words and takes the coordinates' place. */
  .bezel-head .game-clock { grid-template-columns: auto; row-gap: 0; line-height: 10px; letter-spacing: 0; }
  .bezel-head #game-clock-period { font-size: 0; }
  .bezel-head #game-clock-period:not(:empty)::before { content: attr(data-short); font-size: var(--f-micro); vertical-align: top; }
  .bezel-head:has(.game-clock:not([hidden])) .coords { display: none; }
}
@media (max-width: 860px) {
  .bezel-foot { flex-wrap: wrap; gap: var(--s1) var(--s3); }
  .bezel-foot .mode { margin-left: 0; width: 100%; }
}

/* Grey stand-ins for cards while a page's first data loads, so the frame paints at once. */
.skeleton-card {
  min-height: 180px;
  background: var(--panel-2);
  border: var(--hair) solid var(--rule-faint);
  animation: skeleton-pulse 1.4s ease-in-out infinite;
}
.dex-grid .skeleton-card { min-height: 236px; }
@keyframes skeleton-pulse { 50% { opacity: .55; } }
