: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;
  /* 9vw put "DUNGEON MATCH" one character wider than a 590px window, so it
     wrapped to two lines and cost the title screen ~56px it did not have.
     7.6vw holds one line down to about a 460px window and still hits the 3.6rem
     ceiling on a desktop; below that the flex wrap takes over, which is what a
     phone wants anyway. */
  font-size: clamp(2.1rem, 7.6vw, 3.6rem);
  line-height: 0.95;
  /* Wraps by width rather than by a <br>, so it is one line on a desktop and
     two on a phone with nothing to keep in sync. */
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  column-gap: 0.3em;
  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));
}
/* The title screen groups by question, not by button. */
.mode-head {
  font-family: var(--font-display);
  font-size: 0.68rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--dim);
  text-align: center;
  margin: 10px 0 -2px;
}
.mode-head:first-child { margin-top: 0; }

.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;
  /* SAFE centre. A centred flex column that overflows is clipped at BOTH ends
     and the top cannot be scrolled back to - the wordmark was simply gone.
     `safe` centres while it fits and falls back to the start when it does not.
     The plain value stays first for anything that does not understand it. */
  justify-content: center;
  justify-content: safe center;
  gap: 10px;
  text-align: center;
  /* Four modes plus Records and How to play is two buttons more than this
     screen was built for, and the wordmark was being clipped off the top.
     Scrolls rather than crops, and the hero shrinks first on a short window. */
  overflow-y: auto;
  padding-top: 12px;
  padding-bottom: 12px;
}
@media (max-height: 1040px) {
  .title-hero { width: min(34vw, 132px); margin: 4px 0 2px; }
  #screen-title { gap: 7px; }
}
.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; }
/* One column on a phone, two wherever the window can hold them. Four stacked
   buttons plus two more below them is what pushed this screen past the fold. */
.mode-groups, .title-extra { display: grid; grid-template-columns: 1fr; gap: 10px; }
.mode-group { display: flex; flex-direction: column; gap: 10px; }
@media (min-width: 540px) {
  .btn-col { width: min(620px, 92vw); }
  .mode-groups { grid-template-columns: 1fr 1fr; gap: 14px; }
  .title-extra { grid-template-columns: 1fr 1fr; }
}
.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; }

/* HOW TO PLAY. Same three-band dialog as Records: a header that stays, a body
   that scrolls, a footer that stays. On a phone the frame scrolls INSIDE the
   panel, so Next and the dots are never below the fold. */
#screen-how {
  justify-content: center;
  align-items: center;
  padding: 12px;
  background: rgba(6, 4, 3, 0.72);
}
.wiz-dialog {
  display: flex;
  flex-direction: column;
  width: min(560px, 100%);
  max-height: min(88vh, 780px);
  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;
}
.wiz-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;
}
.wiz-top h2 {
  font-family: var(--font-display);
  font-size: 1.15rem;
  letter-spacing: 0.06em;
  color: var(--gold);
  margin: 0;
}
/* min-height:0 is what actually lets a flex child scroll; without it the body
   grows and pushes the dots and the buttons off the screen. */
.wiz-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 16px;
  text-align: left;
}
.wiz-step h3 {
  font-family: var(--font-display);
  font-size: 1.05rem;
  letter-spacing: 0.04em;
  color: var(--gold);
  margin: 0 0 10px;
}
.wiz-step p { margin: 0 0 10px; line-height: 1.5; }
.wiz-step p:last-child { margin-bottom: 0; }
.wiz-tip {
  color: var(--dim);
  font-size: 0.9rem;
  border-left: 2px solid rgba(201, 162, 74, 0.4);
  padding-left: 10px;
}
.wiz-list { margin: 0 0 10px; padding-left: 18px; }
.wiz-list li { margin-bottom: 7px; line-height: 1.45; }
.wiz-modes { width: 100%; border-collapse: collapse; margin-bottom: 12px; }
.wiz-modes th {
  text-align: left;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.78rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--dim);
  padding: 12px 0 6px;
}
.wiz-modes tr:first-child th { padding-top: 0; }
.wiz-modes td {
  vertical-align: top;
  padding: 4px 0;
  line-height: 1.4;
}
.wiz-modes td:first-child { white-space: nowrap; padding-right: 14px; }
.ring-red { color: #e8695c; }

/* THE WORLD BOARD inside Records. A ranking is a table, so it gets columns that
   line up rather than a paragraph per row: place, who, how far, when. */
.rec-world { margin: 0 0 14px; }
.rec-world-h {
  font-family: var(--font-display);
  font-size: 0.82rem;
  letter-spacing: 0.06em;
  color: var(--gold);
  margin-bottom: 5px;
}
.rec-world-b { font-size: 0.88rem; color: var(--dim); }
.wrow {
  display: grid;
  grid-template-columns: 1.6em 1fr auto auto;
  gap: 8px;
  align-items: baseline;
  padding: 3px 6px;
  border-radius: 6px;
}
.wrow + .wrow { margin-top: 1px; }
/* Your own row. A table you are IN reads completely differently from one you
   are only looking at, and finding yourself in it should not take a search. */
.wrow.mine {
  background: rgba(201, 162, 74, 0.14);
  box-shadow: inset 0 0 0 1px rgba(201, 162, 74, 0.35);
}
.wrow.mine .wh { color: var(--gold); font-weight: 700; }
.wr {
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--dim);
}
.wh {
  color: #e8d8b8;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.wm { font-variant-numeric: tabular-nums; color: #e8d8b8; }
.wd { font-size: 0.78rem; color: var(--dim); font-variant-numeric: tabular-nums; }
/* A phone has no room for the date beside three other columns, and it is the
   least of the four. */
@media (max-width: 460px) {
  .wrow { grid-template-columns: 1.6em 1fr auto; }
  .wd { display: none; }
}
.ring-blue { color: #6fb3e0; }

/* THE DOTS. How far in you are, and clickable so the weapons frame is one tap
   away rather than five. */
.wiz-dots {
  display: flex;
  justify-content: center;
  gap: 9px;
  padding: 12px 16px 4px;
  flex: none;
}
.wiz-dot {
  appearance: none;
  border: 0;
  padding: 0;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: rgba(240, 230, 210, 0.28);
  cursor: pointer;
  transition: background 0.15s, transform 0.15s;
}
.wiz-dot:hover { background: rgba(240, 230, 210, 0.55); }
.wiz-dot.on { background: #f0e6d2; transform: scale(1.25); }
.wiz-dot:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.wiz-nav.solo { grid-template-columns: 1fr; }
.wiz-nav {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  padding: 10px 16px 16px;
  flex: none;
}
@media (prefers-reduced-motion: reduce) {
  .wiz-dot { transition: none; }
}
.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;
  /* The stats column is back. On desktop app.js reparents .hud-stats into this
     header, which is where it has always sat; on a phone it lives in .board-wrap
     and this column collapses to nothing. */
  grid-template-columns: auto 1fr auto auto;
  grid-template-areas: "brand meta stats btns";
  gap: 10px;
  align-items: center;
  padding: 6px 2px 8px;
}
.brand { grid-area: brand; }
.hud-meta { grid-area: meta; }
@media (min-width: 701px) {
  /* CENTRED OVER THE BOARD, not left-aligned in whatever column is left over.
     The crumbs sat in the 1fr column beside the pills, so "FLOOR 2 · THE UPPER
     HALLS · ESCAPE" drifted left while the board it describes is centred in the
     window. Taking them out of flow and pinning them to the middle puts the
     label over the thing it labels. The topbar keeps its height because the
     pills and buttons are still in the grid. */
  .topbar { position: relative; }
  .hud-meta {
    position: absolute;
    /* An absolutely positioned GRID ITEM is placed against its own grid area,
       not the container - so left:50% centred the crumbs inside their own
       column and left them 68px off the board. Spanning every column makes the
       whole row the containing block. */
    grid-area: 1 / 1 / 2 / -1;
    left: 50%;
    transform: translateX(-50%);
    justify-content: center;
    white-space: nowrap;
    pointer-events: none;
  }
}
/* ONE ELEMENT, TWO HOMES. app.js moves it: header on desktop (grid-area
   stats), directly over the board on a phone. The grid-area is inert wherever
   it is not a child of the grid, so both rules can simply be true. */
.hud-stats {
  grid-area: stats;
  display: flex;
  gap: 6px;
  justify-content: center;
  margin: 0;
  flex: none;
}
.stat-pills { grid-area: btns; }
.pill-move b { font-variant-numeric: tabular-nums; }

/* PHONE: the numbers drop below the crumbs, full width, directly above the
   board. Four crumbs and two pills across 375px wrapped into three lines and
   the board paid for it in height. */
@media (max-width: 700px) {
  .topbar {
    grid-template-columns: 1fr auto;
    grid-template-areas: "meta btns";
    row-gap: 6px;
    /* The home chip is position:fixed at top-left and the topbar starts at y=0,
       so the first crumb was reading "FLO OR 1" from underneath it. */
    padding: 4px 2px 6px 50px;
  }
  .hud-stats { gap: 8px; margin: 0 0 6px; }
  /* One line, always. The zone name is the only variable-length thing up here,
     so it is the one allowed to shorten - the floor number and the mode are
     both two characters and should never be the thing that wraps. */
  .hud-meta { flex-wrap: nowrap; min-width: 0; gap: 8px; }
  .hud-meta .crumb { white-space: nowrap; }
  .crumb-zone {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .hud-stats .pill { flex: 1 1 0; text-align: center; padding: 5px 6px; }
}
.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;
  /* A COLUMN, because it holds the numbers and then the board. It was a row
     centring one child; adding the pill strip put them side by side and shoved
     the board off the screen. */
  flex-direction: column;
  align-items: center;
  /* SAFE centre, for the second time tonight and the same reason: a centred
     flex column that overflows is clipped at BOTH ends, and with overflow
     hidden the pill row simply vanished off the top. */
  justify-content: center;
  justify-content: safe 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%;
  /* THE GAME'S SCROLLBAR, NOT THE OPERATING SYSTEM'S. This card scrolls
     whenever a delver has a bag and spells, and it was drawing a stock light
     scrollbar down the side of a near-black panel - the same mistake the foe
     rail had. Same palette as everywhere else here. */
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: rgba(201, 162, 74, 0.45) rgba(0, 0, 0, 0.3);
  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::-webkit-scrollbar { width: 8px; }
.infocard::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.3); border-radius: 99px; }
.infocard::-webkit-scrollbar-thumb {
  background: rgba(201, 162, 74, 0.45);
  border-radius: 99px;
}
.infocard::-webkit-scrollbar-thumb:hover { background: rgba(201, 162, 74, 0.7); }
.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);
}

/* NO LINE AVAILABLE. Deliberately readable rather than alarming: this is a
   position you played into, not a failure, and it clears itself when the board
   settles. The token keeps its ring so the delver is still findable. */
/* The weapon a chest is holding, on the chest. Small and cornered so it reads
   as a label rather than competing with the chest art itself. */
/* The cell is the positioning context now, since the weapon sits outside the
   token's clip. */
.cell { position: relative; }
/* The weapon SITS ON the chest rather than in a badge stuck to it. The first
   cut was a bordered box with object-fit:cover, which cropped the art and read
   as a UI sticker pasted over the world. These files carry alpha, so the
   silhouette can just lie across the lid with a shadow under it. */
.chest-wep {
  position: absolute;
  /* ABOVE THE LID. .token carries z-index:2 and .cell is a grid, so the chest
     is a grid item with its own stacking context - it painted straight over a
     z-index:auto sibling and the sword was in the DOM, sized and placed, and
     invisible in every mode. */
  z-index: 3;
  /* INSIDE the cell. Overhanging looked right in isolation and vanished in
     place: later cells paint after this one, so the part hanging over the edge
     was covered by the neighbouring gems. */
  right: 1%;
  bottom: 1%;
  width: 58%;
  height: 58%;
  pointer-events: none;
}
.chest-wep img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  filter: drop-shadow(0 2px 3px rgba(0,0,0,.85)) drop-shadow(0 0 6px rgba(226,192,61,.55));
  transform: rotate(-18deg);
}

/* THE WAY OUT. Cool against a hall of gold and red, because it is the one
   thing on the board that is not a fight. */
/* THE WAY DOWN, once the hall is quiet. A hole, not a door: Escape's exit is an
   arch you walk OUT of and this is a shaft you drop THROUGH, so they must not
   read alike at a glance. Rings going dark do that with no art. */
.token.stair {
  position: absolute; inset: 4%;
  display: grid; place-items: center;
  animation: stairPulse 2.8s ease-in-out infinite;
}
.token.stair img { width: 100%; height: 100%; object-fit: contain; display: block; }
/* The glow moves to a drop-shadow now that the tile is a picture with alpha -
   a box-shadow would draw a rectangle behind a rounded stone. */
@keyframes stairPulse {
  0%,100% { filter: drop-shadow(0 0 4px rgba(226,192,61,.28)); }
  50%     { filter: drop-shadow(0 0 11px rgba(226,192,61,.6)); }
}
@media (prefers-reduced-motion: reduce) { .token.stair { animation: none; } }

.token.exit {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5em;
  font-weight: 700;
  color: #bfe6ff;
  border-radius: 8px;
  background: radial-gradient(circle at 50% 60%, rgba(90,170,230,.55) 0%, rgba(20,50,80,.85) 75%);
  box-shadow: inset 0 0 0 2px rgba(150,215,255,.9), 0 0 18px rgba(120,190,255,.45);
  animation: exitGlow 2.4s ease-in-out infinite;
}
@keyframes exitGlow {
  0%, 100% { box-shadow: inset 0 0 0 2px rgba(150,215,255,.75), 0 0 12px rgba(120,190,255,.35); }
  50%      { box-shadow: inset 0 0 0 2px rgba(190,235,255,1),  0 0 26px rgba(120,190,255,.7); }
}
@media (prefers-reduced-motion: reduce) { .token.exit { animation: none; } }
.token.exit { position: relative; }
/* BARRED. Iron in FRONT of a door that is still visibly a door - the arrow has
   to read through the gaps, or the cell stops looking like a way out and starts
   looking like a wall. First cut used three fat opaque bars and the middle one
   sat exactly over the arrow. */
.token.exit.barred {
  animation: none;
  filter: brightness(0.82) saturate(0.75);
}
.token.exit.barred span { position: relative; z-index: 1; }
.token.exit.barred::after {
  content: "";
  position: absolute;
  inset: 6% 10%;
  z-index: 2;
  background: repeating-linear-gradient(90deg,
    rgba(206, 216, 228, 0.78) 0 5.5%,
    transparent 5.5% 20%);
  border-top: 2px solid rgba(206, 216, 228, 0.8);
  border-bottom: 2px solid rgba(206, 216, 228, 0.8);
  border-radius: 2px;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.7));
  pointer-events: none;
}
.card-art.exit-art {
  display: flex; align-items: center; justify-content: center;
  font-size: 2.2rem; color: #bfe6ff;
  background: radial-gradient(circle at 50% 60%, rgba(90,170,230,.5) 0%, rgba(20,50,80,.9) 75%);
}

.token.unit.noline { opacity: 0.42; filter: grayscale(0.55); }
.party-row.noline .pface { opacity: 0.55; filter: grayscale(0.5); }
/* Its own line under the name rather than trailing it. Inline, it turned
   "The Lancer" into "The Lancer · no line" and wrapped mid-phrase on a card
   that is wider than it is roomy. */
.party-row.noline .pname::after {
  content: 'no line';
  display: block;
  color: var(--dim);
  font-family: var(--font-ui);
  font-size: 0.66rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-top: 1px;
}

.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. */
    /* WIDER RAILS, SHORTER CARDS. The cards were tall and narrow, so five
       foes needed a scrollbar while there was dead width either side of them.
       Height is the scarce axis in a single-screen game; width was going
       spare. */
    grid-template-columns: minmax(236px, 300px) auto minmax(236px, 300px);
    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; }
  /* The party card was a 108px portrait with everything stacked beneath it,
     which is the tallest possible way to say three short things. Portrait on
     the left, the rest beside it. */
  /* The party is only ever three, so unlike the foe rail there is no reason to
     squeeze. It keeps the horizontal shape - that is what uses the new width -
     but takes its height back: a bigger portrait and room around it. */
  .party-row {
    display: grid;
    grid-template-columns: 92px 1fr;
    align-items: center;
    gap: 6px 13px;
    padding: 13px 13px 14px;
    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: 92px;
    height: 92px;
    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: 1rem;
    color: var(--ink);
    text-align: left;
    margin-bottom: 4px;
  }
  .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: 7px;
    padding: 0;
  }
  /* Sized so SEVEN fit without scrolling, which is what an escape hall holds
     and one more than a deep Delve floor. */
  .foe-card {
    min-width: 0;
    display: grid;
    grid-template-columns: 50px 1fr;
    grid-template-rows: auto auto auto;
    gap: 1px 9px;
    align-items: center;
    text-align: left;
    padding: 5px 10px;
    border-left: 3px solid var(--danger);
  }
  .fface {
    grid-row: 1 / 4;
    /* Matches the 56px column. Shrinking the column and leaving the portrait at
       78 put the picture on top of the name. */
    width: 50px;
    height: 50px;
    margin: 0;
    border-width: 2px;
    box-shadow: 0 4px 12px 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; justify-content: safe 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. */
/* Two across, however many rows that takes. Four columns in one row on a 375px
   phone gives each 80px, which holds neither "Keep them moving" nor a five-digit
   score. */
.best-cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px 14px;
}
.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;
  box-sizing: border-box;
  padding: 2px;
  border-radius: 5px;
  border: 1px solid rgba(201, 162, 74, 0.5);
  /* OPAQUE. These files carry alpha, so a 40%-black chip let the portrait
     through the blade and the icon read as a smudge on the face rather than a
     thing hanging on a hook. Dark stone behind it, and `contain` so the
     silhouette sits in the slot instead of being cropped to fill it. */
  background: linear-gradient(180deg, #2b2018 0%, #15100b 100%);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.65), inset 0 1px 0 rgba(255, 255, 255, 0.07);
  object-fit: contain;
}
@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. */
  /* Bottom-left is under the portrait now, not under the health bar, so the
     chip sits there without the meter having to reserve room for it. */
  .party-row .wep {
    position: absolute;
    left: 12px;
    bottom: 8px;
    width: 26px;
    height: 26px;
    padding: 3px;
    border-radius: 7px;
  }
  .xp-bar { display: block; margin-left: 0; margin-right: 0; }
}


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


/* ── CARRIED ITEMS ─────────────────────────────────────────────────────────
   The tray sits above the board because arming one is a thing you do while
   looking at the board, not a thing you go to a menu for. Hidden entirely when
   empty: a floor that gave you nothing should not show an empty shelf. */
.item-tray{display:flex;flex-wrap:wrap;gap:6px;justify-content:center;margin:0}
/* In the dock it is one flex child among the buttons: it must not stretch the
   way .dock .btn does, and the dock has to be allowed to wrap so a second flask
   pushes a line down here rather than squeezing the buttons. */
.dock{flex-wrap:wrap}
.dock .item-tray{flex:0 1 auto;width:100%}
.item-tray[hidden]{display:none}
.item-chip{display:flex;align-items:center;gap:6px;padding:5px 10px;border-radius:999px;
  cursor:pointer;color:#eafff7;background:#ffffff12;border:1px solid #ffffff2b;
  font:800 11px/1 system-ui,-apple-system,Segoe UI,Roboto,sans-serif}
.item-chip:hover{background:#ffffff20}
.item-chip .ic{font-size:14px;line-height:1}
.item-chip .qty{opacity:.7;font-weight:700}
.item-chip.armed{color:#04231a;background:#ffc85b;border-color:#ffc85b;
  box-shadow:0 0 0 3px #ffc85b33}
/* While a flask is armed the board is a target, so say so plainly rather than
   letting the player discover it by tapping and being surprised. */
.board.aiming{cursor:crosshair}
.board.aiming .cell{outline:1px solid #ffc85b1f;outline-offset:-1px}


/* ── TELLING THE TWO CHESTS APART ──────────────────────────────────────────
   Paul: "your chests do not indicate what is gold and what is weapon? They are
   the same." One chest picture, two contents - the badge does the telling, and
   a cool rim on the arms chest separates them at a glance across the board
   before you have read anything. Gold keeps the warm default it always had. */
.token.chest.chest-arms{box-shadow:0 0 0 2px #7fd8ffcc,0 0 10px #7fd8ff55}

/* A glyph badge rather than an image, for the flasks - they have no art and
   inventing some would be worse than a clear symbol. Sized against the cell so
   it scales with the board, like every other token on it. */
.chest-wep.chest-item{display:grid;place-items:center;font-size:min(15px,3.2cqw);
  line-height:1;background:#0c1b17e6;border-radius:50%;
  box-shadow:0 0 0 1px #ffffff33}
/* The purse says coin without needing a picture of one. */
.chest-wep.chest-coin{display:grid;place-items:center;border-radius:50%;
  background:radial-gradient(circle at 35% 32%,#ffe9a8,#e0a52c 62%,#9c6b13);
  box-shadow:0 0 0 1px #00000055,0 1px 3px #0008}


/* ── POISONED GROUND ───────────────────────────────────────────────────────
   Rot bites creatures and delvers alike, so where it lies has to be legible at
   a glance - it was drawn nowhere at all, which made it a hazard the player
   could be hurt by and never see. Three stages so a square about to clear
   reads differently from one just laid: the trail should look like it is
   draining, not like a flat stain. Painted with an inset ring rather than a
   fill so the relic underneath stays readable - the square is still a tile you
   might want to match. */
.cell.rotted{position:relative}
.cell.rotted:after{content:"";position:absolute;inset:0;border-radius:inherit;
  pointer-events:none;z-index:1;
  box-shadow:inset 0 0 0 2px #7ee04688, inset 0 0 10px #4fae2a66;
  background:radial-gradient(circle at 50% 50%,#7ee0461f,#2f6a1a33)}
.cell.rotted.rot3:after{opacity:1}
.cell.rotted.rot2:after{opacity:.72}
.cell.rotted.rot1:after{opacity:.42}


/* Real art on the chips and the badges now, so they need sizing rather than
   the glyph centring the emoji versions used. */
.item-chip .ic-art{width:18px;height:18px;object-fit:contain;display:block}
.chest-wep.chest-coin{background:none;box-shadow:none}
.chest-wep.chest-coin img{width:100%;height:100%;object-fit:contain}


/* ── THE SHIELD GAUGE ──────────────────────────────────────────────────────
   Sits directly under the health bar and is scaled to the same maximum, so a
   full shield bar and a full health bar are the same length and "full" reads
   identically on both. That is the whole point of replacing the corner badge:
   a number told you what you had, a gauge tells you what you could have. */
.sh-bar{position:relative;height:4px;margin-top:2px;border-radius:3px;overflow:hidden;
  background:#0b1a2acc;box-shadow:inset 0 0 0 1px #ffffff14}
.sh-fill{height:100%;transform-origin:left center;border-radius:3px;
  background:linear-gradient(180deg,#8fd4ff,#2f7fc4);transition:transform .18s ease}
.sh-bar.full .sh-fill{background:linear-gradient(180deg,#bfe9ff,#4aa6e8);
  box-shadow:0 0 6px #7fd8ffaa}
.hp-num .sh-num{margin-left:6px;color:#8fd4ff;font-weight:800}
.hp-num .sh-num:before{content:"\1F6E1";margin-right:2px;font-size:.85em}


/* ── DROWNED GROUND ────────────────────────────────────────────────────────
   A row the flood has taken cannot be matched and cannot be stood on, so it
   must not look like ground. It was drawn identically to dry floor while
   matchable() quietly refused it - a board that looked full and was not.
   The surface row is brighter so the waterline reads as a moving edge rather
   than a flat region, which is the thing the player is actually racing. */
/* BEHIND the contents, and CONTINUOUS across the grid. Paul: "flood overlays
   over the character instead of filling the background of the cells
   continuously across like water."

   Two mistakes in one rule. It painted at z-index 4, on top of everything
   including the delver standing in it - so a character in the water was under
   the water rather than in it. And it was inset to the cell with the cell's own
   rounded corners, so a flooded row read as a line of separate blue tiles
   instead of a body of water.

   Now it sits at z-index 0, under every token, and bleeds 2.5px past the cell
   on every side - exactly half the 5px grid gap, so neighbours meet without
   overlapping and the tint is never painted twice along an edge - so neighbouring cells merge into one
   surface. Contents are dimmed but the delver is NOT - somebody standing in
   rising water is the thing you most need to see.

   AND THE TEXTURE IGNORES THE GRID. Paul made a tileable water and a tileable
   lava for this. Painting either one per-cell would have defeated the point:
   every cell would show the same crop of the same tile and the flood would read
   as a row of identical stamps - which is the squares-not-water problem again,
   just prettier. So each cell is told where it SITS (--wx/--wy, its column and
   row, negated) and offsets the texture by that much times the real grid pitch,
   cell plus the 5px gap. The tile then spans three cells and runs unbroken
   across the whole flooded region, seams and all falling wherever the artwork
   put them rather than on every cell edge. */
.cell.drowned:before{content:"";position:absolute;inset:-2.5px;border-radius:0;
  z-index:0;pointer-events:none;
  background-repeat:no-repeat,repeat;
  background-size:auto,calc(var(--cell) * 3);
  background-position:0 0,
    calc(var(--wx,0) * (var(--cell) + 5px) + 2.5px)
    calc(var(--wy,0) * (var(--cell) + 5px) + 2.5px)}
/* A tint over the texture, so depth still reads and the art stays readable
   under the crew standing in it. */
.cell.drowned.is-water:before{
  background-image:linear-gradient(180deg,#0a3a6147,#04182b8c),url(../assets/water.webp)}
.cell.drowned.is-lava:before{
  background-image:linear-gradient(180deg,#5e1c0640,#1d070380),url(../assets/lava.webp)}
/* Under the surface, but legible: these relics CAN still be matched, so dimming
   them to near-invisible would be a lie about what the board can do. Enough of a
   shift to read as underwater, not enough to read as disabled. The crew is not
   dimmed at all - somebody standing in rising water is the thing you most need
   to see. */
.cell.drowned .gem{opacity:.82;filter:saturate(.85) brightness(.88)}
.cell.drowned .entity,.cell.drowned .token{position:relative;z-index:6}
/* The waterline is an edge, so it gets a highlight the body of water does not. */
.cell.drowned.surface.is-water:before{
  background-image:linear-gradient(180deg,#cdf0ffb3 0,#7fc8ff45 22%,#0a3a6166 70%,#04182b99),
    url(../assets/water.webp);
  box-shadow:inset 0 3px 0 #d8f3ffcc}
.cell.drowned.surface.is-lava:before{
  background-image:linear-gradient(180deg,#ffd9a8b3 0,#ff8b3d45 22%,#5e1c0666 70%,#1d070399),
    url(../assets/lava.webp);
  box-shadow:inset 0 3px 0 #ffe6c6cc}
