:root {
  --bg: #100c0a;
  --ink: #f3e6d0;
  --dim: #a48a72;
  --gold: #e2c03d;
  --gold-deep: #b8891e;
  --stone: #2a211c;
  --stone-2: #3a2d24;
  --blade: #e4453a;
  --ward: #4d8eef;
  --ember: #f08a2a;
  --vial: #3cb56a;
  --coin: #e2c03d;
  --danger: #ff5a5a;
  /* One colour means "this is a foe", everywhere it appears. The board token,
     the rail card and the inspector portrait each used to pick their own --
     salmon, gold and pink respectively -- so the same creature was three
     different colours depending on where you looked at it. */
  --foe: #e03b28;
  --foe-soft: rgba(224, 59, 40, 0.45);
  --font-ui: "Source Sans 3", system-ui, sans-serif;
  --font-display: Cinzel, Georgia, serif;
  --cell: 46px; /* fallback; app.js sizes the board to the viewport */
  --radius: 14px;
  --glow: 0 0 18px rgba(226, 192, 61, 0.28);
}

* { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }
html, body {
  height: 100%;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-ui);
  user-select: none;
  overflow: hidden;
}
body {
  /* The veil keeps tiles readable over the backdrop. Its strength is per-zone,
     because the halls range from 51/255 mean luminance down to 16 for the deep
     dark -- one setting either washes out the bright halls or erases the dark. */
  background:
    linear-gradient(180deg,
      rgba(16, 12, 10, var(--veil-top, 0.30)) 0%,
      rgba(16, 12, 10, var(--veil-bottom, 0.74)) 100%),
    var(--hall, url("../assets/hall.jpg")) center / cover no-repeat,
    var(--bg);
}
button, input { font: inherit; }
.screen {
  position: absolute;
  inset: 0;
  display: none;
  flex-direction: column;
  padding: env(safe-area-inset-top) 16px env(safe-area-inset-bottom);
}
.screen.active { display: flex; }

.wordmark {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: 0.14em;
  font-size: clamp(2.1rem, 9vw, 3.6rem);
  line-height: 0.95;
  background: linear-gradient(180deg, #ffe08a 0%, #e2c03d 45%, #b8891e 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  text-align: center;
  filter: drop-shadow(0 4px 16px rgba(226, 192, 61, 0.28));
}
.tagline {
  text-align: center;
  /* --dim (#a48a72) is a panel colour, and this is the one line on the title
     screen with no panel behind it - it sits directly on a lit stone
     photograph, where a low-contrast brown reads as smudged texture rather
     than as words. Lifted to the parchment ink the buttons use, with a dark
     halo so it holds over the bright torch areas too. */
  color: #e8d8b8;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95), 0 0 12px rgba(0, 0, 0, 0.8);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  font-size: 0.86rem;
  font-weight: 600;
  margin-top: 0.45rem;
}

#screen-title {
  align-items: center;
  justify-content: center;
  gap: 10px;
  text-align: center;
}
.title-hero {
  width: min(42vw, 180px);
  aspect-ratio: 1;
  border-radius: 50%;
  object-fit: cover;
  border: 3px solid #c9a24a;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.55), var(--glow);
  margin: 10px 0 6px;
}
.daily-chip {
  color: var(--gold);
  font-size: 0.82rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-bottom: 8px;
}
.btn-col { display: flex; flex-direction: column; gap: 10px; width: min(320px, 86vw); margin-top: 8px; }
.btn {
  appearance: none;
  border: 0;
  cursor: pointer;
  border-radius: 14px;
  padding: 0.92rem 1.2rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  transition: transform 0.12s ease, filter 0.12s ease;
}
.btn:active { transform: scale(0.97); }
.btn-primary {
  background: linear-gradient(180deg, #f5d06a, #c9922e);
  color: #1a1008;
  box-shadow: 0 5px 0 #8a6414, 0 10px 22px rgba(0, 0, 0, 0.4);
}
.btn-secondary {
  background: linear-gradient(180deg, #5a4536, #3d2e24);
  color: var(--ink);
  box-shadow: 0 5px 0 #241810;
}
.btn-ghost {
  background: transparent;
  color: var(--dim);
  border: 1px solid rgba(164, 138, 114, 0.35);
  box-shadow: none;
  padding: 0.65rem 1rem;
}
.btn[disabled] { opacity: 0.45; pointer-events: none; }

#screen-how {
  overflow: auto;
  gap: 12px;
  padding-bottom: 24px;
}
.how-card {
  background: rgba(16, 12, 10, 0.78);
  border: 1px solid rgba(201, 162, 74, 0.25);
  border-radius: 16px;
  padding: 16px;
  max-width: 560px;
  margin: 0 auto;
  width: 100%;
}
/* RECORDS. Reuses .how-card as its frame, so the panel is the game's panel
   and not a second visual language for the same kind of page. */
/* RECORDS DIALOG. Three bands: a header that stays, a body that scrolls, and
   a footer that stays. The whole thing is capped against the viewport so on a
   phone the list scrolls INSIDE the panel and the way out is never below the
   fold - which it was when this was a plain page and the Back button sat after
   however many achievements there happened to be. */
#screen-records {
  justify-content: center;
  align-items: center;
  /* The dimmed surround is also the way out - see the pointerdown handler. */
  background: rgba(6, 4, 3, 0.72);
}
.rec-dialog {
  display: flex;
  flex-direction: column;
  width: min(560px, 100%);
  /* min-height:0 on the scroller is what actually lets a flex child scroll;
     without it the body grows and pushes the footer off the screen. */
  max-height: min(86vh, 760px);
  background: rgba(16, 12, 10, 0.97);
  border: 1px solid rgba(201, 162, 74, 0.3);
  border-radius: 16px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6);
  overflow: hidden;
}
.rec-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 14px 16px 12px;
  border-bottom: 1px solid rgba(201, 162, 74, 0.22);
  flex: none;
}
.rec-top h2 {
  font-family: var(--font-display);
  font-size: 1.15rem;
  letter-spacing: 0.06em;
  color: var(--gold);
  margin: 0;
}
.rec-x {
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 9px;
  border: 1px solid rgba(201, 162, 74, 0.3);
  background: rgba(0, 0, 0, 0.35);
  color: var(--ink);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.rec-x:hover { border-color: rgba(201, 162, 74, 0.7); color: var(--gold); }
.rec-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 4px 16px 16px;
  scrollbar-width: thin;
  scrollbar-color: rgba(201, 162, 74, 0.45) rgba(0, 0, 0, 0.25);
}
.rec-scroll::-webkit-scrollbar { width: 8px; }
.rec-scroll::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.25); }
.rec-scroll::-webkit-scrollbar-thumb {
  background: rgba(201, 162, 74, 0.45);
  border-radius: 99px;
}
.rec-bottom {
  flex: none;
  display: flex;
  justify-content: center;      /* Paul: the button should be centered. */
  padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
  border-top: 1px solid rgba(201, 162, 74, 0.22);
  background: rgba(10, 7, 6, 0.6);
}
.rec-bottom .btn { min-width: 180px; }

@media (max-width: 520px) {
  .rec-dialog { max-height: 92vh; border-radius: 14px; }
  .rec-top { padding: 12px 13px 10px; }
  .rec-top h2 { font-size: 1.02rem; }
  .rec-scroll { padding: 4px 13px 13px; }
  .rec-bottom { padding: 10px 13px calc(10px + env(safe-area-inset-bottom)); }
  .rec-bottom .btn { width: 100%; min-width: 0; }
  /* The value stays on the same line as its label; only the note wraps. */
  .rec-k { font-size: 0.86rem; }
  .rec-v { font-size: 0.9rem; }
  .ach { grid-template-columns: auto 1fr; row-gap: 2px; }
  .ach-s { grid-column: 2; justify-self: start; }
}

/* The badge tooltip. A real element, because the native title attribute on a
   16x17 badge needs a second of stillness and does not exist on touch at all. */
.badge-tip {
  position: fixed;
  z-index: 80;
  max-width: 260px;
  padding: 8px 11px;
  border-radius: 10px;
  background: rgba(12, 9, 7, 0.97);
  border: 1px solid rgba(201, 162, 74, 0.5);
  color: var(--ink);
  font-size: 0.8rem;
  line-height: 1.45;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s ease;
}
.badge-tip.show { opacity: 1; }

.rec-h {
  font-family: var(--font-display);
  font-size: 0.82rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold);
  border-bottom: 1px solid rgba(201, 162, 74, 0.22);
  padding-bottom: 4px;
  margin: 16px 0 8px;
}
.rec-h:first-child { margin-top: 0; }
.rec-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: 4px 10px;
  padding: 5px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.rec-k { color: var(--ink); font-size: 0.9rem; }
.rec-v {
  color: var(--gold);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  font-size: 0.95rem;
}
.rec-n {
  grid-column: 1 / -1;
  color: var(--dim);
  font-size: 0.76rem;
}
.rec-ach { display: grid; gap: 7px; }
.ach {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 9px;
  padding: 7px 10px;
  border: 1px solid rgba(201, 162, 74, 0.2);
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.28);
  /* Locked achievements are shown, not hidden: a list you can see the end of
     is the reason to come back. Dimmed so the earned ones still read first. */
  opacity: 0.5;
}
.ach.got { opacity: 1; border-color: rgba(201, 162, 74, 0.55); }
.ach-i { font-size: 1.15rem; }
.ach-t { color: var(--ink); font-size: 0.88rem; }
.ach-s {
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dim);
}
.ach.got .ach-s { color: var(--gold); }
.rec-foot {
  margin-top: 16px;
  color: var(--dim);
  font-size: 0.78rem;
  line-height: 1.5;
}

.how-card h2 {
  font-family: var(--font-display);
  font-size: 1.15rem;
  color: var(--gold);
  margin-bottom: 8px;
}
.how-card p, .how-card li {
  color: #ead9c2;
  line-height: 1.45;
  font-size: 0.95rem;
}
.how-card ol { padding-left: 1.15rem; display: grid; gap: 8px; }
.legend {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
  margin-top: 12px;
}
.legend-row {
  display: grid;
  grid-template-columns: 42px 1fr;
  gap: 10px;
  align-items: center;
}
.legend-row img {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  object-fit: cover;
  transform: scale(1.35);
  border: 1px solid rgba(201, 162, 74, 0.4);
}

#screen-play { padding-left: 10px; padding-right: 10px; }
.topbar {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 10px;
  align-items: center;
  padding: 6px 2px 8px;
}
.brand {
  display: none;
  font-family: var(--font-display);
  font-size: 1.05rem;
  letter-spacing: 0.06em;
  color: var(--gold);
  white-space: nowrap;
}
.brand span { color: var(--dim); margin-left: 0.35em; }
.hud-meta {
  display: flex;
  gap: 10px;
  align-items: baseline;
  color: var(--dim);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.hud-meta b { color: var(--gold); font-weight: 700; }
/* ---------------------------------------------------------------------------
   One ring colour per delver, declared once. The board token, the health row
   and the inspector card all read it from here, so they cannot drift apart.
   --------------------------------------------------------------------------- */
.unit-hero { --ring: #f0d48a; --ring-soft: rgba(240, 212, 138, 0.38); }
.unit-ally { --ring: #5fc9d8; --ring-soft: rgba(95, 201, 216, 0.42); }
.unit-mage { --ring: #a98cf0; --ring-soft: rgba(169, 140, 240, 0.45); }

.party { display: flex; flex-direction: column; gap: 3px; margin-top: 4px; }
/* The CARD is not clickable - only the portrait inside it is. See renderParty:
   the row carries the level badge, the block badge and the XP bar, and a row
   hit box swallowed all three. */
.party-row { display: flex; align-items: center; gap: 7px; }
.pface, .fface { cursor: pointer; }
.party-row.down { opacity: 0.4; filter: grayscale(0.8); }
.pface {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  overflow: hidden;
  flex: none;
  border: 3px solid transparent;
  background:
    linear-gradient(#241a13, #241a13) padding-box,
    conic-gradient(from -90deg,
      var(--ring, #c9a24a) calc(var(--hp, 1) * 360deg),
      rgba(0, 0, 0, 0.62) 0) border-box;
}
/* A captive is an objective, not a health bar, and a downed delver has no
   meter to draw -- both keep a plain ring. */
.party-row.bound .pface,
.party-row.down .pface {
  border-color: var(--ring, #c9a24a);
  background: #241a13;
}
.party-face {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Framed on the head and shoulders. A whole chibi figure at this size is a
     smudge; the face is what you actually recognise. */
  object-position: center 20%;
  transform: scale(1.28);
  display: block;
}
.party-row .blk {
  flex: none;
  font-size: 0.72rem;
  color: #8fd4ff;
  border: 1px solid rgba(93, 168, 255, 0.5);
  border-radius: 999px;
  padding: 0 6px;
}
.hp-bar {
  flex: 1;
  height: 11px;
  background: #2a1614;
  border: 1px solid #5a3228;
  border-radius: 99px;
  overflow: hidden;
}
.hp-fill {
  height: 100%;
  width: 100%;
  background: linear-gradient(180deg, #e4453a, #9a2018);
  transform-origin: left;
  transition: transform 0.25s ease;
}
/* EXPERIENCE IS A BAR. Thin on purpose: it is ambient progress, not a stat to
   read, and the exact numbers are one hover (or one tap on the card) away.
   Hidden by default and shown only in the wide layout - the phone strip has no
   room for a second bar and does not need one. */
.xp-bar {
  display: none;
  height: 3px;
  background: rgba(0, 0, 0, 0.45);
  border-radius: 99px;
  overflow: hidden;
  margin-top: 5px;
}
.xp-fill {
  height: 100%;
  transform-origin: left center;
  background: linear-gradient(90deg, #6f8fd0, #a9c4f5);
  transition: transform 0.35s ease;
}

.hp-num { font-variant-numeric: tabular-nums; font-size: 0.76rem; color: var(--ink); min-width: 46px; flex: none; }
.stat-pills { display: flex; gap: 6px; }
.pill {
  background: rgba(20, 14, 10, 0.7);
  border: 1px solid rgba(201, 162, 74, 0.28);
  border-radius: 999px;
  padding: 6px 10px;
  font-size: 0.78rem;
  color: var(--gold);
}
.icon-btn {
  width: 40px;
  height: 40px;
  border-radius: 12px;
  border: 1px solid rgba(201, 162, 74, 0.3);
  background: rgba(20, 14, 10, 0.65);
  color: var(--ink);
  cursor: pointer;
}

.foe-row {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding: 2px 2px 10px;
  scrollbar-width: thin;
}
.foe-card {
  min-width: 86px;
  background: rgba(16, 12, 10, 0.72);
  border: 1px solid rgba(201, 162, 74, 0.22);
  border-radius: 12px;
  padding: 6px 8px 8px;
  text-align: center;
}
.foe-card.dead { opacity: 0.35; filter: grayscale(0.7); }
.fface {
  width: 54px;
  height: 54px;
  border-radius: 50%;
  overflow: hidden;
  border: 3px solid transparent;
  background:
    linear-gradient(#241a13, #241a13) padding-box,
    conic-gradient(from -90deg,
      var(--foe) calc(var(--hp, 1) * 360deg),
      rgba(0, 0, 0, 0.62) 0) border-box;
  box-shadow: 0 0 0 2px var(--foe-soft);
  margin: 0 auto;
}
.foe-card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 22%;
  transform: scale(1.22);
  display: block;
}
.foe-card .name { font-size: 0.68rem; color: var(--dim); margin-top: 4px; }
.foe-hp { height: 6px; background: #2a1614; border-radius: 99px; overflow: hidden; margin-top: 4px; }
.foe-hp > span { display: block; height: 100%; background: #e4453a; }

.flavor {
  text-align: center;
  color: var(--dim);
  font-size: 0.82rem;
  /* Two lines are always reserved. A one-line message and a wrapped one must
     occupy the same height, or the board resizes as the text changes. */
  min-height: 2.4em;
  font-style: italic;
  margin-bottom: 6px;
}

.board-wrap {
  position: relative;
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
  min-width: 0;
  overflow: hidden;
}
.board {
  display: grid;
  gap: 5px;
  padding: 10px;
  background: rgba(12, 9, 7, 0.72);
  border: 1px solid rgba(201, 162, 74, 0.28);
  border-radius: 18px;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.45), inset 0 0 40px rgba(0, 0, 0, 0.35);
  touch-action: none;
  position: relative;
}
.cell {
  width: var(--cell);
  height: var(--cell);
  border-radius: 12px;
  background: linear-gradient(180deg, #3a2c24, #241910);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.45);
  position: relative;
  display: grid;
  place-items: center;
}
/* Cells a line must touch to be legal. Quiet, but always visible. */
.cell.anchor::after {
  content: "";
  position: absolute;
  inset: 2px;
  border-radius: 10px;
  border: 1px dashed rgba(226, 192, 61, 0.5);
  pointer-events: none;
}
.cell.path { box-shadow: inset 0 0 0 2px var(--gold), 0 0 12px rgba(226, 192, 61, 0.45); }
.cell.path-ok { box-shadow: inset 0 0 0 2px #7dffb0, 0 0 12px rgba(60, 181, 106, 0.4); }
.cell.path-ok.t4 { box-shadow: inset 0 0 0 3px #b8ffcf, 0 0 16px rgba(120, 255, 176, 0.55); }
.cell.path-ok.t5 { box-shadow: inset 0 0 0 3px #ffe9a3, 0 0 20px rgba(255, 214, 92, 0.65); }
.cell.path-ok.t6 { box-shadow: inset 0 0 0 3px #ffc46a, 0 0 24px rgba(255, 150, 40, 0.75); }
.cell.path-ok.t7 { box-shadow: inset 0 0 0 3px #fff2c9, 0 0 30px rgba(255, 120, 40, 0.9); animation: chainPulse 0.7s ease-in-out infinite; }
@keyframes chainPulse { 0%,100% { filter: brightness(1); } 50% { filter: brightness(1.35); } }
.cell.path::after, .cell.path-ok::after { display: none; }
.gem {
  width: 86%;
  height: 86%;
  border-radius: 50%;
  overflow: hidden;
  transform: scale(1);
  transition: transform 0.12s ease, opacity 0.12s ease;
  pointer-events: none;
}
.gem-blade { box-shadow: inset 0 0 0 2px rgba(228, 69, 58, 0.9); }
.gem-ward  { box-shadow: inset 0 0 0 1px rgba(77, 142, 239, 0.45); }
.gem-ember { box-shadow: inset 0 0 0 2px rgba(240, 138, 42, 0.95); }
.gem-vial  { box-shadow: inset 0 0 0 1px rgba(60, 181, 106, 0.45); }
.gem-coin  { box-shadow: inset 0 0 0 1px rgba(226, 192, 61, 0.4); }
.gem img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.34);
}
.cell.pop .gem { animation: pop 0.22s ease forwards; }
@keyframes pop {
  0% { transform: scale(1); opacity: 1; }
  100% { transform: scale(0.2); opacity: 0; }
}
.token {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  overflow: hidden;
  background: #241a13;
  border: 2px solid #c9a24a;
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.55), 0 5px 14px rgba(0, 0, 0, 0.6);
  z-index: 2;
  pointer-events: none;
}
/* Same disc treatment as the party, in a warmer red so friend and foe stay
   instantly separable. */
.token.foe {
  border-color: var(--foe);
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.6), 0 5px 14px rgba(0, 0, 0, 0.6),
              0 0 14px var(--foe-soft);
}
.token.foe img { filter: brightness(1.1) contrast(1.05); }
/* Hierarchy is carried by the ring, not by size, so delvers stay obviously the
   important pieces without eating their neighbours' cells. */
.token.unit {
  border-color: var(--ring, #f0d48a);
  border-width: 3px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.7),
              0 0 0 2px var(--ring-soft, rgba(240, 212, 138, 0.35)),
              0 0 14px 2px var(--ring-soft, rgba(240, 212, 138, 0.3));
}
.token img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 20%;
  /* The disc shrank to fit its cell, so the face grows inside it instead. */
  transform: scale(1.22);
}
.floater {
  position: absolute;
  left: 50%;
  top: 20%;
  transform: translate(-50%, 0);
  font-weight: 800;
  font-size: 0.85rem;
  pointer-events: none;
  animation: floatUp 0.7s ease forwards;
  text-shadow: 0 2px 0 #000;
  z-index: 5;
}
@keyframes floatUp {
  from { opacity: 1; transform: translate(-50%, 0); }
  to { opacity: 0; transform: translate(-50%, -22px); }
}

.dock {
  display: flex;
  gap: 8px;
  justify-content: center;
  padding: 10px 0 8px;
}
.dock .btn { flex: 1; max-width: 160px; padding: 0.7rem 0.8rem; font-size: 0.9rem; }

.log {
  text-align: center;
  min-height: 2.6em;
  font-size: 0.88rem;
  color: #f0d48a;
  padding-bottom: 4px;
}

.overlay {
  position: absolute;
  inset: 0;
  background: rgba(10, 7, 6, 0.78);
  display: none;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 20;
}
.overlay.show { display: flex; }
.overlay.dismissible { cursor: pointer; }
.overlay.dismissible .panel { cursor: default; }
.ov-close {
  position: absolute;
  top: 8px;
  right: 10px;
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.35);
  color: rgba(232, 219, 198, 0.75);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}
.ov-close:hover { color: #f2e4c6; background: rgba(0, 0, 0, 0.55); }
.panel {
  position: relative;
  width: min(420px, 94vw);
  transition: width 0.18s ease;
  background: linear-gradient(180deg, #2a211c, #16110e);
  border: 1px solid rgba(201, 162, 74, 0.4);
  border-radius: 18px;
  padding: 22px 18px;
  text-align: center;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
}
.panel h2 {
  font-family: var(--font-display);
  color: var(--gold);
  margin-bottom: 8px;
}
.panel p { color: #ead9c2; margin-bottom: 14px; line-height: 1.4; }
.panel .btn-col { width: 100%; }

.tutor {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 84px;
  margin: 0;
  max-width: 34em;
  text-align: center;
  background: rgba(16, 12, 10, 0.92);
  border: 1px solid rgba(201, 162, 74, 0.4);
  color: #f3e6d0;
  padding: 10px 14px;
  border-radius: 12px;
  font-size: 0.88rem;
  max-width: 90vw;
  z-index: 6;
  display: none;
  /* Informational only. It floats over the board on desktop, so it must never
     eat a press -- doing so both blocked the tiles under it and stopped the
     press that was supposed to dismiss it. */
  pointer-events: none;
}
.tutor.show { display: block; }
/* A short screen needs every pixel for the board. */
@media (max-height: 720px) {
  .foe-card { min-width: 64px; padding: 4px 6px 6px; }
  .foe-card img { width: 34px; height: 34px; }
  .foe-card .name { font-size: 0.6rem; }
  .flavor { display: none; }
  .tutor { font-size: 0.8rem; padding: 7px 10px; }
}

@media (min-width: 860px) {
  #screen-play {
    max-width: 1440px;
    margin: 0 auto;
    left: 0;
    right: 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* ===========================================================================
   Turn animation layer
   Effects are drawn into .fx-layer, which app.js re-attaches after every
   render. Nothing in here should ever intercept a pointer.
   =========================================================================== */
.fx-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 6;
  overflow: visible;
}
.fx-layer > * { position: absolute; pointer-events: none; }
.fx-svg { left: 0; top: 0; overflow: visible; }
.fx-stroke { fill: none; stroke-linecap: round; stroke-linejoin: round; }
.fx-stroke-core { stroke: #fff8e6; }
.fx-stroke-glow { stroke: #e4453a; filter: blur(3px); }
.fx-stroke-glow.fx-ward  { stroke: #4d8eef; }
.fx-stroke-glow.fx-ember { stroke: #f08a2a; }
.fx-stroke-glow.fx-vial  { stroke: #3cb56a; }
.fx-stroke-glow.fx-coin  { stroke: #e2c03d; }

/* Impact burst on a struck token. */
.fx-impact {
  border-radius: 50%;
  animation: fxBurst 0.42s cubic-bezier(.15,.8,.3,1) forwards;
  background: radial-gradient(circle, rgba(255,255,255,0.95) 0%, rgba(228,69,58,0.6) 45%, rgba(228,69,58,0) 70%);
}
.fx-impact.fx-ember { background: radial-gradient(circle, rgba(255,236,190,0.95) 0%, rgba(240,138,42,0.7) 45%, rgba(240,138,42,0) 70%); }
.fx-impact.fx-kill  { background: radial-gradient(circle, rgba(255,255,255,0.9) 0%, rgba(255,120,60,0.55) 40%, rgba(0,0,0,0) 72%); }
.fx-impact.fx-hurtburst { background: radial-gradient(circle, rgba(255,120,110,0.9) 0%, rgba(200,30,30,0.5) 45%, rgba(200,30,30,0) 70%); }
@keyframes fxBurst {
  0%   { transform: scale(0.25); opacity: 0; }
  30%  { transform: scale(0.85); opacity: 1; }
  100% { transform: scale(1.35); opacity: 0; }
}

/* Ward: a ring that closes around the delver who moved. */
.fx-shield {
  border-radius: 50%;
  border: 3px solid rgba(141, 197, 255, 0.95);
  box-shadow: 0 0 18px rgba(77, 142, 239, 0.8), inset 0 0 18px rgba(77, 142, 239, 0.5);
  animation: fxShield 0.6s cubic-bezier(.2,.8,.25,1) forwards;
}
@keyframes fxShield {
  0%   { transform: scale(1.5) rotate(-25deg); opacity: 0; }
  40%  { transform: scale(0.92) rotate(0deg);  opacity: 1; }
  100% { transform: scale(1.05) rotate(6deg);  opacity: 0; }
}
.token.fx-warded {
  animation: fxWarded 0.6s ease;
  box-shadow: 0 0 0 3px rgba(141, 197, 255, 0.9), 0 0 22px rgba(77, 142, 239, 0.85);
}
@keyframes fxWarded { 0% { transform: scale(1); } 45% { transform: scale(1.14); } 100% { transform: scale(1); } }

/* Ember: a wave across the whole board, then flame on each foe. */
.fx-wave {
  left: -10%; top: -10%; width: 120%; height: 120%;
  background: radial-gradient(ellipse at 50% 110%, rgba(255,196,86,0.55) 0%, rgba(240,138,42,0.35) 35%, rgba(240,138,42,0) 68%);
  animation: fxWave 0.6s ease-out forwards;
}
@keyframes fxWave {
  0%   { transform: scale(0.35) translateY(18%); opacity: 0; }
  35%  { opacity: 1; }
  100% { transform: scale(1.15) translateY(-6%); opacity: 0; }
}
.fx-flame {
  border-radius: 50% 50% 46% 46%;
  background: linear-gradient(0deg, rgba(255,90,20,0.95) 0%, rgba(255,170,50,0.9) 45%, rgba(255,240,190,0.85) 100%);
  filter: blur(1px);
  animation: fxFlame 0.62s ease-out forwards;
  transform-origin: 50% 85%;
}
@keyframes fxFlame {
  0%   { transform: scaleY(0.3) scaleX(0.8); opacity: 0; }
  35%  { transform: scaleY(1.15) scaleX(1);  opacity: 0.95; }
  70%  { transform: scaleY(0.95) scaleX(0.9); opacity: 0.8; }
  100% { transform: scaleY(1.3) scaleX(0.5);  opacity: 0; }
}

/* Vial and Coin. */
.fx-heal {
  border-radius: 50%;
  background: radial-gradient(circle, rgba(180,255,210,0.7) 0%, rgba(60,181,106,0.4) 45%, rgba(60,181,106,0) 70%);
  animation: fxBurst 0.5s ease-out forwards;
}
.fx-mote {
  width: 6px; height: 6px; margin: -3px 0 0 -3px;
  border-radius: 50%;
  background: #9dffc4;
  box-shadow: 0 0 8px rgba(93, 255, 168, 0.9);
  animation: fxMote 0.8s ease-out forwards;
}
@keyframes fxMote {
  0%   { opacity: 0; transform: translateY(6px) scale(0.6); }
  25%  { opacity: 1; }
  100% { opacity: 0; transform: translateY(-30px) scale(1); }
}
.fx-coin {
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255,246,196,0.85) 0%, rgba(226,192,61,0.45) 45%, rgba(226,192,61,0) 70%);
  animation: fxBurst 0.5s ease-out forwards;
}
.fx-spark {
  width: 6px; height: 6px; margin: -3px 0 0 -3px;
  border-radius: 50%;
  background: #ffe9a3;
  box-shadow: 0 0 8px rgba(255, 214, 92, 0.95);
  animation: fxSpark 0.6s ease-out forwards;
}
@keyframes fxSpark {
  0%   { opacity: 0; transform: translate(0,0) scale(0.5); }
  20%  { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--dx,0), var(--dy,0)) scale(0.9); }
}

/* The dash trail left behind the delver. */
.fx-trail {
  border-radius: 12px;
  background: radial-gradient(circle, rgba(255,232,170,0.5) 0%, rgba(226,192,61,0) 70%);
  animation: fxTrail 0.42s ease-out forwards;
}
@keyframes fxTrail {
  0%   { opacity: 0.9; transform: scale(1); }
  100% { opacity: 0;   transform: scale(0.6); }
}

/* Struck, hurt, killed. */
.token.fx-struck { animation: fxStruck 0.38s ease; }
@keyframes fxStruck {
  0%   { transform: translate(0,0); filter: brightness(1); }
  18%  { transform: translate(-4px,1px); filter: brightness(2.6) saturate(0.4); }
  42%  { transform: translate(4px,-1px); filter: brightness(1.7); }
  70%  { transform: translate(-2px,0); }
  100% { transform: translate(0,0); filter: brightness(1); }
}
.token.fx-hurt { animation: fxStruck 0.36s ease; box-shadow: 0 0 0 3px rgba(255, 90, 90, 0.85); }
.token.fx-crumble { animation: fxCrumble 0.42s cubic-bezier(.4,0,.7,.4) forwards; }
@keyframes fxCrumble {
  0%   { transform: scale(1) rotate(0deg); opacity: 1; filter: brightness(2.2); }
  100% { transform: scale(0.25) rotate(28deg); opacity: 0; filter: brightness(0.6); }
}
.fx-blockpop {
  border-radius: 50%;
  border: 2px solid rgba(141, 197, 255, 0.9);
  animation: fxBurst 0.45s ease-out forwards;
}

/* Tiles dropping in once the turn has settled. */
.gem.fx-drop { animation: fxDrop 0.28s cubic-bezier(.3,1.3,.5,1) backwards; }
@keyframes fxDrop {
  0%   { transform: translateY(-34px); opacity: 0; }
  100% { transform: translateY(0); opacity: 1; }
}

/* Board shake on heavy hits. */
.board.fx-shake { animation: fxShake 0.3s ease; }
.board.fx-shake-hard { animation: fxShakeHard 0.36s ease; }
@keyframes fxShake {
  0%,100% { transform: translate(0,0); }
  25% { transform: translate(-3px,1px); }
  60% { transform: translate(3px,-1px); }
}
@keyframes fxShakeHard {
  0%,100% { transform: translate(0,0); }
  15% { transform: translate(-6px,2px); }
  35% { transform: translate(6px,-3px); }
  60% { transform: translate(-4px,1px); }
  80% { transform: translate(3px,0); }
}

/* Bars ease rather than jump, so a hit reads as damage taken. */
.foe-hp > span { transition: width 0.28s ease; }
.floater.big { font-size: 1.05rem; }


/* ===========================================================================
   Chain feedback and the tap-to-read inspector
   =========================================================================== */
.chain-badge {
  position: absolute;
  left: 50%;
  top: 6px;
  transform: translate(-50%, -8px);
  display: none;
  align-items: center;
  gap: 8px;
  padding: 5px 12px;
  border-radius: 999px;
  background: rgba(12, 9, 7, 0.9);
  border: 1px solid rgba(201, 162, 74, 0.55);
  color: var(--ink);
  font-size: 0.82rem;
  z-index: 8;
  pointer-events: none;
  white-space: nowrap;
}
.chain-badge.show { display: flex; animation: badgeIn 0.16s ease forwards; }
.chain-badge b { font-size: 1.05rem; color: #7dffb0; font-variant-numeric: tabular-nums; }
.chain-badge span { color: var(--dim); letter-spacing: 0.04em; text-transform: uppercase; font-size: 0.72rem; }
.chain-badge.t4 b { color: #b8ffcf; }
.chain-badge.t5 { border-color: rgba(255, 214, 92, 0.8); }
.chain-badge.t5 b, .chain-badge.t5 span { color: #ffe9a3; }
.chain-badge.t6 { border-color: rgba(255, 150, 40, 0.9); box-shadow: 0 0 16px rgba(255, 150, 40, 0.4); }
.chain-badge.t6 b, .chain-badge.t6 span { color: #ffc46a; }
.chain-badge.t7 { border-color: #fff2c9; box-shadow: 0 0 24px rgba(255, 140, 40, 0.7); }
.chain-badge.t7 b, .chain-badge.t7 span { color: #fff2c9; }
@keyframes badgeIn {
  from { opacity: 0; transform: translate(-50%, -14px) scale(0.9); }
  to   { opacity: 1; transform: translate(-50%, -8px) scale(1); }
}

/* The word-and-points banner a completed long line throws up. */
.fx-banner {
  transform: translate(-50%, -50%);
  text-align: center;
  white-space: nowrap;
  animation: bannerPop 1.1s cubic-bezier(.2,.9,.3,1) forwards;
  z-index: 9;
}
.fx-banner-word {
  display: block;
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: #fff3d0;
  text-shadow: 0 0 14px rgba(255, 170, 60, 0.95), 0 3px 0 #000;
}
.fx-banner-pts {
  display: block;
  margin-top: 1px;
  font-size: 0.95rem;
  font-weight: 800;
  color: #ffd65c;
  text-shadow: 0 2px 0 #000;
}
@keyframes bannerPop {
  0%   { opacity: 0; transform: translate(-50%, -30%) scale(0.6); }
  22%  { opacity: 1; transform: translate(-50%, -50%) scale(1.12); }
  38%  { transform: translate(-50%, -50%) scale(1); }
  75%  { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -110%) scale(1); }
}

/* Tiles falling into place from where they actually came from. */
.gem.fx-fall { animation: fxFall 0.34s cubic-bezier(.35,.05,.3,1) backwards; }
@keyframes fxFall {
  0%   { transform: translateY(calc(var(--drop, 40px) * -1)); }
  100% { transform: translateY(0); }
}

.infocard {
  position: absolute;
  left: 50%;
  bottom: 12px;
  transform: translate(-50%, 12px);
  width: min(420px, calc(100% - 24px));
  max-height: 70%;
  overflow-y: auto;
  background: linear-gradient(180deg, rgba(30, 22, 17, 0.98), rgba(16, 12, 10, 0.98));
  border: 1px solid rgba(201, 162, 74, 0.55);
  border-radius: 16px;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.75), var(--glow);
  padding: 14px;
  z-index: 12;
  display: none;
}
.infocard.show { display: block; animation: cardIn 0.18s ease forwards; }
@keyframes cardIn {
  from { opacity: 0; transform: translate(-50%, 26px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}
.card-head { display: flex; align-items: center; gap: 14px; }
/* The inspector is the one place with room to actually look at the art, and at
   56px it was a thumbnail of a thumbnail. Every source image is square, so a
   square container with object-fit: cover crops nothing but the circle's own
   corners -- making it bigger genuinely shows more creature. */
.card-art {
  width: 132px; height: 132px; flex: none;
  border-radius: 50%; object-fit: cover;
  border: 3px solid #c9a24a;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.55);
}
.card-art.foe { border-color: var(--foe); }
.card-art[class*="unit-"] { border-color: var(--ring, #c9a24a); }
.infocard h3 {
  font-family: var(--font-display);
  color: var(--gold);
  font-size: 1.1rem;
  margin: 0;
}
.card-sub { color: var(--dim); font-size: 0.8rem; margin-top: 2px; }
.card-blurb { font-size: 0.84rem; line-height: 1.45; margin: 10px 0 0; color: var(--ink); }
.card-note { font-size: 0.78rem; line-height: 1.45; margin: 10px 0 0; color: var(--dim); }
.card-note b { color: var(--danger); }
.card-traits { margin: 8px 0 0; padding-left: 18px; font-size: 0.8rem; color: #ffe9a3; }
.card-table { width: 100%; border-collapse: collapse; margin-top: 10px; font-size: 0.78rem; }
.card-table th {
  text-align: left; color: var(--dim); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.68rem;
  padding-bottom: 4px; border-bottom: 1px solid rgba(201, 162, 74, 0.25);
}
.card-table td { padding: 4px 0; border-bottom: 1px solid rgba(201, 162, 74, 0.12); color: var(--ink); }
.card-table td b { color: var(--gold); }
.card-table td:first-child { color: var(--dim); width: 4.5em; }
.card-table td:last-child { text-align: right; color: #ffd65c; width: 4.5em; }
.card-cols { display: flex; gap: 14px; margin-top: 10px; }
.card-cols > div { flex: 1; min-width: 0; }
.card-cols h4 {
  margin: 0 0 4px; font-size: 0.68rem; color: var(--dim);
  text-transform: uppercase; letter-spacing: 0.06em; font-weight: 600;
}
.card-cols ul { margin: 0; padding-left: 16px; font-size: 0.78rem; color: var(--ink); }
.card-cols li { padding: 1px 0; }
.card-cols b { color: var(--gold); }
.card-close {
  width: 100%; margin-top: 12px; padding: 8px;
  border-radius: 10px;
  border: 1px solid rgba(201, 162, 74, 0.4);
  background: rgba(201, 162, 74, 0.12);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 0.84rem;
  cursor: pointer;
}
.how-tip { font-size: 0.82rem; color: var(--gold); margin-top: 12px; }
.foe-card { cursor: pointer; }


/* The delver who will ride the line currently being drawn. */
.cell.rider .token {
  box-shadow: 0 0 0 3px #7dffb0, 0 0 22px rgba(125, 255, 176, 0.85);
  animation: riderPulse 0.9s ease-in-out infinite;
}
@keyframes riderPulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.08); }
}
.chain-badge .rider-name { color: #7dffb0; text-transform: none; letter-spacing: 0.02em; }

/* A tile the dungeon offered rather than dealt. */
.gem.fx-planted { animation: plantIn 0.55s ease-out backwards; }
@keyframes plantIn {
  0%   { box-shadow: 0 0 0 0 rgba(255, 240, 200, 0); filter: brightness(2.4); }
  35%  { box-shadow: 0 0 14px 3px rgba(255, 240, 200, 0.85); filter: brightness(1.6); }
  100% { box-shadow: 0 0 0 0 rgba(255, 240, 200, 0); filter: brightness(1); }
}


/* Score and purse read as two separate things. */
.pill { display: flex; align-items: baseline; gap: 5px; }
.pill span { font-size: 0.62rem; letter-spacing: 0.09em; text-transform: uppercase; color: var(--dim); }
.pill b { font-variant-numeric: tabular-nums; font-size: 0.9rem; }
.pill-score b { color: var(--ink); }
.pill-gold b { color: var(--gold); }

/* The stair: where gold turns into staying power. */
.offers { display: none; margin: 12px 0 4px; text-align: left; }
.offers.show { display: block; }
.offers-purse {
  text-align: center;
  font-size: 0.76rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 8px;
}
.offers-purse b { color: var(--gold); font-size: 0.95rem; }
.offer {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  margin-bottom: 7px;
  padding: 9px 11px;
  border-radius: 12px;
  border: 1px solid rgba(201, 162, 74, 0.42);
  background: rgba(201, 162, 74, 0.1);
  color: var(--ink);
  font-family: var(--font-ui);
  text-align: left;
  cursor: pointer;
}
.offer:hover:not(:disabled) { background: rgba(201, 162, 74, 0.2); }
.offer.broke { opacity: 0.42; cursor: default; }
.offer-cost {
  flex: none;
  min-width: 3.2em;
  text-align: center;
  font-weight: 800;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
}
.offer-text { font-size: 0.78rem; line-height: 1.35; color: var(--dim); }
.offer-text b { display: block; color: var(--ink); font-size: 0.86rem; margin-bottom: 1px; }

/* ===========================================================================
   The captive, her rescue, and levels
   =========================================================================== */

/* Bound: greyed out, chained, and ringed in danger rather than in gold. */
.token.unit.bound {
  border-color: var(--ring, #b8563c);
  animation: captivePulse 1.7s ease-in-out infinite;
  z-index: 4;
}
/* The captive art is its own picture -- chained to a wall, or behind bars -- so
   nothing is drawn over it. It only gets pulled back a little so the free
   portrait reads as brighter the moment it swaps in. */
.token.unit.bound img { filter: brightness(0.82) saturate(0.9); }
@keyframes captivePulse {
  0%, 100% {
    box-shadow: 0 4px 14px rgba(0,0,0,0.7), 0 0 0 2px var(--ring-soft, rgba(184,86,60,0.5)),
                0 0 16px 3px rgba(255, 140, 74, 0.5);
  }
  50% {
    box-shadow: 0 4px 14px rgba(0,0,0,0.7), 0 0 0 4px rgba(255, 176, 96, 0.95),
                0 0 34px 10px rgba(255, 130, 60, 0.8);
  }
}
.cell.captive::after {
  content: "";
  position: absolute;
  inset: 1px;
  border-radius: 11px;
  border: 2px dashed rgba(255, 150, 90, 0.9);
  animation: captiveCell 1.7s ease-in-out infinite;
  pointer-events: none;
}
@keyframes captiveCell {
  0%, 100% { opacity: 0.55; transform: scale(1); }
  50%      { opacity: 1;    transform: scale(1.06); }
}

/* Cutting her loose. */
.fx-unlock {
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255,255,255,0.95) 0%, rgba(255,214,92,0.7) 38%, rgba(255,150,40,0) 70%);
  animation: fxBurst 0.6s cubic-bezier(.15,.8,.3,1) forwards;
}
.fx-shard {
  width: 7px; height: 4px; margin: -2px 0 0 -3px;
  border-radius: 1px;
  background: linear-gradient(90deg, #cdd3da, #5b6169);
  box-shadow: 0 0 6px rgba(255, 224, 150, 0.8);
  animation: fxShard 0.75s cubic-bezier(.2,.7,.3,1) forwards;
}
@keyframes fxShard {
  0%   { opacity: 0; transform: translate(0,0) rotate(0deg) scale(0.6); }
  15%  { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--dx,0), var(--dy,0)) rotate(300deg) scale(1); }
}
.token.fx-freed { animation: fxFreed 0.85s ease forwards; }
@keyframes fxFreed {
  0%   { filter: brightness(0.82); transform: scale(1); }
  35%  { filter: brightness(2.3); transform: scale(1.24); }
  100% { filter: brightness(1); transform: scale(1); }
}
/* The anchor cells her freedom just opened. */
.cell.fx-opened { animation: fxOpened 0.7s ease backwards; }
@keyframes fxOpened {
  0%   { box-shadow: inset 0 0 0 0 rgba(125,255,176,0); }
  40%  { box-shadow: inset 0 0 0 3px rgba(125,255,176,0.95), 0 0 18px rgba(125,255,176,0.6); }
  100% { box-shadow: inset 0 0 0 0 rgba(125,255,176,0); }
}

.fx-levelup {
  border-radius: 50%;
  border: 2px solid rgba(255, 233, 163, 0.9);
  background: radial-gradient(circle, rgba(255,240,190,0.5) 0%, rgba(226,192,61,0) 68%);
  animation: fxShield 0.7s cubic-bezier(.2,.8,.25,1) forwards;
}

/* Level pip and captive tag in the party rows. */
.party-row .lvl {
  flex: none;
  min-width: 1.5em;
  text-align: center;
  font-size: 0.68rem;
  font-weight: 800;
  color: #1a1209;
  background: var(--gold);
  border-radius: 4px;
  padding: 1px 3px;
}
/* THE NAMED RIDER. Deliberately the same gold ring in both places - on the
   party card and on the board token - because they are one fact, and a player
   should not have to learn two signals for it. */
.party-row.lead {
  border-color: rgba(226, 192, 61, 0.75);
  box-shadow: 0 0 0 1px rgba(226, 192, 61, 0.35), 0 0 18px rgba(226, 192, 61, 0.16);
}
.party-row.lead .pface { border-color: var(--gold); }
.token.unit.lead {
  box-shadow: 0 0 0 3px var(--gold), 0 0 14px rgba(226, 192, 61, 0.5);
}

.party-row.bound { opacity: 0.85; }
.party-row.bound .pface {
  border-color: var(--ring, #e0785a);
  box-shadow: 0 0 0 2px rgba(255, 110, 74, 0.6);
  animation: captiveFacePulse 1.9s ease-in-out infinite;
}
@keyframes captiveFacePulse {
  0%, 100% { box-shadow: 0 0 0 2px rgba(255, 110, 74, 0.45); }
  50%      { box-shadow: 0 0 0 3px rgba(255, 110, 74, 0.95); }
}
.party-row.bound .hp-fill { background: linear-gradient(180deg, #e0785a, #8a2f1c); }
.party-row .bound-tag {
  flex: none;
  font-size: 0.62rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: #ff8f74;
  border: 1px solid rgba(255, 143, 116, 0.55);
  border-radius: 999px;
  padding: 0 6px;
}


/* The Emberwright. */


/* The picture a loss ends on. Cropped to a band so the figure reads without the
   panel becoming a lightbox. */
/* The death art is a scene, not a portrait -- a body on flagstones, a dropped
   spear, a cell door. At 150px of a 420px box it was a letterboxed strip with
   the story cropped out, so a panel that carries art gets a wider box and a
   much taller window, capped against the viewport so it cannot overflow on a
   short screen. */
.panel.has-art { width: min(560px, 96vw); }
.ov-art {
  display: block;
  width: calc(100% + 28px);
  margin: -18px -14px 14px;
  height: min(330px, 42vh);
  object-fit: cover;
  object-position: center 45%;
  border-radius: 16px 16px 0 0;
  border-bottom: 1px solid rgba(201, 162, 74, 0.3);
  -webkit-mask-image: linear-gradient(180deg, #000 68%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 68%, transparent 100%);
}
.ov-art[hidden] { display: none; }
/* The death art is a square scene with the subject high in the frame. On a
   short screen the visible band is thin, so a mid-image crop lands on the torso
   and cuts the head off -- bias the window upward as the band narrows. */
@media (max-height: 720px) {
  .ov-art { height: min(210px, 34vh); object-position: center 34%; }
}
@media (max-height: 560px) {
  .ov-art { height: min(150px, 30vh); object-position: center 30%; }
}


/* Thrown from the captive's cell when a hall opens with someone bound in it. */
.fx-beacon {
  border-radius: 50%;
  border: 3px solid rgba(255, 176, 96, 0.9);
  animation: fxBeacon 1.25s cubic-bezier(.15,.7,.3,1) forwards;
}
@keyframes fxBeacon {
  0%   { transform: scale(0.22); opacity: 0; }
  18%  { opacity: 1; }
  100% { transform: scale(1.1); opacity: 0; }
}
.cell.fx-marked { animation: fxMarked 0.9s ease-in-out 3; }
@keyframes fxMarked {
  0%, 100% { background: linear-gradient(180deg, #3a2c24, #241910); }
  50%      { background: linear-gradient(180deg, #6b4326, #3a2213); }
}

/* ===========================================================================
   Play layout
   Phone: one column, party then foes then board, as before.
   Desktop: the party rail on the left, foes on the right, and the board takes
   the height those rows used to eat.
   =========================================================================== */
.arena { display: flex; flex-direction: column; min-height: 0; flex: 1; }
.side { min-width: 0; }
.side-title { display: none; }
.side-party { order: 1; }
.side-foes  { order: 2; }
.board-wrap { order: 3; }

.pbody { display: contents; }
.pname { display: none; }
.pmeter { display: contents; }
.foe-meta { display: none; }

@media (min-width: 1120px) {
  .brand { display: block; }
  .hud-meta { justify-content: center; font-size: 0.84rem; }
  .crumb {
    background: rgba(20, 14, 10, 0.55);
    border: 1px solid rgba(201, 162, 74, 0.2);
    border-radius: 999px;
    padding: 3px 12px;
  }

  .arena {
    display: grid;
    /* The centre column sizes to the board rather than swallowing the leftover
       width, and the whole trio is centred. Otherwise the board floats in the
       middle of a 1fr column with dead space either side of it. */
    grid-template-columns: minmax(200px, 244px) auto minmax(200px, 244px);
    justify-content: center;
    gap: 14px;
    align-items: stretch;
    flex: 1;
  }
  .side {
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-height: 0;
    overflow-y: auto;
    /* A DARK GAME MUST NOT BORROW THE OS SCROLLBAR. `thin` was set and
       scrollbar-color was not, so a full-height default light scrollbar was
       painted down the side of a near-black dungeon whenever the foe list ran
       past the rail - which is every floor with five or more foes. Coloured
       from the same palette as everything else here, and gutter-stable so the
       cards do not shift sideways the moment a fifth foe appears. */
    scrollbar-width: thin;
    scrollbar-color: rgba(201, 162, 74, 0.45) rgba(0, 0, 0, 0.25);
    scrollbar-gutter: stable;
    padding-bottom: 6px;
  }
  .side::-webkit-scrollbar { width: 8px; }
  .side::-webkit-scrollbar-track {
    background: rgba(0, 0, 0, 0.25);
    border-radius: 99px;
  }
  .side::-webkit-scrollbar-thumb {
    background: rgba(201, 162, 74, 0.45);
    border-radius: 99px;
  }
  .side::-webkit-scrollbar-thumb:hover { background: rgba(201, 162, 74, 0.7); }
  .side-party, .side-foes, .board-wrap { order: 0; }
  .side-title {
    display: block;
    font-family: var(--font-display);
    font-size: 0.74rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--dim);
    border-bottom: 1px solid rgba(201, 162, 74, 0.22);
    padding-bottom: 5px;
  }

  /* Party as cards down the left. */
  .party { gap: 10px; }
  .party-row {
    flex-direction: column;
    align-items: stretch;
    gap: 7px;
    padding: 11px 11px 12px;
    background: linear-gradient(180deg, rgba(38, 28, 21, 0.85), rgba(20, 14, 10, 0.85));
    border: 1px solid rgba(201, 162, 74, 0.22);
    border-left: 3px solid var(--ring, #c9a24a);
    border-radius: 13px;
    position: relative;
  }
  .pface {
    width: 108px;
    height: 108px;
    align-self: center;
    border-width: 3px;
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.55);
  }
  .pbody { display: block; }
  .pname {
    display: block;
    font-family: var(--font-display);
    font-size: 0.95rem;
    color: var(--ink);
    text-align: center;
    margin-bottom: 5px;
  }
  .pmeter { display: flex; align-items: center; gap: 7px; }
  /* ALIGNED TO THE HEALTH BAR ABOVE IT, not to the card. The XP bar is a
     sibling of .pmeter, so it ignored both the 33px the meter reserves for the
     weapon icon and the 53px the health NUMBER occupies (46px min-width + the
     7px flex gap) - it started 33px left of the health bar and ran 53px past
     its right edge. Two bars that measure different things must at least start
     and stop in the same place. */
  .xp-bar { display: block; margin-left: 33px; margin-right: 53px; }
  /* The badges answer a question, so they should look like they will. */
  .party-row .lvl, .party-row .blk { cursor: help; }
  .party-row .lvl { position: absolute; top: 9px; right: 9px; }
  .party-row .blk { position: absolute; top: 9px; left: 9px; }
  .party-row .bound-tag { align-self: center; }
  .party-row.bound { border-left-color: #ff8f74; }

  /* Foes as cards down the right. */
  .foe-row {
    flex-direction: column;
    overflow-x: visible;
    gap: 10px;
    padding: 0;
  }
  .foe-card {
    min-width: 0;
    display: grid;
    grid-template-columns: 78px 1fr;
    grid-template-rows: auto auto auto;
    gap: 3px 11px;
    align-items: center;
    text-align: left;
    padding: 10px 12px;
    border-left: 3px solid var(--danger);
  }
  .fface {
    grid-row: 1 / 4;
    width: 78px;
    height: 78px;
    margin: 0;
    border-width: 3px;
    box-shadow: 0 5px 14px rgba(0, 0, 0, 0.5);
  }
  .foe-card .name { margin: 0; font-size: 0.84rem; color: var(--ink); align-self: end; }
  .foe-card .foe-hp { margin: 0; align-self: start; }
  .foe-meta {
    display: flex;
    justify-content: space-between;
    grid-column: 2;
    font-size: 0.68rem;
    color: var(--dim);
    font-variant-numeric: tabular-nums;
  }
  .foe-atk { color: var(--danger); font-weight: 700; }
  .foe-atk::before { content: "\2694 "; }

  .board-wrap { align-items: center; }
  .flavor { font-size: 0.9rem; margin-top: 8px; }
  .log { font-size: 0.95rem; }
  /* CLEAR OF THE LOG LINE. This is the rule that governs the desktop view, and
     at 92px the tutor's bottom edge landed 9px INSIDE the log - so the first
     sentence a new player reads, "Draw 3 matching relics", was printed on top
     of "The same hall, the world over." The base rule's 84px is not the one to
     change: it never applies here, which is why raising it alone did nothing.
     Measured, not guessed - dock 61px + log 40px puts the log's top edge 101px
     up, and this leaves 15px of air above it. */
  .tutor { bottom: 116px; }
}

/* The captive shouting for help, every few turns. */
.fx-shout {
  transform-origin: 0 100%;
  padding: 5px 11px;
  border-radius: 12px;
  background: #f6ecd6;
  color: #1b1208;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.82rem;
  white-space: nowrap;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.6);
  animation: fxShout 2.4s cubic-bezier(.2,.9,.3,1) forwards;
  z-index: 10;
}
.fx-shout.left { transform-origin: 100% 100%; translate: -100% 0; }
.fx-shout::after {
  content: "";
  position: absolute;
  left: 8px;
  bottom: -6px;
  border: 6px solid transparent;
  border-top-color: #f6ecd6;
  border-bottom: 0;
}
.fx-shout.left::after { left: auto; right: 8px; }
@keyframes fxShout {
  0%   { opacity: 0; scale: 0.4; rotate: -8deg; }
  12%  { opacity: 1; scale: 1.14; rotate: 3deg; }
  20%  { scale: 1; rotate: 0deg; }
  30%  { rotate: -3deg; }
  40%  { rotate: 3deg; }
  50%  { rotate: 0deg; }
  82%  { opacity: 1; translate: 0 0; }
  100% { opacity: 0; translate: 0 -14px; }
}
.fx-shout.left { animation-name: fxShoutLeft; }
@keyframes fxShoutLeft {
  0%   { opacity: 0; scale: 0.4; rotate: 8deg;  translate: -100% 0; }
  12%  { opacity: 1; scale: 1.14; rotate: -3deg; translate: -100% 0; }
  20%  { scale: 1; rotate: 0deg;  translate: -100% 0; }
  40%  { rotate: -3deg; translate: -100% 0; }
  50%  { rotate: 0deg;  translate: -100% 0; }
  82%  { opacity: 1; translate: -100% 0; }
  100% { opacity: 0; translate: -100% -14px; }
}


/* ===========================================================================
   Zones. The backdrop comes from --hall (set per floor in app.js); these carry
   the stone the board is cut from, so a hall feels like its own place rather
   than the same board on a new wallpaper.
   =========================================================================== */
body.zone-flood { --bg: #0b1416; }
body.zone-flood .board {
  border-color: rgba(120, 190, 200, 0.3);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.45), inset 0 0 40px rgba(0, 30, 40, 0.4);
}
body.zone-flood .cell { background: linear-gradient(180deg, #23383c, #132226); }

body.zone-crypt { --bg: #0d1210; }
body.zone-crypt .board {
  border-color: rgba(150, 190, 130, 0.26);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.5), inset 0 0 40px rgba(10, 26, 14, 0.45);
}
body.zone-crypt .cell { background: linear-gradient(180deg, #2c332a, #171d16); }

body.zone-ember { --bg: #170b06; }
body.zone-ember .board {
  border-color: rgba(240, 138, 42, 0.34);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.5), inset 0 0 44px rgba(80, 20, 0, 0.5);
}
body.zone-ember .cell { background: linear-gradient(180deg, #46281a, #24120b); }

body.zone-deep { --bg: #06070b; }
body.zone-deep .board {
  border-color: rgba(120, 130, 180, 0.22);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.7), inset 0 0 50px rgba(0, 0, 0, 0.6);
}
body.zone-deep .cell { background: linear-gradient(180deg, #22242e, #0f1015); }


/* Darker halls get a thinner veil so the art survives it. */
body.zone-flood { --veil-top: 0.24; --veil-bottom: 0.68; }
body.zone-crypt { --veil-top: 0.26; --veil-bottom: 0.70; }
body.zone-ember { --veil-top: 0.30; --veil-bottom: 0.76; }
body.zone-deep  { --veil-top: 0.10; --veil-bottom: 0.48; }

/* The frozen hall is 112/255 mean luminance, more than twice as bright as any
   other backdrop, so it takes the heaviest veil of the six or the relics
   wash out against it. */
body.zone-frost { --bg: #0a1218; --veil-top: 0.44; --veil-bottom: 0.86; }
body.zone-frost .board {
  border-color: rgba(150, 200, 230, 0.32);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.45), inset 0 0 44px rgba(10, 34, 54, 0.45);
}
body.zone-frost .cell { background: linear-gradient(180deg, #2b3d4c, #16222c); }

/* ===========================================================================
   Weapon proficiency
   Which delver should swing at what. The icons are the weapon tiles the player
   already reads on the board, so there is no new vocabulary to learn.
   =========================================================================== */
.foe-types { display: flex; gap: 5px; grid-column: 2; }
/* No circle and no crop. A weapon cropped into a 22px disc is an unreadable
   smudge -- the whole tile, bigger, is the only thing that reads at this size.
   Meaning comes from colour and the slash, not from a ring. */
.wbadge {
  width: 30px;
  height: 30px;
  position: relative;
  flex: none;
  display: block;
}
.wbadge img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
.wbadge.weak img {
  filter: drop-shadow(0 0 3px rgba(125, 255, 176, 0.95))
          drop-shadow(0 0 7px rgba(125, 255, 176, 0.55));
}
.wbadge.resist img { filter: grayscale(0.9) brightness(0.55); }
/* A slash, so "resists" cannot be misread as "weak to". */
.wbadge.resist::after {
  content: "";
  position: absolute;
  left: -1px; right: -1px; top: 50%;
  height: 2px;
  border-radius: 2px;
  background: #e0785a;
  box-shadow: 0 0 4px rgba(224, 120, 90, 0.8);
  transform: rotate(-45deg);
}

/* The inspector's answer to "who should hit this". */
.card-h {
  margin: 12px 0 6px;
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dim);
  font-weight: 600;
}
.card-h span { text-transform: none; letter-spacing: 0; opacity: 0.7; }
.matchups { list-style: none; margin: 0; padding: 0; }
.matchup {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 5px 8px;
  border-radius: 9px;
  border-left: 3px solid transparent;
  background: rgba(255, 255, 255, 0.03);
  margin-bottom: 4px;
  font-size: 0.8rem;
}
/* Same problem the foe badges had: a weapon cropped into a small disc is an
   unreadable smudge. Show the whole tile instead, and larger. */
.matchup img {
  width: 34px; height: 34px;
  object-fit: contain; flex: none;
}
.matchup.good img {
  filter: drop-shadow(0 0 3px rgba(125, 255, 176, 0.9));
}
.matchup.poor img { filter: grayscale(0.85) brightness(0.65); }
.matchup .who { flex: 1; color: var(--ink); }
.matchup .dmg { font-weight: 800; font-variant-numeric: tabular-nums; color: var(--ink); }
.matchup.good { border-left-color: #7dffb0; background: rgba(125, 255, 176, 0.08); }
.matchup.good .dmg { color: #7dffb0; }
.matchup.poor { border-left-color: #e0785a; opacity: 0.72; }
.matchup.poor .dmg { color: #e0785a; }


/* ---- Your best -------------------------------------------------------- */
/* Sits under the daily chip on the title screen. Hidden entirely until there
   is something to boast about, so a first-time player sees a clean title. */
.bests {
  display: none;
  width: min(400px, 92vw);
  margin: 10px auto 0;
  padding: 10px 14px 12px;
  border: 1px solid rgba(196, 150, 74, 0.28);
  border-radius: 12px;
  background: rgba(14, 10, 8, 0.55);
}
.bests.show { display: block; }
.bests-title {
  font-family: 'Cinzel', serif;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #c4964a;
  text-align: center;
  margin-bottom: 8px;
}
/* Two columns, one per mode. Wider than the old panel because each column now
   carries a name as well as its numbers. */
.best-cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.best-col { display: grid; gap: 5px; align-content: start; }
.best-mode {
  font-family: 'Cinzel', serif;
  font-size: 11px;
  letter-spacing: 0.06em;
  text-align: center;
  color: #c4964a;
  padding-bottom: 3px;
  border-bottom: 1px solid rgba(196, 150, 74, 0.22);
}
.best-when {
  text-align: center;
  font-size: 10px;
  color: rgba(232, 219, 198, 0.5);
}
.best-stat {
  text-align: center;
  padding: 6px 4px;
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.3);
}
.best-stat span {
  display: block;
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(232, 219, 198, 0.62);
}
.best-stat b {
  display: block;
  font-family: 'Cinzel', serif;
  font-size: 20px;
  line-height: 1.15;
  color: #f2e4c6;
}
.best-sub {
  margin-top: 8px;
  text-align: center;
  font-size: 11px;
  color: rgba(232, 219, 198, 0.6);
}

/* ---- Treasure --------------------------------------------------------- */
/* Not a foe and not a delver, so it gets neither ring. A warm glow instead,
   which also stops it reading as a tile you could draw through. */
.token.chest {
  border: 0;
  background: none;
  box-shadow: none;
  filter: drop-shadow(0 0 7px rgba(255, 196, 84, 0.6));
  animation: chest-breathe 2.6s ease-in-out infinite;
}
.token.chest img {
  border-radius: 6px;
  transform: scale(1.06);
}
@keyframes chest-breathe {
  0%, 100% { filter: drop-shadow(0 0 6px rgba(255, 196, 84, 0.45)); }
  50%      { filter: drop-shadow(0 0 12px rgba(255, 214, 122, 0.85)); }
}
@media (prefers-reduced-motion: reduce) {
  .token.chest { animation: none; }
}

/* ---- What this line will hit ------------------------------------------ */
/* Drawn while the line is being drawn. `doomed` is a foe the blade reaches
   normally; `speared` is one only the spear's second ring gets, so the reach
   is legible on the board rather than buried in a card. */
.cell.doomed .token.foe {
  box-shadow: 0 0 0 3px rgba(255, 96, 72, 0.95), 0 0 14px rgba(255, 96, 72, 0.6);
}
.cell.reached .token.foe {
  box-shadow: 0 0 0 3px rgba(120, 214, 255, 0.95), 0 0 16px rgba(120, 214, 255, 0.7);
  animation: spearPing 0.9s ease-in-out infinite;
}
@keyframes spearPing {
  0%, 100% { box-shadow: 0 0 0 3px rgba(120, 214, 255, 0.9), 0 0 10px rgba(120, 214, 255, 0.5); }
  50%      { box-shadow: 0 0 0 4px rgba(160, 230, 255, 1), 0 0 20px rgba(120, 214, 255, 0.85); }
}
@media (prefers-reduced-motion: reduce) {
  .cell.reached .token.foe { animation: none; }
}
.matchup .reach {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 8px;
  font-size: 10px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #9fe4ff;
  background: rgba(120, 214, 255, 0.14);
  border: 1px solid rgba(120, 214, 255, 0.4);
}

/* ---- The Delver's bag -------------------------------------------------- */
/* Shown at the stair only. Choosing a weapon here is a commitment made before
   the next hall is visible, so it gets the weight of a real control. */
.bagpick { display: none; margin: 4px 0 12px; }
.bagpick.show { display: block; }
.bag-title {
  font-family: 'Cinzel', serif;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #c4964a;
  margin-bottom: 8px;
}
.bag-row { display: flex; gap: 10px; justify-content: center; }
.bagitem {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 9px 6px 8px;
  border-radius: 12px;
  border: 1px solid rgba(201, 162, 74, 0.28);
  background: rgba(0, 0, 0, 0.32);
  color: rgba(232, 219, 198, 0.72);
  cursor: pointer;
}
.bagitem img { width: 42px; height: 42px; border-radius: 8px; }
.bagname { font-weight: 700; font-size: 13px; }
.bagreach { font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; opacity: 0.7; }
.bagitem.on {
  border-color: rgba(255, 214, 122, 0.95);
  background: rgba(255, 196, 84, 0.14);
  color: #f6e9cd;
  box-shadow: 0 0 12px rgba(255, 196, 84, 0.28);
}
.bagitem:not(.on):hover { border-color: rgba(201, 162, 74, 0.6); color: #e8dbc6; }

/* The weapon a delver is currently holding, on their party card. */
/* The weapon a delver is holding. NOT absolutely positioned by default: only
   the wide layout makes .party-row a positioning context, so on a phone an
   absolute corner icon escaped the row entirely and landed on top of the
   Shuffle button. Here it is simply the last item in the flex row. */
.party-row .wep {
  width: 22px;
  height: 22px;
  flex: none;
  border-radius: 5px;
  border: 1px solid rgba(201, 162, 74, 0.5);
  background: rgba(0, 0, 0, 0.4);
  object-fit: cover;
}
@media (min-width: 1120px) {
  /* BOTTOM-LEFT, AND THE METER RESERVES THE SPACE. This was bottom-right,
     which is exactly where .hp-num sits at the end of .pmeter - so the icon
     was painted over the last character of the health text and "29/29" read
     as "29/2". Two changes, because either alone is not enough: moving it to
     the left corner only trades which end gets covered, and reserving space
     without moving it leaves the number crowded against the icon. */
  .party-row .wep {
    position: absolute;
    left: 11px;
    bottom: 10px;
    width: 26px;
    height: 26px;
    border-radius: 6px;
  }
  .party-row .pmeter { padding-left: 33px; }
}


/* ---- Health rings ------------------------------------------------------ */
/* Drawn on the token itself. This is what lets the phone layout drop the foe
   rail entirely: the cards were eating vertical space the board needed, and a
   ring answers "how hurt is it" in the place the player is already looking. */
/* The token's own outline IS the meter. It is painted into the border band with
   background-clip, so it is never drawn over the portrait, and because every box
   here is border-box the ring costs the token no width at all -- tokens stay
   exactly cell sized. The empty arc is a dark gap, so a hurt thing reads as a
   part-drawn circle rather than as a differently coloured one. */
.token.foe,
.token.unit:not(.bound) {
  border-color: transparent;
  border-width: 3px;
  background:
    linear-gradient(#241a13, #241a13) padding-box,
    conic-gradient(from -90deg,
      var(--hpc) calc(var(--hp, 1) * 360deg),
      rgba(0, 0, 0, 0.62) 0) border-box;
}
/* The arc LENGTH is the meter, not its colour: the ring keeps the red/gold that
   separates foe from delver at a glance. Hue-shifting it green-to-red read as a
   healthy foe being friendly, which is worse than no meter at all. */
.token.foe { --hpc: var(--foe); }
.token.unit:not(.bound) { --hpc: var(--ring, #f0d48a); }
/* Friend and foe were separated by ring colour, which the meter now owns, so
   the glow around the disc has to carry that instead. */
.token.foe {
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.6), 0 5px 14px rgba(0, 0, 0, 0.6),
              0 0 0 3px var(--foe-soft);
}
.token.unit:not(.bound) {
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.7),
              0 0 0 3px var(--ring-soft, rgba(240, 212, 138, 0.55)),
              0 0 14px 2px var(--ring-soft, rgba(240, 212, 138, 0.3));
}
/* A captive has no ring: they are not a health bar, they are an objective. */
.token.unit.bound::after { display: none; }

/* ---- Phone: the board takes the space the foe rail was using ----------- */
@media (max-width: 899px) {
  .side-foes { display: none; }
}


/* The wide layout has the room to go further still. */
@media (min-width: 1120px) {
  .infocard { width: min(560px, calc(100% - 24px)); padding: 18px; }
  .card-art { width: 190px; height: 190px; }
  .infocard h3 { font-size: 1.32rem; }
}
/* A short screen cannot spare it -- the card scrolls, and a huge portrait would
   push everything worth reading below the fold. */
@media (max-height: 640px) {
  .card-art { width: 84px; height: 84px; }
}

/* ---- Crossing into a new hall ------------------------------------------ */
/* On a phone the board covers nearly all of the backdrop, so the zone art
   cannot carry the hall's identity on its own. This says it once, plainly, and
   the HUD crumb keeps saying it after. */
.fx-zone {
  position: absolute;
  left: 50%;
  top: 42%;
  transform: translate(-50%, -50%);
  font-family: 'Cinzel', serif;
  font-size: clamp(1.15rem, 5.2vw, 2rem);
  letter-spacing: 0.06em;
  color: #f6e9cd;
  text-align: center;
  white-space: nowrap;
  padding: 10px 22px;
  border-radius: 14px;
  background: rgba(12, 9, 7, 0.72);
  border: 1px solid rgba(201, 162, 74, 0.55);
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.7);
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.9);
  pointer-events: none;
  z-index: 14;
  animation: fxZone 2.4s cubic-bezier(.2,.7,.3,1) forwards;
}
@keyframes fxZone {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.92); }
  12%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  75%  { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -62%) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .fx-zone { animation: none; opacity: 1; }
}
/* The hall you are in, kept in the HUD where the art cannot reach. */
.crumb-zone {
  color: var(--gold);
  font-family: 'Cinzel', serif;
  letter-spacing: 0.04em;
}
@media (max-width: 520px) {
  .crumb-zone { font-size: 0.72rem; }
}

/* ---- The Emberwright choosing who to open a path for -------------------- */
.cell.aim .token.unit {
  box-shadow: 0 0 0 3px rgba(169, 140, 240, 0.95), 0 0 18px rgba(169, 140, 240, 0.7);
  animation: aimPulse 1.1s ease-in-out infinite;
  cursor: pointer;
}
@keyframes aimPulse {
  0%, 100% { box-shadow: 0 0 0 3px rgba(169, 140, 240, .85), 0 0 12px rgba(169, 140, 240, .5); }
  50%      { box-shadow: 0 0 0 4px rgba(196, 176, 255, 1),  0 0 24px rgba(169, 140, 240, .9); }
}
@media (prefers-reduced-motion: reduce) { .cell.aim .token.unit { animation: none; } }
