/* ═══ GREAT WORK — candlelit print-house ══════════════════════════════════════ */

/* ─── Tokens ──────────────────────────────────────────────────────────────────── */
:root {
  --aspd: 1;                       /* animation speed divisor (set from settings) */

  --bg-0:      #150e18;
  --bg-1:      #221726;
  --leather:   #2c1c26;
  --leather-2: #382430;

  --wood:       #5d3f28;
  --wood-dark:  #3a2818;
  --wood-light: #7d573a;

  --parch:     #f2e7cb;
  --parch-2:   #e4d4ae;
  --parch-ink: #3a2c1a;
  --parch-dim: #7a6647;

  --ivory:    #f8f0dc;
  --ivory-2:  #e8dab6;
  --tile-ink: #2c2214;

  --brass:      #d9a64b;
  --brass-hi:   #f6d68a;
  --brass-dark: #8a5f1d;

  --steel:      #b9c3ce;   /* the ratchet's colour — it has no paint of its own */

  --inkblue:      #8db8ff;
  --inkblue-deep: #1d3354;
  --pressred:     #ff8273;
  --press-deep:   #58231f;
  --setviolet:    #cfa6ff;
  --sets-deep:    #3c2358;

  --good: #8fdfae;
  --bad:  #ff7066;
  --warn: #ffc169;
  --muted: #9c8a96;

  --crimson: #c94f43;
  --azure:   #4f86c9;
  --jade:    #3fa371;
  --amber:   #d29a3a;

  --serif: 'Fraunces', Georgia, 'Times New Roman', serif;
  --sans:  'Inter', ui-sans-serif, system-ui, 'Segoe UI', sans-serif;
  /* Pencil — only where something is written by hand (The Redactor's wrappers).
     The faces behind Caveat exist per platform, so an offline or CSP-blocked
     build still gets handwriting rather than the serif. */
  --hand:  'Caveat', 'Segoe Script', 'Bradley Hand', 'Noto Serif Display', cursive;

  --tile-w: 68px;
  --tile-h: 72px;

  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 18px;
}

/* ─── Reset & stage ───────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }

body {
  color: var(--parch);
  font-family: var(--sans);
  font-size: 14px;
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
  min-height: 100dvh;
  overflow-x: hidden;
  overscroll-behavior-y: contain;      /* no pull-to-refresh mid-game */
  touch-action: manipulation;          /* no double-tap zoom */
  user-select: none;
  -webkit-user-select: none;
  background:
    radial-gradient(1100px 640px at 50% -8%, rgba(233, 178, 102, 0.13), transparent 62%),
    radial-gradient(900px 540px at 50% 112%, rgba(135, 62, 92, 0.16), transparent 58%),
    linear-gradient(180deg, #281a2c 0%, #1f1322 46%, #170d1a 100%);
}

/* leather grain + vignette + ghost glyph */
body::before {
  content: '&';
  position: fixed;
  right: -4vw; bottom: -14vh;
  font-family: var(--serif);
  font-weight: 900;
  font-size: 46vw;
  line-height: 1;
  color: rgba(243, 222, 184, 0.025);
  transform: rotate(-9deg);
  pointer-events: none;
  z-index: 0;
}
body::after {
  content: '';
  position: fixed; inset: 0;
  pointer-events: none;
  z-index: 1;
  box-shadow: inset 0 0 22vmin rgba(8, 2, 10, 0.62);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E");
}

/* ─── The Deadline's light ─────────────────────────────────────────────────────
   Everything transitions, so it reads as the light going out rather than as a
   repaint. Hung off `body.deadline-on`, toggled in renderStatus (js/render.js). */
body {
  transition: background calc(1.4s / var(--aspd)) ease;
}
body.deadline-on {
  background:
    radial-gradient(1100px 640px at 50% -8%, rgba(233, 122, 96, 0.16), transparent 62%),
    radial-gradient(900px 540px at 50% 112%, rgba(150, 42, 48, 0.2), transparent 58%),
    linear-gradient(180deg, #2b1620 0%, #200f18 46%, #170a10 100%);
}
body.deadline-on::before { color: rgba(255, 190, 175, 0.03); }
body.deadline-on::after {
  box-shadow:
    inset 0 0 26vmin rgba(10, 2, 4, 0.72),
    inset 0 0 0 1px rgba(255, 130, 115, 0.12);
}
body::before, body::after { transition: box-shadow calc(1.4s / var(--aspd)) ease, color calc(1.4s / var(--aspd)) ease; }

/* The shelf and the workbench are cut from one board, so they darken together. */
.shelf, .sundries { transition: background calc(1.4s / var(--aspd)) ease, box-shadow calc(1.4s / var(--aspd)) ease; }
body.deadline-on .shelf,
body.deadline-on .sundries {
  background: linear-gradient(180deg, #6b4130 0%, #4d2f22 18%, #2e1a14 100%);
  box-shadow:
    inset 0 2px 0 rgba(255, 190, 170, 0.16),
    inset 0 -6px 12px rgba(0, 0, 0, 0.45),
    0 10px 26px rgba(0, 0, 0, 0.55);
}

/* The status cards take the same cooling, the chapter label brass to ember. */
.status-card { transition: border-color calc(1.4s / var(--aspd)) ease, background calc(1.4s / var(--aspd)) ease; }
body.deadline-on .status-card {
  background: linear-gradient(170deg, #3a2028, #2b1620);
  border-color: rgba(255, 130, 115, 0.16);
}
body.deadline-on .chapter-label { color: #e9866f; }

body.deadline-on .logo { text-shadow: 0 2px 16px rgba(233, 122, 96, 0.35); }
body.deadline-on .logo-mark { color: #e08a70; }

@media (prefers-reduced-motion: reduce) {
  body, body::before, body::after, .shelf, .sundries, .status-card { transition: none; }
}

/* ─── Header ──────────────────────────────────────────────────────────────────── */
header {
  position: relative;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 26px 4px;
  max-width: 1120px;
  margin: 0 auto;
}

/* Small capitals, as a title page sets them — size down, tracking up. */
.logo {
  font-family: var(--serif);
  font-weight: 900;
  font-size: 23px;
  font-variant-caps: small-caps;
  letter-spacing: 1.6px;
  color: var(--parch);
  text-shadow: 0 2px 14px rgba(233, 178, 102, 0.25);
  white-space: nowrap;
}
.logo-mark { color: var(--brass); font-size: 20px; }

.dict-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--muted);
  margin-top: 6px;
}
.dict-dot--good { background: var(--good); box-shadow: 0 0 8px rgba(143, 223, 174, 0.7); }
.dict-dot--warn { background: var(--warn); box-shadow: 0 0 8px rgba(255, 193, 105, 0.7); }
.dict-dot--wait { background: var(--muted); animation: dictPulse calc(1.2s / var(--aspd)) ease infinite; }
@keyframes dictPulse { 50% { opacity: 0.3; } }

.header-spacer { flex: 1; }

.coin-purse {
  display: flex;
  align-items: center;
  gap: 7px;
  font-family: var(--serif);
  font-weight: 700;
  font-size: 21px;
  color: var(--brass-hi);
  background: rgba(35, 22, 14, 0.55);
  border: 1px solid rgba(217, 166, 75, 0.35);
  border-radius: 999px;
  padding: 5px 16px 5px 10px;
  box-shadow: inset 0 1px 0 rgba(255, 230, 170, 0.08), 0 4px 14px rgba(0, 0, 0, 0.35);
}

.coin {
  display: inline-block;
  width: 0.85em; height: 0.85em;
  border-radius: 50%;
  background: radial-gradient(circle at 34% 28%, #ffeBa8, #d8a937 58%, #96691a);
  box-shadow: inset 0 0 0 1.5px rgba(110, 72, 8, 0.55), 0 1px 2px rgba(0, 0, 0, 0.4);
  vertical-align: -0.08em;
}
.coin--lg { width: 1em; height: 1em; }

.btn-icon {
  background: rgba(35, 22, 14, 0.55);
  border: 1px solid rgba(243, 222, 184, 0.16);
  color: var(--parch);
  font-size: 17px;
  width: 38px; height: 38px;
  border-radius: 50%;
  cursor: pointer;
  transition: transform calc(0.12s / var(--aspd)), border-color calc(0.12s / var(--aspd));
}
.btn-icon:hover { border-color: var(--brass); transform: rotate(40deg); }

/* ─── Table layout ────────────────────────────────────────────────────────────── */
.table {
  position: relative;
  z-index: 5;
  max-width: 1120px;
  margin: 0 auto;
  padding: 6px 26px 40px;
  display: grid;
  gap: 14px;
  min-height: calc(100vh - 72px);
  align-content: center;
}

/* ─── Patron shelf · sundries workbench ───────────────────────────────────────── */
.shelf-row {
  display: grid;
  grid-template-columns: 1fr auto;   /* a third column appears with the ghosts */
  gap: 10px;
  align-items: stretch;
}
.shelf-row:has(.ghost-btn:not(.hidden)) { grid-template-columns: 1fr auto auto; }

.shelf {
  display: grid;
  /* Extra seats from the Colophon grow this — see renderShelf() */
  grid-template-columns: repeat(var(--seat-count, 5), 1fr);
  gap: 10px;
  padding: 10px 12px 12px;
  background: linear-gradient(180deg, var(--wood-light) 0%, var(--wood) 18%, var(--wood-dark) 100%);
  border-radius: var(--r-md);
  border: 1px solid rgba(0, 0, 0, 0.45);
  box-shadow:
    inset 0 2px 0 rgba(255, 220, 170, 0.18),
    inset 0 -6px 12px rgba(0, 0, 0, 0.35),
    0 10px 26px rgba(0, 0, 0, 0.45);
}

.patron {
  position: relative;
  min-height: 64px;
  border-radius: var(--r-sm);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: 7px 6px 6px;
  text-align: center;
  cursor: help;
}

/* An occupied seat can be dragged — seat order is the running order. */
.patron[data-patron] {
  cursor: grab;
  touch-action: none;               /* the gesture drags the card, not the page */
  user-select: none;
  -webkit-user-select: none;
}
.patron[data-patron]:active { cursor: grabbing; }
.patron--held {
  opacity: 0.3;
  filter: grayscale(0.5);
}

.patron--empty {
  border: 1.5px dashed rgba(243, 222, 184, 0.22);
  color: rgba(243, 222, 184, 0.3);
  font-size: 17px;
  gap: 2px;
}
.patron--empty::after {
  content: 'patron seat';
  font-size: 8px;
  font-weight: 800;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(243, 222, 184, 0.28);
}

/* One ring token per rarity, so the lit state below can add a glow layer. */
.patron--common   { --ring: rgba(138, 95, 29, 0.35); }
.patron--uncommon { --ring: rgba(47, 116, 92, 0.55); }
.patron--rare     { --ring: rgba(112, 64, 160, 0.55); }

/* ─── A distinguished patron (postnoms) ────────────────────────────────────────
   Foiled, because the ×Mult a postnom carries is nowhere in the desc. The sheen
   is a background layer, not an overlay, so it sits under the emoji and name. */
.patron.patron--postnom {
  --ring: rgba(214, 172, 74, 0.85);
  background:
    linear-gradient(115deg,
      rgba(255, 255, 255, 0) 30%,
      rgba(255, 246, 214, 0.55) 44%,
      rgba(255, 214, 140, 0.75) 50%,
      rgba(255, 246, 214, 0.55) 56%,
      rgba(255, 255, 255, 0) 70%),
    linear-gradient(170deg, #fdf3d4 0%, #ecd39a 100%);
  background-size: 260% 100%, 100% 100%;
  background-position: 120% 0, 0 0;
  animation: foilSheen calc(5.2s / var(--aspd)) ease-in-out infinite;
  box-shadow:
    inset 0 0 0 2px rgba(255, 252, 240, 0.85),
    inset 0 0 0 3.5px var(--ring),
    inset 0 0 0 5px rgba(180, 132, 40, 0.35),
    0 4px 14px rgba(0, 0, 0, 0.5),
    0 0 22px rgba(224, 180, 81, 0.5);
}
.patron.patron--postnom .patron-name,
.offer-patron.offer-patron--postnom .op-name { color: #7a5310; font-weight: 800; }

/* The letters as a gilt tab, in the one corner nothing else claims (livery pin
   top-left, ✕ top-right, laurel bottom-right). */
.patron-postnom {
  position: absolute;
  bottom: 3px; left: 4px;
  z-index: 3;
  padding: 1px 4px 1.5px;
  border-radius: 3px;
  font-family: var(--serif);
  font-size: 8px;
  font-weight: 900;
  letter-spacing: 0.6px;
  line-height: 1.25;
  color: #4a3208;
  background: linear-gradient(175deg, #ffe9ae 0%, #e0b451 100%);
  box-shadow: inset 0 0 0 1px rgba(122, 83, 19, 0.55), 0 1px 2px rgba(0, 0, 0, 0.3);
}
@keyframes foilSheen {
  0%, 100% { background-position: 130% 0, 0 0; }
  50%      { background-position: -30% 0, 0 0; }
}
@media (prefers-reduced-motion: reduce) {
  .patron.patron--postnom { animation: none; background-position: 50% 0, 0 0; }
}
/* The Market's calling card wears the same foil. */
.offer-patron.offer-patron--postnom {
  background:
    linear-gradient(115deg,
      rgba(255, 255, 255, 0) 30%,
      rgba(255, 246, 214, 0.5) 44%,
      rgba(255, 214, 140, 0.7) 50%,
      rgba(255, 246, 214, 0.5) 56%,
      rgba(255, 255, 255, 0) 70%),
    linear-gradient(170deg, #fbf1d6 0%, #f0dcae 100%);
  background-size: 260% 100%, 100% 100%;
  animation: foilSheen calc(5.2s / var(--aspd)) ease-in-out infinite;
  box-shadow:
    inset 0 0 0 2.5px rgba(214, 172, 74, 0.75),
    0 6px 18px rgba(0, 0, 0, 0.5),
    0 0 20px rgba(214, 172, 74, 0.28);
}
.op-postnom { color: #8a5c12; font-weight: 800; letter-spacing: 0.3px; }

/* A calling card for someone already dead is skinned further down, with the
   Market's own cards — it has to overrule the livery and body rules there. */

@media (prefers-reduced-motion: reduce) {
  .offer-patron.offer-patron--postnom { animation: none; }
}

.patron--common, .patron--uncommon, .patron--rare {
  background: linear-gradient(170deg, var(--parch) 0%, var(--parch-2) 100%);
  color: var(--parch-ink);
  border: 1px solid var(--brass-dark);
  box-shadow:
    inset 0 0 0 2px rgba(255, 248, 226, 0.6),
    inset 0 0 0 3.5px var(--ring),
    0 4px 10px rgba(0, 0, 0, 0.45);
  transition: filter calc(0.22s / var(--aspd)) ease,
              opacity calc(0.22s / var(--aspd)) ease,
              transform calc(0.22s / var(--aspd)) ease;
}

/* ─── A ghost (a patron The Ripper killed, or one hired dead) ──────────────────
   Not the parchment card drained but its negative: slate under moonlight,
   light-inked, edged in the dashed glow the ghost TILE wears, breathing.
   Only ever seen behind the graveyard door. */
.patron--ghost.patron--common,
.patron--ghost.patron--uncommon,
.patron--ghost.patron--rare {
  background:
    radial-gradient(125% 80% at 50% -12%, rgba(126, 178, 240, 0.22), transparent 72%),
    linear-gradient(170deg, #1a2338 0%, #0d1322 100%);
  color: #d3e3fb;
  border: 1px dashed rgba(178, 214, 255, 0.5);
  box-shadow:
    inset 0 0 0 2px rgba(150, 195, 250, 0.12),
    inset 0 0 20px rgba(96, 148, 214, 0.22),
    0 4px 16px rgba(0, 0, 0, 0.65),
    0 0 20px rgba(122, 172, 236, 0.25);
  animation: ghostBreath calc(3.6s / var(--aspd)) ease-in-out infinite;
}
.patron--ghost .patron-emoji {
  filter: grayscale(0.45) brightness(1.4) drop-shadow(0 0 8px rgba(150, 205, 255, 0.6));
  opacity: 0.92;
}
.patron--ghost .patron-name {
  color: #d3e3fb;
  text-shadow: 0 0 8px rgba(140, 195, 255, 0.5);
}
/* Dark red on near-black is a smudge; the ✕ goes spectral with the rest. */
.patron--ghost .patron-x {
  background: #1d2c46;
  border-color: rgba(150, 195, 250, 0.45);
  color: #cfe4ff;
}
.patron--ghost.patron--empty {
  border-color: rgba(150, 180, 230, 0.2);
  color: rgba(180, 205, 240, 0.3);
}
.patron--ghost.patron--empty::after { content: 'unhaunted'; color: rgba(180, 205, 240, 0.28); }
@keyframes ghostBreath {
  0%, 100% { opacity: 0.82; }
  50%      { opacity: 1; }
}

/* The knife going in: the victim's card flares white and drains. */
.patron--murdered {
  animation: murdered calc(0.9s / var(--aspd)) ease-out;
}
@keyframes murdered {
  0%   { filter: none; transform: none; }
  18%  { filter: brightness(2.4) saturate(0.2); transform: translateY(-4px) rotate(-1.5deg); }
  100% { filter: grayscale(1) brightness(0.6); transform: none; opacity: 0.35; }
}

/* ─── The graveyard door ───────────────────────────────────────────────────────
   Hidden by renderGhosts until there is something behind it. */
.ghost-btn {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  width: 52px;
  padding: 8px 4px;
  border-radius: var(--r-md);
  border: 1px solid rgba(0, 0, 0, 0.45);
  background: linear-gradient(180deg, #2b3550 0%, #171d2e 100%);
  color: #c3d3ef;
  cursor: pointer;
  box-shadow:
    inset 0 2px 0 rgba(170, 200, 245, 0.18),
    inset 0 -6px 12px rgba(0, 0, 0, 0.4),
    0 10px 26px rgba(0, 0, 0, 0.45);
  transition: transform calc(0.14s / var(--aspd)), box-shadow calc(0.16s / var(--aspd));
}
.ghost-btn:hover { transform: translateY(-2px); }
.ghost-btn-mark  { font-size: 20px; line-height: 1; filter: drop-shadow(0 0 6px rgba(150, 190, 255, 0.5)); }
.ghost-btn-count {
  font-family: var(--sans);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.6px;
  color: #9fb6da;
}

/* The same door in the Market's table heading — a chip, since it sits on text. */
.market-ghosts {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-left: auto;
  padding: 3px 10px 3px 7px;
  border-radius: 999px;
  border: 1px solid rgba(150, 180, 230, 0.4);
  background: linear-gradient(180deg, #2b3550 0%, #1b2236 100%);
  color: #c3d3ef;
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.4px;
  cursor: pointer;
  transition: transform calc(0.12s / var(--aspd)), border-color calc(0.14s / var(--aspd));
}
.market-ghosts:hover { transform: translateY(-1px); border-color: rgba(190, 215, 255, 0.7); }
.market-ghosts .ghost-btn-mark { font-size: 13px; }

/* The sheet sits above the Market, which is itself a modal at z-index 200 —
   the graveyard is the front-most thing in the room while it is open. */
#ghostModal { z-index: 210; }
/* The whole room goes dark, not just the slab in it — a parchment frame around
   a black graveyard reads as a hole cut in the page. */
.sheet.sheet--ghosts {          /* doubled: .sheet's parchment is set further down */
  max-width: 720px;
  background:
    radial-gradient(700px 320px at 50% -10%, rgba(120, 170, 235, 0.16), transparent 70%),
    linear-gradient(175deg, #1a2237 0%, #10162a 70%, #0a0f1e 100%);
  color: #d3e3fb;
  border-color: rgba(150, 195, 250, 0.35);
  box-shadow:
    inset 0 0 0 1px rgba(150, 195, 250, 0.1),
    inset 0 0 60px rgba(60, 100, 160, 0.18),
    0 36px 90px rgba(0, 0, 0, 0.8);
}
.sheet.sheet--ghosts h2 {
  color: #e4efff;
  text-shadow: 0 0 12px rgba(140, 195, 255, 0.45);
}
.sheet--ghosts .sheet-note { color: #9fb8d8; }
.sheet.sheet--ghosts .x { color: #9fb8d8; }
.sheet.sheet--ghosts .x:hover { background: rgba(140, 186, 240, 0.16); color: #e4efff; }
.sheet--ghosts .shelf--ghosts {
  gap: 8px;
  padding: 10px 12px 12px;
  /* Darker than anything standing on it — the cards are the only light in
     the room, and their glow needs somewhere to fall. */
  background:
    radial-gradient(90% 120% at 50% 0%, rgba(70, 110, 170, 0.14), transparent 70%),
    linear-gradient(180deg, #0a0e1a 0%, #04060d 100%);
  border-color: rgba(0, 0, 0, 0.7);
  box-shadow: inset 0 0 26px rgba(0, 0, 0, 0.8);
}
.ghost-tally {
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: #9fb6da;
  margin-left: 8px;
}

/* The seat's guild hint: a small enamel livery pin in the top-left corner. */
.patron[class*="patron--g-"]::before {
  content: '';
  position: absolute;
  top: 4px;
  left: 4px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, var(--livery-brt), var(--livery) 70%);
  box-shadow: 0 0 0 1px rgba(20, 12, 8, 0.5);
  z-index: 2;
}

/* A dual-livery seat wears a second pin beside the first. It rides the same
   pseudo-element as extra box-shadow dots — ::after is spoken for by
   --armed and --empty — so one badge carries both enamels. */
.patron[class*="patron--g2-"]::before {
  box-shadow:
    0 0 0 1px rgba(20, 12, 8, 0.5),
    9px 0 0 0 var(--livery2),
    9px 0 0 1px rgba(20, 12, 8, 0.5);
}

/* The tiles a tube has laid out, said three ways so they read in a rack of ten:
   they lift, they ring, and everything else steps back (.zone--offering). */
.tile--tube-offer {
  outline: 3px solid #ffd98c;
  outline-offset: 3px;
  filter: drop-shadow(0 0 10px rgba(255, 200, 105, 0.7));
  animation: tube-offer-breathe 1.15s ease-in-out infinite alternate;
}
@keyframes tube-offer-breathe {
  from { outline-color: rgba(255, 214, 140, 0.6);
         filter: drop-shadow(0 0 6px rgba(255, 196, 96, 0.45)); }
  to   { outline-color: #fff0c4;
         filter: drop-shadow(0 0 18px rgba(255, 205, 120, 0.95)); }
}

/* Only a tube lays out an offer — the ratchet, loupe and tongs take any tile, so
   their armed zones must dim nothing, hence a class of its own rather than
   .zone--painting. A picked tile is excluded from both rules. */
.zone--offering .tile--tube-offer:not(.tile--selected) {
  transform: translateY(-7px) scale(1.07);
  z-index: 4;
}
.zone--offering .tile:not(.tile--tube-offer):not(.tile--selected) {
  opacity: 0.38;
  filter: grayscale(0.75) brightness(0.7);
}

.patron-emoji { font-size: 23px; line-height: 1; filter: drop-shadow(0 1px 1px rgba(0,0,0,0.25)); }
.patron-name {
  font-family: var(--serif);
  font-weight: 700;
  font-size: 10.5px;
  letter-spacing: 0.2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

.patron-x {
  position: absolute;
  top: -7px; right: -7px;
  width: 19px; height: 19px;
  border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, 0.4);
  background: #7c2f28;
  color: #ffd9d2;
  font-size: 10px;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity calc(0.15s / var(--aspd)), transform calc(0.15s / var(--aspd));
}
.patron:hover .patron-x { opacity: 1; }
.patron-x:hover { transform: scale(1.15); }

/* A laurel a patron wears. Bottom-right because every other corner is spoken
   for: livery pin top-left, ✕ top-right, standing-to-gain badge outside it. */
.patron-laurel {
  position: absolute;
  bottom: 3px; right: 6px;
  display: flex;
  align-items: center;
  gap: 1px;
  font-size: 11px;
  line-height: 1;
  z-index: 3;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35));
}
/* The card's own ink, not gold — gold on cream is a number nobody can read. */
.patron-laurel b {
  font-size: 9.5px;
  font-weight: 900;
  color: #7a5313;
  filter: none;
}

/* ── The workbench: sundries bought at the Shop, spent mid-page ──────────────── */
.sundries {
  display: grid;
  /* Extra slots from the Colophon grow this — see renderSundries() */
  grid-template-columns: repeat(var(--slot-count, 2), 74px);
  gap: 10px;
  padding: 10px 12px 12px;
  background: linear-gradient(180deg, var(--wood-light) 0%, var(--wood) 18%, var(--wood-dark) 100%);
  border-radius: var(--r-md);
  border: 1px solid rgba(0, 0, 0, 0.45);
  box-shadow:
    inset 0 2px 0 rgba(255, 220, 170, 0.18),
    inset 0 -6px 12px rgba(0, 0, 0, 0.35),
    0 10px 26px rgba(0, 0, 0, 0.45);
}

.sundry {
  position: relative;
  min-height: 64px;
  border-radius: var(--r-sm);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 7px 5px 6px;
  font-family: var(--sans);
  transition: transform calc(0.12s / var(--aspd)), box-shadow calc(0.15s / var(--aspd));
}
.sundry--crimson { --paintcol: var(--crimson); }
.sundry--azure   { --paintcol: var(--azure); }
.sundry--jade    { --paintcol: var(--jade); }
.sundry--amber   { --paintcol: var(--amber); }

button.sundry, .sundry--card {
  background: linear-gradient(170deg, var(--parch) 0%, var(--parch-2) 100%);
  color: var(--parch-ink);
  border: 1px solid var(--brass-dark);
  box-shadow:
    inset 0 0 0 2px rgba(255, 248, 226, 0.6),
    inset 0 0 0 3.5px rgba(138, 95, 29, 0.35),
    0 4px 10px rgba(0, 0, 0, 0.45);
  cursor: pointer;
}
button.sundry:hover { transform: translateY(-2px); }

.sundry--armed {
  transform: translateY(-3px);
  outline: 2.5px solid var(--paintcol, var(--warn));
  outline-offset: 2px;
  box-shadow:
    inset 0 0 0 2px rgba(255, 248, 226, 0.6),
    0 0 18px var(--paintcol, var(--warn)),
    0 6px 12px rgba(0, 0, 0, 0.45);
}
.sundry--armed:hover { transform: translateY(-3px); }

/* The workbench's answer to the patron card's ✕: hidden until hover, by design.
   Touch never hovers, so the slot's long-press popover carries the same act. */
.sundry-x {
  position: absolute;
  top: -6px; right: -6px;
  width: 18px; height: 18px;
  border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, 0.4);
  background: #7c2f28;
  color: #ffd9d2;
  font-family: var(--sans);
  font-size: 9px;
  line-height: 16px;
  text-align: center;
  cursor: pointer;
  opacity: 0;
  z-index: 4;
  transition: opacity calc(0.15s / var(--aspd)), transform calc(0.15s / var(--aspd));
}
.sundry:hover .sundry-x { opacity: 1; }
.sundry-x:hover { transform: scale(1.15); }

/* ── One tap left ─────────────────────────────────────────────────────────────
   An armed tool with its targets picked waits on a tap *on the tool*, so it
   breathes in --paintcol above the dimmed table (.table--tool-ready). */
.sundry--ready {
  position: relative;
  z-index: 3;
  animation: tool-ready calc(1.15s / var(--aspd)) ease-in-out infinite;
}
@keyframes tool-ready {
  0%, 100% {
    transform: translateY(-3px) scale(1);
    box-shadow:
      inset 0 0 0 2px rgba(255, 248, 226, 0.6),
      0 0 18px var(--paintcol, var(--warn)),
      0 6px 12px rgba(0, 0, 0, 0.45);
  }
  50% {
    transform: translateY(-5px) scale(1.05);
    box-shadow:
      inset 0 0 0 2.5px rgba(255, 248, 226, 0.95),
      0 0 32px 7px var(--paintcol, var(--warn)),
      0 9px 18px rgba(0, 0, 0, 0.5);
  }
}
/* The label swaps to the instruction ("Paint it"), so give it the weight of one. */
.sundry--ready .sundry-name {
  color: #fff8e2;
  text-shadow: 0 0 8px var(--paintcol, var(--warn));
}

.sundry--empty {
  border: 1.5px dashed rgba(243, 222, 184, 0.22);
  color: rgba(243, 222, 184, 0.3);
  font-size: 15px;
  gap: 2px;
}
.sundry--empty::after {
  content: 'sundry';
  font-size: 8px;
  font-weight: 800;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(243, 222, 184, 0.28);
}

.sundry-name {
  font-family: var(--serif);
  font-weight: 700;
  font-size: 10.5px;
  letter-spacing: 0.2px;
  white-space: nowrap;
}

/* A squeeze-tube of paint: crimped metal shoulder up top, colour below */
.paint-tube {
  width: 17px; height: 26px;
  border-radius: 3px 3px 7px 7px;
  border: 1.5px solid rgba(40, 25, 10, 0.55);
  position: relative;
  margin-top: 6px;
  box-shadow:
    inset -3px -4px 6px rgba(0, 0, 0, 0.22),
    inset 2px 2px 3px rgba(255, 255, 255, 0.38);
}
.paint-tube::before {
  content: '';
  position: absolute;
  top: -7px; left: 50%;
  transform: translateX(-50%);
  width: 9px; height: 6px;
  border-radius: 2px 2px 0 0;
  background: linear-gradient(180deg, #cfc8b8, #a49d8d);
  border: 1.5px solid rgba(40, 25, 10, 0.5);
}
.paint-tube--crimson { background: var(--crimson); }
.paint-tube--azure   { background: var(--azure); }
.paint-tube--jade    { background: var(--jade); }
.paint-tube--amber   { background: var(--amber); }

/* The parcel: one shape in five papers — the wrapped tile's brown and the four
   registers' packages, the SAME MARK recoloured. Each wrapping overrides only
   these five tokens. */
.wrapped-mark {
  --paper-a: #a8834e; --paper-b: #8f6b3a; --paper-c: #74542b;
  --twine-a: #f0e2c2; --twine-b: #cfbb92;
  position: relative;
  display: block;
  width: 22px; height: 22px;
  border-radius: 3px;
  background:
    linear-gradient(150deg, var(--paper-a) 0%, var(--paper-b) 46%, var(--paper-c) 100%);
  border: 1.5px solid rgba(34, 20, 6, 0.6);
  box-shadow: inset 0 2px 3px rgba(255, 236, 200, 0.3), inset 0 -4px 6px rgba(0, 0, 0, 0.34);
  overflow: hidden;
}
/* Pale twine on dark paper — a dark cross on pale reads as a window pane. */
.wrapped-mark::before,
.wrapped-mark::after {
  content: '';
  position: absolute;
  background: linear-gradient(90deg, var(--twine-a), var(--twine-b));
  box-shadow: 0 1px 1.5px rgba(30, 18, 4, 0.55);
}
.wrapped-mark::before { left: -1px; right: -1px; top: 50%; height: 2.5px; transform: translateY(-50%); }
.wrapped-mark::after  { top: -1px; bottom: -1px; left: 50%; width: 2.5px; transform: translateX(-50%); }

.sundry--wrapped .wrapped-mark { margin-top: 3px; }
.wrapped-mark--offer { width: 26px; height: 26px; margin-top: 4px; }

/* ── The registers' packages: the same parcel, four papers ────────────────────
   The plain brown wrapper is deliberately NOT recoloured. */
.sundry--pkg-romantic .wrapped-mark {
  --paper-a: #e58aa6; --paper-b: #c9577a; --paper-c: #a53d5e;
  --twine-a: #fff0f4; --twine-b: #f3c3d2;
}
.sundry--pkg-spooky .wrapped-mark {
  --paper-a: #4a5b58; --paper-b: #33413f; --paper-c: #202a29;
  --twine-a: #e6e9df; --twine-b: #b3b9a8;
}
.sundry--pkg-cute .wrapped-mark {
  --paper-a: #8fd8e8; --paper-b: #6ab3d8; --paper-c: #a98ede;
  --twine-a: #fff6c8; --twine-b: #ffd98a;
}
/* rude keeps the default brown — see above. The slot says only "Parcel": the
   paper already says which of the four it is, and the tooltip names it. */

/* ── The applicators ──────────────────────────────────────────────────────────
   A tube's slot with a metal in it, so the armed glow takes the material. */
.sundry--app-rainbow { --paintcol: #ffb066; }
.sundry--app-cursed  { --paintcol: var(--crimson); }
/* An armed slot has no paint of its own to glow with — it takes the paper's */
.sundry--wrapped.sundry--armed { --paintcol: #cbab7e; }

/* The reshuffle sundry: no colour of its own, so a brass badge, not a tube. */
.sundry--reshuffle { cursor: help; }
.sundry-shuffle {
  display: grid;
  place-items: center;
  width: 24px; height: 24px;
  margin-top: 3px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, var(--brass-hi), var(--brass) 70%, var(--brass-dark));
  color: #3a2606;
  font-size: 13px;
  font-weight: 900;
  box-shadow: inset 0 -3px 5px rgba(120, 80, 10, 0.35), inset 0 2px 3px rgba(255, 255, 255, 0.5);
}
.sundry-shuffle--offer { margin-top: 5px; width: 26px; height: 26px; }

/* The ⇅ mark is the ratchet's market face; on the workbench it wears arrows. */
/* Toolbox glyphs: no styled housing, the emoji is the housing. */
.sundry-glyph {
  font-size: 21px;
  line-height: 1;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.3));
}
.sundry-glyph--offer { font-size: 25px; margin-top: 4px; }

.ratchet-mark {
  display: grid;
  place-items: center;
  width: 24px; height: 24px;
  margin-top: 3px;
  border-radius: 5px;
  background: linear-gradient(160deg, #d8dde3, #97a1ac 55%, #6d7681);
  color: #2b3138;
  font-size: 14px;
  font-weight: 900;
  box-shadow: inset 0 -3px 5px rgba(40, 50, 60, 0.4), inset 0 2px 3px rgba(255, 255, 255, 0.7);
}
.offer-ratchet .ratchet-mark { margin-top: 5px; width: 26px; height: 26px; }

.ratchet-arrows { display: flex; gap: 4px; margin-top: 3px; }
.ratchet-arrow {
  display: grid;
  place-items: center;
  width: 24px; height: 24px;
  border-radius: 4px;
  background: linear-gradient(160deg, #d8dde3, #97a1ac 60%, #6d7681);
  color: #21262c;
  font-size: 12px;
  font-weight: 900;
  cursor: pointer;
  box-shadow: inset 0 -2px 4px rgba(40, 50, 60, 0.4), inset 0 1px 2px rgba(255, 255, 255, 0.7);
  transition: transform calc(0.1s / var(--aspd)), filter calc(0.1s / var(--aspd)),
              opacity calc(0.1s / var(--aspd));
  opacity: 0.45;
}
/* Both arrows stay legible — the dim one is an offer, not a disabled control. */
.ratchet-arrow--on {
  opacity: 1;
  box-shadow: inset 0 -2px 4px rgba(40, 50, 60, 0.4), inset 0 1px 2px rgba(255, 255, 255, 0.7),
              0 0 0 1.5px var(--brass-hi);
}
.ratchet-arrow:hover { transform: translateY(-2px); filter: brightness(1.15); opacity: 1; }
.sundry--ratchet.sundry--armed { --paintcol: var(--steel); }

/* An armed tube tints both zones and marks picked tiles with a brush. */
.zone--painting {
  outline: 2.5px dashed var(--paintcol, var(--warn));
  outline-offset: 3px;
}
.zone--painting .tile--selected {
  box-shadow:
    inset 0 1.5px 0 rgba(255, 255, 255, 0.85),
    0 5px 0 -1px #b3955e,
    0 0 0 2.5px var(--paintcol, var(--warn)),
    0 0 18px var(--paintcol, var(--warn)),
    0 12px 20px rgba(0, 0, 0, 0.5);
}
.zone--painting .tile--selected::after {
  content: '🖌';
  font-size: 13px;
}
/* An armed ratchet picks the same way, so the picked letter wears the tool. */
.zone--stepping .tile--selected::after { content: '⇅'; }

/* ── The table, waiting on the tool ───────────────────────────────────────────
   Everything the gesture no longer involves steps back. Dimming is per-element
   rather than on the zones: a filter on a zone would take the picked tile too. */
.table--tool-ready #shelf,
.table--tool-ready .status-row,
.table--tool-ready .boss-bar,
.table--tool-ready .readout,
.table--tool-ready .pouch,
.table--tool-ready .action-row,
.table--tool-ready .tile:not(.tile--selected) {
  filter: saturate(0.3) brightness(0.55);
  opacity: 0.75;
}
/* Only the ones with no transition of their own get one here — .tile, .pouch
   and .readout already declare theirs, and a second `transition` would drop
   the properties they were animating. */
#shelf, .status-row, .boss-bar, .action-row {
  transition: filter calc(0.22s / var(--aspd)) ease, opacity calc(0.22s / var(--aspd)) ease;
}

/* ── Patrons with something to add ────────────────────────────────────────────
   Every patron the word already satisfies wakes — lift, glow, gold-leaf sweep,
   and a badge of what it stands to contribute. The rest dim out of the way. */

.shelf--live .patron:not(.patron--armed) {
  filter: saturate(0.5) brightness(0.7);
  opacity: 0.72;
}

.patron--armed {
  z-index: 2;
  border-color: var(--brass-hi);
  transform: translateY(-3px);
  animation: patronLit calc(2.2s / var(--aspd)) ease-in-out infinite;
}
@keyframes patronLit {
  0%, 100% {
    box-shadow:
      inset 0 0 0 2px rgba(255, 248, 226, 0.75),
      inset 0 0 0 3.5px var(--ring),
      0 6px 14px rgba(0, 0, 0, 0.5),
      0 0 14px 1px rgba(246, 214, 138, 0.4);
  }
  50% {
    box-shadow:
      inset 0 0 0 2px rgba(255, 248, 226, 0.95),
      inset 0 0 0 3.5px var(--ring),
      0 6px 14px rgba(0, 0, 0, 0.5),
      0 0 26px 4px rgba(246, 214, 138, 0.75);
  }
}

/* Gold leaf catching the light. Clipped by the pseudo-element's own box, so
   the card keeps its overflow and the ✕ still hangs outside the corner. */
.patron--armed::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(105deg,
    rgba(255, 255, 255, 0) 38%,
    rgba(255, 253, 240, 0.72) 50%,
    rgba(255, 255, 255, 0) 62%);
  background-size: 260% 100%;
  animation: patronSheen calc(2.6s / var(--aspd)) ease-in-out infinite;
}
@keyframes patronSheen {
  0%        { background-position: 165% 0; opacity: 0; }
  18%, 55%  { opacity: 0.9; }
  100%      { background-position: -65% 0; opacity: 0; }
}
/* Keep the face and name above the sweep */
.patron-emoji, .patron-name { position: relative; z-index: 1; }
.patron-x { z-index: 3; }

/* Waking up: one flourish, settling into the standing lift */
.patron--just-armed {
  animation: patronLit calc(2.2s / var(--aspd)) ease-in-out infinite,
             patronRise calc(0.42s / var(--aspd)) ease-out;
}
@keyframes patronRise {
  0%   { transform: translateY(4px)  scale(0.94); }
  60%  { transform: translateY(-6px) scale(1.06); }
  100% { transform: translateY(-3px) scale(1); }
}

/* What it stands to add — the readout's own language for Points and Mult */
.patron-take {
  position: absolute;
  top: -8px; left: -7px;
  z-index: 4;
  padding: 1px 5px;
  border-radius: 999px;
  font-family: var(--sans);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.3px;
  white-space: nowrap;
  color: #fff6e2;
  border: 1px solid rgba(255, 248, 226, 0.5);
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
}
.patron-take--points { background: linear-gradient(180deg, #2f5590, var(--inkblue-deep)); }
.patron-take--mult   { background: linear-gradient(180deg, #8e3a32, var(--press-deep)); }
.patron-take--coins  { background: linear-gradient(180deg, #b8862c, #7a5313); }
/* A brush badge wears the colour it is about to lay (The Illuminator). */
.patron-take--paint-amber   { background: linear-gradient(180deg, #b8862c, #7a5313); }
.patron-take--paint-jade    { background: linear-gradient(180deg, #2f7a56, #14452f); }
.patron-take--paint-crimson { background: linear-gradient(180deg, #a53f33, #5e1d15); }
.patron-take--paint-azure   { background: linear-gradient(180deg, #2f5590, var(--inkblue-deep)); }

.patron--just-armed .patron-take {
  animation: takePop calc(0.42s / var(--aspd)) cubic-bezier(0.2, 1.4, 0.4, 1);
}
@keyframes takePop {
  0%   { transform: scale(0)    rotate(-14deg); }
  65%  { transform: scale(1.22) rotate(4deg); }
  100% { transform: scale(1)    rotate(0); }
}

/* The number moved while you kept composing */
.patron-take--bump { animation: takeBump calc(0.36s / var(--aspd)) ease; }
@keyframes takeBump {
  0%   { transform: scale(1); }
  45%  { transform: scale(1.32); }
  100% { transform: scale(1); }
}

/* ── Firing: the promise paid, during the print cinematic ────────────────── */
.patron--firing {
  animation: patronFire calc(0.52s / var(--aspd)) ease;
}
@keyframes patronFire {
  0%   { transform: translateY(-3px) scale(1); }
  35%  { transform: translateY(-11px) scale(1.14) rotate(-2.5deg);
         box-shadow:
           inset 0 0 0 2px rgba(255, 248, 226, 1),
           0 0 0 5px rgba(246, 214, 138, 0.6),
           0 0 34px 6px rgba(246, 214, 138, 0.85),
           0 12px 24px rgba(0, 0, 0, 0.5); }
  100% { transform: translateY(-3px) scale(1); }
}

/* ─── Status row ──────────────────────────────────────────────────────────────── */
.status-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 12px;
  align-items: stretch;
}

.status-card {
  background: linear-gradient(170deg, var(--leather-2), var(--leather));
  border: 1px solid rgba(243, 222, 184, 0.1);
  border-radius: var(--r-md);
  padding: 10px 16px;
  box-shadow: inset 0 1px 0 rgba(255, 235, 200, 0.06), 0 6px 18px rgba(0, 0, 0, 0.35);
}

.chapter-card { display: flex; flex-direction: column; justify-content: center; gap: 1px; min-width: 168px; }
.chapter-label {
  font-size: 10.5px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 1.4px;
  color: var(--brass);
}
.chapter-name {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 500;
  font-size: 15px;
  color: var(--parch);
}
.page-label { font-size: 11.5px; color: var(--muted); font-weight: 600; }
.page-of { opacity: 0.65; font-weight: 400; }

.deadline-tag {
  display: inline-block;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: #ffb3a6;
  background: rgba(124, 47, 40, 0.55);
  border: 1px solid rgba(255, 130, 115, 0.45);
  padding: 2px 8px;
  border-radius: 999px;
}

.quota-card { display: flex; flex-direction: column; justify-content: center; gap: 7px; }
.quota-card--deadline { border-color: rgba(255, 130, 115, 0.35); }
.quota-top { display: flex; align-items: baseline; justify-content: space-between; }
.quota-word {
  font-size: 10.5px; font-weight: 800;
  text-transform: uppercase; letter-spacing: 1.4px;
  color: var(--muted);
}
.quota-nums { font-family: var(--serif); font-size: 19px; color: var(--parch); font-variant-numeric: tabular-nums; }
.quota-nums b { color: var(--inkblue); font-weight: 700; }
.quota-sep { opacity: 0.4; margin: 0 3px; }

.quota-bar {
  height: 9px;
  border-radius: 5px;
  background: rgba(8, 4, 10, 0.6);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.6);
  overflow: hidden;
}
.quota-fill {
  height: 100%;
  width: 0%;
  border-radius: 5px;
  background: linear-gradient(90deg, #4f86c9, #8db8ff);
  box-shadow: 0 0 10px rgba(141, 184, 255, 0.5);
  transition: width calc(0.45s / var(--aspd)) cubic-bezier(.2,.7,.3,1), background calc(0.45s / var(--aspd));
}
.quota-fill--done {
  background: linear-gradient(90deg, #d9a64b, #f6d68a);
  box-shadow: 0 0 12px rgba(246, 214, 138, 0.65);
}

.pips-card { display: flex; flex-direction: column; justify-content: center; gap: 8px; min-width: 150px; }
.pips-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.pips-label {
  font-size: 10px; font-weight: 800;
  text-transform: uppercase; letter-spacing: 1.1px;
  color: var(--muted);
}
.pips { display: flex; gap: 5px; }
.pip {
  width: 11px; height: 11px;
  border-radius: 50%;
  border: 1.5px solid rgba(243, 222, 184, 0.25);
  background: transparent;
  transition: background calc(0.25s / var(--aspd)), box-shadow calc(0.25s / var(--aspd));
}
.pip--word.pip--on { background: var(--brass); border-color: var(--brass-hi); box-shadow: 0 0 7px rgba(217, 166, 75, 0.6); }
.pip--swap.pip--on { background: var(--azure); border-color: #8ec6ff; box-shadow: 0 0 7px rgba(79, 134, 201, 0.6); }

/* ─── The editor's bar (Deadline pages) ──────────────────────────────────────
   Wears the Deadline's crimson livery, and flares when the verdict lands. */
.boss-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 14px;
  border-radius: var(--r-md);
  background: linear-gradient(170deg, rgba(90, 32, 28, 0.5), rgba(52, 16, 16, 0.55));
  border: 1px solid rgba(255, 130, 115, 0.35);
  box-shadow: inset 0 1px 0 rgba(255, 200, 180, 0.07), 0 6px 18px rgba(0, 0, 0, 0.35);
}
.boss-bar--warn { border-color: rgba(255, 130, 115, 0.7); box-shadow: inset 0 1px 0 rgba(255, 200, 180, 0.07), 0 0 14px rgba(201, 60, 45, 0.35); }
.boss-emoji { font-size: 19px; line-height: 1; filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.5)); }
.boss-name {
  font-size: 10.5px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 1.4px;
  color: #ffb3a6;
  white-space: nowrap;
}
.boss-rule {
  flex: 1;
  min-width: 0;
  font-family: var(--serif);
  font-style: italic;
  font-size: 13.5px;
  line-height: 1.25;
  color: var(--parch);
}
.boss-verdict {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.6px;
  padding: 3px 9px;
  border-radius: 999px;
  white-space: nowrap;
}
.boss-verdict--ok  { color: #90e8ba; background: rgba(28, 78, 52, 0.55); border: 1px solid rgba(144, 232, 186, 0.4); }
.boss-verdict--bad { color: #ffb3a6; background: rgba(124, 47, 40, 0.65); border: 1px solid rgba(255, 130, 115, 0.55); }

/* The page's record: one mark per word printed, oldest first. */
.boss-trail { display: flex; gap: 3px; align-items: center; flex-shrink: 0; }
.boss-mark {
  width: 17px; height: 17px;
  display: grid; place-items: center;
  border-radius: 50%;
  font-size: 9.5px; font-weight: 800;
  border: 1px solid rgba(243, 222, 184, 0.25);
}
.boss-mark--passed { color: #90e8ba; background: rgba(28, 78, 52, 0.5);  border-color: rgba(144, 232, 186, 0.45); }
.boss-mark--spiked { color: #ffb3a6; background: rgba(124, 47, 40, 0.6); border-color: rgba(255, 130, 115, 0.6); }

.boss-bar--firing  { animation: bossFire calc(0.52s / var(--aspd)) ease; }
.boss-bar--spiking { animation: bossSpike calc(0.52s / var(--aspd)) ease; }
@keyframes bossFire {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.015); box-shadow: 0 0 0 3px rgba(246, 214, 138, 0.4), 0 0 24px rgba(246, 214, 138, 0.5); }
  100% { transform: scale(1); }
}
@keyframes bossSpike {
  0%   { transform: translateX(0); }
  20%  { transform: translateX(-4px); }
  45%  { transform: translateX(4px); box-shadow: 0 0 0 3px rgba(201, 60, 45, 0.55), 0 0 26px rgba(201, 60, 45, 0.65); }
  70%  { transform: translateX(-2px); }
  100% { transform: translateX(0); }
}

/* ─── Readout ─────────────────────────────────────────────────────────────────── */
.readout {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 8px 18px;
  height: 76px;                    /* fixed — the row must never change the page height */
  border-radius: var(--r-md);
  background: rgba(12, 6, 14, 0.45);
  border: 1px solid rgba(243, 222, 184, 0.08);
  transition: opacity calc(0.3s / var(--aspd)),
              filter calc(0.22s / var(--aspd)) ease;
}
.readout--idle { opacity: 0.45; }
.readout--slam { animation: roSlam calc(0.5s / var(--aspd)) ease; }
@keyframes roSlam {
  0% { transform: scale(1); }
  30% { transform: scale(1.045); }
  100% { transform: scale(1); }
}

.slab {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 96px;
  padding: 5px 16px 7px;
  border-radius: 10px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 4px 10px rgba(0, 0, 0, 0.4);
}
.slab--points { background: linear-gradient(180deg, #24416b, var(--inkblue-deep)); }

.slab-label {
  font-size: 9.5px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 1.6px;
  opacity: 0.75;
}
.slab--points .slab-label { color: var(--inkblue); }

.slab-num {
  font-family: var(--serif);
  font-weight: 900;
  font-size: 27px;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
}
.slab--points .slab-num { color: #cfe2ff; text-shadow: 0 0 14px rgba(141, 184, 255, 0.5); }

.ro-op {
  font-family: var(--serif);
  font-size: 21px;
  color: var(--muted);
}

.ro-total-wrap { min-width: 120px; text-align: center; }

/* Colour multiplier chips — one per colour, dim while ×1. Always all five, so
   the row's width never changes as multipliers come and go. */
.ro-chips {
  display: flex;
  justify-content: center;
  gap: 6px;
}
.chip {
  min-width: 40px;
  padding: 5px 8px 6px;
  border-radius: 999px;
  text-align: center;
  font-weight: 800;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  color: #0e0712;
  opacity: 0.3;
  transition: opacity calc(0.2s / var(--aspd)), transform calc(0.2s / var(--aspd)), box-shadow calc(0.2s / var(--aspd));
}
.chip--on { opacity: 1; }
.chip--pop { animation: chipPop calc(0.42s / var(--aspd)) cubic-bezier(.3,1.5,.5,1); }
@keyframes chipPop {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.35); }
  100% { transform: scale(1); }
}
/* The measure — the word's length, in steel so it reads apart from the paints. */
.chip--length  { background: #55677a;        color: #eaf2fa; }
.chip--crimson { background: var(--crimson); color: #fff0ec; }
.chip--azure   { background: var(--azure);   color: #eef6ff; }
.chip--jade    { background: var(--jade);    color: #ecfff5; }
.chip--amber   { background: var(--amber);   color: #fff8ea; }
.chip--purple  { background: #8a5fb0;        color: #f6ecff; }
.chip--cursed  { background: #6d1a15;        color: #ffd9cf; }
/* The cursed chip appears only when a cursed tile is in the word. */
.chip--cursed:not(.chip--on) { display: none; }
.chip--on.chip--length  { box-shadow: 0 0 10px rgba(125, 143, 160, 0.65); }
.chip--on.chip--crimson { box-shadow: 0 0 10px rgba(201, 79, 67, 0.6); }
.chip--on.chip--azure   { box-shadow: 0 0 10px rgba(79, 134, 201, 0.6); }
.chip--on.chip--jade    { box-shadow: 0 0 10px rgba(63, 163, 113, 0.6); }
.chip--on.chip--amber   { box-shadow: 0 0 10px rgba(210, 154, 58, 0.6); }
.chip--on.chip--purple  { box-shadow: 0 0 10px rgba(138, 95, 176, 0.6); }
.chip--on.chip--cursed  { box-shadow: 0 0 12px rgba(201, 60, 45, 0.75); }
.ro-total {
  font-family: var(--serif);
  font-weight: 900;
  font-size: 42px;
  line-height: 1;
  color: var(--brass-hi);
  text-shadow: 0 0 22px rgba(246, 214, 138, 0.45), 0 2px 4px rgba(0, 0, 0, 0.6);
  font-variant-numeric: tabular-nums;
}

.hidden { display: none !important; }

/* ─── Word groove ─────────────────────────────────────────────────────────────── */
/* A full row is reserved, so the groove doesn't grow when the first tile lands. */
.word-groove {
  position: relative;
  min-height: 98px;
  border-radius: var(--r-lg);
  padding: 12px 14px;
  display: flex;
  flex-wrap: wrap;
  align-content: center;
  justify-content: center;
  gap: 9px;
  background: linear-gradient(180deg, #190e16, #241420);
  border: 1px solid rgba(217, 166, 75, 0.28);
  box-shadow:
    inset 0 4px 16px rgba(0, 0, 0, 0.65),
    inset 0 0 0 1px rgba(0, 0, 0, 0.4),
    0 1px 0 rgba(255, 235, 200, 0.05);
  transition: border-color calc(0.25s / var(--aspd)), box-shadow calc(0.25s / var(--aspd));
}
.word-groove:has(.tile) {
  border-color: rgba(217, 166, 75, 0.55);
  box-shadow:
    inset 0 4px 16px rgba(0, 0, 0, 0.65),
    0 0 24px rgba(217, 166, 75, 0.08);
}
.word-groove[data-placeholder]:not([data-placeholder=''])::after {
  content: attr(data-placeholder);
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-family: var(--serif);
  font-style: italic;
  font-size: 16px;
  color: rgba(243, 222, 184, 0.18);
  pointer-events: none;
}
.word-groove--reject {
  animation: grooveReject calc(0.42s / var(--aspd)) ease;
}
@keyframes grooveReject {
  0%, 100% { transform: translateX(0); border-color: rgba(217, 166, 75, 0.28); }
  20% { transform: translateX(-7px); border-color: rgba(255, 112, 102, 0.8); }
  45% { transform: translateX(6px);  border-color: rgba(255, 112, 102, 0.8); }
  70% { transform: translateX(-4px); border-color: rgba(255, 112, 102, 0.6); }
}

/* ─── Rack row ────────────────────────────────────────────────────────────────── */
.rack-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 12px;
  align-items: center;
}

/* Likewise the rack: --rack-reserve is measured in renderRack() from the whole
   hand (rack AND word), and the floor here holds an empty rack open. */
.rack {
  min-height: max(100px, var(--rack-reserve, 0px));
  border-radius: var(--r-md);
  padding: 13px 14px 15px;
  display: flex;
  flex-wrap: wrap;
  align-content: center;
  justify-content: center;
  gap: 9px;
  background: linear-gradient(180deg, var(--wood-light) 0%, var(--wood) 22%, var(--wood-dark) 100%);
  border: 1px solid rgba(0, 0, 0, 0.5);
  box-shadow:
    inset 0 2px 0 rgba(255, 220, 170, 0.16),
    inset 0 -8px 14px rgba(0, 0, 0, 0.4),
    0 10px 26px rgba(0, 0, 0, 0.45);
}

/* ─── Pouch & tray buttons ────────────────────────────────────────────────────── */
.pouch {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px 6px;
  border-radius: var(--r-md);
  transition: transform calc(0.15s / var(--aspd)),
              filter calc(0.22s / var(--aspd)) ease,
              opacity calc(0.22s / var(--aspd)) ease;
}
.pouch:hover { transform: translateY(-3px); }
.pouch--bump { animation: pouchBump calc(0.3s / var(--aspd)) ease; }
@keyframes pouchBump {
  0%, 100% { transform: scale(1); }
  40% { transform: scale(1.12) rotate(-3deg); }
}

.pouch-svg { width: 58px; height: 60px; overflow: visible; }
.pouch-body {
  fill: url(#) #6b4630;
  fill: #6b4630;
  stroke: #2e1c10;
  stroke-width: 2;
}
.pouch-cinch { fill: none; stroke: #2e1c10; stroke-width: 2; stroke-linecap: round; }
.tray-body { fill: #56402a; stroke: #241708; stroke-width: 2; }
.tray-slats { fill: none; stroke: #241708; stroke-width: 1.6; opacity: 0.6; }
.pouch--empty .pouch-svg { opacity: 0.55; filter: grayscale(0.4); }

.pouch-icon {
  position: relative;
  display: inline-flex;
  line-height: 0;
}

.pouch-count {
  position: absolute;
  top: -3px; right: -8px;
  min-width: 23px;
  padding: 2px 5px;
  border-radius: 999px;
  background: var(--brass);
  color: #3a2606;
  font-weight: 800;
  font-size: 12px;
  line-height: 1.25;          /* .pouch-icon zeroes it for the SVG — restore it here */
  border: 1.5px solid rgba(58, 38, 6, 0.5);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
  font-variant-numeric: tabular-nums;
  z-index: 2;
}
.pouch-label {
  font-size: 9.5px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 1.4px;
  color: var(--muted);
}

/* ─── Tiles ───────────────────────────────────────────────────────────────────── */
.tile {
  position: relative;
  width: var(--tile-w);
  height: var(--tile-h);
  border-radius: 9px;
  background: linear-gradient(168deg, var(--ivory) 0%, #f0e4c4 55%, var(--ivory-2) 100%);
  color: var(--tile-ink);
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  touch-action: none;               /* tiles drag; the page doesn't scroll from them */
  flex-shrink: 0;
  display: grid;
  place-items: center;
  border: 1px solid rgba(86, 60, 24, 0.5);
  /* A trim is a ring composed into every state's shadow via --trim-ring, so
     hovering or selecting a tile can never wipe the metal off it. */
  --trim-ring: 0 0 rgba(0, 0, 0, 0);
  --tile-lift: #b3955e;
  --tile-shadow:
    var(--trim-ring),
    inset 0 1.5px 0 rgba(255, 255, 255, 0.85),
    inset 0 -2px 3px rgba(120, 90, 40, 0.25),
    0 5px 0 -1px var(--tile-lift),
    0 9px 16px rgba(0, 0, 0, 0.5);
  box-shadow: var(--tile-shadow);
  transition:
    transform calc(0.12s / var(--aspd)) ease,
    box-shadow calc(0.15s / var(--aspd)) ease,
    filter calc(0.12s / var(--aspd)) ease;
}
@media (hover: hover) {
  .tile:hover:not(.tile--held):not(.tile--mini) {
    transform: translateY(-4px) rotate(-1deg) scale(1.05);
    z-index: 3;
    box-shadow:
      var(--trim-ring),
      inset 0 1.5px 0 rgba(255, 255, 255, 0.85),
      inset 0 -2px 3px rgba(120, 90, 40, 0.25),
      0 7px 0 -1px var(--tile-lift),
      0 16px 26px rgba(0, 0, 0, 0.55);
  }
  /* Inside an offer card the card reacts, not the tile. (Needs to out-specify
     the lift rule above, hence the same two :not()s.) */
  .offer-tile .tile:hover:not(.tile--held):not(.tile--mini) {
    transform: none;
    box-shadow: var(--tile-shadow);
  }
}
.tile:active { cursor: grabbing; }

/* the original tile while its drag ghost is being carried */
.tile--held {
  opacity: 0.3;
  filter: grayscale(0.5);
}

.drag-ghost {
  z-index: 330;
  transform: scale(1.1) rotate(2.5deg);
  filter: drop-shadow(0 18px 28px rgba(0, 0, 0, 0.7));
  transition: none;
}

.tile--ghost { visibility: hidden; }

.tile--selected {
  transform: translateY(-7px) rotate(-1.5deg);
  box-shadow:
    inset 0 1.5px 0 rgba(255, 255, 255, 0.85),
    0 5px 0 -1px #b3955e,
    0 0 0 2.5px var(--warn),
    0 0 18px rgba(255, 193, 105, 0.55),
    0 12px 20px rgba(0, 0, 0, 0.5);
}
.tile--selected::after {
  content: '⇄';
  position: absolute;
  top: -19px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 12px;
  font-weight: 900;
  color: var(--warn);
  text-shadow: 0 1px 3px rgba(0,0,0,0.7);
}

.tile-letter {
  font-family: var(--serif);
  font-weight: 900;
  font-size: 30px;
  line-height: 1;
  letter-spacing: -0.5px;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.55);
}
.tile-letter[data-len='2'] { font-size: 21px; letter-spacing: -1px; }
.tile-letter[data-len='3'] { font-size: 16px; letter-spacing: -0.8px; }
.tile-letter[data-len='4'],
.tile-letter[data-len='5'] { font-size: 24px; letter-spacing: -0.5px; }

/* A glyph of four or more letters gets a double-wide tile (the Scientist's
   OLOGY loan). Width rides --tile-w, so the mini variant halves with it. */
.tile--wide { width: calc(var(--tile-w) * 2); }

/* The fleuron — dressed apart so it never reads as a yellow letter. */
.tile--fleuron {
  background: linear-gradient(160deg, #f9ecbe 0%, #ecd287 55%, #d9b45c 100%);
  border-color: rgba(122, 90, 20, 0.65);
  --tile-lift: #a8842e;
}
.tile--fleuron .tile-letter {
  color: #7a5a10;
  font-size: 34px;
  text-shadow: 0 1px 0 rgba(255, 246, 205, 0.85);
}
.tile--fleuron .tile-pts { color: #7a5a10; }

/* The glyph takes the wash colour inline (render.js) and this mutes it. The
   waterline says "temporary" to a colour-blind eye too. */
.tile--washed .tile-letter {
  opacity: 0.55;
  text-decoration: underline wavy 1.5px;
  text-underline-offset: 4px;
}

.tile--interrobang {
  background: linear-gradient(168deg, #fbeec4 0%, #f0d795 55%, #e6c87e 100%);
  border-color: rgba(140, 100, 20, 0.7);
  --tile-lift: #a8801f;
  --trim-ring: inset 0 0 0 3px #d6ac4a;
}
.tile--interrobang .tile-letter {
  color: #6d4a12;
  text-shadow: 0 1px 0 rgba(255, 250, 232, 0.8);
}

/* ─── The medieval sorts (The Medievalist's stall) ─────────────────────────────
   A warmer, redder lead than the ordinary case — the glyph carries the rest. */
.tile--medieval {
  background: linear-gradient(168deg, #f6e6c8 0%, #e8d0a4 55%, #dfc496 100%);
  border-color: rgba(110, 70, 26, 0.6);
  --tile-lift: #9d7638;
}
.tile--medieval .tile-letter {
  color: #5d2f10;
  text-shadow: 0 1px 0 rgba(255, 244, 220, 0.7);
}

/* ─── In manuscript (The Redactor) ─────────────────────────────────────────────
   The wrapper covers whatever the tile was — trim ring, metal, nick notch,
   paint. It wins by weight: the class is added last and these override. */
.tile--wrapped {
  background:
    repeating-linear-gradient(101deg,
      rgba(140, 130, 118, 0.05) 0 3px, rgba(140, 130, 118, 0) 3px 7px),
    linear-gradient(168deg, #d9d4ca 0%, #cdc7bb 55%, #c3bcaf 100%);
  color: #4a4741;
  border-color: rgba(70, 66, 60, 0.45);
  --trim-ring: 0 0 rgba(0, 0, 0, 0);
  --tile-lift: #8d877c;
  background-blend-mode: multiply, normal;
}
.tile--wrapped .tile-letter {
  /* Caveat sits small on its em, so the size is pushed up to match the cast
     letters beside it. */
  font-family: var(--hand);
  font-weight: 700;
  font-size: 40px;
  letter-spacing: 0;
  color: #55514a !important;
  opacity: 0.92;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.35);
  transform: rotate(-4deg);
}
/* Ligatures and the longer glyphs need the same nudge the cast faces get. */
.tile--wrapped .tile-letter[data-len='2'] { font-size: 30px; }
.tile--wrapped .tile-letter[data-len='3'] { font-size: 23px; }
.tile--wrapped .tile-letter[data-len='4'],
.tile--wrapped .tile-letter[data-len='5'] { font-size: 32px; }
/* The corner figure reads 0 (restingPoints says so) — dim it. */
.tile--wrapped .tile-pts { opacity: 0.4; }
/* Nothing shows through the paper: no nick notch, no rainbow sheen, no curse. */
.tile--wrapped.tile--nick-left,
.tile--wrapped.tile--nick-right,
.tile--wrapped.tile--nick-side { --nick-r: 9px; }
.tile--wrapped::after,
.tile--wrapped::before { display: none; }

.tile-pts {
  position: absolute;
  right: 5px; bottom: 4px;
  font-size: 11px;
  font-weight: 800;
  opacity: 0.7;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.tile-alt {
  position: absolute;
  right: 4px; top: 3px;
  font-size: 9.5px;
  font-weight: 800;
  color: #9b8a6e;
  line-height: 1;
}

/* ── Nicks ─────────────────────────────────────────────────────────────────────
   The mask does the cutting — a disc removed from the edge, taking border, trim
   ring and shadow with it — and .tile-nick paints the lip that survives inside
   the bite. The notched edge *is* the direction. */
.tile {
  --nick-r: 9px;
}

.tile--nick-right {
  -webkit-mask-image: radial-gradient(circle var(--nick-r) at 100% 50%, transparent calc(var(--nick-r) - 0.6px), #000 var(--nick-r));
          mask-image: radial-gradient(circle var(--nick-r) at 100% 50%, transparent calc(var(--nick-r) - 0.6px), #000 var(--nick-r));
}
.tile--nick-left {
  -webkit-mask-image: radial-gradient(circle var(--nick-r) at 0% 50%, transparent calc(var(--nick-r) - 0.6px), #000 var(--nick-r));
          mask-image: radial-gradient(circle var(--nick-r) at 0% 50%, transparent calc(var(--nick-r) - 0.6px), #000 var(--nick-r));
}
/* Two bites: a pixel must survive BOTH masks, so they intersect rather than add.
   Where intersect is unsupported the masks union to "no bite" — the tile simply
   renders whole, which is a harmless way to fail. */
.tile--nick-side {
  -webkit-mask-image: radial-gradient(circle var(--nick-r) at 0% 50%, transparent calc(var(--nick-r) - 0.6px), #000 var(--nick-r)),
                      radial-gradient(circle var(--nick-r) at 100% 50%, transparent calc(var(--nick-r) - 0.6px), #000 var(--nick-r));
          mask-image: radial-gradient(circle var(--nick-r) at 0% 50%, transparent calc(var(--nick-r) - 0.6px), #000 var(--nick-r)),
                      radial-gradient(circle var(--nick-r) at 100% 50%, transparent calc(var(--nick-r) - 0.6px), #000 var(--nick-r));
  -webkit-mask-composite: source-in;
          mask-composite: intersect;
}

/* A disc whose middle the mask eats, leaving a rim that reads as depth. */
.tile-nick {
  position: absolute;
  top: 50%;
  width:  calc(var(--nick-r) * 2 + 9px);
  height: calc(var(--nick-r) * 2 + 9px);
  transform: translateY(-50%);
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(circle,
    rgba(88, 60, 22, 0.55) 0 calc(var(--nick-r) + 1.5px),
    rgba(88, 60, 22, 0.22) calc(var(--nick-r) + 3px),
    rgba(88, 60, 22, 0) calc(var(--nick-r) + 4.5px));
}
.tile-nick--r { right: calc(var(--nick-r) * -1 - 4.5px); }
.tile-nick--l { left:  calc(var(--nick-r) * -1 - 4.5px); }

/* Dark-bodied trims want a lighter lip to stay visible */
.tile--trim-purple .tile-nick,
.tile--trim-silver .tile-nick {
  background: radial-gradient(circle,
    rgba(40, 28, 10, 0.5) 0 calc(var(--nick-r) + 1.5px),
    rgba(40, 28, 10, 0.2) calc(var(--nick-r) + 3px),
    rgba(40, 28, 10, 0) calc(var(--nick-r) + 4.5px));
}

/* Each trim only sets --trim-ring and its lift colour; the shadow composition
   lives on .tile, so the ring survives hover, selection and every other state. */
.tile--trim-gold {
  --trim-ring: inset 0 0 0 3px #d9a64b;
  --tile-lift: #a87f2c;
  overflow: hidden;
}
.tile--trim-gold::before {
  content: '';
  position: absolute;
  top: -60%; left: -80%;
  width: 55%; height: 220%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 235, 0.55), transparent);
  transform: rotate(18deg);
  animation: gildShimmer calc(3.4s / var(--aspd)) ease-in-out infinite;
}
@keyframes gildShimmer {
  0%, 62%  { left: -80%; }
  100%     { left: 160%; }
}

.tile--trim-silver {
  --trim-ring: inset 0 0 0 3px #aab4c4;
  --tile-lift: #7e8794;
}
/* Silver pays +6 Points and the corner figure is where that lands. Grown and
   boosted numbers still win — those report a change from the face value. */
.tile--trim-silver .tile-pts:not(.tile-pts--grown):not(.tile-pts--boosted) {
  opacity: 1;
  font-weight: 900;
  color: #6b7a8f;
  text-shadow: 0 1px 0 rgba(250, 252, 255, 0.95), 0 0 7px rgba(160, 174, 194, 0.8);
}

/* Cobalt refunds a Discard, so it wears the discard pips' own blues. */
.tile--trim-cobalt {
  --trim-ring: inset 0 0 0 1.5px #8ec6ff, inset 0 0 0 3.5px #3f68ad;
  --tile-lift: #2c4a78;
}

.tile--trim-purple {
  /* the faint violet halo rides in the ring, so it survives every state too */
  --trim-ring: inset 0 0 0 3px #8a5fb0, 0 0 12px rgba(150, 90, 220, 0.3);
  --tile-lift: #5e3f7c;
}

/* ── Materials — what a tile is cast from ────────────────────────────────────
   The material replaces the tile's body and sits *under* paint, trims and nicks
   — a cursed tile can still be gilded and painted, because a trim only sets
   --trim-ring, which composes over whatever body the material paints. */

/* Cursed — hellbox iron. Dark, hot at the edges, never quite still. */
.tile--mat-cursed {
  background:
    radial-gradient(120% 80% at 50% 0%, #5e1414 0%, #2a0808 55%, #170404 100%);
  border-color: #6d1a15;
  color: #ffd9cf;
  --tile-lift: #350a0a;
  animation: cursedBreathe calc(3.2s / var(--aspd)) ease-in-out infinite;
}
.tile--mat-cursed .tile-letter {
  color: #ffcabd;
  text-shadow: 0 0 9px rgba(255, 70, 40, 0.85), 0 1px 0 rgba(0, 0, 0, 0.6);
}
.tile--mat-cursed .tile-pts { color: #ff9d8e; opacity: 0.95; }
.tile--mat-cursed .tile-alt { color: #ff9d8e; }
@keyframes cursedBreathe {
  0%, 100% { filter: brightness(1); }
  50%      { filter: brightness(1.22) saturate(1.15); }
}

/* Ghost — pale, translucent, and casting almost no shadow. */
.tile--mat-ghost {
  background: linear-gradient(168deg, rgba(232, 245, 255, 0.72), rgba(186, 214, 244, 0.52));
  border: 1px dashed rgba(226, 242, 255, 0.85);
  color: #1d2b3a;
  --trim-ring: 0 0 16px rgba(168, 214, 255, 0.5);
  --tile-lift: rgba(120, 160, 200, 0.3);
  backdrop-filter: blur(2px);
}
.tile--mat-ghost .tile-letter {
  color: #23384d;
  text-shadow: 0 0 8px rgba(255, 255, 255, 0.95), 0 0 14px rgba(170, 215, 255, 0.8);
}
.tile--mat-ghost .tile-pts { color: #33506b; }

/* Rose metal — a party favour, so the letter takes the manuscript hand. */
.tile--mat-rose {
  background: linear-gradient(168deg, #ffd9e6 0%, #f3aec6 52%, #e089a8 100%);
  border-color: #c97d9a;
  color: #6d2340;
  --trim-ring: 0 0 14px rgba(255, 170, 200, 0.55);
  --tile-lift: #d98fae;
}
.tile--mat-rose .tile-letter {
  font-family: var(--hand);
  font-weight: 700;
  color: #8c2b50;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
}
.tile--mat-rose .tile-pts { color: #a85072; }

/* Rainbow. Kept pale so a painted letter still reads against it. */
/* A short pitch, so every tile shows the whole spectrum whatever its size. */
/* The drift below slides this wash sideways for ever, which asks two things of
   the pattern.
   1. The step must equal one *horizontal* period. Bands repeat along their own
      axis, and a sideways step advances them only by its projection onto that
      axis, so the sideways period is P/sin θ.
   2. The image must tile onto itself, or the wash meets its own edge mid-band
      and drags a seam across the tile. That needs background-size set to one
      lattice cell — P/sin θ across by P/|cos θ| down — in WHOLE pixels, since
      tiling is snapped to the device grid and fractions desynchronise it.
   Both fall out of choosing the angle so |tan θ| = 3, making the cell 81 × 243
   at 108.435°, and the loop closes exactly. */
.tile--mat-rainbow {
  background: repeating-linear-gradient(108.435deg,
    #ffc9c0 0px, #ffdca8 10.978px, #fbf29c 21.955px, #b9edb4 32.933px,
    #a9dcff 43.91px, #cfc0ff 54.888px, #ffc2e6 65.866px, #ffc9c0 76.843px);
  border-color: rgba(120, 90, 140, 0.55);
  background-size: 81px 243px;
  animation: rainbowDrift calc(6s / var(--aspd)) linear infinite;
}
@keyframes rainbowDrift {
  0%   { background-position: 0 0; }
  100% { background-position: 81px 0; }   /* exactly one cell — repaints the start */
}
.tile--mat-rainbow .tile-letter,
.tile--mat-rainbow .tile-pts,
.tile--mat-rainbow .tile-alt {
  text-shadow:
    0 0 3px rgba(255, 255, 255, 0.95),
    0 0 7px rgba(255, 255, 255, 0.8),
    0 1px 0 rgba(255, 255, 255, 0.9);
}

/* The Enthusiast's gift — lent, not owned, hence the brass and the stitching. */
.tile--gift {
  border: 1.5px dashed rgba(246, 214, 138, 0.85);
  box-shadow:
    0 0 0 3px rgba(217, 166, 75, 0.22),
    0 0 14px rgba(246, 214, 138, 0.5),
    0 6px 12px rgba(0, 0, 0, 0.4);
  animation: giftGlow calc(2.6s / var(--aspd)) ease-in-out infinite;
}
.tile--gift .tile-pts { color: var(--brass); }
@keyframes giftGlow {
  0%, 100% { box-shadow: 0 0 0 3px rgba(217, 166, 75, 0.22), 0 0 14px rgba(246, 214, 138, 0.45), 0 6px 12px rgba(0, 0, 0, 0.4); }
  50%      { box-shadow: 0 0 0 3px rgba(217, 166, 75, 0.4),  0 0 22px rgba(246, 214, 138, 0.75), 0 6px 12px rgba(0, 0, 0, 0.4); }
}

/* The Eeeditor's E — lent too, but taking a place in your hand rather than
   riding beside it, so cold ink-blue where the gift is brass. */
.tile--lent {
  background: linear-gradient(168deg, #e8eef7, #c6d4e6);
  border: 1.5px dashed rgba(45, 79, 122, 0.7);
  color: #1d3354;
  --tile-lift: rgba(70, 100, 140, 0.35);
}
.tile--lent .tile-letter {
  color: #2d4f7a;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.9);
}
.tile--lent .tile-pts { color: #4a6d99; }
.tile--lent::after {
  content: '';
  position: absolute;
  left: 22%; right: 22%; bottom: 5px;
  height: 3px;
  border-top: 1px solid rgba(45, 79, 122, 0.55);
  border-bottom: 1px solid rgba(45, 79, 122, 0.55);
  pointer-events: none;
}

/* Mini tiles (inspectors, market case) */
.tile--mini {
  --tile-w: 42px;
  --tile-h: 45px;
  cursor: pointer;
  border-radius: 7px;
}
.tile--mini .tile-letter { font-size: 17px; }
.tile--mini .tile-letter[data-len='2'] { font-size: 13px; }
.tile--mini .tile-letter[data-len='3'] { font-size: 10px; }
.tile--mini .tile-letter[data-len='4'],
.tile--mini .tile-letter[data-len='5'] { font-size: 14px; }
.tile--mini .tile-pts { font-size: 8.5px; right: 3px; bottom: 2px; }
.tile--mini .tile-alt { font-size: 7.5px; }
.tile--mini { --nick-r: 6.5px; }
.tile--mini.tile--trim-gold   { box-shadow: inset 0 0 0 2px #d9a64b, 0 3px 6px rgba(0,0,0,0.35); }
.tile--mini.tile--trim-silver { box-shadow: inset 0 0 0 2px #aab4c4, 0 3px 6px rgba(0,0,0,0.35); }
.tile--mini.tile--trim-cobalt  { box-shadow: inset 0 0 0 1px #8ec6ff, inset 0 0 0 2.5px #3f68ad, 0 3px 6px rgba(0,0,0,0.35); }
.tile--mini.tile--trim-purple  { box-shadow: inset 0 0 0 2px #8a5fb0, 0 3px 6px rgba(0,0,0,0.35); }

/* A stall's chosen tile */
.tile--stall-sel {
  outline: 2.5px solid var(--bad);
  outline-offset: 2px;
  transform: translateY(-3px);
  box-shadow: 0 0 16px rgba(255, 112, 102, 0.5), 0 6px 12px rgba(0,0,0,0.4);
}

/* Tiles a stall has no use for. Nothing sets this at present, but the click
   guard in sheets.js honours it, so a future stall can lock a tile for free. */
.tile--stall-locked {
  opacity: 0.35;
  cursor: default;
  filter: grayscale(0.4);
}

/* Scoring states */
.tile--scoring {
  animation: tileScore calc(0.36s / var(--aspd)) cubic-bezier(.3,1.4,.5,1);
  z-index: 4;
}
@keyframes tileScore {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.22) translateY(-6px);
         box-shadow: 0 0 0 3.5px rgba(141, 184, 255, 0.65), 0 14px 24px rgba(0, 0, 0, 0.5); }
  100% { transform: scale(1); }
}

.tile--nick-firing {
  animation: nickFire calc(0.48s / var(--aspd)) ease;
  z-index: 4;
}
@keyframes nickFire {
  0%   { transform: scale(1) rotate(0); }
  35%  { transform: scale(1.16) rotate(-3deg);
         box-shadow: 0 0 0 4px rgba(246, 214, 138, 0.7), 0 0 26px rgba(246, 214, 138, 0.6); }
  100% { transform: scale(1) rotate(0); }
}

.tile--nick-hit {
  animation: nickHit calc(0.46s / var(--aspd)) ease;
}
@keyframes nickHit {
  0%   { transform: scale(1); filter: brightness(1); }
  40%  { transform: scale(1.14); filter: brightness(1.3);
         box-shadow: 0 0 0 3px rgba(246, 214, 138, 0.55), 0 10px 18px rgba(0,0,0,0.45); }
  100% { transform: scale(1); filter: brightness(1); }
}

/* Points written onto the tile before scoring — a swell, not a leap. */
.tile--boosted {
  animation: tileBoost calc(0.52s / var(--aspd)) ease;
  z-index: 4;
}
@keyframes tileBoost {
  0%   { transform: scale(1); filter: brightness(1); }
  45%  { transform: scale(1.12) translateY(-3px); filter: brightness(1.25) saturate(1.2);
         box-shadow: 0 0 0 3.5px rgba(144, 232, 186, 0.7), 0 0 24px rgba(144, 232, 186, 0.55); }
  100% { transform: scale(1); filter: brightness(1); }
}

/* Paint promised but not yet earned (The Illuminator). The letter is already in
   its new colour — render.js paints a COPY — and this is the wet-ink edge. */
.tile--illuminating {
  outline: 2px dashed var(--glow, rgba(246, 214, 138, 0.75));
  outline-offset: 2px;
  animation: illuminating calc(2.4s / var(--aspd)) ease-in-out infinite;
}
@keyframes illuminating {
  0%, 100% { outline-color: color-mix(in srgb, var(--glow, #f6d68a) 45%, transparent); }
  50%      { outline-color: var(--glow, #f6d68a); }
}
@media (prefers-reduced-motion: reduce) {
  .tile--illuminating { animation: none; }
}

/* The brush landing at print — a boosted tile's swell, in the new colour. */
.tile--painted {
  animation: tilePainted calc(0.62s / var(--aspd)) ease;
  z-index: 4;
}
@keyframes tilePainted {
  0%   { transform: scale(1); filter: brightness(1); }
  45%  { transform: scale(1.14) translateY(-4px); filter: brightness(1.3) saturate(1.35);
         box-shadow: 0 0 0 4px var(--glow, #fff), 0 0 28px var(--glow, #fff); }
  100% { transform: scale(1); filter: brightness(1); }
}

.tile--set-glow {
  animation: setGlow calc(0.62s / var(--aspd)) ease;
}
@keyframes setGlow {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.13) translateY(-4px);
         box-shadow: 0 0 0 3.5px var(--glow, #fff), 0 0 26px var(--glow, #fff); }
  100% { transform: scale(1); }
}

/* ─── Action row ──────────────────────────────────────────────────────────────── */
.action-row {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
}
.action-spacer { flex: 1; }

.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--sans);
  font-weight: 800;
  font-size: 13px;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--parch);
  background: linear-gradient(180deg, var(--leather-2), var(--leather));
  border: 1px solid rgba(243, 222, 184, 0.18);
  border-radius: 11px;
  padding: 10px 18px;
  cursor: pointer;
  box-shadow: 0 3px 0 rgba(10, 4, 12, 0.8), 0 6px 14px rgba(0, 0, 0, 0.35);
  transition:
    transform calc(0.08s / var(--aspd)),
    box-shadow calc(0.08s / var(--aspd)),
    filter calc(0.14s / var(--aspd)),
    opacity calc(0.14s / var(--aspd));
}
.btn:hover:not(:disabled) { filter: brightness(1.18); }
.btn:active:not(:disabled) { transform: translateY(2px); box-shadow: 0 1px 0 rgba(10, 4, 12, 0.8), 0 3px 8px rgba(0, 0, 0, 0.35); }
.btn:disabled { opacity: 0.35; cursor: not-allowed; }

.btn kbd {
  font-family: var(--sans);
  font-size: 9.5px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  opacity: 0.55;
}

.btn-quiet { background: linear-gradient(180deg, rgba(60, 40, 55, 0.7), rgba(40, 25, 38, 0.7)); }

.btn-discard {
  background: linear-gradient(180deg, #2c4a78, #1d3354);
  border-color: rgba(141, 184, 255, 0.35);
  color: #d6e6ff;
  box-shadow: 0 3px 0 #101e33, 0 6px 14px rgba(0, 0, 0, 0.35);
}

.btn-print {
  background: linear-gradient(180deg, var(--brass-hi) 0%, var(--brass) 45%, #b8821f 100%);
  border: 1px solid rgba(80, 50, 5, 0.6);
  color: #3a2606;
  text-shadow: 0 1px 0 rgba(255, 240, 200, 0.5);
  box-shadow: 0 4px 0 var(--brass-dark), 0 8px 18px rgba(217, 166, 75, 0.25);
}
.btn-print:hover:not(:disabled) { filter: brightness(1.08); box-shadow: 0 4px 0 var(--brass-dark), 0 8px 24px rgba(217, 166, 75, 0.45); }
.btn-print:active:not(:disabled) { transform: translateY(3px); box-shadow: 0 1px 0 var(--brass-dark); }
.btn-print kbd { color: #5c3f10; opacity: 0.8; }

.btn-big { font-size: 15px; padding: 13px 30px; border-radius: 13px; }

.btn-danger {
  background: linear-gradient(180deg, #8a3a32, #66241e);
  border-color: rgba(255, 130, 115, 0.4);
  color: #ffd9d2;
  box-shadow: 0 3px 0 #3d120e, 0 6px 14px rgba(0, 0, 0, 0.35);
}

/* ─── Status bar: the manuscript, and messages ────────────────────────────────── */
.log {
  margin: 0;
  height: 24px;              /* fixed: message length must not move the page */
  overflow: hidden;
  font-family: var(--serif);
  font-style: italic;
  font-size: 14.5px;
  color: var(--muted);
  text-align: center;
}
.log--good { color: var(--good); }
.log--bad  { color: var(--bad); }
.log--warn { color: var(--warn); }

/* Flush right, so the newest word is always in view and the rest trail off. */
.log--manuscript {
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 0.42em;
  white-space: nowrap;
  padding: 0 2px;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 14%, #000 100%);
          mask-image: linear-gradient(90deg, transparent 0, #000 14%, #000 100%);
}

.ms-word {
  flex: none;
  color: rgba(243, 222, 184, 0.42);
  letter-spacing: 0.3px;
}
.ms-word--last {
  color: var(--brass-hi);
  font-weight: 700;
}
.ms-word--new { animation: msInk calc(0.5s / var(--aspd)) cubic-bezier(.2,1.3,.4,1) both; }
@keyframes msInk {
  0%   { opacity: 0; transform: translateY(5px) scale(0.9); filter: blur(1.5px); }
  100% { opacity: 1; transform: none; filter: none; }
}

.ms-dot   { flex: none; color: rgba(243, 222, 184, 0.2); font-style: normal; }
.ms-blank { color: rgba(243, 222, 184, 0.25); }

/* ─── FX layer ────────────────────────────────────────────────────────────────── */
#fx {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 300;
}

.fly-clone {
  position: fixed;
  margin: 0;
  z-index: 310;
  pointer-events: none;
}

.floater {
  position: fixed;
  transform: translateX(-50%);
  font-family: var(--serif);
  font-weight: 900;
  font-size: 21px;
  white-space: nowrap;
  z-index: 320;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.85), 0 0 18px rgba(0, 0, 0, 0.5);
}
.fl-points { color: var(--inkblue); }
.fl-mult   { color: var(--pressred); }
.fl-aura   { color: var(--brass-hi); }
.fl-coin    { color: var(--brass-hi); font-size: 18px; }
.fl-curse   { color: #ff7a66; font-size: 19px; font-weight: 800; text-shadow: 0 0 8px rgba(201, 60, 45, 0.65); }
.fl-spike   { color: #ffb3a6; font-size: 18px; font-weight: 800; text-shadow: 0 0 9px rgba(201, 60, 45, 0.75); }
.fl-boost   { color: #90e8ba; font-size: 19px; }
/* Copy rather than arithmetic, so it takes the serif's italic and rises above
   the chip's own ×N floater. */
.fl-flourish {
  color: #f3debc;
  font-style: italic;
  font-size: 18px;
  font-weight: 700;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.9), 0 0 20px rgba(246, 214, 138, 0.35);
}
.fl-refresh { color: #f0a86e; font-size: 17px; }
.fl-return  { color: #b9e3ff; font-size: 17px; }
.fl-set   { font-size: 19px; }
.fl-set--length  { color: #cfdeeb; }
.fl-set--crimson { color: #ff9d8e; }
.fl-set--azure   { color: #8ec6ff; }
.fl-set--jade    { color: #90e8ba; }
.fl-set--amber   { color: #ffd68c; }
.fl-set--purple  { color: #cfa6ff; }
.fl-set--cursed  { color: #ff7a66; }
.fl-mat--cursed  { color: #ff7a66; font-size: 19px; }
.fl-mat--ghost   { color: #dceaff; font-size: 19px; }
.fl-mat--rainbow { color: #ffe9c9; font-size: 19px; }
.fl-mat--rose    { color: #ffc6dc; font-size: 19px; }

.sparkle {
  position: fixed;
  width: 9px; height: 9px;
  border-radius: 2px;
  background: linear-gradient(135deg, #fff3cf, var(--brass));
  box-shadow: 0 0 8px rgba(246, 214, 138, 0.8);
  z-index: 315;
}

/* ─── Banner ──────────────────────────────────────────────────────────────────── */
#banner {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
  z-index: 350;
  opacity: 0;
  transition: opacity calc(0.28s / var(--aspd)) ease;
}
#banner.banner--show { opacity: 1; }
/* A dismissible banner covers the screen, so taking pointer events here both
   dismisses it and stops the tap reaching the board underneath. */
#banner.banner--dismissible { pointer-events: auto; cursor: pointer; }
#banner.banner--dismissible .banner-inner::after {
  content: 'tap to go on';
  display: block;
  margin-top: 16px;
  font-family: var(--sans);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 2.2px;
  text-transform: uppercase;
  color: rgba(243, 222, 184, 0.42);
}

.banner-inner {
  text-align: center;
  padding: 30px 64px;
  background: radial-gradient(ellipse at center, rgba(16, 8, 18, 0.93) 30%, rgba(16, 8, 18, 0.55) 75%, transparent 100%);
  transform: scale(0.94);
  transition: transform calc(0.3s / var(--aspd)) cubic-bezier(.2,1.3,.4,1);
}
#banner.banner--show .banner-inner { transform: scale(1); }

.banner-rule { color: var(--brass); font-size: 19px; opacity: 0.9; }
.banner-rule--flip { transform: scaleX(-1); }

.banner-title {
  font-family: var(--serif);
  font-weight: 900;
  font-size: 43px;
  letter-spacing: 1px;
  color: var(--parch);
  text-shadow: 0 0 30px rgba(233, 178, 102, 0.4);
  margin: 6px 0 2px;
}
.banner-sub {
  font-family: var(--serif);
  font-style: italic;
  font-size: 17px;
  color: var(--brass-hi);
  margin-bottom: 6px;
}

/* ─── Modals & sheets ─────────────────────────────────────────────────────────── */
.modal {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(10, 4, 12, 0.72);
  backdrop-filter: blur(5px);
  z-index: 200;
  place-items: center;
  padding: 22px;
  overflow: auto;
}
.modal.show { display: grid; }

.sheet {
  background:
    radial-gradient(700px 320px at 50% -10%, rgba(255, 250, 235, 0.5), transparent 70%),
    linear-gradient(175deg, var(--parch) 0%, #ead9b4 70%, #e0cda2 100%);
  color: var(--parch-ink);
  border-radius: 16px;
  border: 1px solid #a98545;
  box-shadow:
    inset 0 0 0 1px rgba(255, 250, 230, 0.55),
    inset 0 0 60px rgba(170, 130, 60, 0.18),
    0 36px 90px rgba(0, 0, 0, 0.75);
  padding: 24px 30px 26px;
  width: min(940px, 94vw);
  max-height: 92vh;
  overflow: auto;
  animation: sheetIn calc(0.3s / var(--aspd)) cubic-bezier(.2,1.2,.4,1);
}
@keyframes sheetIn {
  from { transform: translateY(22px) scale(0.97); opacity: 0; }
  to   { transform: translateY(0) scale(1); opacity: 1; }
}

.sheet h2 {
  margin: 0;
  font-family: var(--serif);
  font-weight: 900;
  font-size: 26px;
  color: var(--parch-ink);
}

.sheet-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 18px;
  margin-bottom: 14px;
}

.sheet .x {
  background: none;
  border: none;
  color: var(--parch-dim);
  font-size: 17px;
  cursor: pointer;
  padding: 4px 9px;
  border-radius: 8px;
}
.sheet .x:hover { background: rgba(120, 90, 40, 0.15); color: var(--parch-ink); }

.sheet-note { margin: 2px 0 0; font-size: 12.5px; color: var(--parch-dim); }
.sheet-note b { color: var(--parch-ink); }

.sheet--settings { width: min(480px, 94vw); }
.sheet--inspector { width: min(680px, 94vw); }

.sheet--dark {
  background: linear-gradient(175deg, #322034 0%, #241626 100%);
  color: var(--parch);
  border-color: rgba(217, 166, 75, 0.4);
  box-shadow: inset 0 0 0 1px rgba(255, 235, 200, 0.08), 0 36px 90px rgba(0, 0, 0, 0.8);
}
.sheet--dark h2 { color: var(--parch); }

/* ─── Market ─────────────────────────────────────────────────────────────────── */
.market-title {
  background: linear-gradient(120deg, #7c5410, #b8821f);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent !important;
}

.reward-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 6px 12px;
  max-width: 380px;
  font-size: 12px;
  color: var(--parch-dim);
}
.reward-part b { color: #2f6e3e; }
.reward-total {
  font-family: var(--serif);
  font-weight: 700;
  font-size: 16px;
  color: var(--parch-ink);
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding-left: 10px;
  border-left: 1px solid rgba(120, 90, 40, 0.3);
}

.market-grid {
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  gap: 26px;
}
@media (max-width: 780px) { .market-grid { grid-template-columns: 1fr; } }

.market-sec {
  margin: 0 0 10px;
  font-family: var(--serif);
  font-weight: 700;
  font-size: 17px;
  display: flex;
  align-items: baseline;
  gap: 9px;
}
.market-sub { font-family: var(--sans); font-weight: 500; font-size: 11px; color: var(--parch-dim); }
.market-sub--done { color: #2f6e3e; font-weight: 700; }
/* The reordering hint — an instruction, not a count. Dropped on narrow sheets. */
.market-sub--hint { font-style: italic; opacity: 0.75; margin-left: 8px; }
@media (max-width: 560px) { .market-sub--hint { display: none; } }

.offer-list { display: grid; gap: 9px; }

/* ── Patrons as engraved business cards ────────────────────────────────────────
   Ivory stock, a double-rule frame (hairline tinted by rarity), and a portrait
   panel holding an emoji unless the patron def carries a `portrait` image. */
.offer-patron {
  position: relative;
  display: grid;
  grid-template-columns: 84px 1fr;
  gap: 9px;
  align-items: stretch;
  width: min(340px, 100%);
  margin-inline: auto;
  padding: 6px;
  /* One height for every card, whatever the effect text runs to. */
  min-height: var(--card-h, 108px);
  border-radius: 4px;
  background: linear-gradient(170deg, var(--ivory) 0%, var(--parch) 55%, var(--parch-2) 100%);
  border: 1px solid rgba(138, 95, 29, 0.55);
  box-shadow:
    inset 0 0 0 3px rgba(255, 252, 240, 0.9),
    inset 0 0 0 4px var(--hairline, rgba(138, 95, 29, 0.4)),
    0 4px 12px rgba(60, 40, 15, 0.3);
  transform: rotate(-0.7deg);
}
.offer-patron:nth-child(even) { transform: rotate(0.6deg); }
.offer-patron--uncommon { --hairline: rgba(47, 116, 92, 0.55);  --title-col: #2f745c; }
.offer-patron--rare     { --hairline: rgba(112, 64, 160, 0.55); --title-col: #7040a0; }

/* ── Guild livery ──────────────────────────────────────────────────────────────
   The hairline stays the rarity's — rarity and guild are separate facts. One
   variable set serves the card and the seat's livery pin alike (the values are
   COLOURS' glyph/bright pairs in js/constants.js). */
.offer-patron--g-crimson, .patron--g-crimson {
  --livery: #b23a2e; --livery-brt: #ff9d8e; --livery-soft: rgba(178, 58, 46, 0.16);
}
.offer-patron--g-jade, .patron--g-jade {
  --livery: #2d8a5c; --livery-brt: #90e8ba; --livery-soft: rgba(45, 138, 92, 0.16);
}
.offer-patron--g-amber, .patron--g-amber {
  --livery: #a87010; --livery-brt: #ffd68c; --livery-soft: rgba(168, 112, 16, 0.18);
}
.offer-patron--g-azure, .patron--g-azure {
  --livery: #2e6fb2; --livery-brt: #8ec6ff; --livery-soft: rgba(46, 111, 178, 0.16);
}

/* A dual-livery patron carries its second dye in these classes. */
.offer-patron--g2-crimson, .patron--g2-crimson { --livery2: #b23a2e; --livery2-brt: #ff9d8e; }
.offer-patron--g2-jade,    .patron--g2-jade    { --livery2: #2d8a5c; --livery2-brt: #90e8ba; }
.offer-patron--g2-amber,   .patron--g2-amber   { --livery2: #a87010; --livery2-brt: #ffd68c; }
.offer-patron--g2-azure,   .patron--g2-azure   { --livery2: #2e6fb2; --livery2-brt: #8ec6ff; }

/* The ribbon rides the card's tilt, which keeps the stack looking hand-laid. */
.offer-patron[class*="offer-patron--g-"]::after {
  content: '';
  position: absolute;
  top: -3px;
  right: 16px;
  width: 13px;
  height: 34px;
  background: linear-gradient(175deg, var(--livery-brt) -45%, var(--livery) 60%);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 80%, 0 100%);
  box-shadow: 0 2px 3px rgba(60, 40, 15, 0.35);
  z-index: 1;
}
/* The second ribbon: a slightly shorter silk bound in beside the first —
   two dyes under one stitch. ::after is the first ribbon's, so this one
   takes the card's ::before, which offer cards otherwise leave free. */
.offer-patron[class*="offer-patron--g2-"]::before {
  content: '';
  position: absolute;
  top: -3px;
  right: 31px;
  width: 13px;
  height: 27px;
  background: linear-gradient(175deg, var(--livery2-brt) -45%, var(--livery2) 60%);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 80%, 0 100%);
  box-shadow: 0 2px 3px rgba(60, 40, 15, 0.35);
  z-index: 1;
}

.offer-patron[class*="offer-patron--g-"] .op-portrait {
  border-color: var(--livery);
  box-shadow:
    inset 0 0 0 2px var(--livery-soft),
    inset 0 -16px 20px -12px var(--livery-soft);
}
.op-guild { color: var(--livery); }

.op-portrait {
  display: grid;
  place-items: center;
  min-height: 92px;
  border-radius: 2px;
  overflow: hidden;
  background:
    radial-gradient(circle at 50% 36%, rgba(255, 252, 240, 0.95), rgba(206, 185, 143, 0.6) 78%),
    repeating-linear-gradient(0deg, rgba(120, 90, 40, 0.14) 0 1px, transparent 1px 3px);
  border: 1px solid rgba(138, 95, 29, 0.4);
}
.op-portrait img { width: 100%; height: 100%; object-fit: cover; display: block; }
.op-portrait .op-emoji {
  font-size: 37px;
  filter: grayscale(0.2) sepia(0.3) drop-shadow(0 2px 2px rgba(90, 60, 20, 0.3));
  opacity: 0.9;
}

.op-card-body {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 1px;
  padding: 6px 8px 6px 2px;
  min-width: 0;
}
.op-card-body .op-name {
  font-family: var(--serif);
  font-weight: 700;
  font-size: 14.5px;
  letter-spacing: 0.2px;
  color: var(--parch-ink);
}
.op-title {
  font-size: 8.5px;
  font-weight: 800;
  letter-spacing: 1.9px;
  text-transform: uppercase;
  color: var(--title-col, #8a5f1d);
}
.op-card-body .op-desc { margin-top: 4px; font-size: 11.5px; }

/* Price tag tied to the card's corner; SOLD stamped across its face.
   (The doubled selector outranks the market skin's `position: relative`.) */
.offer-patron .btn-price,
.sheet--market .offer-patron .btn-price {
  position: absolute;
  right: -9px;
  bottom: -9px;
  z-index: 2;
}
.offer-patron .op-sold {
  position: absolute;
  inset: 0;
  margin: auto;
  width: max-content;
  height: max-content;
  z-index: 2;
}

.offer--sold { opacity: 0.45; }

.op-emoji { font-size: 27px; line-height: 1; }
.op-body { min-width: 0; }
.op-name { font-family: var(--serif); font-weight: 700; font-size: 14px; }
.op-rarity {
  font-family: var(--sans);
  font-size: 9px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--parch-dim);
  margin-left: 5px;
}
.op-desc { font-size: 12px; color: #5c4a2e; line-height: 1.35; }
.op-sold {
  font-family: var(--serif);
  font-style: italic;
  font-size: 12.5px;
  color: var(--parch-dim);
}

/* ─── A calling card for someone already dead ──────────────────────────────────
   Placed here, after the livery and body rules, because it overrules them: the
   parchment goes to slate and the ink to moonlight, the ghost tile's palette at
   card scale. It breathes on the same 3.6s as the seats behind the graveyard
   door, so the shop and the graveyard speak one language. */
.offer-patron.offer-patron--ghost {
  background:
    radial-gradient(120% 85% at 50% -15%, rgba(126, 178, 240, 0.22), transparent 68%),
    linear-gradient(170deg, #202b45 0%, #151d31 55%, #0d1220 100%);
  border: 1px dashed rgba(178, 214, 255, 0.5);
  box-shadow:
    inset 0 0 0 3px rgba(140, 186, 240, 0.1),
    inset 0 0 26px rgba(104, 156, 220, 0.2),
    0 6px 20px rgba(0, 0, 0, 0.6),
    0 0 26px rgba(122, 172, 236, 0.28);
  animation: ghostBreath calc(3.6s / var(--aspd)) ease-in-out infinite;
}
.offer-patron--ghost .op-name {
  color: #e4efff;
  text-shadow: 0 0 9px rgba(150, 200, 255, 0.5);
}
.offer-patron--ghost .op-title  { color: #86b0dd; }
.offer-patron--ghost .op-desc   { color: #aac4e4; }
.offer-patron--ghost .op-rarity { color: #7e9dc4; }
.offer-patron--ghost .op-sold   { color: #9db6d6; }
/* A livery dye is a dark ink meant for parchment; on slate it needs its
   bright twin (the same pair the seat's livery pin uses). */
.offer-patron--ghost .op-guild  { color: var(--livery-brt); }
.offer-patron.offer-patron--ghost .op-portrait {
  background:
    radial-gradient(circle at 50% 34%, rgba(150, 198, 250, 0.26), rgba(10, 15, 28, 0.92) 76%),
    repeating-linear-gradient(0deg, rgba(150, 195, 245, 0.08) 0 1px, transparent 1px 3px);
  border-color: rgba(150, 195, 250, 0.4);
  box-shadow: inset 0 0 18px rgba(90, 140, 205, 0.25);
}
.offer-patron.offer-patron--ghost .op-portrait .op-emoji {
  filter: grayscale(0.45) brightness(1.4) drop-shadow(0 0 9px rgba(150, 205, 255, 0.65));
  opacity: 0.95;
}
.op-ghost {
  color: #a8d4ff;
  font-weight: 800;
  letter-spacing: 0.2px;
  text-shadow: 0 0 8px rgba(120, 180, 255, 0.45);
}

.btn-price {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-weight: 800;
  font-size: 14px;
  color: #3a2606;
  background: linear-gradient(180deg, var(--brass-hi), var(--brass));
  border: 1px solid rgba(80, 50, 5, 0.55);
  border-radius: 9px;
  padding: 7px 13px;
  cursor: pointer;
  box-shadow: 0 2.5px 0 var(--brass-dark), 0 4px 9px rgba(120, 80, 10, 0.3);
  transition: transform calc(0.08s / var(--aspd)), filter calc(0.12s / var(--aspd));
  white-space: nowrap;
}
.btn-price:hover:not(:disabled) { filter: brightness(1.09); }
.btn-price:active:not(:disabled) { transform: translateY(2px); box-shadow: 0 0.5px 0 var(--brass-dark); }
.btn-price:disabled { opacity: 0.45; cursor: not-allowed; filter: grayscale(0.5); }

.offer-tiles {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 11px;
}
.offer-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9px;
  padding: 13px 10px 12px;
  border-radius: 11px;
  background: rgba(255, 250, 235, 0.55);
  border: 1px solid rgba(138, 95, 29, 0.4);
  box-shadow: 0 2px 7px rgba(100, 70, 20, 0.15);
}
.offer-tile .tile { cursor: help; }

/* Sold state is a class toggle, so buying never rebuilds the sheet. The label
   is given the price button's exact metrics so swapping them shifts nothing. */
.offer-patron .op-sold, .offer-tile .op-sold, .offer-paint .op-sold { display: none; }
.offer--sold .op-sold { display: inline-flex; }
.offer--sold .btn-price { display: none; }
.btn-price, .offer--sold .op-sold {
  min-height: 34px;
  align-items: center;
  justify-content: center;
}

/* Sundry offers (paint tubes) — .paint-pot survives for the opening draft */
.market-sec--paint { margin-top: 16px; }
.offer-paint .paint-tube { margin-top: 2px; }
.offer-paint {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 11px;
  align-items: center;
  padding: 9px 12px;
  border-radius: 11px;
  background: rgba(255, 250, 235, 0.55);
  border: 1px solid rgba(138, 95, 29, 0.45);
  box-shadow: 0 2px 7px rgba(100, 70, 20, 0.15);
}
.paint-pot {
  width: 26px; height: 30px;
  border-radius: 4px 4px 8px 8px;
  border: 2px solid rgba(40, 25, 10, 0.55);
  position: relative;
  box-shadow: inset 0 -8px 10px rgba(0, 0, 0, 0.2), inset 0 2px 3px rgba(255, 255, 255, 0.4);
}
.paint-pot::before {
  content: '';
  position: absolute;
  top: -6px; left: 50%;
  transform: translateX(-50%);
  width: 16px; height: 6px;
  border-radius: 3px 3px 0 0;
  background: #6b4630;
  border: 1.5px solid rgba(40, 25, 10, 0.55);
}
.paint-pot--crimson { background: var(--crimson); }
.paint-pot--azure   { background: var(--azure); }
.paint-pot--jade    { background: var(--jade); }
.paint-pot--amber   { background: var(--amber); }

/* ── The Gilder's key: what each metal is worth ──────────────────────────────── */
.trim-key {
  display: flex;
  flex-wrap: wrap;
  gap: 5px 16px;
  margin: 0 0 12px;
  padding: 9px 12px;
  border-radius: 10px;
  background: rgba(8, 4, 10, 0.3);
  border: 1px dashed rgba(243, 222, 184, 0.2);
  font-size: 11.5px;
  color: var(--muted);
}
.trim-key-item { display: inline-flex; align-items: center; gap: 6px; }
.trim-key-item b { color: var(--parch); font-family: var(--serif); font-size: 12.5px; }

.trim-swatch {
  width: 13px; height: 13px;
  flex: none;
  border-radius: 3px;
  background: linear-gradient(168deg, var(--ivory), var(--ivory-2));
  box-shadow: inset 0 0 0 2.5px var(--metal, #d9a64b);
}
/* The Dresser's key: a notch bitten out of the side it reaches toward */
.nick-swatch {
  width: 13px; height: 13px;
  flex: none;
  border-radius: 3px;
  background: linear-gradient(168deg, var(--ivory), var(--ivory-2));
  box-shadow: inset 0 0 0 1px rgba(88, 60, 22, 0.45);
}
.nick-swatch--right {
  -webkit-mask-image: radial-gradient(circle 4px at 100% 50%, transparent 3.4px, #000 4px);
          mask-image: radial-gradient(circle 4px at 100% 50%, transparent 3.4px, #000 4px);
}
.nick-swatch--left {
  -webkit-mask-image: radial-gradient(circle 4px at 0% 50%, transparent 3.4px, #000 4px);
          mask-image: radial-gradient(circle 4px at 0% 50%, transparent 3.4px, #000 4px);
}

.trim-swatch--gold    { --metal: #d9a64b; }
.trim-swatch--silver  { --metal: #aab4c4; }
.trim-swatch--cobalt  { --metal: #3f68ad; box-shadow: inset 0 0 0 1px #8ec6ff; }
.trim-swatch--purple  { --metal: #8a5fb0; }

/* ── The compost heap: the type case, dark and loamy, nothing bought ────────── */
.market-compost { margin-top: 22px; }
/* Six columns whatever the heap holds, so it reads as a bed with room in it.
   The doubled class out-specifies the type case's wood a few rules down. */
.offer-tiles--compost { grid-template-columns: repeat(6, 1fr); }
.sheet--market .offer-tiles.offer-tiles--compost {
  background: linear-gradient(180deg, #2b2418 0%, #1d1810 60%, #141009 100%);
  border-color: rgba(60, 90, 50, 0.45);
  box-shadow:
    inset 0 2px 0 rgba(120, 170, 110, 0.12),
    inset 0 -6px 12px rgba(0, 0, 0, 0.4),
    0 8px 20px rgba(0, 0, 0, 0.45);
}
.sheet--market .offer-tile.offer-compost {
  background: linear-gradient(180deg, #1b2318 0%, #121a10 100%);
  border-color: rgba(63, 163, 113, 0.4);
}
.offer-compost .btn-price {
  background: linear-gradient(180deg, #3f8a5c, #29603f);
  border-color: rgba(144, 232, 186, 0.4);
  color: #eafff3;
  box-shadow: 0 2px 0 #1d472e;
}
.offer-compost .btn-price:active:not(:disabled) { box-shadow: 0 0.5px 0 #1d472e; }

/* A tile The Chapman has made free stops looking like a price at all */
.offer-tile--free .btn-price {
  background: linear-gradient(180deg, #b8862c, #7a5313);
  border-color: rgba(246, 214, 138, 0.5);
  text-transform: uppercase;
  letter-spacing: 1px;
  font-size: 11px;
}

@media (max-width: 780px) { .offer-tiles--compost { grid-template-columns: repeat(3, 1fr); } }

/* ── Stalls — market pitches under a striped awning ──────────────────────────── */
.market-stalls { margin-top: 22px; }

.stall-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 11px;
}
@media (max-width: 780px) { .stall-row { grid-template-columns: 1fr; } }

.offer-stall {
  position: relative;
  overflow: hidden;
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 12px;
  align-items: center;
  padding: 15px 13px 11px;
  border-radius: 11px;
  background: rgba(255, 250, 235, 0.55);
  border: 1px solid rgba(138, 95, 29, 0.5);
  box-shadow: 0 2px 7px rgba(100, 70, 20, 0.15);
}
.offer-stall::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 6px;
  background: repeating-linear-gradient(90deg,
    var(--awning, rgba(178, 58, 46, 0.75)) 0 14px,
    rgba(242, 231, 203, 0.9) 14px 28px);
  border-bottom: 1px solid rgba(138, 95, 29, 0.35);
}

/* Visiting is free, so the fee sits under the button as a caption rather than
   on it. */
.stall-action { display: grid; justify-items: center; gap: 4px; }
.stall-from {
  font-size: 9.5px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  gap: 3px;
  white-space: nowrap;
}
.stall-from .coin { width: 11px; height: 11px; }

/* Each stall flies its own colours */
.awning--smelter     { --awning: #b23a2e; }
.awning--painter     { --awning: #4f86c9; }
.awning--gilder      { --awning: #b8821f; }
.awning--stereotyper { --awning: #5f6d80; }
.awning--punchcutter { --awning: #8a5fb0; }
.awning--dresser     { --awning: #a8632c; }

/* Inside a stall, the sheet itself wears the awning */
.sheet--stall {
  position: relative;
  overflow: hidden;
  padding-top: 32px;
}
.sheet--stall::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 9px;
  background: repeating-linear-gradient(90deg,
    var(--awning, rgba(178, 58, 46, 0.75)) 0 18px,
    rgba(242, 231, 203, 0.9) 18px 36px);
  border-bottom: 1px solid rgba(138, 95, 29, 0.35);
}

/* The purse: coins stay in view wherever spending happens */
.market-purse {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-top: 7px;
  padding: 4px 15px 5px 11px;
  border-radius: 999px;
  background: rgba(217, 166, 75, 0.16);
  border: 1px solid rgba(138, 95, 29, 0.5);
  box-shadow: inset 0 1px 0 rgba(255, 250, 230, 0.5);
  font-family: var(--serif);
  font-weight: 900;
  font-size: 19px;
  color: var(--parch-ink);
  font-variant-numeric: tabular-nums;
}

.stall-emoji { font-size: 26px; line-height: 1; filter: drop-shadow(0 1px 1px rgba(0,0,0,0.2)); }

.stall-warn { margin: -6px 0 10px; color: #8a3a32; font-weight: 600; }

/* The Painter's colour choice */
.painter-colours {
  display: flex;
  gap: 10px;
  margin: 2px 0 12px;
}
.paint-swatch {
  width: 34px; height: 34px;
  border-radius: 50%;
  border: 2px solid rgba(40, 25, 10, 0.5);
  cursor: pointer;
  box-shadow: inset 0 -6px 8px rgba(0, 0, 0, 0.2), inset 0 2px 3px rgba(255, 255, 255, 0.4);
  transition: transform calc(0.12s / var(--aspd)), outline-color calc(0.12s / var(--aspd));
}
.paint-swatch--crimson { background: var(--crimson); }
.paint-swatch--azure   { background: var(--azure); }
.paint-swatch--jade    { background: var(--jade); }
.paint-swatch--amber   { background: var(--amber); }
.paint-swatch:hover { transform: translateY(-2px); }
.paint-swatch--sel {
  outline: 2.5px solid #2f6e3e;
  outline-offset: 2px;
  transform: scale(1.1);
}

/* The spread a proposal stall lays out */
.proposal-grid { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 600px) { .proposal-grid { grid-template-columns: repeat(2, 1fr); } }

.market-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 22px;
  padding-top: 16px;
  border-top: 1px dashed rgba(120, 90, 40, 0.4);
  flex-wrap: wrap;
}
.market-spacer { flex: 1; }

.market-foot .btn-quiet {
  background: rgba(120, 90, 40, 0.13);
  color: var(--parch-ink);
  border-color: rgba(120, 90, 40, 0.4);
  box-shadow: 0 2px 0 rgba(100, 70, 20, 0.3);
  text-transform: none;
  letter-spacing: 0.2px;
  font-weight: 700;
}

.mini-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  padding: 12px;
  border-radius: 11px;
  background: rgba(120, 90, 40, 0.1);
  border: 1px dashed rgba(120, 90, 40, 0.35);
  max-height: 46vh;
  overflow: auto;
}

/* ── The case's tally: what the collection holds, by colour ───────────────────
   The colour rides a dot rather than the text, so numbers stay legible on dark
   leather and an empty bucket can fade. */
.case-tally {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}
.case-tally-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px 3px 7px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.2px;
  color: var(--parch);
  background: rgba(35, 22, 14, 0.5);
  border: 1px solid rgba(243, 222, 184, 0.18);
  cursor: help;
}
.case-tally-chip b {
  font-family: var(--serif);
  font-size: 13px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--brass-hi);
}
.case-tally-chip--none { opacity: 0.45; }
.case-tally-chip--none b { color: var(--muted); }
.case-tally-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--chip, var(--muted));
  box-shadow: 0 0 0 1px rgba(10, 4, 12, 0.55), 0 0 6px var(--chip, transparent);
}
.case-tally-chip--crimson { --chip: #ff9d8e; }
.case-tally-chip--azure   { --chip: #8ec6ff; }
.case-tally-chip--jade    { --chip: #90e8ba; }
.case-tally-chip--amber   { --chip: #ffd68c; }
.case-tally-chip--bare    { --chip: rgba(243, 222, 184, 0.4); }
/* Rainbow metal is every colour at once, and its chip says so. */
.case-tally-chip--rainbow .case-tally-dot {
  background: conic-gradient(#ff9d8e, #ffd68c, #90e8ba, #8ec6ff, #ff9d8e);
  box-shadow: 0 0 0 1px rgba(10, 4, 12, 0.55), 0 0 7px rgba(255, 235, 200, 0.5);
}

/* ═══ The Market — a candlelit skin over the shop views ════════════════════════
   Scoped under .sheet--market so the draft keeps its parchment. */

.sheet--market {
  background:
    radial-gradient(700px 320px at 50% -10%, rgba(233, 178, 102, 0.16), transparent 65%),
    linear-gradient(175deg, #342030 0%, #251523 55%, #1e1120 100%);
  color: var(--parch);
  border-color: rgba(217, 166, 75, 0.4);
  box-shadow:
    inset 0 0 0 1px rgba(255, 235, 200, 0.07),
    inset 0 0 70px rgba(8, 2, 10, 0.5),
    0 36px 90px rgba(0, 0, 0, 0.8);
}
.sheet--market .market-title {
  background: linear-gradient(120deg, var(--brass-hi), var(--brass));
  -webkit-background-clip: text;
  background-clip: text;
}
.sheet--market .market-sec  { color: var(--parch); }
.sheet--market .market-sub  { color: var(--muted); }
.sheet--market .sheet-note   { color: var(--muted); }
.sheet--market .sheet-note b { color: var(--parch); }
.sheet--market .market-foot { border-top-color: rgba(243, 222, 184, 0.16); }
.sheet--market .market-foot .btn-quiet {
  background: rgba(243, 222, 184, 0.07);
  color: var(--parch);
  border-color: rgba(243, 222, 184, 0.22);
  box-shadow: 0 2px 0 rgba(10, 4, 12, 0.6);
}
.sheet--market .market-purse {
  background: rgba(35, 22, 14, 0.55);
  border-color: rgba(217, 166, 75, 0.4);
  box-shadow: inset 0 1px 0 rgba(255, 230, 170, 0.08);
  color: var(--brass-hi);
}
.sheet--market .mini-grid {
  background: rgba(8, 4, 10, 0.35);
  border-color: rgba(243, 222, 184, 0.2);
}
.sheet--market .stall-warn { color: #ffb3a6; }
.sheet--market .pickable.picked { outline-color: #8fdfae; box-shadow: 0 0 16px rgba(143, 223, 174, 0.3); }

/* ── The table, restated at the top of the sheet ──────────────────────────────
   The board's patron shelf again, on the same .shelf/.patron classes. */
.sheet--market .market-shelf { margin-bottom: 6px; }
.sheet--market .market-shelf .shelf {
  gap: 8px;
  padding: 8px 10px 10px;
}
.sheet--market .market-shelf .patron { min-height: 56px; }
.sheet--market .market-shelf .patron[data-patron] { cursor: pointer; }
/* A full table: the tally turns urgent — dismissing is the only way to hire. */
.sheet--market .market-shelf--wanted .market-sub { color: #ffb3a6; font-weight: 800; }

/* ── The workbench, beside the table ──────────────────────────────────────────
   The table flexes, the bench hugs its slots at the end of the line. Flex-wrap
   is the responsive rule — too narrow for both and the bench drops below, no
   media query needed. Nothing arms from here, so the slots are plain cards. */
.sheet--market .market-hold {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: 2px 16px;
  margin-bottom: 6px;
}
.sheet--market .market-hold .market-shelf { flex: 1 1 380px; margin-bottom: 0; }
.sheet--market .market-bench { flex: 0 1 auto; }
.sheet--market .sundries--market {
  gap: 8px;
  padding: 8px 10px 10px;
}
.sheet--market .sundries--market .sundry { min-height: 56px; }

/* Returning from a stall: the market slides back in from the left */
.sheet--return { animation: shopReturn calc(0.28s / var(--aspd)) cubic-bezier(.2, 1, .4, 1); }
@keyframes shopReturn {
  from { transform: translateX(-46px); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
/* …and a stall slides in from the right, as if walking over */
.sheet--stall { animation: stallIn calc(0.28s / var(--aspd)) cubic-bezier(.2, 1, .4, 1); }
@keyframes stallIn {
  from { transform: translateX(46px); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

/* ── Patron business cards on the dark counter ───────────────────────────────── */
.sheet--market .offer-patron {
  box-shadow:
    inset 0 0 0 3px rgba(255, 252, 240, 0.9),
    inset 0 0 0 4px var(--hairline, rgba(138, 95, 29, 0.4)),
    0 6px 16px rgba(0, 0, 0, 0.55);
}

/* ── Tiles in a type case ────────────────────────────────────────────────────── */
.sheet--market .offer-tiles {
  background: linear-gradient(180deg, var(--wood-light) 0%, var(--wood) 20%, var(--wood-dark) 100%);
  border: 1px solid rgba(0, 0, 0, 0.5);
  border-radius: var(--r-md);
  padding: 10px;
  box-shadow:
    inset 0 2px 0 rgba(255, 220, 170, 0.15),
    inset 0 -6px 12px rgba(0, 0, 0, 0.35),
    0 8px 20px rgba(0, 0, 0, 0.45);
}
.sheet--market .offer-tile {
  background: linear-gradient(180deg, #1e1018 0%, #2a1822 100%);
  border: 1px solid rgba(0, 0, 0, 0.55);
  border-radius: 8px;
  box-shadow:
    inset 0 3px 10px rgba(0, 0, 0, 0.65),
    inset 0 0 0 1px rgba(255, 235, 200, 0.05);
}

/* ── Sundries on a wooden shelf ──────────────────────────────────────────────── */
.sheet--market .offer-paint {
  background: linear-gradient(180deg, rgba(93, 63, 40, 0.6), rgba(58, 40, 24, 0.6));
  border: 1px solid rgba(0, 0, 0, 0.45);
  box-shadow: inset 0 1px 0 rgba(255, 220, 170, 0.12);
}
.sheet--market .offer-paint .op-name { color: var(--parch); }

/* ── Stall façades ───────────────────────────────────────────────────────────── */
.sheet--market .offer-stall {
  background: linear-gradient(180deg, #2a1822 0%, #1e1018 100%);
  border-color: rgba(0, 0, 0, 0.5);
  box-shadow: inset 0 0 0 1px rgba(255, 235, 200, 0.05), 0 5px 14px rgba(0, 0, 0, 0.45);
}
.sheet--market .offer-stall .op-name { color: var(--parch); }
.sheet--market .offer-stall .op-desc { color: var(--muted); }
.sheet--market .stall-emoji {
  display: grid;
  place-items: center;
  width: 42px; height: 42px;
  background: linear-gradient(170deg, var(--parch), var(--parch-2));
  border: 1px solid var(--brass-dark);
  border-radius: 8px;
  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.5), inset 0 0 0 2px rgba(255, 248, 226, 0.5);
  font-size: 22px;
}

/* ── Prices as swing tags ────────────────────────────────────────────────────── */
.sheet--market .btn-price {
  position: relative;
  clip-path: polygon(10px 0, 100% 0, 100% 100%, 10px 100%, 0 50%);
  padding: 7px 13px 7px 20px;
  border: none;
  border-radius: 4px;
  background: linear-gradient(180deg, var(--parch), var(--parch-2));
  color: var(--parch-ink);
  box-shadow: none;
  filter: drop-shadow(0 3px 4px rgba(0, 0, 0, 0.5));
  transform: rotate(-2deg);
}
.sheet--market .btn-price::before {
  content: '';
  position: absolute;
  left: 7px; top: 50%;
  transform: translateY(-50%);
  width: 5px; height: 5px;
  border-radius: 50%;
  background: rgba(30, 16, 24, 0.85);
}
.sheet--market .btn-price:hover:not(:disabled) { transform: rotate(0deg) scale(1.04); filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.55)) brightness(1.04); }
.sheet--market .btn-price:active:not(:disabled) { transform: rotate(0deg) scale(0.98); }
.sheet--market .btn-price:disabled { filter: grayscale(0.6) drop-shadow(0 2px 3px rgba(0, 0, 0, 0.4)); opacity: 0.45; }

/* The day's haggle: the tag's own paper is tinted — cool green when the patron
   is going a Coin under, warm red a Coin over. One coin either way is worth a
   glance, not a sentence, so it says nothing in words until the tag is hovered.
   After the tag's own rule, so it wins on order at equal specificity. */
.sheet--market .btn-price--under { background: linear-gradient(180deg, #dff2e3, #b6ddc4); }
.sheet--market .btn-price--over  { background: linear-gradient(180deg, #fbdfd9, #eebbb0); }

/* ── SOLD, stamped in ink ────────────────────────────────────────────────────── */
.sheet--market .op-sold {
  font-family: var(--sans);
  font-style: normal;
  font-weight: 900;
  font-size: 11px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: #d9564a;
  border: 2.5px solid currentColor;
  border-radius: 5px;
  padding: 3px 9px;
  transform: rotate(-8deg);
  opacity: 0.9;
}
.sheet--market .offer--sold { opacity: 1; }
.sheet--market .offer--sold > *:not(.op-sold) { opacity: 0.4; }
.sheet--market .offer--sold .op-sold { animation: stampIn calc(0.28s / var(--aspd)) cubic-bezier(.2, 1.4, .4, 1) both; }
@keyframes stampIn {
  0%   { transform: rotate(-8deg) scale(2.4); opacity: 0; }
  55%  { transform: rotate(-8deg) scale(0.92); opacity: 1; }
  100% { transform: rotate(-8deg) scale(1); opacity: 0.9; }
}

/* ── The reward tally, printed as a receipt ──────────────────────────────────── */
.sheet--market .reward-line {
  background: linear-gradient(175deg, var(--parch), var(--parch-2));
  border-bottom: 2px dashed rgba(120, 90, 40, 0.5);
  border-radius: 3px 3px 0 0;
  padding: 8px 13px 9px;
  transform: rotate(0.8deg);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.45);
  color: var(--parch-dim);
}
.sheet--market .reward-part b { color: #2f6e3e; }
.sheet--market .reward-total  { color: var(--parch-ink); }

/* ─── Boosted values ──────────────────────────────────────────────────────────── */
/* Ash. A destroyed tile chars where it sits — it never flies to the tray. */
.tile--burning {
  animation: tileBurn calc(0.62s / var(--aspd)) ease-in forwards;
  pointer-events: none;
}
@keyframes tileBurn {
  0% { filter: none; transform: none; opacity: 1; }
  22% {
    filter: brightness(1.55) saturate(1.7) drop-shadow(0 0 13px rgba(255, 138, 43, 0.95));
    transform: translateY(-5px) scale(1.07) rotate(-2deg);
  }
  58% {
    filter: brightness(1.15) sepia(0.75) drop-shadow(0 0 20px rgba(209, 71, 26, 0.8));
    transform: translateY(3px) scale(0.95) rotate(1.5deg);
  }
  100% {
    filter: brightness(0.3) sepia(1) contrast(0.55);
    transform: translateY(16px) scale(0.7) rotate(4deg);
    opacity: 0;
  }
}

/* A grown tile wears its corner number in jade wherever it appears. */
.tile-pts--grown {
  opacity: 1;
  font-weight: 800;
  color: var(--jade);
  text-shadow: 0 1px 0 rgba(240, 255, 244, 0.85), 0 0 7px rgba(90, 190, 130, 0.55);
}

/* In the word groove a tile's corner number is what it will actually pay, and
   changes when that beats its resting value. Only the word can cause that — a
   nick's reach, a Monogrammist's echo — never a silver trim. */
.tile-pts--boosted {
  opacity: 1;
  font-size: 13px;
  font-weight: 900;
  color: #8a5f1d;
  text-shadow: 0 1px 0 rgba(255, 245, 215, 0.9), 0 0 8px rgba(217, 166, 75, 0.7);
}
.tile--mini .tile-pts--boosted { font-size: 10px; }

/* …unless the patron paying it is writing it into the tile for good: a jade
   trellis pays the Points the print then grows in, so the groove shows them in
   jade. Ordered after --boosted so it wins on a tile carrying both classes. */
.tile-pts--growing {
  color: var(--jade);
  text-shadow: 0 1px 0 rgba(240, 255, 244, 0.9), 0 0 8px rgba(90, 190, 130, 0.75);
}

/* renderWord staggers the delays so the change ripples outward from the nick. */
.pts-pop {
  animation: ptsPop calc(0.5s / var(--aspd)) cubic-bezier(.3, 1.6, .5, 1) both;
  transform-origin: bottom right;
}
@keyframes ptsPop {
  0%   { transform: scale(1); }
  35%  { transform: scale(2.1); filter: brightness(1.5); }
  100% { transform: scale(1); }
}

/* ─── The manuscript ──────────────────────────────────────────────────────────── */
/* No badge — the count is in the tooltip (see renderStatus in js/render.js). */
.btn-icon--manuscript { position: relative; font-size: 15px; }

/* A book rather than a ledger: chapters, running prose, folio in the margin. */
.sheet--manuscript {
  width: min(660px, 94vw);
  padding-bottom: 30px;
}
.book-head { border-bottom: 1px solid rgba(138, 95, 29, 0.3); padding-bottom: 12px; }

.book {
  margin-top: 4px;
  max-height: 64vh;
  overflow: auto;
  padding: 4px 4px 4px 2px;
}
.book-blank {
  margin: 26px 0 14px;
  text-align: center;
  font-family: var(--serif);
  font-style: italic;
  font-size: 15px;
  color: var(--parch-dim);
}

/* ── A chapter heading ──────────────────────────────────────────────────────── */
.book-chapter { margin-top: 22px; }
.book-chapter:first-child { margin-top: 14px; }

.book-chapter-head {
  display: grid;
  /* number · title · the rule that runs between them · the tally */
  grid-template-columns: auto auto 1fr auto;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 9px;
}
.book-chapter-num {
  font-family: var(--serif);
  font-weight: 900;
  font-size: 11px;
  letter-spacing: 2.4px;
  text-transform: uppercase;
  color: var(--parch-dim);
  white-space: nowrap;
}
.book-chapter-title {
  margin: 0;
  font-family: var(--serif);
  font-weight: 700;
  font-style: italic;
  font-size: 18px;
  color: var(--parch-ink);
  white-space: nowrap;
}
/* The rule gets its own grid cell: an ::after pinned to the title had nothing to
   measure against, and a stretched cell made it zero-width. */
.book-rule {
  border-top: 1px solid rgba(138, 95, 29, 0.4);
  align-self: center;
  min-width: 12px;
}
.book-chapter-tally {
  font-size: 10.5px;
  letter-spacing: 0.3px;
  color: var(--parch-dim);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ── One page of that chapter ───────────────────────────────────────────────── */
.book-leaf {
  display: grid;
  grid-template-columns: 26px 1fr;
  align-items: baseline;
  gap: 8px;
  padding: 3px 0;
}
.book-folio {
  font-family: var(--serif);
  font-size: 12px;
  font-style: italic;
  text-align: right;
  color: var(--parch-dim);
  font-variant-numeric: tabular-nums;
  cursor: help;
}
/* The Deadline is marked, not numbered — a fleuron where the folio would sit. */
.book-folio--deadline { color: var(--brass-dark); font-style: normal; font-size: 13px; }

.book-prose {
  margin: 0;
  font-family: var(--serif);
  font-size: 15px;
  line-height: 1.85;
  color: var(--parch-ink);
}

/* ── A word, and its score as a footnote figure ─────────────────────────────── */
/* A word and its figure never come apart, but the prose has to break between
   entries — hence a real space in the markup, not merely this margin. book-*
   rather than ms-*: .ms-word is the board strip's, and pale on parchment. */
.book-entry { white-space: nowrap; }
.book-entry + .book-entry { margin-left: 0.3em; }
.book-word {
  font-variant-caps: small-caps;
  text-transform: lowercase;
  letter-spacing: 0.6px;
}
.book-score {
  font-size: 0.6em;
  vertical-align: super;
  margin-left: 1px;
  color: var(--parch-dim);
  font-variant-numeric: tabular-nums;
}
/* The first word of a chapter takes the initial, as an opening line does */
.book-initial {
  font-size: 1.5em;
  font-weight: 900;
  line-height: 0;
  text-transform: uppercase;
  color: var(--brass-dark);
}
/* The best word of the run is illuminated */
.book-entry--best .book-word {
  font-weight: 900;
  color: #6b4a12;
  background: linear-gradient(180deg, rgba(217, 166, 75, 0.34), rgba(217, 166, 75, 0.14));
  border-radius: 3px;
  padding: 1px 5px;
  margin: 0 1px;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}
.book-entry--best .book-score { color: var(--brass-dark); font-weight: 800; }

/* ─── The Colophon (end-of-chapter upgrade) ─────────────────────────────────────
   Bigger cards, centred, no prices — built on the same dark market skin. */
.sheet--colophon { width: min(760px, 96vw); text-align: center; }
.sheet--colophon .sheet-head { justify-content: center; text-align: center; }
.sheet--colophon .market-sec { justify-content: center; }

.colophon-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
  margin-top: 6px;
}
@media (max-width: 620px) { .colophon-grid { grid-template-columns: 1fr; } }

.colophon-card {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9px;
  padding: 22px 14px 18px;
  border-radius: 13px;
  cursor: pointer;
  font-family: var(--sans);
  text-align: center;
  background: linear-gradient(175deg, #2a1822 0%, #1e1018 100%);
  border: 1px solid rgba(217, 166, 75, 0.4);
  box-shadow:
    inset 0 0 0 1px rgba(255, 235, 200, 0.06),
    0 8px 20px rgba(0, 0, 0, 0.45);
  transition: transform calc(0.12s / var(--aspd)), box-shadow calc(0.12s / var(--aspd)), border-color calc(0.12s / var(--aspd));
}
.colophon-card:hover {
  transform: translateY(-4px);
  border-color: var(--brass);
  box-shadow: inset 0 0 0 1px rgba(255, 235, 200, 0.08), 0 0 22px rgba(217, 166, 75, 0.25), 0 12px 26px rgba(0, 0, 0, 0.5);
}

.colophon-icon { font-size: 34px; line-height: 1; filter: drop-shadow(0 2px 3px rgba(0,0,0,0.4)); }
.colophon-card .paint-pot { width: 30px; height: 34px; }

.colophon-card-name {
  font-family: var(--serif);
  font-weight: 700;
  font-size: 15px;
  color: var(--parch);
}
.colophon-card-desc { font-size: 11.5px; color: var(--muted); line-height: 1.4; }

/* Paint cards borrow the letter's own colour for their border glow */
.colophon-card--crimson:hover { border-color: var(--crimson); box-shadow: inset 0 0 0 1px rgba(255,235,200,0.08), 0 0 22px rgba(201,79,67,0.35), 0 12px 26px rgba(0,0,0,0.5); }
.colophon-card--azure:hover   { border-color: var(--azure);   box-shadow: inset 0 0 0 1px rgba(255,235,200,0.08), 0 0 22px rgba(79,134,201,0.35), 0 12px 26px rgba(0,0,0,0.5); }
.colophon-card--jade:hover    { border-color: var(--jade);    box-shadow: inset 0 0 0 1px rgba(255,235,200,0.08), 0 0 22px rgba(63,163,113,0.35), 0 12px 26px rgba(0,0,0,0.5); }
.colophon-card--amber:hover   { border-color: var(--amber);   box-shadow: inset 0 0 0 1px rgba(255,235,200,0.08), 0 0 22px rgba(210,154,58,0.35), 0 12px 26px rgba(0,0,0,0.5); }

.colophon-card--picked {
  animation: colophonPick calc(0.56s / var(--aspd)) ease;
}
@keyframes colophonPick {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.08) translateY(-6px);
         box-shadow: inset 0 0 0 1px rgba(255,235,200,0.1), 0 0 0 3px var(--brass-hi), 0 0 34px rgba(246, 214, 138, 0.7); }
  100% { transform: scale(1); }
}

/* ─── Speech bubbles (a patron's unsolicited opinion) ───────────────────────────
   Rides the #fx layer above the shelf — never blocks a tap, never gates flow. */
.speech-bubble {
  position: fixed;
  transform: translate(-50%, 0);
  max-width: 148px;
  padding: 7px 11px 8px;
  border-radius: 12px;
  background: linear-gradient(175deg, var(--parch) 0%, var(--parch-2) 100%);
  color: var(--parch-ink);
  border: 1px solid rgba(138, 95, 29, 0.5);
  box-shadow: 0 5px 16px rgba(0, 0, 0, 0.5);
  font-family: var(--serif);
  font-style: italic;
  font-weight: 600;
  font-size: 12px;
  line-height: 1.25;
  text-align: center;
  pointer-events: none;
  z-index: 320;
}
/* The Wordler marks in Wordle's own squares: no styling of their own beyond
   room to breathe, and none of the bubble's italic prose voice. */
.speech-bubble--wordle {
  font-family: var(--sans);
  font-style: normal;
  font-size: 19px;
  letter-spacing: 2px;
  line-height: 1.1;
  padding: 8px 11px;
  /* The bubble is sized for a line of prose; a row of squares must not wrap,
     or the marking reads as two guesses instead of one. */
  max-width: none;
  white-space: nowrap;
}

.speech-bubble::after {
  content: '';
  position: absolute;
  left: 50%; bottom: -7px;
  transform: translateX(-50%);
  width: 0; height: 0;
  border-left: 7px solid transparent;
  border-right: 7px solid transparent;
  border-top: 8px solid var(--parch-2);
}

/* ─── Opening draft ───────────────────────────────────────────────────────────── */
.sheet--draft { width: min(860px, 96vw); }
.sheet--draft .market-sec { margin-top: 18px; }
.sheet--draft .market-sec:first-of-type { margin-top: 4px; }

.draft-paints {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 9px;
}
.offer-tiles--draft { grid-template-columns: repeat(5, 1fr); }

.pickable {
  position: relative;
  cursor: pointer;
  transition:
    transform calc(0.12s / var(--aspd)),
    box-shadow calc(0.12s / var(--aspd)),
    opacity calc(0.12s / var(--aspd));
}
.pickable:hover:not(.pick-locked) { transform: translateY(-2px); }

.pickable .pick-mark {
  position: absolute;
  top: -8px; right: -8px;
  width: 23px; height: 23px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 12px;
  font-weight: 900;
  background: #2f6e3e;
  color: #eafff0;
  border: 1.5px solid rgba(0, 0, 0, 0.35);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
  opacity: 0;
  transform: scale(0.5);
  transition: opacity calc(0.14s / var(--aspd)), transform calc(0.14s / var(--aspd));
}
.pickable.picked .pick-mark { opacity: 1; transform: scale(1); }
.pickable.picked {
  outline: 2.5px solid #2f6e3e;
  outline-offset: 2px;
  box-shadow: 0 0 16px rgba(47, 110, 62, 0.35);
}
.pick-locked { opacity: 0.4; cursor: default; }

/* ─── Settings ────────────────────────────────────────────────────────────────── */
.set-row {
  display: grid;
  grid-template-columns: 130px 1fr;
  align-items: center;
  gap: 14px;
  padding: 13px 2px;
  border-bottom: 1px dashed rgba(120, 90, 40, 0.3);
}
.set-row > label {
  font-family: var(--serif);
  font-weight: 700;
  font-size: 14.5px;
}
.set-control { display: flex; align-items: center; gap: 12px; }
.set-control--stack { flex-direction: column; align-items: flex-start; gap: 6px; }
.set-value {
  font-weight: 800;
  font-size: 14px;
  min-width: 42px;
  font-variant-numeric: tabular-nums;
}
.set-note { font-size: 12px; color: var(--parch-dim); }

input[type='range'] {
  flex: 1;
  accent-color: #b8821f;
  cursor: pointer;
}
input[type='checkbox'] {
  width: 19px; height: 19px;
  accent-color: #b8821f;
  cursor: pointer;
}

.file-label {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-weight: 700;
  font-size: 12.5px;
  color: var(--parch-ink);
  background: rgba(120, 90, 40, 0.13);
  border: 1px solid rgba(120, 90, 40, 0.4);
  border-radius: 9px;
  padding: 8px 14px;
  cursor: pointer;
}
.file-label:hover { background: rgba(120, 90, 40, 0.22); }
.file-input { display: none; }

.dev-tools { margin-top: 14px; }
.dev-tools summary {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--parch-dim);
  cursor: pointer;
}
.dev-row { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
.dev-row .btn {
  background: rgba(120, 90, 40, 0.13);
  color: var(--parch-ink);
  border-color: rgba(120, 90, 40, 0.4);
  box-shadow: none;
  padding: 7px 12px;
  font-size: 11px;
}

/* ─── End screens ─────────────────────────────────────────────────────────────── */
.modal--center { z-index: 260; }

.sheet--end {
  width: min(560px, 94vw);
  text-align: center;
  display: grid;
  justify-items: center;
  gap: 10px;
  padding: 36px 38px;
}

.end-flourish {
  width: 64px; height: 64px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 30px;
  background: radial-gradient(circle at 35% 30%, #a4453c, #6e231c);
  color: #ffd9d2;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45), inset 0 2px 4px rgba(255, 200, 180, 0.3);
}
.end-flourish--win {
  background: radial-gradient(circle at 35% 30%, var(--brass-hi), #a87018);
  color: #4a3104;
}

.end-title {
  font-family: var(--serif);
  font-weight: 900;
  font-size: 31px;
  margin: 2px 0 0;
}
.end-title--win {
  background: linear-gradient(120deg, #7c5410, #c89030);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.sheet--dark .end-title { color: var(--parch); }

.end-sub {
  margin: 0;
  font-family: var(--serif);
  font-style: italic;
  font-size: 15px;
  opacity: 0.75;
  max-width: 400px;
}

.run-stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(150px, 1fr));
  gap: 10px;
  margin: 14px 0 16px;
  width: 100%;
}
.run-stat {
  display: grid;
  gap: 2px;
  padding: 12px 10px;
  border-radius: 11px;
  background: rgba(120, 90, 40, 0.12);
  border: 1px solid rgba(138, 95, 29, 0.3);
}
.sheet--dark .run-stat { background: rgba(243, 222, 184, 0.06); border-color: rgba(243, 222, 184, 0.14); }
.run-stat-num {
  font-family: var(--serif);
  font-weight: 900;
  font-size: 21px;
  word-break: break-word;
}
.run-stat-label {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  opacity: 0.6;
}

/* The Neologist's coining sheet: six letterpress slots to fill */
.coin-input {
  width: min(320px, 80vw);
  margin-top: 4px;
  padding: 10px 12px;
  font-family: var(--serif);
  font-size: 30px;
  font-weight: 700;
  letter-spacing: 10px;
  text-align: center;
  text-transform: uppercase;
  color: var(--parch-ink);
  background: rgba(255, 252, 240, 0.7);
  border: 1px solid #a98545;
  border-radius: 10px;
  box-shadow: inset 0 2px 6px rgba(90, 60, 20, 0.22);
}
.coin-input:focus {
  outline: none;
  border-color: #7c5410;
  box-shadow: inset 0 2px 6px rgba(90, 60, 20, 0.22), 0 0 0 3px rgba(200, 144, 48, 0.3);
}
.coin-input::placeholder { opacity: 0.28; letter-spacing: 10px; }
.coin-note {
  min-height: 16px;
  font-family: var(--serif);
  font-style: italic;
  font-size: 13px;
  opacity: 0.7;
}
.coin-note--bad { color: #8e3a32; opacity: 1; }

.end-actions { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }
.sheet--end .btn-quiet { text-transform: none; letter-spacing: 0.2px; }
.sheet--dark .btn-quiet { color: var(--parch); }
.sheet--end:not(.sheet--dark) .btn-quiet {
  background: rgba(120, 90, 40, 0.13);
  color: var(--parch-ink);
  border-color: rgba(120, 90, 40, 0.4);
  box-shadow: 0 2px 0 rgba(100, 70, 20, 0.3);
}

/* ─── Exchange mode ───────────────────────────────────────────────────────────── */
.rack--discard {
  outline: 2.5px dashed var(--warn);
  outline-offset: 3px;
  box-shadow:
    inset 0 2px 0 rgba(255, 220, 170, 0.16),
    inset 0 -8px 14px rgba(0, 0, 0, 0.4),
    0 0 22px rgba(255, 193, 105, 0.25),
    0 10px 26px rgba(0, 0, 0, 0.45);
}

.btn-discard--armed {
  filter: brightness(1.25);
  box-shadow: 0 3px 0 #101e33, 0 0 0 2.5px var(--warn), 0 0 18px rgba(255, 193, 105, 0.45);
}

/* ─── Popover (tap / long-press details) ──────────────────────────────────────── */
.tip-pop {
  position: fixed;
  z-index: 400;
  /* The tip is a label, not a surface: clicks pass through (and hide it), so it
     can never swallow a tap. Only its buttons catch the pointer. */
  pointer-events: none;
  max-width: 270px;
  padding: 11px 13px 12px;
  border-radius: 12px;
  background: linear-gradient(175deg, var(--parch) 0%, #ead9b4 100%);
  color: var(--parch-ink);
  border: 1px solid #a98545;
  box-shadow:
    inset 0 0 0 1px rgba(255, 250, 230, 0.55),
    0 14px 40px rgba(0, 0, 0, 0.6);
  font-size: 12.5px;
  line-height: 1.4;
  animation: tipIn calc(0.16s / var(--aspd)) ease-out;
}
@keyframes tipIn {
  from { transform: translateY(4px) scale(0.97); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
.tip-pop { max-width: 300px; }
.tip-head {
  font-family: var(--serif);
  font-weight: 700;
  font-size: 15px;
  margin-bottom: 4px;
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.tip-pts {
  font-family: var(--sans);
  font-size: 10px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--parch-dim);
}
.tip-line { color: #5c4a2e; margin-top: 3px; }

/* The calling card a ghost hands you, in the graveyard's colours. */
.tip-pop--ghost {
  background:
    radial-gradient(120% 80% at 50% -10%, rgba(126, 178, 240, 0.2), transparent 70%),
    linear-gradient(175deg, #202b45 0%, #0f1728 100%);
  color: #dbe9ff;
  border: 1px dashed rgba(178, 214, 255, 0.55);
  box-shadow:
    inset 0 0 0 1px rgba(140, 186, 240, 0.12),
    inset 0 0 24px rgba(104, 156, 220, 0.18),
    0 14px 40px rgba(0, 0, 0, 0.7),
    0 0 24px rgba(122, 172, 236, 0.25);
}
.tip-pop--ghost .tip-line,
.tip-pop--ghost .tip-feat  { color: #aac4e4; }
.tip-pop--ghost .op-rarity { color: #7e9dc4; }
.tip-pop--ghost .tip-btn {
  background: rgba(140, 186, 240, 0.14);
  color: #dbe9ff;
  border-color: rgba(150, 195, 250, 0.45);
  box-shadow: 0 2px 0 rgba(58, 92, 148, 0.55);
}

/* ── The Wordler's board ──────────────────────────────────────────────────────
   Every guess he has marked, newest last — Wordle without the previous rows is
   a memory test, not a puzzle. The word is set in the tile face so it reads as
   type; the squares carry the verdict. */
.wordle-board {
  margin: 9px 0 2px;
  padding: 7px 9px;
  border-radius: 7px;
  background: rgba(45, 138, 92, 0.07);
  border: 1px dashed rgba(45, 138, 92, 0.35);
}
.wordle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.wordle-row + .wordle-row { margin-top: 3px; }
.wordle-word {
  font-family: var(--serif);
  font-style: normal;
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 1.6px;
  color: var(--parch-ink);
}
.wordle-mark { font-size: 11px; letter-spacing: 1px; line-height: 1; }

/* ── The Azure Prince's cypher ────────────────────────────────────────────────
   A row of blank sorts with one place marked: print that many tiles with an
   azure one standing THERE and he reads it. Boxes rather than letters on
   purpose — the puzzle is a shape, not a word, and naming a letter would give
   away a great deal more than the Prince means to. */
.cypher {
  display: flex;
  gap: 4px;
  margin: 9px 0 2px;
  padding: 7px 8px;
  border-radius: 7px;
  background: rgba(46, 111, 178, 0.07);
  border: 1px dashed rgba(46, 111, 178, 0.35);
}
.cypher-box {
  width: 15px; height: 17px;
  border-radius: 3px;
  background: linear-gradient(170deg, var(--ivory), var(--ivory-2));
  box-shadow: inset 0 0 0 1px rgba(120, 90, 40, 0.35), 0 1px 1px rgba(0, 0, 0, 0.18);
}
/* The marked place: azure through and through, and lit, so it is found at a
   glance rather than counted to. */
.cypher-box--marked {
  background: linear-gradient(170deg, #8ec6ff, #2e6fb2);
  box-shadow: inset 0 0 0 1px rgba(20, 60, 105, 0.75), 0 0 8px rgba(46, 111, 178, 0.65);
}
.tip-feat {
  margin-top: 7px;
  padding-top: 7px;
  border-top: 1px dashed rgba(120, 90, 40, 0.3);
  color: #5c4a2e;
}
.tip-feat b {
  display: block;
  color: var(--parch-ink);
  font-family: var(--serif);
  font-size: 13px;
  margin-bottom: 1px;
}
.tip-calc { margin-top: 7px; font-weight: 600; }
.tip-btn {
  pointer-events: auto;
  margin-top: 9px;
  width: 100%;
  justify-content: center;
  background: rgba(120, 90, 40, 0.13);
  color: var(--parch-ink);
  border-color: rgba(120, 90, 40, 0.4);
  box-shadow: 0 2px 0 rgba(100, 70, 20, 0.3);
  text-transform: none;
  letter-spacing: 0.2px;
  font-weight: 700;
  padding: 8px 12px;
}

/* ─── Scrollbars ──────────────────────────────────────────────────────────────── */
::-webkit-scrollbar { width: 7px; height: 7px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(160, 120, 70, 0.4); border-radius: 4px; }

/* ─── Touch devices ───────────────────────────────────────────────────────────── */
@media (hover: none) {
  /* keyboard hints mean nothing on a phone; the log line guides exchange */
  .btn kbd { display: none; }
  /* the ✕ never appears without hover — the tap popover carries dismissal,
     and the workbench's long-press popover carries the same for a sundry */
  .patron-x, .sundry-x { display: none; }
}

/* ─── Small screens ───────────────────────────────────────────────────────────── */
@media (max-width: 880px) {
  .status-row { grid-template-columns: 1fr 1fr; }
  .quota-card { grid-column: 1 / -1; order: -1; }
  .shelf { grid-template-columns: repeat(var(--seat-count, 5), 1fr); gap: 6px; }
  .patron-name { display: none; }
  .sundries { grid-template-columns: repeat(var(--slot-count, 2), 52px); gap: 6px; }
  .sundry-name { display: none; }
  :root { --tile-w: 52px; --tile-h: 56px; }
  .tile-letter { font-size: 22px; }
  .rack-row { grid-template-columns: 1fr; }
  .pouch { flex-direction: row; justify-content: center; }

  /* Name and verdict up top, the demand on its own line, so a narrow screen
     doesn't squeeze the rule into a sliver. */
  .boss-bar { flex-wrap: wrap; row-gap: 3px; padding: 7px 12px; }
  .boss-rule { flex-basis: 100%; order: 4; font-size: 13px; }
  .boss-verdict { margin-left: auto; }
}

/* ─── Phones ──────────────────────────────────────────────────────────────────── */
@media (max-width: 600px) {
  :root { --tile-w: 54px; --tile-h: 58px; }

  header { padding: 6px 12px 0; gap: 10px; }
  .logo { font-size: 16.5px; letter-spacing: 1px; }
  .coin-purse { font-size: 16px; padding: 3px 11px 3px 8px; }
  .btn-icon { width: 33px; height: 33px; font-size: 15px; }

  .table {
    padding: 2px 10px calc(8px + env(safe-area-inset-bottom));
    gap: 6px;
    align-content: start;          /* fixed heights + start = nothing re-centres */
    min-height: calc(100vh - 46px);
    min-height: calc(100dvh - 46px);
  }

  /* Constant height filled or empty, so a first hire doesn't shove the board. */
  .shelf-row { gap: 5px; }
  .shelf { padding: 6px 6px 7px; gap: 5px; }
  .patron { min-height: 40px; padding: 4px 3px 3px; }
  .patron-emoji { font-size: 19px; }
  .patron-take { top: -6px; left: -4px; font-size: 8.5px; padding: 0 4px; }
  .patron--empty::after { display: none; }
  .patron--empty { font-size: 13px; }
  .sundries { grid-template-columns: repeat(var(--slot-count, 2), 40px); gap: 4px; padding: 6px 6px 7px; }
  .sundry { min-height: 40px; padding: 4px 3px 3px; }
  .sundry--empty::after { display: none; }
  .paint-tube { width: 13px; height: 20px; margin-top: 5px; }
  .paint-tube::before { width: 7px; height: 5px; top: -6px; }
  .sundry-shuffle { width: 18px; height: 18px; font-size: 10px; margin-top: 4px; }

  .status-card { padding: 5px 11px; }
  .chapter-card, .pips-card { min-width: 0; }
  /* the chapter's title is announced in the banner — the phone needs the pixels */
  .chapter-name { display: none; }
  .quota-card { gap: 4px; }
  .quota-bar { height: 7px; }
  .quota-nums { font-size: 17px; }
  .pips-card { gap: 5px; }
  .status-row { gap: 6px; }

  .readout { gap: 5px; padding: 5px 6px; height: 62px; }
  .slab { min-width: 54px; padding: 2px 6px 4px; }
  .slab-label { font-size: 8.5px; letter-spacing: 1.1px; }
  .slab-num { font-size: 19px; }
  .ro-op { font-size: 13px; }
  .ro-chips { gap: 3px; }
  .chip { min-width: 31px; font-size: 10.5px; padding: 4px 3px 5px; }
  /* Seven chips don't fit a phone's readout: the measure's chip appears the
     moment a sixth letter lights it, like the cursed chip does. */
  .chip--length:not(.chip--on) { display: none; }
  .ro-total-wrap { min-width: 56px; }
  .ro-total { font-size: 25px; }

  /* one row of tiles reserved in the groove, two in the rack (a full hand) */
  .word-groove { min-height: 76px; padding: 8px; gap: 6px; }

  .rack-row { grid-template-columns: 1fr 1fr; gap: 6px; }
  .rack {
    grid-row: 1; grid-column: 1 / -1;
    gap: 6px; padding: 8px 8px 10px;
    /* two full rows: 2×58 tiles + 6 gap + 18 padding + 2 border */
    min-height: max(142px, var(--rack-reserve, 0px));
  }
  #bagBtn  { grid-row: 2; grid-column: 1; }
  #trayBtn { grid-row: 2; grid-column: 2; }
  .pouch { padding: 2px 6px; }
  .pouch-svg { width: 36px; height: 38px; }

  .action-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
  .action-spacer { display: none; }
  .btn { justify-content: center; padding: 10px 6px; }
  .btn-big, .btn-print { grid-column: 1 / -1; padding: 12px 10px; font-size: 16px; }

  .log { height: 32px; font-size: 12.5px; line-height: 1.25; }

  .banner-title { font-size: 29px; }
  .banner-inner { padding: 24px 30px; }

  .sheet { padding: 16px 14px 18px; }
  .sheet h2 { font-size: 21px; }
  .market-foot .btn { flex: 1 1 auto; justify-content: center; }
  .run-stats { grid-template-columns: 1fr 1fr; }
  .set-row { grid-template-columns: 1fr; gap: 6px; }

  .offer-tiles--draft { grid-template-columns: repeat(2, 1fr); }
  .draft-paints { grid-template-columns: 1fr; }
  .offer-patron { grid-template-columns: 68px 1fr; }
  .op-portrait { min-height: 78px; }
  .op-portrait .op-emoji { font-size: 30px; }
  .sheet--draft .market-sec { margin-top: 14px; }

  /* the shop head is a title beside a reward tally — too wide for a phone */
  .sheet-head { flex-direction: column; align-items: stretch; gap: 8px; }
  .reward-line { justify-content: flex-start; max-width: none; }
  .sheet--market .sheet-head .x, .sheet--inspector .sheet-head .x,
  .sheet--manuscript .sheet-head .x { align-self: flex-end; margin-top: -28px; }
  /* Three columns won't fit: the title takes a line, number and tally share one. */
  .book-chapter-head { grid-template-columns: auto 1fr; row-gap: 1px; }
  .book-chapter-title { grid-column: 1 / -1; grid-row: 1; white-space: normal; }
  .book-chapter-num { grid-row: 2; grid-column: 1; }
  .book-chapter-tally { grid-row: 2; grid-column: 2; text-align: right; }
  /* No room for a rule between two columns that are already tight */
  .book-rule { display: none; }
  .book-leaf { grid-template-columns: 20px 1fr; gap: 6px; }
  .book-prose { font-size: 14px; line-height: 1.8; }
}

@media (prefers-reduced-motion: reduce) {
  .tile--trim-gold::before { animation: none; }
  /* The tube's offer still has to read as offered — hold the outline steady */
  /* An offered tile still has to read as offered — hold the ring and glow at
     their brightest, drop only the breathing. */
  .tile--tube-offer {
    animation: none;
    outline-color: #fff0c4;
    filter: drop-shadow(0 0 14px rgba(255, 205, 120, 0.9));
  }
  /* A lit patron still has to read as lit — hold the glow, drop the motion */
  .patron--armed, .patron--just-armed {
    animation: none;
    box-shadow:
      inset 0 0 0 2px rgba(255, 248, 226, 0.95),
      inset 0 0 0 3.5px var(--ring),
      0 6px 14px rgba(0, 0, 0, 0.5),
      0 0 22px 3px rgba(246, 214, 138, 0.7);
  }
  .patron--armed::after { animation: none; opacity: 0; }
  .patron--ghost.patron--common,
  .patron--ghost.patron--uncommon,
  .patron--ghost.patron--rare,
  .offer-patron.offer-patron--ghost { animation: none; opacity: 0.9; }
  .patron--murdered { animation: none; }
  .patron--just-armed .patron-take, .patron-take--bump { animation: none; }
  /* Materials still have to read as themselves — hold the colour, drop motion. */
  .tile--mat-cursed, .tile--mat-rainbow, .tile--gift { animation: none; }
  .boss-bar--firing, .boss-bar--spiking { animation: none; }
  /* A tool waiting on its last tap still has to read as waiting — hold the glow
     at the top of the breath, drop the breathing. */
  .sundry--ready {
    animation: none;
    transform: translateY(-5px) scale(1.05);
    box-shadow:
      inset 0 0 0 2.5px rgba(255, 248, 226, 0.95),
      0 0 32px 7px var(--paintcol, var(--warn)),
      0 9px 18px rgba(0, 0, 0, 0.5);
  }
  /* A burning tile still has to read as destroyed — char it, don't tumble it */
  .tile--burning {
    animation: none;
    filter: brightness(0.35) sepia(1) contrast(0.6);
    opacity: 0.25;
  }
}
