/* Gauntlet is one landscape scene that fills the Activity frame and never
   scrolls as a page. The layout is five regions (top bar, left rail,
   stage, right rail, command bar): a grid in town, and on the map and in a
   fight a full-screen stage with the rest floating over it (see Immersive
   below). The stage is a size container so the 16×16
   board is always the largest square that fits, whatever the window's aspect
   ratio. Lists that can grow scroll inside their own panel. */

/* SAFE AREAS COME FROM DISCORD FIRST AND `env()` SECOND, AND BOTH HALVES ARE
   LOAD-BEARING. Discord injects `--discord-safe-area-inset-*` into the frame on
   mobile, which is the only source that knows about ITS chrome, where `env()`
   knows only about the notch. `env()` is the fallback for iOS and for a plain
   browser during development, and 0px is the fallback for desktop. */
:root {
  --safe-top: var(--discord-safe-area-inset-top, env(safe-area-inset-top, 0px));
  --safe-right: var(--discord-safe-area-inset-right, env(safe-area-inset-right, 0px));
  --safe-bottom: var(--discord-safe-area-inset-bottom, env(safe-area-inset-bottom, 0px));
  --safe-left: var(--discord-safe-area-inset-left, env(safe-area-inset-left, 0px));

  color-scheme: dark;

  --bg: #0e1418;
  --panel: #192026;
  --panel-raised: #2b393a;
  --card: #232e32;
  --line: #4a5760;
  --ink: #e1e4d3;
  --ink-dim: #a7b6b1;

  --gold: #d59b3d;
  --hp: #c84136;
  --mana: #477ca5;
  --good: #6eaa4a;
  --ally: #477c5c;
  --enemy: #8b4f47;

  /* Region sizes. The stage's height is derived from them so the board column
     can be exactly as wide as the board is tall. */
  --pad: 0.5rem;
  --gap: 0.5rem;
  --top-h: 2.75rem;
  --bar-h: 3.75rem;
  --stage-h: calc(100dvh - var(--safe-top) - var(--safe-bottom) - 2 * var(--pad) - var(--top-h) - var(--bar-h) - 2 * var(--gap));
}

@font-face {
  font-family: 'Sigil Pixel';
  src: url('/assets/fonts/m5x7/m5x7.ttf') format('truetype');
  font-display: swap;
  unicode-range: U+0020-007E, U+00B0, U+00B7, U+00D7;
}

* {
  box-sizing: border-box;
}

/* One type scale for every window: about 11px on a landscape phone, up to 17px
   on a large monitor. Everything below is in rem so the scene scales as one. */
html {
  font-size: clamp(11px, 0.55vw + 0.75vh + 3px, 17px);
}

html,
body {
  height: 100%;
  overflow: hidden;
  /* An overscroll on iOS detaches the content from Discord's own frame and reads
     as the Activity coming loose. */
  overscroll-behavior: none;
}

body {
  margin: 0;
  font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
  background: radial-gradient(ellipse at top, #2b393a, var(--bg) 70%);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  /* A long-press on a phone must not select text or flash a grey box. */
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
  user-select: none;
  -webkit-text-size-adjust: 100%;
}

#app {
  position: fixed;
  inset: 0;
  overflow: hidden;
  display: grid;
  padding: calc(var(--pad) + var(--safe-top)) calc(var(--pad) + var(--safe-right))
    calc(var(--pad) + var(--safe-bottom)) calc(var(--pad) + var(--safe-left));
}

.status {
  place-self: center;
  color: var(--ink-dim);
  text-align: center;
  font-size: 1rem;
}

.status.error {
  color: var(--hp);
}

h1,
h2,
h3 {
  font-family: 'Sigil Pixel', monospace;
  font-weight: normal;
  margin: 0;
}

p {
  margin: 0;
  line-height: 1.4;
}

.muted {
  color: var(--ink-dim);
}

.good {
  color: var(--good);
}

.bad {
  color: var(--hp);
}

/* ---------------------------------------------------------------- Scene */

.scene {
  position: relative;
  display: grid;
  /* On an ultrawide window the rails stop growing at 30rem and the scene
     centres, instead of stretching lists across the whole monitor. */
  width: 100%;
  max-width: calc(var(--stage-h) + 60rem + 2 * var(--gap));
  margin-inline: auto;
  min-width: 0;
  min-height: 0;
  gap: var(--gap);
  grid-template-rows: var(--top-h) minmax(0, 1fr) var(--bar-h);
  /* The stage track may grow to the board's height and no further; the rails
     share what is left. On a wide window that gives the rails room, on a narrow
     one the board shrinks before a rail drops below its minimum. */
  grid-template-columns: minmax(14rem, 1fr) minmax(0, var(--stage-h)) minmax(14rem, 1fr);
  grid-template-areas:
    'top top top'
    'left stage right'
    'bar bar bar';
}

.scene.busy {
  cursor: progress;
}

.top-bar {
  grid-area: top;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
  padding: 0 0.25rem;
  border-bottom: 1px solid var(--line);
}

.top-bar h1 {
  flex: none;
  font-size: 2rem;
  letter-spacing: 0.06em;
  color: var(--gold);
}

.top-context {
  display: flex;
  flex: 0 1 auto;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
  overflow: hidden;
}

/* The last chip (the tourney and its decree) is the one that gives way. */
.top-context > .chip:last-child {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.top-context > .chip:not(:last-child) {
  flex: none;
}

.chip {
  padding: 0.15rem 0.5rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  color: var(--ink-dim);
  font-size: 0.8rem;
  white-space: nowrap;
}

/* A great boss's bar takes the middle of the top bar in a boss fight: name
   and element badges, a wide health bar with phase ticks, and the stun bar
   under it. The board keeps its size. */

/* The notice has a fixed slot so a message never pushes the scene around. */
.notice {
  flex: 1;
  min-width: 0;
  padding: 0.3rem 0.6rem;
  border-left: 3px solid var(--good);
  background: var(--card);
  font-size: 0.85rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  -webkit-user-select: text;
  user-select: text;
}

.notice.bad {
  border-left-color: var(--hp);
  color: var(--ink);
}

.notice.empty {
  visibility: hidden;
}

.top-user {
  flex: none;
  max-width: 10rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-dim);
  font-size: 0.85rem;
}

.rail {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}

.rail-left {
  grid-area: left;
}

.rail-right {
  grid-area: right;
}

.stage {
  grid-area: stage;
  position: relative;
  display: grid;
  place-items: center;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  container-type: size;
}

.command-bar {
  grid-area: bar;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--line);
  background: #0f1716e6;
  /* The last resort on a very narrow window: the bar scrolls sideways inside
     itself rather than growing a row and pushing the board. */
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
}

/* Abilities are the one group that can outgrow the bar; they scroll inside
   themselves so Confirm and End turn always stay on screen. */

.cmd[aria-disabled='true'] {
  opacity: 0.45;
  cursor: default;
}

/* ---------------------------------------------------------------- Panels */

.panel {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  min-height: 0;
  padding: 0.6rem;
  border: 8px solid transparent;
  border-image: url('/assets/ui/gauntlet/panel-frame.png') 8 fill / 8px / 0 stretch;
  background: var(--panel);
  background-clip: padding-box;
  image-rendering: pixelated;
  overflow: hidden auto;
  scrollbar-width: thin;
}

.panel .cmd {
  white-space: normal;
}

.panel.grow {
  flex: 1;
}

.panel h3 {
  flex: none;
  font-size: 1.35rem;
  color: var(--ink);
}

/* A panel title with a control beside it (the roster's +). */
.panel-head {
  display: flex;
  flex: none;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

/* Dismissing a character asks first. */

/* The display name: on a new player's first screen, and in settings. */
.profile-stage {
  justify-content: center;
  max-width: 34rem;
  margin: 0 auto;
}

.profile-form {
  display: grid;
  gap: 0.45rem;
  max-width: 24rem;
}

.profile-form input {
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
  font-size: 1.1rem;
}

.profile-form .cmd {
  justify-self: start;
}

.label {
  color: var(--good);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.eyebrow {
  color: var(--gold);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.12em;
}

.card-list {
  display: grid;
  gap: 0.4rem;
}

.cmd.inspect {
  width: 2rem;
  padding: 0;
  font-family: Georgia, serif;
  font-size: 1.1rem;
  font-style: italic;
  font-weight: 700;
}

.unit-text {
  display: grid;
  gap: 0.15rem;
  min-width: 0;
}

.unit-text strong,
.unit-text small {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.unit-text small {
  color: var(--ink-dim);
  font-size: 0.75rem;
}

.unit-text .numbers {
  font-variant-numeric: tabular-nums;
}

.portrait {
  display: grid;
  place-items: center;
  width: 2.6rem;
  height: 2.6rem;
  border: 1px solid var(--line);
  background: #0d1413;
  overflow: hidden;
}

.portrait.large {
  width: 4.5rem;
  height: 4.5rem;
}

.sprite {
  display: block;
  height: 100%;
  max-width: 100%;
  aspect-ratio: 1;
  /* A four-frame 64x16 idle sheet: at 400% wide, frame k sits at k/3 of the
     way across, so stepping to 133.33% shows each frame once per loop. */
  background: no-repeat 0 0 / 400% 100%;
  background-image: var(--sprite-idle-image);
  image-rendering: pixelated;
  pointer-events: none;
  animation: sprite-idle var(--sprite-idle-duration, 0.9s) steps(4) infinite;
  animation-delay: var(--sprite-idle-delay, 0s);
}

@keyframes sprite-idle {
  to {
    background-position: 133.333% 0;
  }
}

.meter {
  display: block;
  height: 0.35rem;
  border-radius: 1rem;
  background: #0d1413;
  overflow: hidden;
}

.meter span {
  display: block;
  height: 100%;
  background: var(--hp);
  transition: width 180ms ease-out;
}

.rows {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
}

.rows li {
  display: grid;
  grid-template-columns: 3.2rem minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 0.4rem;
  padding: 0.35rem 0;
  border-top: 1px solid #2b292e;
  font-size: 0.85rem;
}

.rows span,
.rows small {
  color: var(--ink-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The turn order: a row of portraits in the top bar. Each is a sprite at
   2× in a frame, green for an ally and red for a foe; the one acting is
   lit and raised, those done are dim, the dead are crossed out, and a foe
   out of sight is a "?". It scrolls sideways inside itself when long. */

/* The pack as slots, Darkest Dungeon style: a grid of square cells, each
   holding one pixel icon. Items drag between slots and zones. Every slot on
   the rail (pack, pocket, cache, floor) is the same size, --slot: five to a
   row across the panel, and never larger than 3.25rem. */

/* Square whatever the slot: the 16×16 icon is never stretched. */

/* ---------------------------------------------------------------- Buttons */

button,
input,
select {
  font: inherit;
  touch-action: manipulation;
}

button {
  cursor: pointer;
}

button:disabled {
  cursor: default;
}

button:focus-visible,
input:focus-visible,
select:focus-visible {
  outline: 3px solid #f0c978;
  outline-offset: 2px;
}

.cmd {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  min-height: 2.75rem;
  padding: 0.4rem 0.85rem;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--panel-raised);
  color: var(--ink);
  font-size: 0.9rem;
  font-weight: 600;
  white-space: nowrap;
  transition: background 120ms ease, border-color 120ms ease;
}

.cmd.small {
  min-height: 2.2rem;
  padding: 0.25rem 0.6rem;
  font-size: 0.8rem;
}

.cmd.primary {
  border-color: var(--gold);
  background: #4a3b1c;
  color: #ffe1a1;
}

.cmd.danger {
  border-color: #7a3a36;
  background: #2a1716;
}

.cmd.mode[aria-pressed='true'] {
  border-color: var(--gold);
  background: #364132;
  color: #ffe1a1;
  box-shadow: inset 0 -3px var(--gold);
}

.cmd:disabled {
  opacity: 0.45;
}

.icon-button {
  min-width: 2.75rem;
  font-size: 1.4rem;
}

/* HOVER ONLY WHERE THERE IS A POINTER: on a touch screen `:hover` sticks after
   a tap and the last button pressed would stay lit. */
@media (hover: hover) and (pointer: fine) {
  .cmd:hover:not(:disabled) {
    border-color: var(--gold);
  }
}

.cmd:active:not(:disabled, [aria-disabled='true']) {
  filter: brightness(1.2);
}

/* ---------------------------------------------------------------- Stage */

.stage-panel {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  width: 100%;
  height: 100%;
  padding: 1rem;
  border: 8px solid transparent;
  border-image: url('/assets/ui/gauntlet/panel-frame.png') 8 fill / 8px / 0 stretch;
  background: var(--panel);
  background-clip: padding-box;
  image-rendering: pixelated;
  overflow: auto;
  scrollbar-width: thin;
}

.stage-panel h2 {
  font-size: 2.2rem;
  line-height: 1;
}

.stage-panel h3 {
  margin-top: 0.4rem;
  font-size: 1.4rem;
}

.slot {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 0.25rem;
  grid-template-rows: minmax(0, 1fr) auto auto;
  aspect-ratio: 3 / 4;
  min-height: 0;
  overflow: hidden;
  padding: 0.4rem;
  border: 1px dashed var(--line);
  background: #0d1413;
  text-align: center;
  font-size: 0.8rem;
}

/* Home: the menu floats over a demo battle playing on the board behind it. */
.scene.mode-home {
  grid-template-rows: var(--top-h) minmax(0, 1fr);
  grid-template-areas:
    'top top top'
    'stage stage stage';
}

.scene.mode-home .rail {
  display: none;
}

/* Battle effects, shared by the demo and real battles (client/battle-fx.js):
   figures slide between tiles, strike, flash when hit, and numbers float up. */
/* Elemental blows float in their element's colour, critical ones too (the
   "!" and the size still mark a crit); a weakness lands big, a resistance
   small and dim. */

/* Sparks flying out from a tile: six dots on a ring, coloured by what hit. */

/* Rising sparks for the kinder effects, flying sparks for blows. */

/* Spells. Each look sets its colours; bolts and meteors fly over the
   figures, blast tiles light up under them, rippling out from the centre. */

/* A trail behind the head. */

/* Light pouring down on a healed (or hastened) ally. */

/* A bubble closing round a warded ally. */

/* A milestone (a level gained, a talent learned) announced over the scene.
   It lives outside #app, so re-renders do not cut it short. */

.sound-toggle {
  flex: none;
  min-width: 2.2rem;
  font-size: 1rem;
}

.sound-toggle[aria-pressed='true'] {
  opacity: 0.6;
}

.home-title {
  color: var(--gold);
  font-family: 'Sigil Pixel', monospace;
  font-size: clamp(2rem, 9cqh, 4.5rem);
  line-height: 1;
  letter-spacing: 0.08em;
  text-shadow: 0 0.1em 0 #6b3f12;
}

/* A hosted lobby, shown at the entrance until the party sets out. */

.code-form {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.code-form input {
  width: 7rem;
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
  font-family: 'Sigil Pixel', monospace;
  font-size: 1.2rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.cmd.choice.chosen {
  border-color: var(--gold);
  background: #2b2a1d;
}

/* Which town provisions go into the run's pack. */

/* The party is chosen: the descent buttons below are the next step. */
.ready {
  margin: 0.75rem 0 auto;
  color: var(--gold);
  font-family: 'Sigil Pixel', monospace;
  font-size: clamp(2rem, 7cqh, 4rem);
  line-height: 1;
  text-align: center;
  text-shadow: 0 0.12em 0 #6b3f12, 0 0 0.6em rgb(216 165 74 / 0.35);
}

.slot .sprite {
  width: 70%;
  height: auto;
  min-height: 0;
}

.slot > .muted {
  grid-row: 1 / -1;
  align-self: center;
}

.slot strong {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.slot small {
  color: var(--ink-dim);
}

.banner {
  padding: 0.4rem 0.75rem;
  border: 1px solid var(--good);
  background: #1c3324;
  font-family: 'Sigil Pixel', monospace;
  font-size: 1.4rem;
  text-align: center;
}

.banner.bad {
  border-color: var(--hp);
  background: #3a1d1c;
  color: var(--ink);
}

/* THE OVERWORLD. The route screen is a top-down map of the floor
   (overworld.js): a 512×320 pixel canvas displayed with hard pixels, the
   largest 16:10 box that fits, with a marker over every room. The rooms one
   step ahead are the buttons. */

/* The window onto the map: it fills the stage, and the map inside is drawn
   larger and moved by dragging (attachPan in overworld.js). */

/* Any zoomable view (zoom-pan.js): mid-drag, nothing under the pointer
   reacts. */

/* Zoom in, zoom out, fit: small square buttons in the view's top right. */

/* --zoom (1.7 to start) × the larger of the window's width and its height at 16:10, so the
   map always covers the window both ways. */

/* THE MAP IS A SHEET OF PAPER ON A TABLE. Past the map's edge (the views
   may be dragged a little beyond it, zoom-pan.js `overscroll`) lies a dark
   table, and the land and the battle board both sit inside a torn
   parchment edge: assets/ui/paper-edge.png, an 80×80 nine-slice with 16px
   slices (tools/sprites/build-paper-edge-16.mjs), drawn one source pixel
   per map pixel. The ring sits mostly outside the content, its dithered
   inner five pixels over the content's edge. */

/* A battle tile is sixteen source pixels. */

/* A room: its 16×16 icon on a dark disc, the size of a fingertip at most
   and shrinking with the map so twenty rooms never overlap. */

/* Over the map: the result of the fight just won, and the co-op lobby. */
/* To the right, since the party sets out from the map's left edge. */

/* The fight's result fades out after a moment (see RESULT_BANNER_MS). */

/* On a small stage the lobby keeps only the code and the head count. */
@container (max-width: 36rem) {
}

/* The map key, under the map, where the way-on buttons used to be. */

/* On a short window the key keeps its icons and names, at 1×. */

/* The stair down from the checkpoint, at the map's right edge. */

.choices {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 0.5rem;
}

.cmd.choice {
  display: grid;
  justify-content: stretch;
  justify-items: start;
  align-content: start;
  gap: 0.3rem;
  min-height: 6rem;
  padding: 0.6rem;
  white-space: normal;
  text-align: left;
  background: var(--card);
}

.cmd.choice small {
  color: var(--ink-dim);
  font-weight: 400;
}

/* The board sits in a window the player can zoom and drag (zoom-pan.js):
   the window fills the stage, and `--zoom` (1 = the board just fits)
   scales the tile size below. */

/* THE BOARD IS A 3/4 TOP-DOWN VIEW. The floor is seen from above, walls show
   a stone top and a brick front, and characters stand upright, one tile wide
   and one tall, on a separate layer. One tile is `--t`: the frame is 17 tiles wide (the
   board plus half a tile of wall each side) and 17.5 tall (plus a wall front
   along the top), and `--t` is the largest size at which that fits the stage,
   times the player's zoom. `--n` is the board's side: 16 for a room, more
   for a boss arena. */

/* In battle the back wall sits in the same dark as the unseen floor. */

.board {
  display: grid;
  grid-template-columns: repeat(var(--n), var(--t));
  grid-template-rows: repeat(var(--n), var(--t));
  width: calc(var(--t) * var(--n));
  height: calc(var(--t) * var(--n));
  background: #0d1413;
}

.tile {
  position: relative;
  min-width: 0;
  min-height: 0;
  margin: 0;
  padding: 0;
  border: 0;
  outline-offset: -3px;
  background: #27352f center / 100% 100% no-repeat;
  image-rendering: pixelated;
  color: var(--ink);
}

.tile.wall {
  background-image: url('/assets/ui/gauntlet/dungeon/tiles/wall-top.png');
}

/* A wall with open floor in front of it shows its brick front on the lower
   part of the tile, which is what makes the board read as seen at an angle. */

/* Thorns a boss grew: impassable, like rock. */

/* A boss's wound-up move: the tiles it will strike, pulsing red. */

/* Floor just in front of a wall sits in its shadow. */
.tile.shaded {
  box-shadow: inset 0 calc(var(--t) * 0.22) calc(var(--t) * 0.12) calc(var(--t) * -0.12) #000a;
}

/* Out of sight, not unknown: the layout stays readable, the occupants do not
   (the server has already removed them). */

/* Rock stays faintly recognisable as rock in the dark, not a hole in the map. */

/* The last ring of sight, where the light gives out. */
.tile.dim {
  filter: brightness(0.62) saturate(0.7);
}

.tile.target::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.tile.chosen {
  z-index: 1;
  box-shadow: inset 0 0 0 3px #ffe1a1, 0 0 0 2px #ffe1a1;
}

/* An area ability's reach: every tile it would hit, lit under the figures.
   Red where it hits a foe, a warning stripe where it catches an ally, green
   where a heal lands. */

/* Characters: one tile wide and one tall, feet at the bottom of their tile,
   drawn in row order so a nearer one's badges overlap one further back. */
.figures {
  position: absolute;
  top: calc(var(--t) * 1.25);
  left: calc(var(--t) * 0.5);
  width: calc(var(--t) * var(--n));
  height: calc(var(--t) * var(--n));
  pointer-events: none;
}

.figure {
  position: absolute;
  left: calc((var(--x) + 0.5) * var(--t));
  top: calc((var(--y) + 0.94) * var(--t));
  z-index: calc(var(--y) + 1);
  display: grid;
  justify-items: center;
  align-items: end;
  width: var(--t);
  height: calc(var(--t) * 0.9);
  transform: translate(-50%, -100%);
}

/* A great boss: one figure over its whole N×N body, feet on its bottom row. */
.figure.big {
  left: calc((var(--x) + var(--size) / 2) * var(--t));
  top: calc((var(--y) + var(--size) - 0.06) * var(--t));
  z-index: calc(var(--y) + var(--size));
  width: calc(var(--t) * var(--size));
  height: calc(var(--t) * var(--size) * 0.95);
}

/* The ground ring: whose side, and whose turn. */
.figure.big::before {
  width: calc(var(--t) * var(--size) * 0.9);
  height: calc(var(--t) * var(--size) * 0.35);
}

.figure::before {
  content: '';
  position: absolute;
  bottom: calc(var(--t) * -0.08);
  width: calc(var(--t) * 0.8);
  height: calc(var(--t) * 0.28);
  border: 2px solid #6fd39a;
  border-radius: 50%;
  background: #0006;
}

.figure.enemy::before {
  border-color: #ff7b6b;
}

.figure .sprite {
  position: relative;
  height: 100%;
}

/* Every sheet is drawn facing right: the party faces right, across the board
   at the enemy, and enemies are mirrored to face left, back at the party. */
.figure.enemy .sprite {
  transform: scaleX(-1);
}

@media (prefers-reduced-motion: reduce) {
  .sprite {
    animation: none;
    background-position: 0 0;
  }
}

/* Mini health and mana bars over a character while the mouse is on it. */

/* ---------------------------------------------------------------- Tooltip */

.tooltip {
  position: fixed;
  z-index: 50;
  max-width: min(22rem, calc(100vw - 16px));
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--gold);
  background: #10161ef2;
  box-shadow: 0 0.5rem 1.5rem #000c;
  pointer-events: none;
  font-size: 0.85rem;
}

.tip-body {
  display: grid;
  gap: 0.35rem;
}

.tip-body strong {
  color: #ffe1a1;
  font-size: 1rem;
}

.tip-body p {
  color: var(--ink-dim);
}

.tip-body dl {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.2rem 0.75rem;
  margin: 0;
}

.tip-body dt {
  color: var(--good);
  font-weight: 700;
}

.tip-body dd {
  margin: 0;
}

.tip-body .why {
  color: #ff9d8f;
}

/* Item tooltips follow one rule: the item's kind sets the title, the line
   under it and the border (consumable green, gear blue, treasure gold); a
   value's tone says what it means (a bonus, money, a warning, a note). */
.tip-body { --kind: #ffe1a1; }
.tip-consumable { --kind: #7fe0a0; }
.tip-gear { --kind: #8fb7ff; }
.tip-treasure { --kind: var(--gold); }

.tip-body strong {
  color: var(--kind);
}

.tooltip:has(.tip-consumable, .tip-gear, .tip-treasure) {
  border-color: var(--kind);
  border-left-width: 3px;
}

.tooltip:has(.tip-consumable) { --kind: #7fe0a0; }
.tooltip:has(.tip-gear) { --kind: #8fb7ff; }
.tooltip:has(.tip-treasure) { --kind: var(--gold); }

/* The words a tip leans on (client/tourney-tips.js GLOSSARY), one a line. */
.tip-keys {
  display: grid;
  gap: 0.2rem;
  margin: 0.1rem 0 0;
  padding: 0.35rem 0 0;
  border-top: 1px solid var(--line);
  list-style: none;
  color: var(--ink-dim);
  font-size: 0.78rem;
}

.tip-keys b {
  color: var(--ink);
}

/* Tipped labels in the tourney are focusable; show it only for the keyboard. */
.mode-tourney [data-tip-touch]:focus:not(:focus-visible) {
  outline: none;
}

.mode-tourney .strip-unit[data-tip],
.mode-tourney .portrait[data-tip],
.mode-tourney .chip[data-tip],
.mode-tourney p[data-tip],
.mode-tourney h3[data-tip] {
  cursor: help;
}

.prize-line,
.passive-line {
  margin: 0.2rem 0;
  font-size: 0.8rem;
}

/* A tip never runs off the screen: the longest are cut at the bottom edge. */
.tooltip {
  max-height: calc(100vh - 16px);
  overflow: hidden;
}

/* The Field Guide: the whole glossary, section by section. */
.guide {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr));
  gap: 0.8rem 1.2rem;
}

.guide-section h4 {
  margin: 0 0 0.3rem;
  color: var(--gold);
  letter-spacing: 0.04em;
}

.guide-section dl {
  display: grid;
  gap: 0.15rem;
  margin: 0;
}

.guide-section dt {
  color: var(--ink);
  font-weight: 700;
}

.guide-section dd {
  margin: 0 0 0.35rem;
  color: var(--ink-dim);
  font-size: 0.85rem;
}

.tip-sub {
  margin-top: -0.25rem;
  color: var(--kind);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.8;
}

.tip-body:is(.tip-consumable, .tip-gear, .tip-treasure) dt {
  color: var(--ink-dim);
  font-weight: 400;
}

.tip-body dd.tone-good { color: #7fe0a0; }
.tip-body dd.tone-gold { color: var(--gold); font-weight: 700; }
.tip-body dd.tone-bad { color: #ff9d8f; }
.tip-body dd.tone-dim { color: var(--ink-dim); }

/* In a rail the label sits above its value; side by side wraps badly there. */

/* ---------------------------------------------------------------- Overlay */

.overlay {
  grid-row: 2;
  grid-column: 1 / -1;
  z-index: 10;
  display: grid;
  place-items: center;
  min-height: 0;
  background: #05080899;
}

.overlay-panel {
  width: min(100%, 48rem);
  max-height: 100%;
  box-shadow: 0 1rem 2rem #000a;
}

.overlay-head {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.columns {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1rem;
}

.columns > div {
  display: grid;
  align-content: start;
  gap: 0.5rem;
}

.sheet {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.2rem 0.75rem;
  margin: 0;
  font-variant-numeric: tabular-nums;
}

.sheet dt {
  color: var(--ink-dim);
}

.sheet dd {
  margin: 0;
  text-align: right;
}

/* ---------------------------------------------------------------- Talents */

/* Three paths side by side (columns), one row per level: a talent tree the
   eye reads down a path and across a level. Each path has its own colour. */

.overlay-panel:has(.talent-layout) {
  width: min(100%, 60rem);
}

/* The line down a path, joining each talent to the one above it. */

/* A short window (a landscape phone) keeps names on the cards; the detail
   panel beside the tree carries the full text. */

/* ---------------------------------------------------------------- Status auras */

/* Lasting conditions show on the figure itself, not only as badges. */
.figure.burning .sprite {
  filter: drop-shadow(0 0 calc(var(--t) * 0.08) #ff7a2a) sepia(0.35) saturate(1.6);
}

.figure.burning::after {
  content: '';
  position: absolute;
  inset: 45% 20% 0;
  border-radius: 50% 50% 30% 30%;
  background: radial-gradient(#ffb04a, #ff5a1f55 60%, transparent 70%);
  mix-blend-mode: screen;
  animation: status-flicker 0.5s steps(2) infinite;
  pointer-events: none;
}

.figure.poisoned .sprite {
  filter: sepia(0.4) hue-rotate(50deg) saturate(1.4);
}

@keyframes status-flicker {
  50% { opacity: 0.55; transform: scaleY(1.12); }
}

.rotate-hint {
  display: none;
}

/* ---------------------------------------------------------------- Immersive */

/* ON THE MAP AND IN A FIGHT THE STAGE IS THE WHOLE SCREEN. The scene leaves
   #app's padding, the stage fills the frame edge to edge, and the top bar,
   rails and command bar float over it. Each rail folds away behind a tab on
   its inner edge (`leftRailClosed`/`rightRailClosed` in state.ui).
   The `--inset-*` values are the stage left uncovered by the panels, which
   the map's own floating pieces and the zoom buttons keep inside.
   Containers that span open space ignore the pointer, so a drag on the map
   or the board passes through them; only the panels themselves catch it. */
.scene.immersive {
  --edge-t: calc(var(--pad) + var(--safe-top));
  --edge-r: calc(var(--pad) + var(--safe-right));
  --edge-b: calc(var(--pad) + var(--safe-bottom));
  --edge-l: calc(var(--pad) + var(--safe-left));
  --rail-w: clamp(14rem, 22vw, 21rem);
  --tab-w: 1.5rem;
  --left-w: calc(var(--rail-w) + var(--tab-w));
  --right-w: calc(var(--rail-w) + var(--tab-w));
  --inset-t: calc(var(--edge-t) + var(--top-h) + var(--gap));
  --inset-b: calc(var(--edge-b) + var(--bar-h) + var(--gap));
  --inset-l: calc(var(--edge-l) + var(--left-w) + var(--gap));
  --inset-r: calc(var(--edge-r) + var(--right-w) + var(--gap));
  position: fixed;
  inset: 0;
  display: block;
  max-width: none;
  margin: 0;
}

.scene.immersive.left-closed {
  --left-w: var(--tab-w);
}

.scene.immersive.right-closed {
  --right-w: var(--tab-w);
}

.scene.immersive > .stage {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: block;
}

.scene.immersive > .top-bar {
  position: absolute;
  top: var(--edge-t);
  left: var(--edge-l);
  right: var(--edge-r);
  z-index: 3;
  height: var(--top-h);
  padding: 0;
  border: 0;
  overflow: visible;
  pointer-events: none;
}

/* Each piece of the top bar is its own small floating plate. */
.scene.immersive > .top-bar > * {
  pointer-events: auto;
}

.scene.immersive > .top-bar > h1,
.scene.immersive > .top-bar > .top-context,
.scene.immersive > .top-bar > .top-user {
  align-self: stretch;
  display: flex;
  align-items: center;
  padding: 0.15rem 0.6rem;
  border: 1px solid var(--line);
  background: #0b100fd9;
  box-shadow: 0 4px 12px #0008;
}

.scene.immersive > .top-bar > .notice {
  align-self: center;
  box-shadow: 0 4px 12px #0008;
}

.scene.immersive > .top-bar > .notice.empty {
  pointer-events: none;
}

.scene.immersive > .command-bar {
  position: absolute;
  left: var(--edge-l);
  right: var(--edge-r);
  bottom: var(--edge-b);
  z-index: 3;
  width: fit-content;
  max-width: calc(100% - var(--edge-l) - var(--edge-r));
  height: var(--bar-h);
  margin-inline: auto;
  background: #0b100fe6;
  box-shadow: 0 -4px 14px #0008;
}

.scene.immersive > .rail {
  position: absolute;
  top: var(--inset-t);
  bottom: var(--inset-b);
  z-index: 2;
  width: var(--rail-w);
  overflow: visible;
  pointer-events: none;
}

.scene.immersive > .rail-left {
  left: var(--edge-l);
}

.scene.immersive > .rail-right {
  right: var(--edge-r);
}

/* Folded, a rail is only its tab at the screen's edge (a right rail keeps
   the selected action's details, which touch players need). */
.scene.immersive > .rail.closed:not(.has-pinned) {
  width: 0;
}

.rail-body,
.rail-pinned {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  min-height: 0;
  pointer-events: none;
}

.rail-body {
  flex: 0 1 auto;
}

/* Under the rail's panels (the map key under the party), never squeezed. */
.rail-after {
  display: flex;
  flex: none;
  pointer-events: none;
}

.rail-after > * {
  pointer-events: auto;
}

.rail-pinned {
  flex: none;
  max-height: 45%;
}

/* The pinned panel's own cap is the pinned slot's. */

.rail-body > *,
.rail-pinned > * {
  pointer-events: auto;
  box-shadow: 0 6px 18px #000a;
}

/* Floating, a panel is as tall as what it holds, up to the rail's height. */
.scene.immersive .rail .panel.grow {
  flex: 0 1 auto;
}

.scene.immersive .rail .panel {
  background-color: #192026ee;
}

.rail-toggle {
  position: absolute;
  top: 0;
  display: grid;
  place-items: center;
  width: var(--tab-w);
  height: 3rem;
  padding: 0;
  border: 1px solid var(--line);
  background: #0b100fe6;
  color: var(--gold);
  font-size: 0.8rem;
  line-height: 1;
  cursor: pointer;
  pointer-events: auto;
  touch-action: manipulation;
  box-shadow: 0 4px 12px #0008;
}

.rail-toggle:hover,
.rail-toggle:focus-visible {
  border-color: var(--gold);
  background: #1f1d15f0;
}

.rail-left > .rail-toggle {
  left: 100%;
  border-left: 0;
  border-radius: 0 4px 4px 0;
}

.rail-right > .rail-toggle {
  right: 100%;
  border-right: 0;
  border-radius: 4px 0 0 4px;
}

/* The board fits between the bars: the top bar floats over its back wall,
   which holds nothing to click, and the command bar never covers a row.
   The third term keeps the wall at least as tall as the top bar on a short
   window or a big arena (the frame is centred, so its first row starts at
   half the spare height plus 1.25 tiles), so no row ever sits under it. */

/* On a small screen the lobby card keeps only the code and the head count,
   and the map hint gives way to the key toggle (the stage is the whole
   screen now, so this is the window's width, not a container's). */

/* The zoom buttons sit just inside whatever the right rail leaves. */

/* The map: no frame, edge to edge, with the room card floating at the top
   between the rails and the hint and key at the bottom. */

/* A ROOM'S SCENE: what the party can do in a treasure, rest or event room,
   or with the feature a won fight left, as a picture with its buttons,
   centred over the map. The 128×72 picture is shown at a whole-number
   scale (3, or 2 on a short window) so its pixels stay square. */

/* The key: two columns in the map's bottom-left corner, beside the party. */

/* With nothing to do on the map but pick a room, there is no command bar,
   and the rails and the key reach down to the screen's bottom edge. */
.scene.immersive.no-bar {
  --inset-b: calc(var(--edge-b) + var(--gap));
}

/* The white flag: retreat (the host) or leave the lobby (a joined player). */

/* Overlays cover the space between the bars. */
.scene.immersive > .overlay {
  position: absolute;
  top: var(--inset-t);
  bottom: var(--inset-b);
  left: var(--edge-l);
  right: var(--edge-r);
}

/* ---------------------------------------------------------------- Sizes */

/* Short but wide (a landscape phone): the title shrinks and the route dots go,
   since the chip already says where you are. */
@media (max-height: 520px) {
  .top-bar h1 {
    font-size: 1.6rem;
  }

  .top-user {
    display: none;
  }
}

/* The how-to hint is the first thing to give up its room in the bar. */

/* Too narrow for rails beside the board: keep the board and the commands. This
   covers Discord's picture-in-picture tile and a squeezed desktop window. */
@media (max-width: 620px) and (orientation: landscape) {
  .scene {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: 'top' 'stage' 'bar';
  }

  .rail,
  .top-context,
  .notice {
    display: none;
  }
}

@media (max-width: 620px) and (orientation: landscape) {
  .scene.immersive {
    --left-w: 0px;
    --right-w: 0px;
  }
}

/* Picture-in-picture is a glance, not a place to play: show only the stage. */
@media (max-height: 300px) {
  .scene {
    grid-template-rows: minmax(0, 1fr);
    grid-template-areas: 'stage';
  }

  .top-bar,
.command-bar,
.scene.immersive > .rail {
    display: none;
  }

  .scene.immersive {
    --inset-b: 0px;
  }
}

/* The scene is built for landscape. On a portrait phone the SDK's orientation
   lock should already have rotated the frame; if it did not, say so instead of
   drawing a board the width of a thumb. */
@media (orientation: portrait) and (max-width: 900px) {
  .rotate-hint {
    position: fixed;
    inset: 0;
    z-index: 20;
    display: grid;
    place-content: center;
    gap: 0.5rem;
    padding: 2rem;
    background: var(--bg);
    text-align: center;
  }

  .rotate-hint strong {
    font-family: 'Sigil Pixel', monospace;
    font-size: 2rem;
    color: var(--gold);
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
  }
}

/* ---------------------------------------------------------------- Market */

.cmd.tab[aria-pressed='true'] {
  border-color: var(--gold);
  background: #364132;
  color: #ffe1a1;
  box-shadow: inset 0 -3px var(--gold);
}

/* ---------------------------------------------------------------- Dungeons */

/* Each dungeon's edge wears its biome's colour. */

/* Character traits: chips with a 16px icon; good ones warm, bad ones grey. */

/* The roster rail is narrow: icons only there, names in the tooltip. */

/* A battle's foes, each a card to open (foesPanel). */

/* ---------------------------------------------------------------- Running the Gauntlet
   The tourney's screens (client/tourney.js). The shop and the replay are
   immersive: the tiltyard canvas sits between the floating rails; the
   others are the usual scene, or a single stage panel when there are no
   rails. */

.scene.no-rails {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas:
    'top'
    'stage'
    'bar';
}

.scene.no-rails.no-bar {
  grid-template-rows: var(--top-h) minmax(0, 1fr);
}

.scene.mode-tourney.no-bar:not(.immersive) {
  grid-template-rows: var(--top-h) minmax(0, 1fr);
  grid-template-areas:
    'top top top'
    'left stage right';
}

.scene.mode-tourney.no-rails.no-bar {
  grid-template-areas:
    'top'
    'stage';
}

/* The realm and the choice screens are wide; the stage track is not a square here. */
.scene.mode-tourney:not(.immersive) {
  grid-template-columns: minmax(14rem, 22rem) minmax(0, 1fr) minmax(0, 0);
  max-width: none;
}

.scene.mode-tourney.no-rails:not(.immersive) {
  grid-template-columns: minmax(0, 1fr);
}

.unit-sprite {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--unit-sheet) no-repeat 0 0 / 400% calc(var(--unit-rows, 3) * 100%);
  image-rendering: pixelated;
  animation: unit-idle 0.9s steps(4) infinite;
}

@keyframes unit-idle {
  to {
    background-position: 133.333% 0;
  }
}

.portrait.small {
  width: 2rem;
  height: 2rem;
}

.tourney-board {
  display: grid;
  gap: 0.25rem;
  max-width: 32rem;
  padding-left: 1.5rem;
}

.tourney-board li > span,
.tourney-board li > strong {
  margin-right: 0.75rem;
}

.choice-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 0.75rem;
}

.choice-card {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  padding: 0.75rem;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--card);
}

.choice-card .cmd {
  margin-top: auto;
}

.choice-card .portrait.large {
  align-self: center;
}

.gold-line {
  color: var(--gold);
  font-weight: 600;
}

.squad-strip,
.rival-roll {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
}

.strip-unit {
  position: relative;
  display: grid;
  width: 2.2rem;
  height: 2.2rem;
  border: 1px solid var(--line);
  background: #0d1413;
}

.strip-unit small {
  position: absolute;
  right: 1px;
  bottom: 0;
  color: var(--ink);
  font-size: 0.65rem;
  text-shadow: 0 0 2px #000;
}

/* Two kingdoms either side of the Crown, which stands in the middle. */
.realm-map {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-template-areas:
    "greenwood crown ashen-crags"
    "mirefen crown cinder-deep";
  gap: 0.75rem;
}

.seat-greenwood {
  grid-area: greenwood;
}

.seat-mirefen {
  grid-area: mirefen;
}

.seat-ashen-crags {
  grid-area: ashen-crags;
}

.seat-cinder-deep {
  grid-area: cinder-deep;
}

.seat {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 0.75rem;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--card);
}

.seat.open {
  border-color: var(--gold);
}

.seat.won {
  opacity: 0.75;
}

.seat .cmd {
  margin-top: auto;
}

.seat-crown {
  grid-area: crown;
  justify-content: center;
  text-align: center;
  background: #2b2a1d;
}

.decree-name {
  color: #ffe1a1;
  font-weight: 700;
}

.won-mark {
  color: var(--gold);
  font-weight: 700;
}

.squad-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
  gap: 0.35rem;
  padding: 0.25rem;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--card);
}

.squad-row.selected {
  border-color: var(--gold);
  background: #2b2a1d;
}

.squad-pick {
  display: grid;
  grid-template-columns: 2.6rem minmax(0, 1fr);
  align-items: center;
  gap: 0.45rem;
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink);
  text-align: left;
}

.squad-pick .unit-text {
  display: grid;
  min-width: 0;
}

.squad-pick strong,
.squad-pick small {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.squad-acts {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.25rem;
}

.offers {
  display: grid;
  gap: 0.3rem;
}

.offer {
  display: grid;
  grid-template-columns: 2.6rem minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.45rem;
  padding: 0.25rem 0.45rem 0.25rem 0.25rem;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--card);
  color: var(--ink);
  text-align: left;
}

.offer.discounted {
  border-color: var(--good);
}

.offer.sold,
.offer[aria-disabled='true'] {
  opacity: 0.45;
  cursor: default;
}

.offer-price {
  color: var(--gold);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* The shop is a grid of icons, each named in its tip: a tile is its
   portrait over its price. */
.shop-panel .offers {
  grid-template-columns: repeat(auto-fill, minmax(3.4rem, 1fr));
}

.shop-panel .offer {
  grid-template-columns: none;
  justify-items: center;
  gap: 0;
  padding: 0;
  text-align: center;
}

.shop-panel .offer .portrait {
  position: relative;
  width: 2.6rem;
  height: 2.6rem;
  overflow: visible;
}

/* A badge in a tile's corner: a tech's kind over its unit, or an item. */
.offer-badge {
  position: absolute;
  right: calc(-2 * var(--ui-px, 1px));
  bottom: calc(-2 * var(--ui-px, 1px));
  width: calc(11 * var(--ui-px, 1px));
  height: calc(11 * var(--ui-px, 1px));
  background: no-repeat center / 100% 100%;
  image-rendering: pixelated;
}

.tech-badge.kind-sharpen { background-image: url('assets/ui/windows/tech-sharpen.png'); }
.tech-badge.kind-branch { background-image: url('assets/ui/windows/tech-branch.png'); }
.tech-badge.kind-harden { background-image: url('assets/ui/windows/tech-harden.png'); }

/* An item has no unit: its badge is the whole icon. */
.item-badge {
  inset: calc(4 * var(--ui-px, 1px));
  width: auto;
  height: auto;
  background-image: url('assets/ui/windows/item.png');
}

.unit-text small.ready {
  color: var(--gold);
  font-weight: 700;
}

.bar-note {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  color: var(--ink-dim);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bar-note.result.won {
  color: #aed065;
  font-weight: 700;
}

.bar-note.result.lost {
  color: #f0a198;
  font-weight: 700;
}

.confirm-abandon {
  display: flex;
  gap: 0.25rem;
}

.cmd.danger {
  border-color: var(--hp);
  background: #4a1f1c;
}

/* The tiltyard: between the rails, under the top and command bars. */
.tiltyard {
  position: absolute;
  top: var(--inset-t);
  right: var(--inset-r);
  bottom: var(--inset-b);
  left: var(--inset-l);
}

.tourney-replay .tiltyard {
  right: var(--edge-r);
  left: var(--edge-l);
}

/* Three canvases stacked: the ground (2D), the figures (WebGL) and the top
   (2D: overlays and the arena's front, and every pointer event). */
.tiltyard-stack {
  position: relative;
  width: 100%;
  height: 100%;
}

.tiltyard-stack > canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  image-rendering: pixelated;
}

.tiltyard-layer {
  pointer-events: none;
}

.tiltyard-layer[hidden] {
  display: none;
}

.tiltyard-canvas {
  touch-action: none;
}

.display-settings {
  display: grid;
  gap: 0.5rem;
}

.display-setting {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.4rem;
}

.display-options {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}

.display-options .cmd[aria-pressed='true'] {
  border-color: var(--gold, #d59b3d);
}

/* The tourney's rails are narrower than a descent's: the field needs the room. */
.scene.mode-tourney.immersive {
  --rail-w: clamp(12rem, 18vw, 17rem);
}

.rival-panel {
  flex: none;
}

.tavern-head {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.tavern-scene {
  width: 16rem;
  max-width: 40%;
  border: 1px solid var(--line);
  image-rendering: pixelated;
}

/* The tourney's rails scroll as one column when the window is short, so no
   panel is squeezed to a sliver. */
.scene.mode-tourney.immersive .rail-body {
  overflow: hidden auto;
  scrollbar-width: thin;
}

.scene.mode-tourney.immersive .rail-body > .panel {
  flex: none;
  overflow: visible;
}

.rival-kind {
  font-size: 0.8rem;
}

.bracket-card {
  flex: none;
}

.bracket {
  display: flex;
  gap: 0.35rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.bracket li {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  padding: 0.3rem 0.25rem;
  font-size: 0.78rem;
  text-overflow: ellipsis;
  white-space: nowrap;
  border: 1px solid var(--line);
  border-radius: 3px;
  color: var(--ink-dim);
  text-align: center;
}

.bracket li.current {
  border-color: var(--gold);
  color: #ffe1a1;
  font-weight: 700;
}

.bracket li.won {
  border-color: var(--good);
  color: #aed065;
}

/* Towers, in the army rail: a row like a squad's. */
.tower-row {
  display: grid;
  grid-template-columns: 2.6rem minmax(0, 1fr);
  align-items: center;
  gap: 0.45rem;
  margin-top: 0.4rem;
  padding: 0.25rem;
  border: 1px dashed var(--line);
  border-radius: 3px;
}

.tower-row .unit-text {
  display: grid;
  min-width: 0;
}

/* Its upgrade sits under the text, as a squad's sell button does. */
.tower-row > .cmd {
  grid-column: 2;
  justify-self: end;
}

.tower-row small {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Doubles: hosting and joining on the start screen, the code while waiting. */
.doubles-panel {
  display: grid;
  gap: 0.4rem;
  margin: 0.8rem 0;
}

.doubles-row {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: 0.6rem;
}

.lobby-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.party-note {
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--gold);
  border-radius: 3px;
  background: #2b2a1d;
}

.party-note strong {
  margin-right: 0.4rem;
  color: var(--gold);
  letter-spacing: 0.08em;
}

.chip.partner-chip.locked-in {
  border-color: #6eaa4a;
  color: #b9e19c;
}

/* The round's honours: top five squads by damage, healing and damage tanked. */
.honours { display: grid; gap: 0.6rem; overflow-y: auto; }
.honour h4 { margin: 0 0 0.3rem; font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--gold); }
.honour ol { margin: 0; padding: 0; list-style: none; display: grid; gap: 0.25rem; }
.honour-row { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 0.45rem; padding: 0.15rem 0.3rem; border-left: 3px solid var(--line); background: var(--card); }
.honour-row.yours { border-left-color: var(--ally); }
.honour-row.partner { border-left-color: var(--mana); }
.honour-row.rival { border-left-color: var(--enemy); }
.honour-row .portrait.small { width: 1.6rem; height: 1.6rem; }
.honour-name { display: grid; line-height: 1.15; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.honour-name small { color: var(--ink-dim); font-size: 0.7rem; }
.honour-row strong { font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------- JRPG windows
   The tourney's screens and the home menu are drawn as a JRPG's
   (design-vault/Art Overhaul/JRPG Interface.md). Every panel is a window:
   an ornate gilded nine-slice (assets/ui/windows, sizes in
   client/window-art.js) round a fill of three flat bands in the chosen
   window colour (`--win-*`). All text is m5x7 at whole-number scales:
   `--ui-px` is one interface pixel, a whole number of device pixels, set by
   main.js (applyInterface); the frames are drawn on the same grid. The
   crawl's screens keep their old look until the cutover removes them. */

:root {
  --ui-px: 1px;
  --win-0: #52446e;
  --win-1: #302b49;
  --win-2: #202332;
  --win-shadow: #10151f;
  --win-ink: #f1e5c7;
  --win-dim: #b6c1bc;
  --win-gold: #ffe3a0;
  --win-good: #d4d89c;
  --win-bad: #ff9d8f;
  /* m5x7 is 16 pixels to the em; text is one or two of its sizes. */
  --type: calc(16 * var(--ui-px));
  --type-2: calc(32 * var(--ui-px));
  --type-3: calc(48 * var(--ui-px));
  --type-lh: calc(12 * var(--ui-px));
}

.jrpg,
body:has(.jrpg) > .tooltip {
  --ink: var(--win-ink);
  --ink-dim: var(--win-dim);
  --gold: var(--win-gold);
  --good: var(--win-good);
  font-family: 'Sigil Pixel', monospace;
  font-size: var(--type);
  line-height: var(--type-lh);
  font-synthesis: none;
  -webkit-font-smoothing: none;
  text-shadow: var(--ui-px) var(--ui-px) 0 var(--win-shadow);
}

/* One face, one size: nothing in the pixel type is bold, spaced or scaled
   off the grid. Titles are the exceptions below. */
.jrpg :not(.home-demo, .home-demo *),
body:has(.jrpg) > .tooltip * {
  font-family: 'Sigil Pixel', monospace !important;
  font-size: var(--type) !important;
  font-weight: normal !important;
  font-style: normal !important;
  letter-spacing: 0 !important;
}

.jrpg p,
.jrpg small,
.jrpg li,
.jrpg dd,
.jrpg dt {
  line-height: var(--type-lh);
}

.jrpg :is(.top-bar h1, .stage-panel h2) {
  font-size: var(--type-2) !important;
  line-height: calc(24 * var(--ui-px));
  color: var(--win-gold);
}

.jrpg .home-title {
  font-size: var(--type-3) !important;
  line-height: calc(36 * var(--ui-px));
  color: var(--win-gold);
  text-shadow: calc(2 * var(--ui-px)) calc(2 * var(--ui-px)) 0 var(--win-shadow);
}

.jrpg :is(h3, h4, strong, .eyebrow) {
  color: var(--win-gold);
}

/* The window: frame and fill. The fill starts where the frame's inner line
   ends (the padding box), so the frame's ornate outer edge shows the scene. */
.jrpg :is(.window, .panel, .command-bar),
body:has(.jrpg) > .tooltip {
  border: calc(12 * var(--ui-px)) solid transparent;
  border-radius: 0;
  border-image: url('assets/ui/windows/window.png') 12 / calc(12 * var(--ui-px)) / 0 round;
  background: linear-gradient(var(--win-0) 0 calc(24 * var(--ui-px)), var(--win-1) calc(24 * var(--ui-px)), var(--win-2)) padding-box;
  image-rendering: pixelated;
  box-shadow: none;
  color: var(--win-ink);
}

.jrpg :is(.panel, .stage-panel) {
  padding: calc(4 * var(--ui-px)) calc(6 * var(--ui-px));
}

body:has(.jrpg) > .tooltip {
  border-width: calc(6 * var(--ui-px));
  border-image: url('assets/ui/windows/tip.png') 6 / calc(6 * var(--ui-px)) / 0 round;
  padding: calc(4 * var(--ui-px)) calc(6 * var(--ui-px));
}

/* A window's title sits on a gilded rule. */
.jrpg :is(.panel, .stage-panel) > :is(h3, .panel-head) {
  padding-bottom: calc(8 * var(--ui-px));
  border-bottom: calc(8 * var(--ui-px)) solid transparent;
  border-image: url('assets/ui/windows/rule.png') 0 16 8 / 0 calc(16 * var(--ui-px)) calc(8 * var(--ui-px)) / 0 round;
}

/* Commands: a bevelled button, gilded when chosen, pressed or primary. */
.jrpg :is(.cmd, .home-option) {
  border: calc(6 * var(--ui-px)) solid transparent;
  border-radius: 0;
  border-image: url('assets/ui/windows/button.png') 6 fill / calc(6 * var(--ui-px)) / 0 stretch;
  background: none;
  padding: 0 calc(4 * var(--ui-px));
  color: var(--win-ink);
  image-rendering: pixelated;
  transition: none;
}

.jrpg :is(.cmd, .home-option):is(.primary, .featured, [aria-pressed='true'], :focus-visible):not(:disabled, [aria-disabled='true']) {
  border-image-source: url('assets/ui/windows/button-lit.png');
  color: var(--win-gold);
}

.jrpg :is(.cmd, .home-option):focus-visible {
  outline: none;
}

.jrpg .cmd.small {
  min-height: 2.2rem;
}

/* The top bar is the scene's title strip, over the field: no frame. */
.jrpg .top-bar {
  border-bottom: 0;
}

.jrpg .chip {
  border-radius: 0;
  border-color: var(--win-gold);
  background: var(--win-1);
  color: var(--win-ink);
}

/* Rows in a window are a JRPG list's: bare lines on the fill, framed in
   gilt only when chosen or under the pointer. The frame's width is kept
   while bare, so choosing never shifts the row. */
.jrpg :is(.squad-row, .offer, .tower-row) {
  border: calc(6 * var(--ui-px)) solid transparent;
  border-radius: 0;
  background: none;
  padding: 0;
  image-rendering: pixelated;
}

.jrpg :is(.squad-row.selected, .offer:not(.sold, [aria-disabled='true']):focus-visible) {
  border-image: url('assets/ui/windows/button-lit.png') 6 fill / calc(6 * var(--ui-px)) / 0 stretch;
  outline: none;
}

.jrpg .offer.discounted:not(:hover) {
  border-image: url('assets/ui/windows/button.png') 6 fill / calc(6 * var(--ui-px)) / 0 stretch;
}

.jrpg :is(.offer-price, .won-mark, .decree-name) {
  color: var(--win-gold);
}

.jrpg .bracket li {
  border-radius: 0;
  border-color: var(--win-2);
  background: var(--win-2);
}

.jrpg .bracket li.current {
  border-color: var(--win-gold);
  color: var(--win-gold);
}

/* Pixel type at two interface pixels needs wider rails and a taller bar. */
.scene.mode-tourney.immersive.jrpg {
  --rail-w: clamp(12rem, max(20vw, calc(180 * var(--ui-px))), 26vw);
}

.jrpg {
  --bar-h: max(3.75rem, calc(24 * var(--ui-px) + 2.75rem));
}

.jrpg .command-bar {
  padding: 0 calc(4 * var(--ui-px));
}

.jrpg :not(.home-demo, .home-demo *)::before,
.jrpg :not(.home-demo, .home-demo *)::after {
  font-family: 'Sigil Pixel', monospace !important;
  font-size: var(--type) !important;
}

/* A choice inside a window (a kingdom's seat, a prize, a tavern's offer) is
   a small window of its own on the same fill, in the lighter gilt. */
.jrpg :is(.choice-card, .seat) {
  border: calc(6 * var(--ui-px)) solid transparent;
  border-radius: 0;
  border-image: url('assets/ui/windows/tip.png') 6 / calc(6 * var(--ui-px)) / 0 round;
  background: none;
  padding: calc(4 * var(--ui-px)) calc(6 * var(--ui-px));
  image-rendering: pixelated;
}

.jrpg .seat:not(.open) {
  filter: saturate(0.4) brightness(0.8);
}

/* On touch a tapped button would keep :hover and look chosen. */
@media (hover: hover) {
  .jrpg :is(.cmd, .home-option):hover:not(:disabled, [aria-disabled='true']) {
    border-image-source: url('assets/ui/windows/button-lit.png');
    color: var(--win-gold);
  }

  .jrpg .offer:not(.sold, [aria-disabled='true']):hover {
    border-image: url('assets/ui/windows/button-lit.png') 6 fill / calc(6 * var(--ui-px)) / 0 stretch;
  }
}

/* Text tones are the window's (client/window-art.js WINDOW_TONES), read on
   its fill; the old ones were chosen for near-black. */
.jrpg :is(.bad, .tone-bad),
body:has(.jrpg) > .tooltip :is(.bad, .tone-bad) {
  color: var(--win-bad) !important;
}

.jrpg :is(.good, .tone-good),
body:has(.jrpg) > .tooltip :is(.good, .tone-good) {
  color: var(--win-good) !important;
}

.jrpg .tone-dim,
body:has(.jrpg) > .tooltip .tone-dim {
  color: var(--win-dim) !important;
}

/* The title screen: its windows beside a demo round (main.js showDemo),
   which has the rest of the stage and takes no input. */
.title-stage {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(18rem, 36%);
  gap: var(--gap);
  width: 100%;
  height: 100%;
  min-height: 0;
}

.title-stage > .title-field {
  position: relative;
  inset: auto;
  min-width: 0;
  min-height: 0;
  pointer-events: none;
}

.title-windows {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  min-width: 0;
  min-height: 0;
  overflow: hidden auto;
  scrollbar-width: none;
}

.title-windows > .tourney-start {
  flex: none;
  height: auto;
  overflow: visible;
}

.title-windows > .daily-board {
  flex: none;
  overflow: visible;
}

/* A command menu: bare words, the gauntlet beside the pointed one. */
.command-list {
  display: grid;
  gap: calc(2 * var(--ui-px));
  margin: 0;
  padding: 0 0 0 calc(18 * var(--ui-px));
  list-style: none;
}

.jrpg .command {
  width: 100%;
  padding: calc(2 * var(--ui-px)) 0;
  border: 0;
  background: none;
  color: var(--win-ink);
  text-align: left;
  cursor: pointer;
}

.jrpg .command-list .command {
  font-size: var(--type-2) !important;
  line-height: calc(24 * var(--ui-px));
}

.jrpg .command:is(.pointed, :focus-visible):not(:disabled) {
  outline: none;
  color: var(--win-gold);
}

.jrpg .command:disabled {
  color: var(--win-dim);
  cursor: default;
  opacity: 0.6;
}

/* The help window: what the pointed command does. */
.jrpg .help-line {
  min-height: calc(3 * var(--type-lh) + 12 * var(--ui-px));
  margin: 0;
  padding: calc(4 * var(--ui-px)) calc(6 * var(--ui-px));
  border: calc(6 * var(--ui-px)) solid transparent;
  border-image: url('assets/ui/windows/tip.png') 6 / calc(6 * var(--ui-px)) / 0 round;
  image-rendering: pixelated;
}

/* The gauntlet cursor (client/menu-cursor.js): beside the chosen command,
   bobbing a pixel between its two frames. */
.menu-cursor {
  position: fixed;
  z-index: 60;
  width: calc(16 * var(--cursor-px, 2px));
  height: calc(16 * var(--cursor-px, 2px));
  background: url('assets/ui/windows/cursor.png') 0 0 / 200% 100% no-repeat;
  image-rendering: pixelated;
  pointer-events: none;
  animation: cursor-bob 0.5s steps(1) infinite;
}

.menu-cursor[hidden] {
  display: none;
}

@keyframes cursor-bob {
  50% { background-position: 100% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .menu-cursor { animation: none; }
}

/* The pointed command is lit as a focused one is. */
.jrpg :is(.cmd, .home-option).pointed:not(:disabled, [aria-disabled='true']),
.jrpg .offer.pointed:not(.sold, [aria-disabled='true']) {
  border-image-source: url('assets/ui/windows/button-lit.png');
  color: var(--win-gold);
}

.jrpg .offer.pointed:not(.sold, [aria-disabled='true']) {
  border-image: url('assets/ui/windows/button-lit.png') 6 fill / calc(6 * var(--ui-px)) / 0 stretch;
}

/* The gauntlet marks the pointed choice; no second focus ring beside it. */
.jrpg .pointed:focus-visible {
  outline: none;
}

/* The army rail is a party status window: a row per squad, its level and an
   XP gauge, its commands open under it once chosen. */
.jrpg .party-line {
  display: flex;
  align-items: center;
  gap: calc(4 * var(--ui-px));
  min-width: 0;
}

.jrpg .party-line .lv {
  color: var(--win-gold);
}

/* A gauge: a bevelled pixel bar, filled to `--fill`. */
.gauge {
  flex: 1 1 auto;
  max-width: calc(48 * var(--ui-px));
  height: calc(5 * var(--ui-px));
  padding: var(--ui-px);
  border: var(--ui-px) solid var(--win-shadow, #10151f);
  background: var(--win-2, #202332);
  box-shadow: inset 0 var(--ui-px) 0 #0006;
}

.gauge > i {
  display: block;
  width: var(--fill, 0%);
  height: 100%;
  background: linear-gradient(#b6c1bc 0 34%, #788d9c 34%);
}

.gauge.xp > i {
  background: linear-gradient(#ffe3a0 0 34%, #bd8c4b 34%);
}

.gauge.xp.full > i {
  background: linear-gradient(#d4d89c 0 34%, #608569 34%);
}

.jrpg .squad-row .squad-acts {
  padding-top: calc(3 * var(--ui-px));
}

.jrpg .ready-flag {
  justify-self: end;
  color: var(--win-good);
}

.scene.tourney-start .top-bar h1 {
  visibility: hidden;
}

/* The battle transition (client/transition.js): over everything. */
.battle-wipe {
  position: fixed;
  inset: 0;
  z-index: 80;
  width: 100vw;
  height: 100vh;
  image-rendering: pixelated;
  pointer-events: none;
}

/* A round's results, over the field's centre. */
.jrpg .results-window,
.jrpg .item-get {
  position: absolute;
  inset: 0;
  z-index: 20;
  width: min(calc(100% - 2rem), calc(280 * var(--ui-px)));
  height: fit-content;
  max-height: calc(100% - 2rem);
  margin: auto;
  gap: calc(4 * var(--ui-px));
  overflow: hidden auto;
}

.jrpg .results-title {
  font-size: var(--type-3) !important;
  line-height: calc(36 * var(--ui-px));
  text-align: center;
  color: var(--win-gold);
  text-shadow: calc(2 * var(--ui-px)) calc(2 * var(--ui-px)) 0 var(--win-shadow);
}

.jrpg .results-title.lost {
  color: var(--win-dim);
}

.jrpg .results-window.animate .results-title {
  animation: results-drop 360ms steps(6) both;
}

@keyframes results-drop {
  from { transform: translateY(calc(-24 * var(--ui-px))); opacity: 0; }
}

.results-gold,
.results-favour {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(6 * var(--ui-px));
  margin: 0;
}

.jrpg .results-gold strong {
  color: var(--win-gold);
  font-size: var(--type-2) !important;
}

.results-xp {
  display: grid;
  gap: calc(2 * var(--ui-px));
  margin: 0;
  padding: 0;
  list-style: none;
}

.results-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) calc(48 * var(--ui-px)) auto auto;
  align-items: center;
  gap: calc(4 * var(--ui-px));
}

.results-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.results-row .gauge {
  max-width: none;
}

.jrpg .results-window.animate .gauge > i {
  animation: gauge-fill 900ms steps(12) 450ms both;
}

@keyframes gauge-fill {
  from { width: var(--from, 0%); }
  to { width: var(--fill, 0%); }
}

.jrpg .lv-up {
  color: var(--win-good);
}

.jrpg .results-window.animate .lv-up {
  animation: lv-flash 240ms steps(1) 1400ms 4 both;
}

@keyframes lv-flash {
  50% { visibility: hidden; }
}

/* A favour breaking: the heart's four frames, whole to shards, once. */
.heart-break {
  flex: none;
  width: calc(16 * var(--cursor-px, 2px));
  height: calc(16 * var(--cursor-px, 2px));
  background: url('assets/ui/windows/heart.png') 100% 0 / 400% 100% no-repeat;
  image-rendering: pixelated;
}

.jrpg .results-window.animate .heart-break {
  animation: heart-break 900ms steps(3, jump-none) 500ms both;
}

@keyframes heart-break {
  from { background-position: 0 0; }
  to { background-position: 100% 0; }
}

/* An item got: a window over a shade, confirmed with OK. */
.item-get-shade {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: #05080880;
}

.jrpg .item-get {
  position: absolute;
  text-align: center;
}

.jrpg .item-get-text {
  color: var(--win-gold);
  font-size: var(--type-2) !important;
  line-height: calc(24 * var(--ui-px));
}

@media (prefers-reduced-motion: reduce) {
  .jrpg .results-window.animate :is(.results-title, .gauge > i, .lv-up, .heart-break) { animation: none; }
}

.jrpg .honour-row {
  border-left-width: calc(2 * var(--ui-px));
  background: none;
}

.jrpg .honour h4 {
  color: var(--win-gold);
}

/* A speaker's line (client/herald.js): portrait, name, the typed text, and
   a bouncing ▼ to go on. The portrait speaks (its two frames) while typing. */
.jrpg .message-window {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: calc(6 * var(--ui-px));
  position: relative;
  overflow: visible;
}

.portrait-art {
  width: calc(48 * var(--ui-px));
  height: calc(48 * var(--ui-px));
  background: url('assets/ui/portraits/portraits.png') 0 calc(var(--row, 0) * 100% / 5) / 200% 600% no-repeat;
  image-rendering: pixelated;
}

.talking .portrait-art {
  animation: portrait-talk 240ms steps(1) infinite;
}

@keyframes portrait-talk {
  50% { background-position-x: 100%; }
}

.message-body {
  display: grid;
  gap: calc(2 * var(--ui-px));
  min-width: 0;
}

.jrpg .message-speaker {
  margin: 0;
  color: var(--win-gold);
}

.jrpg .message-text {
  margin: 0;
  min-height: calc(2 * var(--type-lh));
}

/* The ▼ that goes on: a pixel triangle, bouncing once the line is out. */
.jrpg .message-next {
  position: absolute;
  right: calc(2 * var(--ui-px));
  bottom: calc(-2 * var(--ui-px));
  width: calc(14 * var(--ui-px));
  height: calc(10 * var(--ui-px));
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

.jrpg .message-next::before {
  content: '';
  position: absolute;
  inset: calc(2 * var(--ui-px));
  background: var(--win-gold);
  clip-path: polygon(0 0, 100% 0, 50% 100%);
}

.jrpg .message-window:not(.talking) .message-next::before {
  animation: next-bob 600ms steps(1) infinite;
}

@keyframes next-bob {
  50% { transform: translateY(var(--ui-px)); }
}

.jrpg .talking .message-next {
  visibility: hidden;
}

/* The shop's opening lines float over the top of the field. */
.jrpg .field-message {
  position: absolute;
  top: calc(var(--inset-t, 0px) + 3.5rem);
  left: 0;
  right: 0;
  z-index: 5;
  width: min(calc(100% - 2rem), calc(300 * var(--ui-px)));
  margin: 0 auto;
}

/* The shop's commands float over the field's bottom edge in a slim frame,
   in place of a command bar. */
.jrpg .field-commands {
  position: absolute;
  left: var(--inset-l, 0px);
  right: var(--inset-r, 0px);
  bottom: var(--inset-b, 0px);
  z-index: 3;
  flex-direction: row;
  align-items: center;
  gap: calc(6 * var(--ui-px));
  width: fit-content;
  max-width: calc(100% - var(--inset-l, 0px) - var(--inset-r, 0px));
  margin: 0 auto;
  overflow: visible;
  border-width: calc(6 * var(--ui-px));
  border-image: url('assets/ui/windows/tip.png') 6 / calc(6 * var(--ui-px)) / 0 round;
  padding: calc(3 * var(--ui-px));
  background: var(--win-1) padding-box;
}

.jrpg .field-commands .bar-note {
  flex: 0 1 auto;
  padding-left: calc(3 * var(--ui-px));
}

@media (prefers-reduced-motion: reduce) {
  .talking .portrait-art,
  .jrpg .message-window .message-next::before { animation: none; }
}

/* On a narrow screen the top bar keeps its buttons; the player's name goes. */
@media (max-width: 900px) {
  .jrpg .top-user { display: none; }
}
