/* Gauntlet is one landscape scene that fills the Activity frame and never
   scrolls as a page. The layout is a five-region grid (top bar, left rail,
   stage, right rail, command bar); 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: none;
  align-items: center;
  gap: 0.4rem;
}

.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;
}

.route-track {
  display: flex;
  gap: 0.2rem;
}

.route-track i {
  width: 0.55rem;
  height: 0.55rem;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: #0d1413;
}

.route-track i.visited {
  background: var(--ink-dim);
}

.route-track i.current {
  border-color: var(--good);
  background: var(--good);
}

/* 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. */
.boss-bar {
  flex: 3 1 0;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto auto;
  align-items: center;
  column-gap: 0.6rem;
  row-gap: 0.15rem;
  min-width: 0;
}

.boss-title {
  grid-row: 1 / span 2;
  display: flex;
  align-items: center;
  gap: 0.45rem;
  min-width: 0;
}

.boss-name {
  color: #f0c27a;
  font-family: 'Sigil Pixel', monospace;
  font-size: 1rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.room-feature {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.35rem 0.6rem;
  border: 1px solid #c9a24a88;
  border-radius: 0.5rem;
  background: #2a2416;
}

.room-feature.used { opacity: 0.55; border-color: var(--line); background: var(--card); }

.feature-icon {
  width: 32px;
  height: 32px;
  image-rendering: pixelated;
}

/* Beside the room's title if there is room, under it if not. */
.route-head > .room-feature {
  flex: 0 1 auto;
  min-width: 0;
}

.room-feature-text {
  display: grid;
  min-width: 0;
}

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

.room-feature-text small { color: var(--ink-dim); }

.boss-ahead {
  margin: 0.15rem 0 0;
  color: var(--ink-dim);
  font-size: 0.85rem;
}

.boss-ahead strong { color: #f0c27a; }
.boss-ahead .weak { color: #ff9a7a; }

.boss-windup {
  padding: 0 0.35rem;
  border-radius: 999px;
  background: #e0302866;
  color: #ffd6cf;
  font-size: 0.7rem;
  white-space: nowrap;
  animation: telegraph-pulse 0.9s ease-in-out infinite alternate;
}

.boss-affinity {
  display: flex;
  align-items: center;
  gap: 0.15rem;
}

.boss-affinity small {
  color: var(--ink-dim);
  font-size: 0.65rem;
  text-transform: uppercase;
}

.boss-affinity.weak small { color: #ff9a7a; }

.element-icon {
  width: 16px;
  height: 16px;
  image-rendering: pixelated;
}

.boss-meter {
  position: relative;
  display: block;
  height: 0.9rem;
  border: 1px solid #000;
  background: #1a0d0d;
  box-shadow: inset 0 0 0 1px #ffffff14;
}

.boss-meter.stun {
  height: 0.45rem;
  background: #10141c;
}

.boss-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: calc(var(--frac) * 100%);
  background: linear-gradient(#e0524a, #9c2a27);
  transition: width 0.4s ease-out;
}

.boss-meter.stun .boss-fill { background: linear-gradient(#f2d06b, #b8902e); }
.boss-bar.staggered .boss-meter.stun { box-shadow: 0 0 0.5rem #f2d06b; }

.boss-tick {
  position: absolute;
  top: -2px;
  bottom: -2px;
  left: calc(var(--at) * 100%);
  width: 2px;
  background: #f0e6d0;
}

.boss-numbers {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: #fff;
  font-size: 0.65rem;
  line-height: 1;
  text-shadow: 0 1px 0 #000;
}

.boss-meter.stun .boss-numbers { display: none; }
.boss-bar.fallen { opacity: 0.5; }

/* 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;
}

.bar-group {
  display: flex;
  flex: none;
  align-items: center;
  gap: 0.4rem;
}

.bar-group.end {
  margin-left: auto;
}

.bar-group.actor {
  flex-shrink: 1;
  min-width: 6rem;
  gap: 0.5rem;
  padding-right: 0.75rem;
  border-right: 1px solid var(--line);
}

/* Abilities are the one group that can outgrow the bar; they scroll inside
   themselves so Confirm and End turn always stay on screen. */
.bar-group.modes {
  flex: 0 1 auto;
  min-width: 0;
  overflow-x: auto;
  overflow-y: hidden;
  /* It scrolls (a mouse wheel works too, see main.js) without a scrollbar
     that would appear and take height the moment a button is pressed. */
  scrollbar-width: none;
}

.bar-group.modes::-webkit-scrollbar {
  display: none;
}

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

.bar-group.modes .cmd {
  padding: 0.4rem 0.6rem;
}

.hint {
  color: var(--ink-dim);
  font-size: 0.8rem;
  white-space: nowrap;
}

/* ---------------------------------------------------------------- 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;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.cmd.roster-plus {
  min-width: 2.2rem;
  min-height: 2.2rem;
  padding: 0;
  border-color: var(--gold);
  color: #ffe1a1;
  font-size: 1.3rem;
}

.cmd.roster-plus.unaffordable {
  border-color: var(--line);
  color: var(--ink-dim);
}

/* Dismissing a character asks first. */
.dismiss-confirm {
  display: grid;
  gap: 0.4rem;
  padding: 0.5rem 0.6rem;
  border: 1px solid #7a3a36;
  background: #2a1716;
}

.cmd.ghost.danger {
  border-color: #7a3a36;
  color: #f0b3ad;
}

/* 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;
}

.unit-card {
  display: grid;
  grid-template-columns: 2.6rem minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.35rem 0.45rem;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--card);
  color: var(--ink);
  font: inherit;
  text-align: left;
}

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

.unit-card.active {
  border-color: var(--good);
  box-shadow: inset 3px 0 var(--good);
}

.unit-card.dead {
  opacity: 0.5;
  border-color: var(--hp);
}

.roster-entry {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-auto-columns: auto;
  grid-auto-flow: column;
  gap: 0.3rem;
}

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

.unit-card .check {
  color: var(--gold);
  font-size: 1.1rem;
}

.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;
}

.meter.mana span {
  background: var(--mana);
}

.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;
}

.turn-order {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.turn-order li {
  padding: 0.15rem 0.45rem;
  border: 1px solid var(--line);
  border-left: 3px solid var(--enemy);
  font-size: 0.78rem;
}

.turn-order li.ally {
  border-left-color: var(--ally);
}

.turn-order li.active {
  border-color: var(--gold);
  color: #ffe1a1;
}

.turn-order li.used,
.turn-order li.dead {
  opacity: 0.45;
}

.log {
  display: grid;
  gap: 0.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
  color: var(--ink-dim);
  font-size: 0.8rem;
}

.log li:first-child {
  color: var(--ink);
}

.inv-group {
  display: grid;
  gap: 0.3rem;
}

.inv-group p:not(.label) {
  font-size: 0.85rem;
}

/* 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. */
.pack-panel {
  container-type: inline-size;
  --slot: min(3.25rem, (100cqw - 1rem) / 5);
}

.inv-grid {
  display: grid;
  grid-template-columns: repeat(5, var(--slot));
  gap: 0.25rem;
}

.inv-row {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.inv-zone {
  display: grid;
  gap: 0.25rem;
  align-content: start;
}

.inv-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  min-height: var(--slot, 2.75rem);
}

.floor-zone {
  border: 1px dashed var(--line);
  border-radius: 4px;
  padding: 0.3rem;
}

.inv-slot {
  flex: none;
  width: var(--slot, 2.75rem);
  height: var(--slot, 2.75rem);
  min-width: 0;
  min-height: 0;
  display: grid;
  place-items: center;
  padding: 0.2rem;
  background: var(--card);
  border: 2px solid var(--line);
  border-radius: 3px;
  box-shadow: inset 0 0 0 2px #0b100f;
  cursor: grab;
  touch-action: none;
}

.inv-slot.empty {
  cursor: default;
  opacity: 0.55;
}

.inv-slot.selected {
  border-color: var(--gold);
}

.inv-slot.dragging {
  opacity: 0.35;
}

.inv-dragging [data-drop] {
  outline: 1px dashed transparent;
}

.inv-dragging .drop-over,
.inv-dragging .unit-card.drop-over {
  outline: 2px solid var(--gold);
  outline-offset: 1px;
}

/* Square whatever the slot: the 16×16 icon is never stretched. */
.item-icon {
  width: 100%;
  height: 100%;
  object-fit: contain;
  image-rendering: pixelated;
  pointer-events: none;
}

.drag-ghost {
  position: fixed;
  width: 3rem;
  height: 3rem;
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 1000;
  image-rendering: pixelated;
  filter: drop-shadow(0 2px 3px #000a);
}

.inv-detail {
  border-top: 1px solid var(--line);
  padding-top: 0.4rem;
}

.button-row {
  display: flex;
  gap: 0.35rem;
}

.button-row.wrap {
  flex-wrap: wrap;
}

.button-column {
  display: grid;
  gap: 0.35rem;
}

.assign-row {
  display: grid;
  gap: 0.25rem;
}

/* ---------------------------------------------------------------- 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.ghost {
  background: transparent;
}

.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),
  .unit-card:hover {
    border-color: var(--gold);
  }
}

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

.gauntlet-icon {
  --s: 1.4rem;
  display: inline-block;
  flex: none;
  width: var(--s);
  height: var(--s);
  background-image: url('/assets/ui/gauntlet/gauntlet-icons16.png');
  background-repeat: no-repeat;
  /* Seven native 16px icons in one row; each occupies exactly --s. */
  background-size: calc(var(--s) * 7) var(--s);
  image-rendering: pixelated;
}

.gauntlet-icon-sigil { background-position: 0 0; }
.gauntlet-icon-warrior { background-position: calc(var(--s) * -1) 0; }
.gauntlet-icon-rogue { background-position: calc(var(--s) * -2) 0; }
.gauntlet-icon-mage { background-position: calc(var(--s) * -3) 0; }
.gauntlet-icon-sword { background-position: calc(var(--s) * -4) 0; }
.gauntlet-icon-move { background-position: calc(var(--s) * -5) 0; }
.gauntlet-icon-enemy { background-position: calc(var(--s) * -6) 0; }

/* ---------------------------------------------------------------- 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;
}

.lineup {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.5rem;
  width: min(100%, 36rem);
  margin: auto auto 0;
}

.lineup:last-child {
  margin-bottom: auto;
}

.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;
}

.slot.filled {
  border: 1px solid var(--gold);
  color: var(--ink);
  font: inherit;
  font-size: 0.8rem;
  cursor: pointer;
}

.slot.filled:hover,
.slot.filled:focus-visible {
  background: #2b2a1d;
}

/* 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;
}

.home-stage {
  position: relative;
  display: grid;
  align-items: center;
  justify-items: start;
  padding-left: 2cqw;
  width: 100%;
  height: 100%;
  overflow: hidden;
  container-type: size;
}

.home-demo {
  position: absolute;
  inset: 0 0 0 30%;
  display: grid;
  place-items: center;
  opacity: 0.9;
  filter: saturate(0.85);
  pointer-events: none;
}

.home-demo .board-frame {
  --t: min(70cqw / (var(--n) + 1), 100cqh / (var(--n) + 1.5));
}

/* 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. */
.demo-wrap {
  position: relative;
}

.home-demo .figure,
.figure.fx-slide {
  transition: left 0.35s ease-in-out, top 0.35s ease-in-out, opacity 0.4s;
}

.home-demo .figure.demo-hidden {
  opacity: 0;
}

.figure.fx-appear {
  animation: fx-appear 0.4s ease-out;
}

.figure.fx-strike .sprite {
  animation: fx-strike 0.35s ease-out;
}

.figure.enemy.fx-strike .sprite {
  animation-name: fx-strike-left;
}

.figure.fx-strike .sprite.has-action-sheets {
  background-image: var(--sprite-attack-image);
  animation: sprite-attack 0.4s steps(4), fx-strike 0.4s ease-out;
}

.figure.enemy.fx-strike .sprite.has-action-sheets {
  animation-name: sprite-attack, fx-strike-left;
}

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

.figure.fx-hit .sprite {
  animation: fx-hit 0.45s steps(2) 2;
}

@keyframes fx-appear {
  from { opacity: 0; }
}

@keyframes fx-strike {
  40% { translate: 18% 0; }
}

@keyframes fx-strike-left {
  40% { translate: -18% 0; }
}

@keyframes fx-hit {
  50% { filter: brightness(2.2) sepia(1) hue-rotate(-40deg) saturate(4); }
}

.fx-float {
  position: absolute;
  left: calc((var(--x) + 0.5) * var(--t));
  top: calc(var(--y) * var(--t));
  z-index: 40;
  translate: -50% 0;
  font-family: 'Sigil Pixel', monospace;
  font-size: calc(var(--t) * 0.55);
  text-shadow: 0 2px 0 #000;
  animation: fx-float 1.2s ease-out forwards;
  pointer-events: none;
}

.fx-float.damage { color: #ff6b5e; }
.fx-float.poison { color: #9be06a; }
.fx-float.heal { color: #7fe0a0; }
.fx-float.status { color: #f2d06b; font-size: calc(var(--t) * 0.4); }

@keyframes fx-float {
  from { opacity: 1; transform: translateY(0); }
  to { opacity: 0; transform: translateY(calc(var(--t) * -1.1)); }
}

.fx-float.crit { color: #ffd23f; font-size: calc(var(--t) * 0.72); }
.fx-float.burn { color: #ff9a3c; }
.fx-float.shield { color: #8fd0ff; }
/* 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. */
.fx-float.el-fire { color: #ff9a3c; }
.fx-float.el-frost { color: #a8e4ff; }
.fx-float.el-lightning { color: #fff27a; }
.fx-float.el-poison { color: #9be06a; }
.fx-float.el-holy { color: #ffe9a8; }
.fx-float.el-shadow { color: #c79bff; }
.fx-float.weak { font-size: calc(var(--t) * 0.8); }
.fx-float.resist { font-size: calc(var(--t) * 0.42); opacity: 0.85; }

/* Sparks flying out from a tile: six dots on a ring, coloured by what hit. */
.fx-burst {
  --spark: #ffe0a0;
  position: absolute;
  left: calc((var(--x) + 0.5) * var(--t));
  top: calc((var(--y) + 0.5) * var(--t));
  z-index: 39;
  pointer-events: none;
}

.fx-burst i {
  position: absolute;
  width: calc(var(--t) * 0.12);
  height: calc(var(--t) * 0.12);
  margin: calc(var(--t) * -0.06);
  background: var(--spark);
  box-shadow: 0 0 calc(var(--t) * 0.12) var(--spark);
  animation: fx-spark 0.6s ease-out forwards;
}

.fx-burst.crit { --spark: #ffd23f; }
.fx-burst.crit i { width: calc(var(--t) * 0.18); height: calc(var(--t) * 0.18); }
.fx-burst.arcane { --spark: #b58cff; }
.fx-burst.heal { --spark: #7fe0a0; }
.fx-burst.shield { --spark: #8fd0ff; }
.fx-burst.poison { --spark: #9be06a; }
.fx-burst.burn { --spark: #ff7a2a; }
.fx-burst.chill { --spark: #cfefff; }
.fx-burst.stun { --spark: #f2d06b; }
.fx-burst.haste { --spark: #f2d06b; }
.fx-burst.holy { --spark: #fff3c4; }
.fx-burst.holy i { width: calc(var(--t) * 0.2); height: calc(var(--t) * 0.2); }
.fx-burst.thorns { --spark: #ff6b5e; }
.fx-burst.resist { --spark: #d9d9d9; }

/* Rising sparks for the kinder effects, flying sparks for blows. */
.fx-burst.heal i,
.fx-burst.shield i,
.fx-burst.haste i,
.fx-burst.holy i {
  animation-name: fx-rise;
}

@keyframes fx-spark {
  from { transform: rotate(var(--a)) translateX(0); opacity: 1; }
  to { transform: rotate(var(--a)) translateX(calc(var(--t) * 0.6)); opacity: 0; }
}

@keyframes fx-rise {
  from { transform: rotate(var(--a)) translateX(calc(var(--t) * 0.3)) translateY(0); opacity: 1; }
  to { transform: rotate(var(--a)) translateX(calc(var(--t) * 0.35)) translateY(calc(var(--t) * -0.4)); opacity: 0; }
}

/* Spells. Each look sets its colours; bolts and meteors fly over the
   figures, blast tiles light up under them, rippling out from the centre. */
.fx-bolt, .fx-cell, .fx-meteor, .fx-pillar, .fx-ward, .fx-whirl {
  --core: #f4ecff;
  --glow: #b58cff;
  position: absolute;
  pointer-events: none;
}

.fx-bolt.fire, .fx-cell.fire, .fx-whirl.fire { --core: #fff0b0; --glow: #ff7a2a; }
.fx-bolt.frost, .fx-cell.frost, .fx-whirl.frost { --core: #ffffff; --glow: #8fd8ff; }
.fx-bolt.time, .fx-cell.time, .fx-whirl.time { --core: #fff6d0; --glow: #f2d06b; }
.fx-bolt.poison, .fx-cell.poison, .fx-whirl.poison { --core: #e4ffc8; --glow: #7fc44a; }
.fx-bolt.steel, .fx-cell.steel, .fx-whirl.steel, .fx-whirl.whirl, .fx-cell.whirl { --core: #ffffff; --glow: #c9d3dc; }
.fx-cell.meteor { --core: #ffe39a; --glow: #ff5a1f; }

.fx-bolt {
  left: calc((var(--x) + 0.5) * var(--t));
  top: calc((var(--y) + 0.45) * var(--t));
  z-index: 38;
  width: calc(var(--t) * 0.3);
  height: calc(var(--t) * 0.3);
  margin: calc(var(--t) * -0.15);
  background: var(--core);
  box-shadow: 0 0 0 calc(var(--t) * 0.06) var(--glow), 0 0 calc(var(--t) * 0.35) var(--glow);
  rotate: var(--r);
  opacity: 0;
  animation: fx-fly var(--ms) linear both;
}

/* A trail behind the head. */
.fx-bolt::after {
  content: '';
  position: absolute;
  right: 50%;
  top: 25%;
  width: calc(var(--t) * 0.6);
  height: 50%;
  background: linear-gradient(90deg, transparent, var(--glow));
  opacity: 0.8;
}

.fx-bolt.steel {
  width: calc(var(--t) * 0.42);
  height: calc(var(--t) * 0.08);
  margin: calc(var(--t) * -0.04) calc(var(--t) * -0.21);
  box-shadow: 0 0 calc(var(--t) * 0.1) var(--glow);
}

.fx-bolt.steel::after { width: calc(var(--t) * 0.3); opacity: 0.4; }

@keyframes fx-fly {
  0% { opacity: 1; translate: 0 0; }
  100% { opacity: 1; translate: calc(var(--dx) * var(--t)) calc(var(--dy) * var(--t)); }
}

.fx-cell {
  left: calc(var(--x) * var(--t));
  top: calc(var(--y) * var(--t));
  z-index: 0;
  width: var(--t);
  height: var(--t);
  background: radial-gradient(circle, var(--core) 0 18%, var(--glow) 60%, transparent 100%);
  box-shadow: inset 0 0 0 2px var(--glow);
  opacity: 0;
  animation: fx-zone 0.6s ease-out calc(var(--d) * 60ms) both;
}

@keyframes fx-zone {
  0% { opacity: 0; transform: scale(0.4); }
  30% { opacity: 0.85; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.05); }
}

.fx-meteor {
  left: calc((var(--x) + 0.5) * var(--t));
  top: calc((var(--y) + 0.5) * var(--t));
  z-index: 38;
  width: calc(var(--t) * 0.6);
  height: calc(var(--t) * 0.6);
  margin: calc(var(--t) * -0.3);
  background: radial-gradient(circle at 40% 40%, #fff3c4, #ff9a3c 45%, #8a2a10 75%);
  box-shadow: 0 0 calc(var(--t) * 0.5) #ff7a2a;
  animation: fx-fall var(--ms) ease-in both;
}

@keyframes fx-fall {
  from { translate: calc(var(--t) * -2.5) calc(var(--t) * -6); opacity: 0.6; }
  to { translate: 0 0; opacity: 1; }
}

.fx-whirl {
  left: calc((var(--x) - 0.5) * var(--t));
  top: calc((var(--y) - 0.5) * var(--t));
  z-index: 38;
  width: calc(var(--t) * 2);
  height: calc(var(--t) * 2);
  border: calc(var(--t) * 0.1) dashed var(--glow);
  border-radius: 50%;
  animation: fx-whirl 0.5s ease-out forwards;
}

@keyframes fx-whirl {
  from { opacity: 1; transform: rotate(0) scale(0.5); }
  to { opacity: 0; transform: rotate(270deg) scale(1.15); }
}

/* Light pouring down on a healed (or hastened) ally. */
.fx-pillar {
  --glow: #7fe0a0;
  left: calc((var(--x) + 0.15) * var(--t));
  top: calc((var(--y) - 1.2) * var(--t));
  z-index: 38;
  width: calc(var(--t) * 0.7);
  height: calc(var(--t) * 2.2);
  background: linear-gradient(180deg, transparent, var(--glow) 55%, #ffffffcc 90%, transparent);
  mix-blend-mode: screen;
  transform-origin: bottom;
  animation: fx-pillar 0.85s ease-out forwards;
}

.fx-pillar.haste { --glow: #f2d06b; }

@keyframes fx-pillar {
  0% { opacity: 0; transform: scaleX(0.2); }
  25% { opacity: 0.9; transform: scaleX(1); }
  100% { opacity: 0; transform: scaleX(0.6); }
}

/* A bubble closing round a warded ally. */
.fx-ward {
  left: calc((var(--x) + 0.05) * var(--t));
  top: calc((var(--y) - 0.1) * var(--t));
  z-index: 38;
  width: calc(var(--t) * 0.9);
  height: calc(var(--t) * 1.1);
  border: 2px solid #bfe6ff;
  border-radius: 45%;
  background: radial-gradient(circle at 35% 30%, #ffffff88, #8fd0ff33 45%, #8fd0ff55 80%);
  animation: fx-ward 0.85s ease-out forwards;
}

@keyframes fx-ward {
  0% { opacity: 0; transform: scale(1.6); }
  35% { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(1); }
}

.board-frame.fx-shake {
  animation: fx-shake 0.3s linear;
}

@keyframes fx-shake {
  20% { translate: calc(var(--t) * -0.08) calc(var(--t) * 0.04); }
  40% { translate: calc(var(--t) * 0.08) calc(var(--t) * -0.04); }
  60% { translate: calc(var(--t) * -0.05) 0; }
  80% { translate: calc(var(--t) * 0.04) calc(var(--t) * 0.03); }
}

.fx-banner {
  position: absolute;
  inset: 40% 0 auto;
  z-index: 50;
  padding: calc(var(--t) * 0.3) 0;
  background: linear-gradient(90deg, transparent, #000c 20%, #000c 80%, transparent);
  color: var(--gold);
  font-family: 'Sigil Pixel', monospace;
  font-size: calc(var(--t) * 1.6);
  letter-spacing: 0.2em;
  text-align: center;
  text-shadow: 0 3px 0 #000, 0 0 1rem #d8a54a88;
  animation: fx-banner 2.4s ease-out forwards;
  pointer-events: none;
}

.fx-banner.loss {
  color: #ff6b5e;
  text-shadow: 0 3px 0 #000, 0 0 1rem #c34a4a88;
}

@keyframes fx-banner {
  0% { opacity: 0; transform: scaleX(0.4); }
  12% { opacity: 1; transform: scaleX(1.05); }
  20% { transform: scaleX(1); }
  80% { opacity: 1; }
  100% { opacity: 0; }
}

/* A milestone (a level gained, a talent learned) announced over the scene.
   It lives outside #app, so re-renders do not cut it short. */
.toast {
  position: fixed;
  left: 50%;
  top: calc(var(--safe-top) + 3.5rem);
  z-index: 30;
  translate: -50% 0;
  display: grid;
  justify-items: center;
  gap: 0.1rem;
  padding: 0.5rem 1.2rem;
  border: 1px solid var(--gold);
  background: #0b1110ee;
  box-shadow: 0 0 1.5rem #d8a54a55;
  color: var(--ink);
  text-align: center;
  animation: toast 3.2s ease-out forwards;
  pointer-events: none;
}

.toast strong {
  color: var(--gold);
  font-family: 'Sigil Pixel', monospace;
  font-size: 1.6rem;
  line-height: 1;
}

@keyframes toast {
  0% { opacity: 0; transform: translateY(-0.8rem); }
  10% { opacity: 1; transform: translateY(0); }
  85% { opacity: 1; }
  100% { opacity: 0; }
}

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

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

@media (prefers-reduced-motion: reduce) {
  .figure.fx-slide { transition: none; }
  .figure.fx-appear, .figure.fx-strike .sprite, .figure.fx-hit .sprite { animation: none; }
  .fx-burst, .fx-banner, .fx-bolt, .fx-cell, .fx-meteor, .fx-pillar, .fx-ward, .fx-whirl { display: none; }
  .board-frame.fx-shake { animation: none; }
}

.demo-caption {
  position: absolute;
  left: 50%;
  bottom: 0.6rem;
  translate: -50% 0;
  max-width: 90%;
  padding: 0.3rem 0.7rem;
  background: rgb(0 0 0 / 0.6);
  color: var(--ink);
  font-size: 0.9rem;
  text-align: center;
}

.demo-caption:empty {
  display: none;
}

.home-menu {
  position: relative;
  display: grid;
  justify-items: center;
  gap: 0.6rem;
  width: min(40%, 24rem);
  min-width: 15rem;
  max-height: 100%;
  overflow: auto;
  padding: 1.25rem;
  border: 8px solid transparent;
  border-image: url('/assets/ui/gauntlet/panel-frame.png') 8 fill / 8px / 0 stretch;
  background: rgb(25 32 38 / 0.9);
  background-clip: padding-box;
  text-align: center;
}

.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;
}

.home-options {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0.6rem;
  width: 100%;
  margin-top: 0.4rem;
}

.home-option {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto auto;
  align-items: center;
  column-gap: 0.75rem;
  padding: 0.5rem 0.75rem;
  text-align: left;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
}

.home-option:hover,
.home-option:focus-visible {
  border-color: var(--gold);
  background: #2b2a1d;
}

.home-option strong {
  font-family: 'Sigil Pixel', monospace;
  font-size: 1.5rem;
  font-weight: normal;
  color: var(--gold);
}

.home-option .portrait {
  grid-row: 1 / 3;
  width: 3.2rem;
  height: 3.2rem;
}

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

.home-foot {
  font-size: 0.8rem;
}

/* A hosted lobby, shown at the entrance until the party sets out. */
.lobby-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 1rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--gold);
  background: #1f1d15;
}

.join-code {
  color: var(--gold);
  font-family: 'Sigil Pixel', monospace;
  font-size: 2.2rem;
  letter-spacing: 0.15em;
}

.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. */
.pack-picker {
  display: grid;
  gap: 0.35rem;
  width: min(100%, 36rem);
  margin: 0.75rem auto 0;
}

.pack-picker:last-child {
  margin-bottom: auto;
}

.pack-rows {
  display: grid;
  gap: 0.3rem;
}

.pack-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 2rem 2rem 2rem minmax(0, 1fr);
  align-items: center;
  gap: 0.4rem;
}

.pack-row strong {
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.pack-row .cmd {
  padding: 0.2rem 0;
}

.pack-row small {
  text-align: right;
}

/* 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. */
.route-stage {
  position: relative;
  gap: 0.45rem;
  overflow: hidden;
}

.route-head {
  display: flex;
  flex: none;
  flex-wrap: wrap;
  align-items: end;
  gap: 0.35rem 1rem;
  min-width: 0;
}

.route-head > div {
  flex: none;
}

.route-head .muted {
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  font-size: 0.85rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.route-stage h2 {
  font-size: 1.7rem;
}

.overworld {
  position: relative;
  flex: 1;
  min-height: 0;
  container-type: size;
}

/* The window onto the map: it fills the stage, and the map inside is drawn
   larger and moved by dragging (attachPan in overworld.js). */
.overworld-view {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border: 2px solid #0a0f0e;
  box-shadow: 0 0 0 1px var(--line), 0 6px 18px #0009;
  background: #0e1413;
  cursor: grab;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

.overworld-view.dragging {
  cursor: grabbing;
}

/* Any zoomable view (zoom-pan.js): mid-drag, nothing under the pointer
   reacts. */
.pan-view.dragging * {
  pointer-events: none;
}

/* Zoom in, zoom out, fit: small square buttons in the view's top right. */
.zoom-controls {
  position: absolute;
  top: 0.4rem;
  right: 0.4rem;
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.zoom-button {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: #0b100fd9;
  color: var(--ink);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}

.zoom-button:hover {
  border-color: var(--gold);
}

/* --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. */
.overworld-frame {
  position: absolute;
  top: 0;
  left: 0;
  width: calc(var(--zoom, 1.7) * max(100cqw, 160cqh));
  aspect-ratio: 16 / 10;
  will-change: transform;
}

.overworld-land {
  display: block;
  width: 100%;
  height: 100%;
  image-rendering: pixelated;
  background: #1c2a22;
}

.overworld-nodes {
  position: absolute;
  inset: 0;
}

.overworld-title {
  position: absolute;
  top: 0.45rem;
  left: 0.6rem;
  padding: 0.1rem 0.45rem;
  background: #0b100fcc;
  color: var(--gold);
  font-family: 'Sigil Pixel', monospace;
  font-size: 0.95rem;
  pointer-events: none;
}

/* 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. */
.map-node {
  --size: clamp(1.2rem, 4.4cqw, 2.3rem);
  position: absolute;
  display: grid;
  place-items: center;
  width: var(--size);
  height: var(--size);
  min-height: 0;
  padding: 0;
  border: 2px solid #0b0f0e;
  border-radius: 50%;
  background: #141a19d9;
  box-shadow: 0 2px 0 #0008;
  transform: translate(-50%, -50%);
}

.cmd.map-node {
  min-width: 0;
  gap: 0;
}

.map-node .map-mark {
  width: 80%;
  height: 80%;
}

.map-node.node-unknown {
  --size: clamp(0.95rem, 3.2cqw, 1.6rem);
  border-color: #4b524b;
  background: #141a19a6;
}

.map-node.visited:not(.current) {
  opacity: 0.7;
  filter: saturate(0.4);
}

.map-node.current {
  border-color: var(--good);
  box-shadow: 0 0 0 2px #0b0f0e, 0 0 10px var(--good);
}

.map-node.ahead {
  border-color: var(--gold);
  cursor: pointer;
  animation: map-node-pulse 1.6s ease-in-out infinite;
}

.map-node.ahead:hover:not(:disabled),
.map-node.ahead:focus-visible {
  transform: translate(-50%, -50%) scale(1.18);
}

.map-node.ahead:disabled {
  animation: none;
  cursor: default;
  opacity: 1;
}

@keyframes map-node-pulse {
  50% { box-shadow: 0 0 0 2px #0b0f0e, 0 0 12px var(--gold); }
}

@media (prefers-reduced-motion: reduce) {
  .map-node.ahead { animation: none; }
}

.map-node.node-boss { --size: clamp(1.5rem, 5.4cqw, 2.8rem); border-color: #7a1d22; }

.map-mark {
  display: block;
  image-rendering: pixelated;
  pointer-events: none;
}

/* 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. */
.route-overlay {
  position: absolute;
  top: 5.2rem;
  right: 1.5rem;
  display: grid;
  gap: 0.5rem;
  width: min(26rem, calc(100% - 3rem));
  pointer-events: none;
}

.route-overlay .lobby-card {
  background: #1f1d15f0;
}

/* The fight's result fades out after a moment (see RESULT_BANNER_MS). */
.route-overlay .result-banner {
  animation: result-banner var(--result-banner-ms, 3.5s) linear forwards;
}

@keyframes result-banner {
  0%, 80% { opacity: 1; }
  100% { opacity: 0; visibility: hidden; }
}

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

  .route-overlay .lobby-card {
    gap: 0.6rem;
    padding: 0.3rem 0.5rem;
  }

  .route-overlay .lobby-card .sheet-list,
  .route-overlay .lobby-card .muted {
    display: none;
  }

  .route-overlay .join-code {
    font-size: 1.2rem;
  }
}

.route-overlay > * {
  pointer-events: auto;
  box-shadow: 0 6px 18px #000a;
}

/* The map key, under the map, where the way-on buttons used to be. */
.map-legend {
  display: flex;
  flex: none;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem 1rem;
}

.map-legend ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.75rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.map-legend li {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  font-size: 0.8rem;
  white-space: nowrap;
}

.map-legend .map-mark {
  width: 1.25rem;
  height: 1.25rem;
}

.map-legend .muted {
  margin-left: auto;
  font-size: 0.8rem;
}

/* Why the rooms ahead are not lit yet. */
.map-legend .muted.blocked {
  color: var(--gold);
}

/* On a small stage the key keeps its icons and names, smaller, and drops
   the hint. */
@container (max-width: 36rem) {
  .map-legend ul {
    gap: 0.15rem 0.5rem;
  }

  .map-legend li {
    font-size: 0.68rem;
  }

  .map-legend .map-mark {
    width: 1rem;
    height: 1rem;
  }

  .map-legend .muted:not(.blocked) {
    display: none;
  }
}

/* The stair down from the checkpoint, at the map's right edge. */
.map-node.map-exit {
  --size: clamp(1.5rem, 5.4cqw, 2.8rem);
}

.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. */
.board-view {
  position: absolute;
  inset: 0;
  overflow: hidden;
  container-type: size;
  cursor: grab;
  touch-action: none;
}

.board-view.dragging {
  cursor: grabbing;
}

.pan-view.zooming * {
  transition: none !important;
}

.board-pan {
  position: absolute;
  top: 0;
  left: 0;
  will-change: transform;
}

/* 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. */
.board-frame {
  --n: 16;
  --t: calc(min(100cqw / (var(--n) + 1), 100cqh / (var(--n) + 1.5)) * var(--zoom, 1));
  position: relative;
  width: calc(var(--t) * (var(--n) + 1));
  height: calc(var(--t) * (var(--n) + 1.5));
  padding: calc(var(--t) * 1.25) calc(var(--t) * 0.5) calc(var(--t) * 0.25);
  background: url('/assets/ui/gauntlet/dungeon/tiles/wall-top.png') 0 0 / var(--t) var(--t);
  box-shadow: inset 0 0 0 2px #1d2622, 0 0.5rem 1.5rem #0008;
  image-rendering: pixelated;
}

.board-wall {
  position: absolute;
  top: calc(var(--t) * 0.25);
  left: calc(var(--t) * 0.5);
  right: calc(var(--t) * 0.5);
  height: var(--t);
  background: url('/assets/ui/gauntlet/dungeon/tiles/wall-face.png') 0 0 / var(--t) var(--t) repeat-x;
}

/* In battle the back wall sits in the same dark as the unseen floor. */
.mode-battle .board-wall {
  filter: brightness(0.3) saturate(0.4);
}

.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.floor-1 { background-image: url('/assets/ui/gauntlet/dungeon/tiles/floor-1.png'); }
.tile.floor-2 { background-image: url('/assets/ui/gauntlet/dungeon/tiles/floor-2.png'); }
.tile.floor-3 { background-image: url('/assets/ui/gauntlet/dungeon/tiles/floor-3.png'); }
.tile.floor-4 { background-image: url('/assets/ui/gauntlet/dungeon/tiles/floor-4.png'); }
.tile.rough-1 { background-image: url('/assets/ui/gauntlet/dungeon/tiles/rough-1.png'); }
.tile.rough-2 { background-image: url('/assets/ui/gauntlet/dungeon/tiles/rough-2.png'); }

.tile.elevated.floor-1 { background-image: url('/assets/ui/gauntlet/dungeon/tiles/platform-1.png'); }
.tile.elevated.floor-2 { background-image: url('/assets/ui/gauntlet/dungeon/tiles/platform-2.png'); }
.tile.elevated.floor-3 { background-image: url('/assets/ui/gauntlet/dungeon/tiles/platform-3.png'); }
.tile.elevated.floor-4 { background-image: url('/assets/ui/gauntlet/dungeon/tiles/platform-4.png'); }
.tile.elevated.rough-1 { background-image: url('/assets/ui/gauntlet/dungeon/tiles/platform-rough-1.png'); }
.tile.elevated.rough-2 { background-image: url('/assets/ui/gauntlet/dungeon/tiles/platform-rough-2.png'); }
.tile.elevated-front.floor-1 { background-image: url('/assets/ui/gauntlet/dungeon/tiles/platform-front-1.png'); }
.tile.elevated-front.floor-2 { background-image: url('/assets/ui/gauntlet/dungeon/tiles/platform-front-2.png'); }
.tile.elevated-front.floor-3 { background-image: url('/assets/ui/gauntlet/dungeon/tiles/platform-front-3.png'); }
.tile.elevated-front.floor-4 { background-image: url('/assets/ui/gauntlet/dungeon/tiles/platform-front-4.png'); }
.tile.elevated-front.rough-1 { background-image: url('/assets/ui/gauntlet/dungeon/tiles/platform-rough-front-1.png'); }
.tile.elevated-front.rough-2 { background-image: url('/assets/ui/gauntlet/dungeon/tiles/platform-rough-front-2.png'); }

.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. */
.tile.wall-front {
  background-image: url('/assets/ui/gauntlet/dungeon/tiles/wall-front.png');
}

/* Thorns a boss grew: impassable, like rock. */
.tile.bramble {
  background-image: url('/assets/ui/gauntlet/dungeon/tiles/bramble.png');
}

/* A boss's wound-up move: the tiles it will strike, pulsing red. */
.tile.telegraph::after {
  content: '';
  position: absolute;
  inset: 0;
  background: #e0302880;
  box-shadow: inset 0 0 0 2px #ff6b5e;
  animation: telegraph-pulse 0.9s ease-in-out infinite alternate;
  pointer-events: none;
}

@keyframes telegraph-pulse {
  from { opacity: 0.45; }
  to { opacity: 0.9; }
}

/* 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). */
.tile.shadowed {
  filter: brightness(0.14) saturate(0.2);
}

.tile.elevated.shadowed {
  filter: brightness(0.2) saturate(0.3);
}

/* Rock stays faintly recognisable as rock in the dark, not a hole in the map. */
.tile.wall.shadowed {
  filter: brightness(0.26) saturate(0.3);
}

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

.tile.objective::after {
  content: '';
  position: absolute;
  inset: 18%;
  border: 2px solid #64ebb8;
  transform: rotate(45deg);
  pointer-events: none;
}

.tile.deploy-zone {
  box-shadow: inset 0 0 0 1px #d8a54a88;
}

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

.tile.target-move::before {
  background: #d59b3d55;
  box-shadow: inset 0 0 0 2px #ffdc87;
}

.tile.target-act::before {
  background: #c8413666;
  box-shadow: inset 0 0 0 2px #ff9078;
}

.tile.obstructed::before {
  background: repeating-linear-gradient(45deg, #0000 0 3px, #d8a54a88 3px 5px);
}

.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. */
.aoe-mark {
  position: absolute;
  left: calc(var(--x) * var(--t));
  top: calc(var(--y) * var(--t));
  z-index: 0;
  width: var(--t);
  height: var(--t);
  background: #ffb45c33;
  box-shadow: inset 0 0 0 1px #ffc98088;
  pointer-events: none;
}

.aoe-mark.foe { background: #ff5a4a55; box-shadow: inset 0 0 0 2px #ff9078; }
.aoe-mark.risk { background: repeating-linear-gradient(45deg, #ffcf4a66 0 4px, #0000 4px 8px); box-shadow: inset 0 0 0 2px #ffcf4a; }
.aoe-mark.boon { background: #7fe0a055; box-shadow: inset 0 0 0 2px #7fe0a0; }
.aoe-mark.hover { opacity: 0.75; }

/* 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.active::before {
  border-color: #ffe1a1;
  box-shadow: 0 0 calc(var(--t) * 0.25) #ffd36b;
}

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

.figure .gauntlet-icon {
  --s: calc(var(--t) * 0.8);
  position: relative;
}

.figure.downed .sprite {
  filter: grayscale(1) brightness(0.7);
  /* Lying down, still inside the tile. */
  transform: rotate(-90deg);
  transform-origin: center;
}

.figure.downed .sprite.has-action-sheets {
  background-image: var(--sprite-downed-image);
  animation: sprite-downed 1.2s steps(4) infinite;
  filter: grayscale(0.65) brightness(0.85);
  transform: none;
}

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

/* 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);
}

.figure.enemy.downed .sprite {
  transform: scaleX(-1) rotate(-90deg);
}

.figure.enemy.downed .sprite.has-action-sheets {
  transform: scaleX(-1);
}

@media (prefers-reduced-motion: reduce) {
  .sprite,
  .figure.fx-strike .sprite.has-action-sheets,
  .figure.downed .sprite.has-action-sheets {
    animation: none;
    background-position: 0 0;
  }
}

.figure-hp {
  position: absolute;
  bottom: calc(var(--t) * -0.02);
  width: calc(var(--t) * 0.8);
  height: max(2px, calc(var(--t) * 0.08));
  border: 1px solid #000;
  background: linear-gradient(90deg, var(--hp) calc(var(--hp-frac) * 100%), #000a 0);
}

/* Mini health and mana bars over a character while the mouse is on it. */
.figure-meters {
  position: absolute;
  top: calc(var(--t) * -0.52);
  display: none;
  gap: 1px;
  width: calc(var(--t) * 0.9);
  padding: 1px;
  border: 1px solid #000;
  background: #000c;
}

.figure.hovered {
  z-index: 20;
}

.figure.hovered .figure-meters {
  display: grid;
}

.figure-meter {
  height: max(3px, calc(var(--t) * 0.1));
  background: linear-gradient(90deg, var(--hp) calc(var(--frac) * 100%), #333 0);
}

.figure-meter.mana {
  background: linear-gradient(90deg, var(--mana) calc(var(--frac) * 100%), #333 0);
}

.figure-status {
  position: absolute;
  top: calc(var(--t) * -0.3);
  display: flex;
  gap: 2px;
}

.badge {
  padding: 0 3px;
  border: 1px solid #000;
  font: normal 700 max(7px, calc(var(--t) * 0.22)) / 1.1 system-ui, sans-serif;
  font-style: normal;
  color: #fff;
}

.badge.stun {
  background: #b58b12;
}

.badge.poison {
  background: #7a3fb0;
}

/* ---------------------------------------------------------------- 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); }

.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); }

.action-panel {
  flex: 0 1 auto;
  max-height: 45%;
  font-size: 0.85rem;
}

/* In a rail the label sits above its value; side by side wraps badly there. */
.action-panel .tip-body dl {
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
}

.action-panel .tip-body dd {
  margin-bottom: 0.3rem;
}

/* ---------------------------------------------------------------- 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;
}

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

.recruit-card {
  display: grid;
  justify-items: center;
  gap: 0.35rem;
  padding: 0.6rem;
  border: 1px solid var(--line);
  background: var(--card);
  text-align: center;
}

.recruit-card select {
  width: 100%;
  min-height: 2.2rem;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--bg);
  color: var(--ink);
}

.recruit-card .cmd {
  width: 100%;
}

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

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

.character-head {
  display: flex;
  gap: 0.75rem;
  align-items: center;
}

.stat-strip {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 0.3rem;
  margin-top: 0.4rem;
}

.stat-strip span {
  display: grid;
  justify-items: center;
  padding: 0.3rem 0.2rem;
  border: 1px solid var(--line);
  background: var(--card);
}

.stat-strip small {
  color: var(--ink-dim);
  font-size: 0.65rem;
}

.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;
}

.sheet-list {
  display: grid;
  gap: 0.3rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.sheet-list li {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
}

.ability-chip {
  cursor: help;
}

.talent-row {
  display: grid;
  gap: 0.25rem;
  margin-top: 0.4rem;
}

/* ---------------------------------------------------------------- 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. */
.path-0 { --path: #d9694f; --path-deep: #4a2019; }
.path-1 { --path: #4fb3a6; --path-deep: #163a36; }
.path-2 { --path: #a687e0; --path-deep: #2d2346; }

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

.talent-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.talent-head p {
  margin: 0;
  flex: 1;
}

.chip.talent-ready,
.cmd.talent-ready,
.talent-badge {
  color: var(--gold);
  border-color: var(--gold);
  animation: talent-beckon 1.6s ease-in-out infinite;
}

.talent-badge {
  display: inline;
  border: 0;
  font-weight: 700;
}

@keyframes talent-beckon {
  50% { box-shadow: 0 0 0.6rem #d8a54a88; text-shadow: 0 0 0.4rem #d8a54a; }
}

.talent-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(12rem, 16rem);
  gap: 0.75rem;
  align-items: start;
}

.talent-tree {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-template-rows: auto repeat(4, auto);
  grid-auto-flow: column;
  gap: 0.45rem 0.5rem;
}

.talent-path-head {
  display: grid;
  gap: 0.15rem;
  padding: 0.4rem 0.5rem;
  border-top: 3px solid var(--path);
  background: linear-gradient(var(--path-deep), transparent);
}

.talent-path-head strong {
  color: var(--path);
  font-family: 'Sigil Pixel', monospace;
  font-size: 1.25rem;
  line-height: 1;
}

.talent-path-head small {
  color: var(--ink-dim);
  font-size: 0.72rem;
  line-height: 1.25;
}

.path-progress {
  display: flex;
  gap: 0.2rem;
}

.path-progress i {
  width: 0.9rem;
  height: 0.3rem;
  background: var(--line);
}

.path-progress i.on {
  background: var(--path);
}

.talent-node {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-content: start;
  gap: 0.1rem 0.4rem;
  min-height: 3.2rem;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--card);
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, transform 0.1s;
}

/* The line down a path, joining each talent to the one above it. */
.talent-node::before {
  content: '';
  position: absolute;
  left: 1.05rem;
  top: -0.5rem;
  width: 2px;
  height: 0.45rem;
  background: var(--line);
}

.talent-node.learned::before {
  background: var(--path);
}

.talent-node:hover,
.talent-node:focus-visible {
  border-color: var(--path);
}

.talent-node:active {
  transform: scale(0.98);
}

.talent-tier {
  grid-row: span 2;
  display: grid;
  place-items: center;
  width: 1.3rem;
  height: 1.3rem;
  border: 1px solid var(--path);
  color: var(--path);
  font-family: 'Sigil Pixel', monospace;
  border-radius: 2px;
  font-size: 0.85rem;
}

.talent-node strong {
  font-size: 0.9rem;
  line-height: 1.15;
}

.talent-text {
  grid-column: 2;
  color: var(--ink-dim);
  font-size: 0.72rem;
  line-height: 1.25;
}

.talent-mark {
  position: absolute;
  top: 0.25rem;
  right: 0.35rem;
  color: var(--path);
  font-size: 0.8rem;
}

.talent-mark.lock {
  color: var(--ink-dim);
  font-size: 0.7rem;
  filter: grayscale(1);
}

.talent-node.learned {
  border-color: var(--path);
  background: linear-gradient(135deg, var(--path-deep), var(--card) 80%);
  box-shadow: inset 0 0 0 1px var(--path);
}

.talent-node.learned .talent-tier {
  background: var(--path);
  color: #111;
}

.talent-node.open {
  border-color: var(--gold);
  box-shadow: 0 0 0.5rem #d8a54a44;
}

.talent-node.open .talent-tier {
  animation: talent-beckon 1.6s ease-in-out infinite;
}

.talent-node.passed,
.talent-node.locked,
.talent-node.sealed {
  opacity: 0.55;
}

.talent-node.passed {
  opacity: 0.4;
  filter: saturate(0.3);
}

.talent-node.capstone {
  min-height: 3.8rem;
  border-style: double;
  border-width: 3px;
}

.talent-node.selected {
  outline: 2px solid var(--ink);
  outline-offset: 1px;
  opacity: 1;
}

.talent-node.fresh {
  animation: talent-learned 0.9s ease-out;
}

@keyframes talent-learned {
  0% { transform: scale(1.12); box-shadow: 0 0 1.6rem var(--path), inset 0 0 1rem var(--path); }
  100% { transform: scale(1); }
}

.talent-detail {
  position: sticky;
  top: 0;
  display: grid;
  gap: 0.45rem;
  padding: 0.6rem;
  border: 1px solid var(--path);
  border-left-width: 4px;
  background: var(--panel-raised);
}

.talent-detail h4 {
  margin: 0;
  color: var(--path);
  font-family: 'Sigil Pixel', monospace;
  font-size: 1.5rem;
  line-height: 1;
}

.talent-detail p {
  margin: 0;
}

.talent-rule {
  font-size: 0.75rem;
}

/* A short window (a landscape phone) keeps names on the cards; the detail
   panel beside the tree carries the full text. */
@media (max-height: 560px) {
  .talent-text,
  .talent-path-head small {
    display: none;
  }

  .talent-node {
    min-height: 2.3rem;
  }

  .talent-node.capstone {
    min-height: 2.6rem;
  }
}

@media (max-width: 760px) {
  .talent-layout {
    grid-template-columns: minmax(0, 1fr) minmax(9rem, 12rem);
  }

  .talent-text {
    display: none;
  }
}

/* ---------------------------------------------------------------- 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.chilled .sprite {
  filter: hue-rotate(160deg) saturate(0.6) brightness(1.2);
}

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

.figure.burning.chilled .sprite {
  filter: none;
}

.figure-shield {
  position: absolute;
  z-index: 2;
  inset: 5% 8%;
  border: 2px solid #8fd0ff99;
  border-radius: 45%;
  box-shadow: 0 0 calc(var(--t) * 0.15) #8fd0ff88, inset 0 0 calc(var(--t) * 0.12) #8fd0ff55;
  animation: status-pulse 1.8s ease-in-out infinite;
  pointer-events: none;
}

.figure.hasted .sprite {
  animation-duration: 0.35s;
  filter: drop-shadow(0 0 calc(var(--t) * 0.1) #f2d06b);
}

.figure.stunned .sprite {
  animation-play-state: paused;
}

.badge.burn { background: #b4501f; }
.badge.chill { background: #3f7fa8; }
.badge.root { background: #4f6b2a; }

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

@keyframes status-pulse {
  50% { opacity: 0.55; }
}

.rotate-hint {
  display: none;
}

/* ---------------------------------------------------------------- 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;
  }

  .route-track,
  .top-user,
  .boss-affinity small {
    display: none;
  }

  .boss-name { font-size: 0.8rem; }
}

/* The how-to hint is the first thing to give up its room in the bar. */
@media (max-width: 1200px) {
  .bar-group.end .hint {
    display: none;
  }
}

/* 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;
  }
}

/* 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 {
    display: none;
  }
}

/* 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 */

.market-top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

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

.market-body {
  display: grid;
  gap: 0.5rem;
}

.shop-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: 0.5rem;
}

.shop-card {
  display: grid;
  align-content: start;
  gap: 0.3rem;
  padding: 0.5rem;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--card);
}

.shop-card.deal {
  border-color: var(--gold);
}

.shop-card p {
  margin: 0;
  font-size: 0.8rem;
}

.shop-card-head {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.shop-card-head .item-icon,
.pack-name .item-icon,
.bundle-item .item-icon,
.outfit-slot .item-icon {
  width: 2rem;
  height: 2rem;
  flex: none;
}

.pack-name {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

.pack-name .item-icon {
  width: 1.5rem;
  height: 1.5rem;
}

.shop-price .tone-gold {
  color: var(--gold);
  font-weight: 700;
}

.deal-tag {
  padding: 0.05rem 0.35rem;
  border: 1px solid var(--gold);
  border-radius: 3px;
  color: #ffe1a1;
}

.bundle-icons {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
}

.bundle-item {
  display: inline-flex;
  align-items: center;
}

.cmd.unaffordable {
  border-color: var(--line);
  color: var(--ink-dim);
}

.outfit-worn {
  display: flex;
  gap: 0.35rem;
}

.outfit-slot {
  position: relative;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  border: 2px solid var(--line);
  border-radius: 3px;
  background: var(--card);
}

.outfit-slot.empty {
  opacity: 0.6;
  font-size: 0.65rem;
}

.outfit-slot .icon-button {
  position: absolute;
  top: -0.5rem;
  right: -0.5rem;
  min-width: 1.2rem;
  min-height: 1.2rem;
  padding: 0;
  font-size: 0.8rem;
}

.market-foot {
  align-items: center;
  border-top: 1px solid var(--line);
  padding-top: 0.4rem;
}

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

.dungeon-picker {
  display: grid;
  gap: 0.35rem;
}

.dungeon-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
  gap: 0.35rem;
}

.cmd.dungeon-card {
  display: grid;
  gap: 0.1rem;
  justify-items: start;
  text-align: left;
  border-left-width: 4px;
}

.cmd.dungeon-card small {
  color: var(--ink-dim);
  font-size: 0.72rem;
}

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

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

/* Each dungeon's edge wears its biome's colour. */
.cmd.dungeon-card.biome-1 { border-left-color: #6eaa4a; }
.cmd.dungeon-card.biome-2 { border-left-color: #72a267; }
.cmd.dungeon-card.biome-3 { border-left-color: #a7b6b1; }
.cmd.dungeon-card.biome-4 { border-left-color: #c8412a; }
