/**
 * LORE WARS — the arena.
 *
 * This is a fighting game that happens to be played with cards, so it is built
 * like one. Both health bars sit across the top, mirrored, draining outward
 * from the centre the way every versus game has done it since 1991. The two
 * fighters own the left and right of the arena at full height — they are the
 * stage, not a strip of chrome — and the cards land between them.
 *
 * The rule that killed the old layout: nothing is allowed to be empty. The
 * arena has no padding of dead pixels; art fills what the cards do not.
 *
 * One committed look, no theme following. The only colour that moves is
 * --p1 / --p2, taken from each fighter's own portrait palette.
 */

:root {
  --bg: #08080b;
  --arena: #0d0d12;
  --line: #26262f;
  --ink: #f6f4f1;
  --dim: #8b8b99;
  --p1: #ff6f6a;
  --p2: #f0cd6a;
  --danger: #ff2f3b;
  --gold: #f2b13c;
  --heal: #ff5a63;
  --stock: #f7f4ee;
  --stock-ink: #14131a;

  --ui: ui-sans-serif, -apple-system, 'Segoe UI', system-ui, sans-serif;
  --display: 'Haettenschweiler', 'Arial Narrow Bold', Impact, 'Franklin Gothic Bold', sans-serif;
  --mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
  --card-w: 74px;
  color-scheme: dark;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { height: 100%; margin: 0; overflow: hidden; overscroll-behavior: none; }
body { background: var(--bg); color: var(--ink); font: 400 15px/1.4 var(--ui); -webkit-font-smoothing: antialiased; }
button { font: inherit; color: inherit; border: 0; background: none; cursor: pointer; }

#app { height: 100dvh; display: flex; justify-content: center; }
.game, .pick { width: 100%; max-width: 540px; height: 100dvh; position: relative; overflow: hidden; }
.game { display: grid; grid-template-rows: auto 1fr auto; background: var(--bg); }

/* ----------------------------------------------------------------- hud */

/* A portrait at each outer corner, a heavy bar sweeping in from it, the round
   between them. Sheared, so the two sides read as opposed rather than tabular.
   Everything that is not the fight is one quiet line under the bar. */
.hud {
  padding: calc(6px + env(safe-area-inset-top)) 8px 8px;
  display: grid; grid-template-columns: 1fr auto 1fr; gap: 6px; align-items: start;
  background: linear-gradient(180deg, #000 60%, rgba(0,0,0,.82));
}
.side { display: flex; align-items: flex-start; gap: 7px; min-width: 0; }
.side.p2 { flex-direction: row-reverse; }

.side .mug {
  flex: 0 0 auto; width: 46px; height: 46px; border-radius: 4px; overflow: hidden;
  background-size: cover; background-position: 50% var(--foc, 12%); background-color: #16161d;
  border: 2px solid var(--c); box-shadow: 0 3px 12px rgba(0,0,0,.7);
  display: grid; place-items: center;
}
.side .mug svg { width: 84%; height: 84%; opacity: .7; }

.meter { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; padding-top: 3px; }

/* The bar is the loud thing. Sheared toward the middle, number riding on it. */
.hp {
  position: relative; height: 22px; background: #14131a;
  border: 1px solid rgba(255,255,255,.16);
  transform: skewX(-13deg); overflow: hidden;
}
.side.p2 .hp { transform: skewX(13deg); }
.hp i {
  position: absolute; inset: 0 auto 0 0; height: 100%;
  background: linear-gradient(180deg, color-mix(in srgb, var(--c) 92%, #fff) 0 46%, var(--c) 46% 100%);
  transition: width .32s cubic-bezier(.2,.9,.3,1);
}
.side.p2 .hp i { inset: 0 0 0 auto; }
.hp .num {
  position: absolute; inset: 0; display: grid; place-items: center;
  transform: skewX(13deg);
  font: 400 17px/1 var(--display); color: #0a0a0d;
  text-shadow: 0 1px 0 rgba(255,255,255,.35);
}
.side.p2 .hp .num { transform: skewX(-13deg); }
/* Low: the bar itself sounds the alarm rather than a separate warning. */
.side.low .hp { border-color: var(--danger); animation: hpLow 1s ease-in-out infinite alternate; }
.side.low .hp i { background: var(--danger); }
.side.low .hp .num { color: #fff; text-shadow: 0 1px 3px #000; }
@keyframes hpLow { to { box-shadow: 0 0 16px rgba(255,59,69,.55) } }
@media (prefers-reduced-motion: reduce) { .side.low .hp { animation: none } }

/* One line: who, and only what is actually live. */
.line { display: flex; align-items: center; gap: 5px; min-width: 0; }
.side.p2 .line { flex-direction: row-reverse; }
.line .nm {
  flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 700 8.5px/1 var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--dim);
}
.side.p2 .line .nm { text-align: right; }
/* Named `st`, not `chip`: the boom overlay already owns .chip globally and it
   is a 34px cream card face, which turned every one of these into a blank
   white box. */
.line .st {
  flex: 0 0 auto; font: 700 9px/1 var(--mono); color: var(--dim);
  border: 1px solid var(--line); border-radius: 2px; padding: 3px 4px;
  background: rgba(6,6,9,.6);
}
.line .st b { color: var(--ink); font-weight: 700; }
.line .st.gold { border-color: color-mix(in srgb, var(--gold) 45%, transparent); }
.line .st.gold b { color: var(--gold); }
.line .st.shield { border-color: color-mix(in srgb, var(--c) 55%, transparent); }
.line .st.warn { border-color: var(--danger); color: var(--danger); }



/* --------------------------------------------------------------- arena */

.arena { position: relative; overflow: hidden; background: var(--arena); }

/* The fighters ARE the stage. Full height, facing in, fading to the middle. */
/* Busts, rising from the bottom edge.

   These are full-figure paintings, and shown whole at the size of a phone
   screen a face is about forty pixels across — the fighters were scenery. Each
   side is zoomed onto the top of its own painting and anchored to the floor of
   the arena, so what you see is a head and shoulders coming up out of the
   action bar. `--foc` still says where the head is; the zoom is what makes it
   worth pointing at. */
.stageart { position: absolute; inset: 0; overflow: hidden; }
.stageart i {
  position: absolute; bottom: 0; height: 100%; width: 72%;
  background-repeat: no-repeat;
  background-size: 210% auto;
  background-position: 50% var(--foc, 10%);
  filter: saturate(.9) contrast(1.06);
}
/* Faded on the inner edge only, so the two fighters meet in the dark instead
   of at a straight cut down the middle.

   They used to stop at 88% height with a mask that went fully opaque 9% of the
   way down, which put a hard black band across the top of the arena and read as
   the art being cut off rather than lit. They run the full height now and the
   top fade is a short 4% feather — enough to keep a hairline edge off the
   ceiling, not enough to be a vignette. The darkening that keeps the table
   readable belongs on ::after, over the top, where it can be tuned without
   moving the art. */
.stageart i.l {
  left: -14%;
  -webkit-mask-image: linear-gradient(90deg, #000 0 46%, transparent 96%), linear-gradient(180deg, transparent 0, #000 4%);
  mask-image: linear-gradient(90deg, #000 0 46%, transparent 96%), linear-gradient(180deg, transparent 0, #000 4%);
  -webkit-mask-composite: source-in; mask-composite: intersect;
}
.stageart i.r {
  right: -14%; transform: scaleX(-1);
  -webkit-mask-image: linear-gradient(90deg, #000 0 46%, transparent 96%), linear-gradient(180deg, transparent 0, #000 4%);
  mask-image: linear-gradient(90deg, #000 0 46%, transparent 96%), linear-gradient(180deg, transparent 0, #000 4%);
  -webkit-mask-composite: source-in; mask-composite: intersect;
}
/* The table has to stay readable over them, so the middle stays dark — but
   only the middle. The old top wash ran to 12% at a third opacity on top of a
   mask that had already gone black, which is why the ceiling looked closed.
   The radial does the work; the top edge gets a whisper. */
.stageart::after {
  content: ''; position: absolute; inset: 0;
  background:
    radial-gradient(78% 46% at 50% 50%, rgba(8,8,11,.86) 0 24%, rgba(8,8,11,.14) 100%),
    linear-gradient(180deg, rgba(8,8,11,.22) 0, transparent 6%);
}
.arena.hit::before {
  content: ''; position: absolute; inset: 0; z-index: 4; pointer-events: none;
  background: var(--danger); animation: flash .3s ease-out forwards;
}
@keyframes flash { from { opacity: .5 } to { opacity: 0 } }
.arena.shake { animation: shake .3s cubic-bezier(.36,.07,.19,.97); }
@keyframes shake { 15%{transform:translate(-3px,1px)} 40%{transform:translate(5px,-2px)} 65%{transform:translate(-5px,1px)} 90%{transform:translate(2px,0)} }
@media (prefers-reduced-motion: reduce) { .arena.shake, .arena.hit::before, .side.low .hp i { animation: none } }

/* The round, between them. */
.round {
  align-self: center; text-align: center; min-width: 40px;
  display: flex; flex-direction: column; align-items: center; gap: 1px;
}
.round b { font: 400 20px/1 var(--display); color: var(--ink); }
.round small {
  font: 700 6.5px/1 var(--mono); letter-spacing: .2em; text-transform: uppercase; color: var(--dim);
}

/* Everything playable sits above the stage. */
/* The tools rail is absolute down the left of the stage, 26px wide at left:6,
   so the table has to start clear of it — a full row of cards was laying its
   first card under the % and the deck glyph. Symmetrical, so the row still
   reads as centred. See fitCards, which subtracts the same. */
.play {
  position: relative; z-index: 2; height: 100%;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  padding: 10px 40px;
}

/* The stake, in banks of at most four.

   `--per-bank` and `--card-w` come from fitCards, and the max-width is what
   actually forces the wrap — flex-wrap alone would fill the whole arena before
   breaking, which is how seven cards ended up as one 55px row. The arc is set
   per card by arcRow, which reads where the browser actually broke the line
   rather than guessing from the count. */
.row {
  display: flex; gap: 8px; flex-wrap: wrap;
  justify-content: center; align-items: flex-start;
  max-width: calc(var(--per-bank, 4) * (var(--card-w) + 8px));
  row-gap: 10px;
}
.row > .grp {
  transform: translateY(calc(var(--d, 0) * 5px)) rotate(calc(var(--n, 0) * 3.2deg));
  transform-origin: 50% 180%;
  transition: transform .18s ease-out;
}
.grp { display: flex; flex-direction: column; align-items: center; }
.grp .cards { display: flex; gap: 5px; }
.grp .runbar { height: 0; }
.grp.run .runbar {
  height: auto; align-self: stretch; margin-top: 7px;
  background: var(--gold); color: #14131a;
  font: 700 11px/1 var(--mono); letter-spacing: .1em; text-align: center; padding: 6px 5px;
  box-shadow: 0 0 22px rgba(242,177,60,.4);
}

/* The card a Wager showed you. It sits in the slot the card is about to land
   in, and everything about it says not-yet: set back from the row by a gap of
   its own, tilted off true, and outlined rather than seated. The label is the
   same bar a run uses, so it lands where the eye already looks for one. */
.grp.nextup { margin-left: 10px; opacity: .9; }
.grp.nextup .cards .card {
  transform: rotate(-3deg) scale(.9); transform-origin: 50% 100%;
  box-shadow: 0 0 0 2px rgba(255, 255, 255, .16), 0 8px 20px rgba(0, 0, 0, .5);
}
.grp.nextup .runbar {
  height: auto; align-self: stretch; margin-top: 7px;
  background: none; color: var(--dim); border: 1px dashed rgba(255, 255, 255, .28);
  font: 700 9px/1 var(--ui); letter-spacing: .18em; text-align: center; padding: 5px 4px;
}
/* A power turning up next is a name, not a number — it has no face on the
   table because it never lands there, it goes to the shop. */
.card.pw {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 5px; padding: 8px; background: var(--stock); color: var(--stock-ink); text-align: center;
}
.card.pw .pk {
  font: 700 7px/1 var(--ui); letter-spacing: .14em; text-transform: uppercase;
  background: var(--stock-ink); color: var(--stock); padding: 3px 5px; border-radius: 2px;
}
.card.pw .pn { font: 400 clamp(12px, 2.6vw, 17px)/.95 var(--display); letter-spacing: .02em; text-transform: uppercase; }

.card {
  width: var(--card-w); aspect-ratio: 5/7;
  background: var(--stock); border-radius: 5px; overflow: hidden;
  /* the drawn field sits under everything */
  box-shadow: 0 10px 22px rgba(0,0,0,.7), inset 0 0 0 1px rgba(0,0,0,.45);
  position: relative; animation: slam .22s cubic-bezier(.2,.9,.25,1.25);
}
@keyframes slam { from { transform: translateY(-40px) rotate(-8deg) scale(1.12); opacity: 0 } }
@media (prefers-reduced-motion: reduce) { .card { animation: none } }

/* The drawing fills the card. The marks are cut to alpha by
   art/normalise-marks.py, so they need no blend mode and no per-file patching —
   the invert that berserker-C used to need is baked into the tile now. */
.card .art {
  position: absolute; inset: 1% 1% 15% 1%;
  width: 98%; height: 84%; object-fit: contain;
}

/* The value sits over the art, big enough to read across a table. */
.card .val {
  position: absolute; left: 7%; bottom: 12%;
  font: 400 calc(var(--card-w) * .82)/.78 var(--display);
  letter-spacing: -.01em;
  -webkit-text-stroke: calc(var(--card-w) * .035) var(--stock);
  paint-order: stroke fill;
}
.card .val.c { color: #14131a; }
.card .val.h { color: #b3161c; }
.card .val.d { color: #9d7010; }

.card .bar {
  position: absolute; left: 0; right: 0; bottom: 0; height: 15%;
  display: grid; place-items: center; color: var(--stock);
  font: 700 calc(var(--card-w) * .13)/1 var(--ui); letter-spacing: .16em;
}
.card .bar.c { background: #14131a; }
.card .bar.h { background: #b3161c; }
.card .bar.d { background: #9d7010; }
.card .bar.lk { background: var(--stock); color: #14131a; }
.card.link { background: #14131a; }

/* How much a card has been raised by — a Blade pumped by Rage, a marked card
   worth more than its face. It reads "+1" beside a big number that already
   shows the new value; it used to read "9" beside a big 8, which is two numbers
   and no explanation. Sits INSIDE the card: the card clips its own overflow to
   keep the art off the corners, so a badge hung outside the edge is invisible. */
.card .bump {
  position: absolute; top: 4px; right: 4px; z-index: 3;
  background: var(--gold); color: #14131a;
  font: 700 calc(var(--card-w) * .15)/1 var(--mono); padding: 4px 5px; border-radius: 3px;
  box-shadow: 0 1px 5px rgba(0,0,0,.45);
}
/* And the value itself goes gold, so the raise reads without arithmetic. */
.card .val.lifted { color: #9d7010; }
.card .val.lifted.c { color: #8a6a12; }

/* The take, read like a damage counter. */
.take { display: flex; gap: 14px; align-items: flex-end; }
.take div { text-align: center; min-width: 62px; }
.take .n { display: block; font: 400 clamp(34px,10vw,52px)/.82 var(--display); font-variant-numeric: tabular-nums; }
.take .l { font: 700 9px/1 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--dim); }
.take .dmg .n { color: var(--ink); text-shadow: 0 0 26px rgba(255,255,255,.25); }
.take .heal .n { color: var(--heal); }
.take .gold .n { color: var(--gold); }
.take .z { opacity: .18; }

.callout {
  font: 400 clamp(30px,10vw,46px)/.9 var(--display); letter-spacing: .04em; text-transform: uppercase;
  text-align: center;
}
.callout.bad { color: var(--danger); }
.hint { font: 700 10px/1 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--dim); }
.bark { font: italic 400 13.5px/1.4 var(--ui); color: var(--dim); text-align: center; max-width: 34ch; }
.mini { display: flex; gap: 4px; flex-wrap: wrap; justify-content: center; }
.mini span { background: var(--stock); color: var(--stock-ink); border-radius: 3px; font: 700 14px/1 var(--mono); padding: 6px 7px; }
.mini span.h { color: #b3161c } .mini span.d { color: #9d7010 }
.mini span.lk { background: #14131a; color: var(--stock); font: 700 9px/1 var(--ui); letter-spacing: .14em; padding: 9px 6px; }

/* The shop takes the arena when it is your turn to spend. */
/* The shop. A dark plate under it, because the goods sit over the fighter art
   and a card with no ground behind it competes with a painting. */
.shop {
  display: flex; flex-direction: column; gap: 12px; align-items: center;
  max-width: 100%; padding: 14px 12px 16px; border-radius: 10px;
  background: rgba(6, 6, 9, .78); backdrop-filter: blur(6px);
  border: 1px solid rgba(255, 255, 255, .07);
}

/* What you hold, struck as a coin. The old headline was the count and the
   currency word set in the display face — the same shape as a price, sitting
   directly above two prices. */
.purse { display: flex; align-items: center; gap: 9px; }
.purse .disc {
  display: grid; place-items: center; width: 42px; height: 42px; border-radius: 50%;
  background: radial-gradient(circle at 38% 32%, #f0d081, var(--gold) 62%, #8a6a1f);
  color: #2a2008; font: 700 20px/1 var(--mono);
  box-shadow: inset 0 0 0 2px rgba(0, 0, 0, .28), 0 2px 8px rgba(0, 0, 0, .5);
}
.purse .w {
  font: 400 clamp(18px, 5.4vw, 24px)/1 var(--display); letter-spacing: .06em;
  text-transform: uppercase; color: var(--gold);
}

.shelf { display: flex; gap: 10px; justify-content: center; align-items: stretch; max-width: 100%; }
.buy { padding: 0; background: none; border: 0; display: flex; border-radius: 10px; }
.buy .face3d {
  width: min(var(--w, 38vw), 156px); border: 2px solid rgba(0, 0, 0, .5);
  box-shadow: 0 6px 18px rgba(0, 0, 0, .55);
  transition: transform .14s, box-shadow .14s;
}
/* Shrunk from the size the face was drawn at, so the type steps down with it. */
.buy .face3d { padding: 9px 10px 8px; gap: 5px; border-radius: 8px; }
.buy .face3d .kind { font-size: 7px; padding: 4px 5px; }
.buy .face3d .nm { font-size: clamp(17px, 5vw, 21px); }
.buy .face3d .pow { font-size: 10px; line-height: 1.3; }
.buy .face3d .cost { font-size: 17px; }
.buy .face3d .cost span { font-size: 7px; }

.buy.can .face3d { border-color: var(--gold); }
.buy.can:active .face3d { transform: translateY(2px); box-shadow: 0 3px 10px rgba(0, 0, 0, .55); }
/* Out of reach: the card greys off and its price goes red. That is the whole
   message, and it lives on the price itself rather than in a line of text
   under the button.

   The dimming is a dark plate over the card rather than `filter` on it. A
   filter takes the price down with everything else, and a muddy dark-red
   numeral is exactly the part that still has to be read. */
.buy.cant .face3d .art { filter: grayscale(1) brightness(.86); }
.buy.cant .face3d::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: rgba(8, 8, 12, .42); pointer-events: none;
}
/* The price is the one thing on a card you cannot afford that still has to be
   read, so it sits above the dimming plate and gets brighter, not darker. The
   currency word carries .65 opacity from the base rule, which on top of the
   plate put it under the floor. */
.buy.cant .face3d .cost { position: relative; z-index: 1; color: #ff7b74; }
.buy.cant .face3d .cost span { opacity: .9; }


/* The damage, in flight from the table to the bar that is about to pay it.

   Fixed rather than absolute: it crosses from the middle of the arena to the
   HUD, which are different stacking contexts, and anything that clips would
   eat it on the way. */
.hitfly {
  position: fixed; z-index: 70; pointer-events: none;
  transform: translate(-50%, -50%);
  transition: transform .46s cubic-bezier(.4, 0, .25, 1), opacity .46s ease-in;
  font: 400 34px/1 var(--display); color: var(--c, #ff6f6a);
  text-shadow: 0 2px 0 rgba(0, 0, 0, .8), 0 0 22px rgba(0, 0, 0, .9);
}
.hitfly b { font-weight: inherit; }
.hitfly b::before { content: '\2212'; margin-right: .04em; opacity: .8; }
.hitfly.go { opacity: .15; }

/* And the bar taking it. Kept off `.hp i` so it cannot fight the width
   transition, which is the thing the whole exercise exists to let run. */
.side.struck .hp { animation: struck .34s ease-out; }
@keyframes struck {
  0% { box-shadow: 0 0 0 0 var(--c); filter: brightness(2.2); }
  100% { box-shadow: 0 0 0 6px transparent; filter: none; }
}
.side.struck .mug { animation: mugHit .34s ease-out; }
@keyframes mugHit {
  0% { transform: translateX(0) }
  30% { transform: translateX(-3px) }
  60% { transform: translateX(3px) }
  100% { transform: translateX(0) }
}
/* The drain wants to be seen, so it is slower than a UI transition normally
   would be and it eases out at the end. */
.hp i { transition: width .5s cubic-bezier(.22, .8, .3, 1); }
@media (prefers-reduced-motion: reduce) {
  .hitfly { display: none }
  .hp i { transition: none }
  .side.struck .hp, .side.struck .mug { animation: none }
}

/* --------------------------------------------------------------- pad */

.pad { padding: 9px 10px calc(9px + env(safe-area-inset-bottom)); background: #000; display: flex; flex-direction: column; gap: 7px; }
.pair { display: flex; gap: 8px; position: relative; z-index: 1; }
.go {
  flex: 1; min-height: 66px; border-radius: 6px;
  background: #16161d; border: 2px solid var(--line); color: var(--ink);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
}
.go .big { font: 400 clamp(23px,7vw,30px)/1 var(--display); letter-spacing: .05em; text-transform: uppercase; }
.go .sub { font: 700 9.5px/1 var(--mono); letter-spacing: .06em; color: var(--dim); }
.go.hit { background: var(--p1); border-color: var(--p1); color: #14131a; }
.go.hit .sub { color: #14131a; opacity: .72; }
.go:active:not(:disabled) { transform: translateY(3px); }
.go:disabled { opacity: .3; }
.go.slim { min-height: 40px; flex: 0 1 auto; padding: 0 13px; }
/* A disabled button in the pad cannot fade, because the hand is behind it and
   `opacity` would show the cards straight through the glass. It goes grey
   instead — same message, still opaque. */
.pad .pair .go:disabled { opacity: 1; background: #0e0e13; border-color: #22222b; color: #4c4c57; }
.pad .pair .go.hit:disabled {
  background: color-mix(in srgb, var(--p1) 26%, #0e0e13);
  border-color: color-mix(in srgb, var(--p1) 34%, #22222b); color: #4c4c57;
}

.go.slim .big { font: 700 14px/1 var(--ui); letter-spacing: .05em; }
.waiting { text-align: center; padding: 22px 0 24px; color: var(--dim); font: 700 10px/1 var(--ui); letter-spacing: .18em; text-transform: uppercase; }

/* Down the left edge of the stage, not across the top. They used to sit in the
   header's top-right corner and land squarely on the opponent's name. */
.tools {
  position: absolute; left: 6px; top: 50%; transform: translateY(-50%);
  display: flex; flex-direction: column; gap: 5px; z-index: 8;
}
.tools button {
  width: 26px; height: 26px; border-radius: 13px; background: rgba(6,6,9,.72);
  border: 1px solid var(--line); color: var(--dim); font: 700 10px/1 var(--ui);
  display: grid; place-items: center; backdrop-filter: blur(2px);
}
.tools button.on { color: var(--gold); border-color: var(--gold); }

/* ------------------------------------------------------------ overlays */

.veil { position: fixed; inset: 0; z-index: 40; background: rgba(4,4,6,.86); display: flex; align-items: flex-end; justify-content: center; }
.sheet {
  width: 100%; max-width: 540px; max-height: 84dvh; background: #101015;
  border-top: 2px solid var(--line); border-radius: 14px 14px 0 0;
  padding: 16px 14px calc(16px + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; gap: 10px; animation: slide .18s cubic-bezier(.2,.9,.3,1);
}
@keyframes slide { from { transform: translateY(22px) } }
.sheet h3 { font: 400 clamp(20px,6vw,26px)/1.05 var(--display); letter-spacing: .04em; text-transform: uppercase; margin: 0; }
.sheet .list { display: flex; flex-direction: column; gap: 7px; overflow-y: auto; min-height: 0; padding: 2px; }
.opt { text-align: left; background: #16161d; border: 1px solid var(--line); border-radius: 6px; padding: 10px 11px; display: flex; flex-direction: column; gap: 4px; }
.opt .top { display: flex; align-items: baseline; gap: 6px; }
.opt .kind { font: 700 8px/1 var(--ui); letter-spacing: .13em; text-transform: uppercase; background: var(--gold); color: #14131a; padding: 4px 5px; }
.opt .t { font: 700 15px/1.1 var(--ui); }
.opt .cost { margin-left: auto; font: 700 12px/1 var(--mono); color: var(--gold); }
.opt .p { font: 400 12px/1.4 var(--ui); color: var(--dim); }

/* Picking between cards. Two columns, because four faces in one row on a phone
   are too small to read the rule text off — and the rule text is the decision.
   Three options give 2 + 1, which is the shape of the real choice anyway. */
.sheet .pickgrid {
  /* Equal rows, so four cards of different rule-text lengths are still four
     cards the same size. Ragged heights read as four different KINDS of thing
     when they are four of the same thing. */
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-auto-rows: 1fr; gap: 9px;
  overflow-y: auto; min-height: 0; padding: 2px; align-content: start;
}
/* Flex, not block: a button centres its content in its own box, so as a grid
   item stretched to the row it floated the short cards down off the top edge —
   and dragged the face's height with it. */
.pickcard {
  position: relative; padding: 0; background: none; border: 0; border-radius: 9px;
  display: flex; flex-direction: column; align-items: stretch; justify-content: flex-start;
}
.pickcard:active { transform: translateY(2px); }
.pickcard .face3d.pick { width: 100%; }
.pickcard .new {
  position: absolute; top: -4px; right: -4px; z-index: 4; font-style: normal;
  background: var(--gold); color: #14131a; border-radius: 2px;
  font: 700 8px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase;
  padding: 5px 6px; box-shadow: 0 2px 7px rgba(0,0,0,.5);
}
/* The face has to survive at half the width it was drawn for, and four of them
   have to fit on one screen. So the picker drops the 5/7 shape: the art becomes
   a band and the card takes the height its rule text needs. Keeping the full
   card proportions here put two on screen and pushed the text — the thing you
   are actually choosing between — below the fold. */
.face3d.pick {
  /* Fills its cell, and the cells are equal — see .pickgrid. The 5/7 card shape
     has to go explicitly, or it fights the row height. */
  aspect-ratio: auto; height: 100%; align-self: stretch;
  padding: 8px 9px; gap: 4px; border-radius: 7px;
}
.face3d.pick .kind { font-size: 7px; padding: 4px 5px; }
.face3d.pick .nm { font-size: clamp(15px, 4.6vw, 20px); }
.face3d.pick .art { flex: none; height: 76px; }
.face3d.pick .pow { font-size: 10.5px; line-height: 1.32; }
.face3d.pick .cost { font-size: 16px; margin-top: auto; }
.face3d.pick .art { padding: 6%; }
.logbox { overflow-y: auto; font: 400 12px/1.55 var(--mono); background: #08080a; border: 1px solid var(--line); border-radius: 5px; padding: 8px 10px; }
.logbox p { margin: 0 0 1px; color: var(--dim); }
.logbox .bust { color: var(--danger); font-weight: 700; }
.logbox .save { color: var(--gold); font-weight: 700; }
.logbox .ko, .logbox .end { color: var(--ink); font-weight: 700; }
.logbox .turnStart { border-top: 1px solid var(--line); margin-top: 5px; padding-top: 4px; }
.over { text-align: center; display: flex; flex-direction: column; gap: 4px; }
.over .verdict { font: 400 clamp(40px,14vw,64px)/.88 var(--display); letter-spacing: .04em; text-transform: uppercase; }
.over p { margin: 0; color: var(--dim); font-size: 13px; }

/* ---------------------------------------------------------- card reveal */

.reveal { position: fixed; inset: 0; z-index: 60; background: rgba(4,4,6,.92); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; animation: fade .16s ease-out; }
@keyframes fade { from { opacity: 0 } }
.reveal .lede, .reveal .tap { font: 700 10px/1 var(--ui); letter-spacing: .18em; text-transform: uppercase; color: var(--dim); }
.reveal .tap { animation: fade .3s ease-out .9s both; }
.flip { width: min(66vw, 264px); aspect-ratio: 5/7; perspective: 1300px; }
.flip .inner { position: relative; width: 100%; height: 100%; transform-style: preserve-3d; animation: turn 1s cubic-bezier(.34,.72,.28,1) both; }
@keyframes turn { 0% { transform: rotateY(180deg) scale(.72) } 55% { transform: rotateY(38deg) scale(1.04) } 100% { transform: rotateY(0) scale(1) } }
.flip .back, .flip .front { position: absolute; inset: 0; backface-visibility: hidden; border-radius: 11px; box-shadow: 0 22px 44px rgba(0,0,0,.75); }
.flip .back { transform: rotateY(180deg); background: repeating-linear-gradient(45deg, rgba(0,0,0,.18) 0 7px, transparent 7px 14px), var(--c); border: 2px solid rgba(0,0,0,.45); }
.flip .front { background: var(--stock); color: var(--stock-ink); border: 2px solid rgba(0,0,0,.5); padding: 15px 14px 13px; display: flex; flex-direction: column; gap: 7px; }
.flip .front .kind { align-self: flex-start; font: 700 9px/1 var(--ui); letter-spacing: .16em; text-transform: uppercase; background: var(--c); color: #14131a; padding: 5px 7px; }
.flip .front .nm { font: 400 clamp(24px,7.5vw,32px)/.95 var(--display); letter-spacing: .02em; text-transform: uppercase; }
.flip .front .rule { height: 2px; background: var(--stock-ink); opacity: .17; }
.flip .front .art { flex: 1; min-height: 0; position: relative; display: grid; place-items: center; overflow: hidden; }
.flip .front .art .por { position: absolute; inset: 0; background-size: cover; background-position: 50% 12%; filter: grayscale(1) contrast(1.15); opacity: .4; }
.flip .front .art.bare .por { opacity: .8; filter: grayscale(.2) contrast(1.05); }
.flip .front .art .icon { position: relative; width: 100%; height: 100%; object-fit: contain; mix-blend-mode: multiply; }
.flip .front .pow { font: 400 13px/1.42 var(--ui); }
.flip .front .cost { align-self: flex-end; font: 700 19px/1 var(--mono); color: #8a6a1f; }
.flip .front .cost span { font: 700 9px/1 var(--ui); letter-spacing: .14em; text-transform: uppercase; opacity: .65; }


/* The looping portrait, laid over the still it was made from.

   Two things have to be right or the swap is visible.

   FRAMING. The loop must be cropped exactly like the still under it, and
   `object-position` does not inherit `background-position` — they are separate
   properties that happen to take the same syntax. So each surface states it
   twice, and the two surfaces do genuinely differ: the picker crops everybody
   at a fixed 14% and the lore sheet uses each portrait's own `--foc`. Getting
   this wrong is not subtle; it took the top of Alice's head off.

   STACKING. The loop sits UNDER the veil and the copy, and `.face > *` sets
   every child to position: relative, so the order is spelled out rather than
   left to tree order: loop 0, veil 1, words 2. Without it the video paints
   over the gradient and the name goes unreadable on a light frame. */
.loop {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; pointer-events: none; border: 0; background: none;
}
/* `.face > *` sets position: relative on every child at the same specificity
   as `.loop`, and it wins on order — which put the video in the flow, sized to
   its own aspect, sitting on top of the still instead of over it. You got two
   Alices. It has to be re-stated here, under `.face`, to take. */
.face > .loop { position: absolute; inset: 0; z-index: 0; object-position: 50% 14%; }
.face::before { z-index: 1; }
.face .copy, .face .sweep { z-index: 2; }
.lorehero > .loop { z-index: 0; object-position: 50% var(--foc, 12%); }

/* ------------------------------------------------------------ fighter pick */

.pick { display: grid; grid-template-rows: auto 1fr auto; background: var(--bg); }
.pick .head { padding: calc(12px + env(safe-area-inset-top)) 15px 4px; display: flex; align-items: baseline; gap: 8px; }
.pick .head .side { font: 700 10px/1 var(--ui); letter-spacing: .18em; text-transform: uppercase; color: var(--gold); }
.deck { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; min-height: 0; scrollbar-width: none; }
.deck::-webkit-scrollbar { display: none; }
.face { flex: 0 0 100%; scroll-snap-align: center; position: relative; display: flex; flex-direction: column; justify-content: flex-end; background-size: cover; background-position: 50% 14%; background-color: #16161d; padding: 15px; text-align: left; min-height: 0; }
.face::before { content: ''; position: absolute; inset: 0; background: linear-gradient(180deg, rgba(8,8,11,.1) 28%, rgba(8,8,11,.9) 76%, var(--bg) 100%); }
.face > * { position: relative; }
.face .nm { font: 400 clamp(28px,9vw,40px)/.92 var(--display); letter-spacing: .02em; text-transform: uppercase; }
.face .ti { color: var(--dim); font-size: 13px; margin-top: 2px; }
.face .role { align-self: flex-start; margin-top: 8px; font: 700 9px/1 var(--ui); letter-spacing: .14em; text-transform: uppercase; background: var(--c); color: #14131a; padding: 5px 7px; }
.face .bl { font-size: 12.5px; color: var(--dim); margin: 8px 0 0; max-width: 40ch; line-height: 1.45; }
.face .copy { position: relative; display: flex; flex-direction: column; align-items: flex-start; }

/* ---- the roster arrives, it does not simply appear --------------------- */

/* Same grammar as the faceoff: whoever you land on punches in, their colour
   wipes across, and the words come up under it. Replayed on every change, so
   moving through the roster feels like a fighting game's select screen rather
   than a horizontal list of pictures. */
.face.live { animation: faceIn .5s cubic-bezier(.18,.85,.26,1.02) both; }
@keyframes faceIn {
  from { background-size: 128%; }
  to   { background-size: cover; }
}
/* A slow drift while you sit on someone, so the screen is never quite still. */
.face.live::before { animation: faceVeil .5s ease-out both; }
@keyframes faceVeil { from { opacity: .2 } to { opacity: 1 } }

.face .sweep {
  position: absolute; inset: 0; z-index: 1; pointer-events: none; opacity: 0;
  background: linear-gradient(104deg, transparent 34%, var(--c) 46% 54%, transparent 66%);
  mix-blend-mode: screen;
}
.face.live .sweep { animation: sweep .62s cubic-bezier(.3,.7,.3,1) both; }
@keyframes sweep {
  from { opacity: .9; transform: translateX(-120%) }
  to   { opacity: 0;  transform: translateX(120%) }
}

.face.live .nm   { animation: kickIn .42s .06s cubic-bezier(.2,.9,.25,1.05) both; }
.face.live .ti   { animation: riseIn .38s .16s ease-out both; }
.face.live .role { animation: riseIn .38s .22s ease-out both; }
.face.live .bl   { animation: riseIn .38s .28s ease-out both; }
@keyframes kickIn {
  from { opacity: 0; transform: translateX(-26px) skewX(-9deg) }
  to   { opacity: 1; transform: none }
}
@keyframes riseIn { from { opacity: 0; transform: translateY(12px) } }

@media (prefers-reduced-motion: reduce) {
  .face.live, .face.live::before, .face.live .sweep, .face.live .nm,
  .face.live .ti, .face.live .role, .face.live .bl { animation: none }
  .face .sweep { display: none }
}
.dots { display: flex; gap: 5px; justify-content: center; padding: 8px 0 2px; }
.dots i { width: 6px; height: 6px; border-radius: 3px; background: var(--line); }
.dots i.on { background: var(--gold); width: 18px; }

/* ------------------------------------------------------------ the room */

.home { display: grid; grid-template-rows: auto 1fr auto; background: var(--bg); }
.homebody {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; padding: 16px 14px; text-align: center; min-height: 0; overflow-y: auto;
}
.homebody h1 { margin: 0; font: 400 clamp(46px,17vw,84px)/.86 var(--display); letter-spacing: .03em; }
.homebody p { margin: 0; max-width: 30ch; color: var(--dim); font-size: 13.5px; }

/* Home: wordmark, then the seven fighters, then the buttons. Two rows, not
   three — this screen has no header, so the body takes the slack. */
.home { grid-template-rows: 1fr auto; }
.wordmark { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.wordmark h1 {
  font: 400 clamp(40px,13vw,68px)/.86 var(--display); letter-spacing: .04em; text-transform: uppercase;
}
.pickone {
  font: 700 9px/1 var(--ui) !important; letter-spacing: .22em; text-transform: uppercase;
  color: var(--gold) !important; margin-top: 2px !important;
}

/* Seven portraits, four then three. Each one is a way into a game. */
.roster {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 7px;
  width: 100%; max-width: 520px;
}
.roster .mug {
  flex: 0 1 calc(25% - 6px); min-width: 96px; aspect-ratio: 3 / 4;
  position: relative; overflow: hidden; border-radius: 5px;
  border: 2px solid var(--line); background-color: #14141b;
  background-size: cover; background-position: 50% 14%;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: 7px 6px; gap: 1px; text-align: left;
  transition: border-color .14s ease, transform .14s ease;
}
.roster .mug::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, transparent 34%, rgba(6,6,9,.92) 92%);
}
.roster .mug > * { position: relative; z-index: 1; }
.roster .mug .sig { position: absolute; inset: 0; z-index: 0; opacity: .8; }
.roster .mug .nm {
  font: 400 clamp(12px,3.2vw,15px)/.98 var(--display); letter-spacing: .02em;
  text-transform: uppercase; color: var(--ink);
}
.roster .mug .ar {
  font: 700 7.5px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--c);
}
.roster .mug:hover, .roster .mug:focus-visible { border-color: var(--c); }
.roster .mug:active { transform: translateY(2px); }
.homebody p.err { color: var(--danger); font: 700 12px/1.4 var(--mono); }

.roomcode {
  font: 400 clamp(52px,19vw,86px)/1 var(--display); letter-spacing: .22em;
  padding-left: .22em; color: var(--gold);
}
input.code {
  width: 100%; max-width: 260px; background: #101015; color: var(--ink);
  border: 2px solid var(--line); border-radius: 6px; padding: 12px 8px;
  font: 400 44px/1 var(--display); letter-spacing: .3em; text-align: center; text-indent: .3em;
}
input.code:focus { outline: none; border-color: var(--gold); }

.seats { display: flex; gap: 8px; width: 100%; max-width: 360px; }
.seatbox {
  flex: 1; min-width: 0; aspect-ratio: 3 / 4; border: 2px solid var(--line); border-radius: 6px;
  background-size: cover; background-position: 50% 18%; position: relative; overflow: hidden;
  display: flex; flex-direction: column; justify-content: flex-end; gap: 2px; padding: 8px 7px;
}
.seatbox::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, transparent 30%, rgba(4,4,6,.9) 92%);
}
.seatbox.me { border-color: var(--c, var(--gold)); }
.seatbox > * { position: relative; z-index: 1; }
.seatbox .lab { font: 700 8px/1 var(--ui); letter-spacing: .18em; text-transform: uppercase; color: var(--dim); }
.seatbox .nm { font: 400 17px/1 var(--display); letter-spacing: .03em; text-transform: uppercase; }
.seatbox .st { font: 700 9px/1 var(--mono); letter-spacing: .06em; color: var(--dim); }

/* --------------------------------------------------------------- the kit */

/* Your hand, held below the table.

   The buttons are a bar laid across a hand of cards, and everything here is in
   service of that one picture. The cards arc — middle highest, ends dropping
   away — because a flat row of evenly spaced cards reads as a toolbar and a
   curved one reads as something being held. They sit in shadow at rest and come
   up into the light when you press one, which is why the resting card is dimmed
   rather than merely smaller: it is behind the bar, not in front of it.

   The card is 80x112 because the band is 54 and the buttons are 66. Anything
   taller hangs out below them and off the bottom of the screen, which is what
   the first attempt did; the few pixels of slack absorb the tilt, which swings
   the bottom corners down past the flat edge and otherwise shows a white
   crescent under the buttons.

   The lift is on `transform` alone. Animating `bottom` took the card off the
   compositor and made the rise stutter on a phone at exactly the moment it is
   being looked at. */
.rig {
  position: relative; z-index: 0; height: 54px; margin-bottom: -7px;
  display: flex; justify-content: center; align-items: flex-start; pointer-events: none;
}
.pcard {
  pointer-events: auto; position: relative; flex: 0 0 auto;
  margin: 0 var(--tuck, -13px); padding: 0; background: none; border: 0;
  transform: translateY(calc(var(--d, 0) * 5px)) rotate(calc(var(--n, 0) * 3.4deg));
  transform-origin: 50% 210%;
  transition: transform .2s cubic-bezier(.2, .9, .3, 1.25);
}
.pcard .face3d {
  width: 80px; border: 2px solid rgba(0, 0, 0, .5);
  box-shadow: 0 -3px 12px #000a;
  /* In shadow behind the bar. Bright enough to read the name — the band is
     pointless if you cannot — dark enough that the hand sits back and the
     buttons stay the loudest thing on the screen. */
  filter: brightness(.74) saturate(.78);
  transition: filter .2s, box-shadow .2s;
}
.pcard.live .face3d { border-color: var(--c); }
.pcard.on .face3d { filter: brightness(.62) saturate(.4); }
.pcard.up {
  z-index: 6;
  transform: translateY(-64px) scale(1.08) rotate(calc(var(--n, 0) * .8deg));
}
.pcard.up .face3d { filter: none; box-shadow: 0 -14px 30px #000d; }
/* Shrunk from the size the face was drawn at, so the type steps down with it. */
.pcard .face3d { padding: 6px 7px 6px; gap: 3px; border-radius: 6px; }
.pcard .face3d .kind { font-size: 6px; letter-spacing: .12em; padding: 3px 4px; }
.pcard .face3d .nm { font-size: 13px; line-height: .92; }
.pcard .face3d .pow { font-size: 7px; line-height: 1.25; }
.pcard .face3d .cost { font-size: 11px; }
.pcard .face3d .cost span { font-size: 6px; }

/* The bar the hand is held under.

   Without this the 8px between Flip and Bank was a slot with a scrap of white
   card showing through it, which read as a rendering fault rather than as a
   card behind a button. The plate closes that gap and runs past the bottom of
   the pad so nothing peeks under, and its upward shadow is what makes the
   cards look like they are emerging from under the bar rather than taped
   behind it. */
.pair::before {
  content: ''; position: absolute; inset: -2px -12px -40px; z-index: -1;
  background: #000; box-shadow: 0 -7px 16px 2px rgba(0, 0, 0, .92);
}

/* The enlargement. Fixed over everything, dismissed by a tap anywhere that is
   not the card itself. */
.peek {
  position: fixed; inset: 0; z-index: 60; display: flex;
  align-items: center; justify-content: center; padding: 24px;
  background: rgba(4, 4, 6, .9); opacity: 0; transition: opacity .16s;
}
.peek.in { opacity: 1; }
.peekbox {
  display: flex; flex-direction: column; align-items: stretch; gap: 12px;
  width: min(74vw, 300px);
  transform: translateY(16px) scale(.93);
  transition: transform .22s cubic-bezier(.2, .9, .3, 1.3);
}
.peek.in .peekbox { transform: none; }
.peekbox .face3d.big {
  width: 100%; box-shadow: 0 20px 52px #000d; border: 2px solid rgba(0, 0, 0, .5);
}
/* The enlargement never dims. The fan dims a card you already own because it
   is sitting in shadow behind the button bar and is not actionable — but the
   whole reason to open one is to READ it, and saturate(.55) brightness(.86)
   over a dark scrim renders a permanent as a ghost you can barely make out.
   Ownership is said in words underneath instead; see .peeknote. */
.peekbox .use { min-height: 56px; }
.peeknote {
  text-align: center; font: 700 9px/1 var(--ui); letter-spacing: .18em;
  text-transform: uppercase; color: var(--dim);
}

/* ------------------------------------------------------------- tutorial */

/* Same swipe deck as the fighter carousel, so the gesture is already learned
   by the time a beat asks for it. Art on top, copy pinned to the bottom. */
.learn .head { align-items: center; }
.learn .head .skip {
  margin-left: auto; font: 700 9px/1 var(--ui); letter-spacing: .14em; text-transform: uppercase;
  color: var(--dim); border: 1px solid var(--line); border-radius: 20px; padding: 7px 11px;
}
.beat {
  flex: 0 0 100%; scroll-snap-align: center; min-height: 0;
  display: grid; grid-template-rows: 1fr auto; padding: 4px 15px 0;
}
.beat .art {
  display: grid; place-items: center; min-height: 0; overflow: hidden;
  padding: 8px 0 14px;
  /* The tutorial has room the table does not — show the faces at a size worth
     the art on them. */
  --card-w: clamp(78px, 22vw, 104px);
}
.beat .copy { padding-bottom: 4px; }
.beat .kicker {
  display: block; font: 700 9px/1 var(--mono); letter-spacing: .16em; text-transform: uppercase;
  color: var(--gold); margin-bottom: 7px;
}
.beat h2 {
  margin: 0 0 6px; font: 400 clamp(28px,9vw,38px)/.94 var(--display);
  letter-spacing: .02em; text-transform: uppercase;
}
.beat p { margin: 0; font-size: 13px; line-height: 1.5; color: var(--dim); max-width: 42ch; }

/* A spade never reaches the table in play, so it has no drawn face — here it
   only has to be recognisable as the thing that ends your turn. */
.card.s { background: #14131a; }
.card .val.s {
  left: 0; right: 0; bottom: 16%; text-align: center;
  color: var(--stock); -webkit-text-stroke: 0;
  font-size: calc(var(--card-w) * .74);
}
.card .bar.s { background: var(--danger); }

/* Card faces sit inside columns of differing height here, and a stretching
   flex parent would override their aspect ratio. Pin them. */
.beat .cards { align-items: flex-end; }
.beat .card { flex: 0 0 auto; height: auto; }

.legend { display: flex; gap: 16px; align-items: flex-end; justify-content: center; flex-wrap: wrap; }
.legend .leg { display: flex; flex-direction: column; align-items: center; gap: 7px; }
/* Scoped to .cap on purpose: a bare `.legend .leg span` also matches the card's
   own value and suit-bar spans, and outranks them, which shrank the numerals. */
.legend .leg > .cap {
  font: 700 8.5px/1.3 var(--ui); letter-spacing: .1em; text-transform: uppercase;
  color: var(--dim); text-align: center; max-width: 15ch;
  /* Two lines' worth either way, so cards in adjacent columns line up. */
  min-height: 2.6em;
}
.legend.tall p { font-size: 13.5px; line-height: 1.55; color: var(--dim); text-align: center; max-width: 34ch; }

/* The mocked action bar is a picture of a control, not a control. */
.learn .pair.mock { width: 100%; pointer-events: none; }
.learn .pair.mock .go { min-height: 74px; }

/* The roster needs a control on desktop: one portrait fills the frame, and
   with nothing to swipe the other fighters were unreachable. */
.stage { position: relative; min-height: 0; display: grid; }
.stage .deck { grid-area: 1 / 1; }
.nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 3;
  width: 38px; height: 38px; border-radius: 20px;
  background: rgba(8,8,11,.66); border: 1px solid var(--line); color: var(--ink);
  font: 400 24px/1 var(--ui); display: grid; place-items: center;
  backdrop-filter: blur(3px);
}
.nav.prev { left: 8px; padding-right: 3px; }
.nav.next { right: 8px; padding-left: 3px; }
.nav:disabled { opacity: 0; pointer-events: none; }
.nav:active { transform: translateY(-50%) scale(.92); }
.legend .arrow {
  font: 400 46px/1 var(--ui); color: var(--gold); padding: 0 2px;
  display: grid; place-items: center; min-height: 96px;
}

/* ------------------------------------------------------- spending a power */

/* Anticipation, a held beat you can actually read, then a decisive exit that
   carries the colour of what just happened. */
.spend {
  position: fixed; inset: 0; z-index: 60;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px;
  background: radial-gradient(60% 50% at 50% 45%, color-mix(in srgb, var(--c) 22%, rgba(4,4,6,.93)), rgba(4,4,6,.96));
  perspective: 1200px;
}
.spend .lede {
  font: 700 10px/1 var(--ui); letter-spacing: .22em; text-transform: uppercase; color: var(--c);
  opacity: 0; animation: spendLede .5s ease-out .12s forwards;
}
.stage3d { transform-style: preserve-3d; }
.face3d.big { width: min(74vw, 300px); }
.spend .face3d {
  animation: spendIn .62s cubic-bezier(.16,.9,.24,1) both;
  box-shadow: 0 30px 70px rgba(0,0,0,.75), 0 0 0 1px rgba(0,0,0,.5),
              0 0 60px color-mix(in srgb, var(--c) 45%, transparent);
}
.spend.go .face3d { animation: spendGo .6s cubic-bezier(.4,0,.9,.4) both; }
.spend .flare {
  position: absolute; inset: 0; pointer-events: none; opacity: 0;
  background: radial-gradient(40% 30% at 50% 48%, var(--c), transparent 70%);
}
.spend.go .flare { animation: spendFlare .6s ease-out both; }

@keyframes spendLede { to { opacity: 1 } }
@keyframes spendIn {
  from { transform: translateY(90px) rotateX(38deg) scale(.72); opacity: 0 }
  60%  { transform: translateY(-10px) rotateX(-6deg) scale(1.04); opacity: 1 }
  to   { transform: none; opacity: 1 }
}
@keyframes spendGo {
  0%   { transform: none; opacity: 1 }
  35%  { transform: scale(1.09) rotateX(-4deg); opacity: 1; filter: brightness(1.5) }
  100% { transform: translateY(-52px) scale(.86) rotateX(26deg); opacity: 0; filter: brightness(2.4) }
}
@keyframes spendFlare { 0% { opacity: 0 } 30% { opacity: .85 } 100% { opacity: 0 } }

/* The shared power face: art first, rule underneath, price in the corner. */
.face3d {
  width: min(62vw, 232px); aspect-ratio: 5/7; border-radius: 9px; overflow: hidden;
  background: var(--stock); color: var(--stock-ink); position: relative;
  display: flex; flex-direction: column; padding: 11px 12px 10px; gap: 6px; text-align: left;
}
.face3d .kind {
  align-self: flex-start; font: 700 8px/1 var(--ui); letter-spacing: .16em; text-transform: uppercase;
  background: var(--stock-ink); color: var(--stock); padding: 5px 6px; border-radius: 2px;
}
.face3d .nm {
  font: 400 clamp(21px,6.4vw,30px)/.9 var(--display); letter-spacing: .02em; text-transform: uppercase;
}
.face3d .rule { height: 2px; background: var(--stock-ink); opacity: .25; }
/* One object on a field, and nothing else. The field is tinted by the power's
   own mark, using the same three hues the number cards use, so a Blade power
   and a Blade card read as coming from the same deck — because they do. What
   used to be here is described in powerFace. */
.face3d .art {
  position: relative; flex: 1; min-height: 0; overflow: hidden; border-radius: 4px;
  background: color-mix(in srgb, var(--mk, #55535f) 16%, #e9e7e2);
  display: grid; place-items: center;
  padding: 8%; box-sizing: border-box;
}
/* The band carries the margin as padding and the art simply fills what is left.
   Two things were tried first and both failed for the same underlying reason:
   a percentage height on a child did not resolve against the band's fixed
   height (the Bomb came out 95px inside a 76px band), and absolute insets do
   not size a replaced element at all — the images vanished entirely. Padding
   plus 100% is the one that has no ambiguity in it. */
.face3d .art .ink, .face3d .art .bomb {
  width: 100%; height: 100%; min-width: 0; min-height: 0;
}
.face3d .art .ink { object-fit: contain; }
.face3d .art .bomb { display: grid; place-items: center; }
.face3d .art .bomb svg { max-width: 100%; max-height: 100%; }
.face3d.s-c { --mk: #14131a; }
.face3d.s-h { --mk: #b3161c; }
.face3d.s-d { --mk: #9d7010; }
.face3d.s-s { --mk: #14131a; }
.face3d.s-x { --mk: #55535f; }
/* No drawing for this mark yet — the field carries the card alone, the same way
   an undrawn number card does. */
.face3d .art.bare::after {
  content: ''; width: 34%; aspect-ratio: 1; border-radius: 50%;
  border: 3px solid color-mix(in srgb, var(--mk) 34%, transparent);
}
.face3d .pow { font: 400 11.5px/1.35 var(--ui); color: #3a3944; }
.face3d .cost {
  font: 400 20px/1 var(--display); color: var(--stock-ink);
}
.face3d .cost span {
  font: 700 8px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase; opacity: .55;
}

/* The Bomb, drawn rather than photographed. */
.bombart { position: absolute; inset: 6% 6% 18% 6%; }
.bombart svg { width: 100%; height: 100%; }

/* ------------------------------------------------------------- the Bomb */

/* Everything else on this screen is dealt with in a beat. The Bomb is not.
   It arrives full size, the fuse burns for most of a second while you can only
   watch, and then it takes the table with it — the real cards, thrown off the
   screen. Three phases, driven from ui.js: .in while the fuse burns, .go for
   the blast, then it clears.

   The one design rule here: the fuse beat has to be long enough to hurt. A
   detonation you do not see coming is a flicker; one you see coming and cannot
   stop is the whole feeling of a push-your-luck game. */
.boom {
  position: fixed; inset: 0; z-index: 70;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px;
  background: radial-gradient(62% 48% at 50% 44%, #1c0810, rgba(3,3,5,.97));
  perspective: 1100px; overflow: hidden;
  --card-w: min(52vw, 218px);
}
.boom.bombBust.go { animation: boomKick .5s cubic-bezier(.36,.07,.19,.97) both; }

/* The blast itself: white at the centre, the Bomb's own pink at the edge. */
/* A circle, not a percentage ellipse — the same blast on a phone and on a
   desktop window three times as wide. */
.boom .sky {
  position: absolute; inset: 0; opacity: 0; pointer-events: none; z-index: 2;
  background: radial-gradient(circle at 50% 46%, #fff 0%, #ff3b45 13%, #ff2f6e 24%, transparent 44%);
}
.boom.bombBust.go .sky { animation: boomSky .68s ease-out both; }

/* Positioned, all of it — z-index does nothing to a static box, and without
   this the word ends up painted behind the card it is supposed to punch
   through. */
.boom .kicker {
  font: 700 10px/1 var(--ui); letter-spacing: .24em; text-transform: uppercase;
  color: #ff5a63; position: relative; z-index: 3; animation: boomRise .42s ease-out;
}
.boom .rig { position: relative; display: grid; place-items: center; z-index: 3; }

.boom .boomcard {
  position: relative; z-index: 2;
  box-shadow: 0 30px 70px rgba(0,0,0,.8), 0 0 60px rgba(255,47,110,.28);
  animation: boomIn .44s cubic-bezier(.15,.9,.24,1) both, boomTremble .46s .44s linear both;
}
.boom.go .boomcard { animation: boomAway .5s cubic-bezier(.3,.7,.4,1) both; }
.boom.bombBust.go .boomcard { opacity: 0; animation: boomBlow .5s cubic-bezier(.34,0,.7,.35) both; }

/* The lit end of the fuse, laid over the print where the spark is drawn. It
   grows for the whole of the .in beat — that growth is the clock. */
.boom .fuse {
  position: absolute; left: 68%; top: 18%; width: 30%; aspect-ratio: 1; z-index: 4;
  transform: translate(-50%, -50%); border-radius: 50%; pointer-events: none;
  background: radial-gradient(circle, #fff 0%, #ffd166 24%, #ff3b45 50%, transparent 72%);
  mix-blend-mode: screen; opacity: 0;
  animation: fuseBurn .9s ease-in both;
}
.boom:not(.bombBust) .fuse { animation: fuseOut .8s ease-out both; }
.boom.go .fuse { display: none; }

/* The shrapnel is the hand that was on the table. */
.boom .debris { position: absolute; inset: 0; display: grid; place-items: center; pointer-events: none; z-index: 1; }
.boom .chip {
  position: absolute; --card-w: min(24vw, 98px); opacity: 0;
  box-shadow: 0 10px 26px rgba(0,0,0,.8);
}
.boom.go .chip { animation: boomFly .95s cubic-bezier(.08,.62,.3,1) var(--d) both; }

.boom .verdict {
  text-align: center; display: grid; gap: 7px; align-content: start;
  position: relative; z-index: 4; min-height: 96px; max-width: 90vw; padding: 0 12px;
}
.boom .word {
  font: 400 clamp(54px, 19vw, 112px)/.82 var(--display); letter-spacing: .01em; opacity: 0;
}
.boom.go .word { opacity: 1; animation: boomWord .46s cubic-bezier(.2,.9,.25,1.2); }
.boom.bombBust .word { color: #ff2f3b; text-shadow: 0 0 44px rgba(255,47,59,.5); }
/* "Stepped over" is four times the word "Bust" and cannot be set at 19vw. */
.boom:not(.bombBust) .word { color: var(--stock); font-size: clamp(30px, 9.5vw, 56px); }
.boom .toll {
  font: 700 10px/1.5 var(--ui); letter-spacing: .17em; text-transform: uppercase; color: var(--dim);
  opacity: 0;
}
.boom.go .toll { opacity: 1; animation: boomRise .4s ease-out .1s both; }
.boom.bombBust.go .toll { color: #ff8f95; }
.boom .bark { font: italic 400 13px/1.4 var(--ui); color: var(--dim); opacity: 0; }
.boom.go .bark { opacity: 1; animation: boomRise .4s ease-out .24s both; }

@keyframes boomIn {
  from { transform: translateY(-120px) rotate(-13deg) scale(1.5); opacity: 0 }
  62%  { transform: translateY(9px) rotate(2deg) scale(.97); opacity: 1 }
  to   { transform: none; opacity: 1 }
}
/* The card cannot keep still while the fuse is going. */
@keyframes boomTremble {
  0%   { transform: none }
  20%  { transform: translate(1px, -1px) rotate(.4deg) }
  40%  { transform: translate(-2px, 1px) rotate(-.7deg) }
  60%  { transform: translate(3px, -2px) rotate(1deg) }
  80%  { transform: translate(-4px, 2px) rotate(-1.4deg) scale(1.02) }
  100% { transform: translate(2px, -1px) rotate(.8deg) scale(1.03) }
}
@keyframes boomBlow {
  0%   { transform: scale(1.03); opacity: 1; filter: brightness(1) }
  18%  { transform: scale(1.22); opacity: 1; filter: brightness(3) contrast(1.6) }
  100% { transform: scale(2.6) rotate(6deg); opacity: 0; filter: brightness(6) blur(9px) }
}
@keyframes boomAway {
  from { transform: none; opacity: 1 }
  to   { transform: translateY(30px) scale(.82); opacity: 0 }
}
@keyframes boomFly {
  from { transform: none; opacity: 1 }
  to   { transform: translate(var(--dx), var(--dy)) rotate(var(--rot)) scale(.8); opacity: 0 }
}
@keyframes boomSky {
  0%   { opacity: 0; transform: scale(.35) }
  10%  { opacity: 1 }
  100% { opacity: 0; transform: scale(2.8) }
}
@keyframes boomKick {
  0%, 100% { transform: none }
  12% { transform: translate(9px, -6px) }
  26% { transform: translate(-11px, 5px) }
  42% { transform: translate(7px, 6px) }
  60% { transform: translate(-5px, -4px) }
  80% { transform: translate(3px, 2px) }
}
@keyframes boomWord {
  from { transform: scale(2.3); opacity: 0; filter: blur(6px) }
  to   { transform: none; opacity: 1; filter: none }
}
@keyframes boomRise { from { opacity: 0; transform: translateY(7px) } }
@keyframes fuseBurn {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.28) }
  18%  { opacity: .9; transform: translate(-50%, -50%) scale(.5) }
  55%  { opacity: .75; transform: translate(-50%, -50%) scale(.8) }
  78%  { opacity: 1; transform: translate(-50%, -50%) scale(1.05) }
  100% { opacity: 1; transform: translate(-50%, -50%) scale(1.55) }
}
/* A Bomb that does not go off: the light goes out of it. */
@keyframes fuseOut {
  0%   { opacity: .95; transform: translate(-50%, -50%) scale(.6) }
  40%  { opacity: .5; transform: translate(-50%, -50%) scale(.42) }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(.2) }
}

/* The same card, sitting in the sheet while you decide what to do about it —
   fuse still lit, because nothing has been settled yet. */
.sheet .livebomb {
  --card-w: min(38vw, 150px); position: relative; justify-self: center; margin: 0 auto 4px;
  box-shadow: 0 18px 40px rgba(0,0,0,.75), 0 0 44px rgba(255,47,110,.22);
}
.sheet .livebomb .fuse {
  position: absolute; left: 68%; top: 18%; width: 30%; aspect-ratio: 1;
  transform: translate(-50%, -50%); border-radius: 50%; pointer-events: none;
  background: radial-gradient(circle, #fff 0%, #ffd166 24%, #ff3b45 50%, transparent 72%);
  mix-blend-mode: screen; animation: fuseIdle 1.1s ease-in-out infinite alternate;
}
@keyframes fuseIdle {
  from { opacity: .5; transform: translate(-50%, -50%) scale(.55) }
  to   { opacity: .95; transform: translate(-50%, -50%) scale(.85) }
}

/* No shaking, no shrapnel, no flash — but the card still comes up and the
   verdict is still said, because that is the information, not the decoration. */
@media (prefers-reduced-motion: reduce) {
  .boom, .boom .boomcard, .boom.go .boomcard, .boom.bombBust.go .boomcard, .boom .fuse,
  .boom .kicker, .boom.go .word, .boom.go .toll, .boom.go .bark, .boom .sky {
    animation: none;
  }
  .boom .boomcard { opacity: 1; }
  .boom.bombBust.go .boomcard { opacity: .25; }
  .boom .fuse { opacity: .9; }
  .boom .debris { display: none; }
}

/* The field behind a number card — one hue per mark, every fighter. */
.card .field { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0; }
.card .art, .card .val, .card .bar, .card .bump { z-index: 1; }
/* A second card of the same rank used to sit BEHIND its twin with a sliver
   showing, tucked back 74% of a card width and dimmed — because it scored but
   did not hold the slot in the run.

   There are no runs. Scoring went flat, the solver returns one block per card,
   and every card on the table is now worth exactly its face. So the reason for
   hiding one was deleted and the hiding was not: a table with two 4s and two 7s
   showed five cards out of seven, and the two you could not see were the ones
   the eye most wanted to check. Every card reads, at the same size, in the
   order it landed. */
.grp .cards .card { z-index: 1; }
/* A bought permanent settles in rather than burning off. */
.spend.kept.go .face3d { animation: keptGo .6s cubic-bezier(.3,.8,.3,1) both; }
@keyframes keptGo {
  0%   { transform: none; opacity: 1 }
  40%  { transform: scale(1.06); filter: brightness(1.35) }
  100% { transform: translateY(24px) scale(.5); opacity: 0 }
}

/* ------------------------------------------------------------- your deck */

/* What is left to come, which is the number the Flip button's odds are made
   of. Contents, never order — the same thing the server is willing to tell you. */
.deckbox { display: flex; flex-direction: column; gap: 13px; overflow-y: auto; min-height: 0; }
.deckrow { display: flex; flex-direction: column; gap: 6px; }
.deckrow .dh { display: flex; align-items: baseline; gap: 8px; }
.deckrow .dh b {
  font: 400 17px/1 var(--display); letter-spacing: .04em; text-transform: uppercase;
}
.deckrow .dh s {
  text-decoration: none; font: 700 9px/1 var(--mono); letter-spacing: .1em;
  text-transform: uppercase; color: var(--dim); margin-left: auto;
}
.chips { display: flex; flex-wrap: wrap; gap: 4px; }
.chip {
  font-style: normal; min-width: 30px; height: 34px; border-radius: 4px;
  display: inline-flex; align-items: center; justify-content: center;
  font: 400 17px/1 var(--display); font-variant-numeric: tabular-nums;
  background: var(--stock); color: var(--stock-ink);
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.35);
}
.chip.m-h { background: #fbeceb; color: #b3161c; }
.chip.m-d { background: #faf1de; color: #9d7010; }
.chip.m-s { background: #17171f; color: var(--danger); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--danger) 55%, transparent); }

/* Blacked out: it has been and gone. */
.chip.s-spent {
  background: #101015 !important; color: #35353f !important;
  box-shadow: inset 0 0 0 1px #22222b !important;
}
.chip.s-table { outline: 2px solid var(--gold); outline-offset: 1px; }
.chip.s-store, .chip.s-kit, .chip.s-play { opacity: .85; box-shadow: inset 0 0 0 2px var(--gold); }

.chips.wide { flex-direction: column; }
.chips.wide .chip {
  width: 100%; height: auto; justify-content: flex-start; gap: 8px;
  padding: 8px 10px; font: 400 16px/1 var(--display); letter-spacing: .03em; text-transform: uppercase;
}
.chips.wide .chip s {
  text-decoration: none; margin-left: auto;
  font: 700 8.5px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase; opacity: .6;
}

.deckkey { display: flex; gap: 14px; flex-wrap: wrap; padding-top: 4px; border-top: 1px solid var(--rule, #26262f); }
.deckkey span {
  display: inline-flex; align-items: center; gap: 6px;
  font: 700 8.5px/1 var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--dim);
}
.deckkey .chip { min-width: 18px; height: 18px; }

/* ---------------------------------------------------------------- key art */

/* The print sits behind the wordmark and fades into the ground before it
   reaches the roster, so the fighters stay the brightest thing on the screen. */
/* Alice at the bottom of the Road with all six waiting for her. It carries the
   whole premise, so it is a band in the flow rather than a wash behind
   everything, and the wordmark sits on the empty yellow above their heads. */
/* The key art is the whole screen, not a band inside it. A fixed aspect meant
   every phone taller than 505/620 got a strip of picture and a slab of dead
   black beneath it. Cover, full bleed, and the buttons sit on it. */
/* The home screen is a mirror, not a poster: your fighter fills it, in the coat
   you put on them. art/key/title.jpg is still generated — it is the share card's
   sibling and the thing a link preview shows — but it is no longer the screen. */
.home { background: #06070a; grid-template-rows: auto 1fr auto; }

/* The key art is a plate, composed by art/og/make.py out of the same portraits
   and the same card recipe the game uses. It replaced a cast line built from
   seven live DOM crops, which matched the art perfectly and still read as a
   smear — seven busts at phone width is seven things, none of them big enough
   to be anybody.

   It is full bleed and it carries its own darkness at the head and the foot,
   which is where the wordmark and the buttons go. */
.home::before {
  content: ''; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: linear-gradient(180deg,
    rgba(6,7,9,.9) 0 6%, rgba(6,7,9,.42) 22%, rgba(6,7,9,0) 44% 62%,
    rgba(6,7,9,.55) 78%, rgba(6,7,9,.95) 92%, #06070a 100%);
}
.home .homebody, .home .pad { position: relative; z-index: 1; }
.home .pad { background: none; }
/* The wordmark sits on acid yellow, so it goes dark rather than light. */
/* Light on a dark print. The wordmark was near-black because the key art used
   to be acid yellow; the art is the painted cast on a night road now. */
.home .wordmark h1 { color: #f4f1ea; text-shadow: 0 3px 30px #000, 0 0 70px rgba(0,0,0,.9); }
.home .wordmark p { color: rgba(244,241,234,.72); text-shadow: 0 2px 14px #000; }
/* One place, because five rules were fighting over this box and the last one
   won by accident. The display face sets line-height .86, so its caps sit proud
   of the line box — the top padding is for the letterforms, not the layout. */
.home .homebody {
  position: relative; justify-content: flex-start; gap: 6px; overflow: hidden;
  padding: calc(30px + env(safe-area-inset-top)) 14px 0;
}
.home .pickone { margin-top: 2px !important; }

.home .homebody > * { position: relative; z-index: 1; }
.wordmark h1 { text-shadow: 0 3px 26px rgba(4,4,6,.9); }

/* The Bomb's own print, over the drawn fallback. */
.card .bombink {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 40%; z-index: 1;
}
.card .bombink ~ .bombart { display: none; }
.card.inkless .bombart { display: block; }


/* A stretch that pays nothing says so in words. A bare 0 in the Dust slot
   reads as the game having lost track of a reward. */
.payout.none b { color: var(--dim); }
.payout.none s { color: var(--dim); }

/* ----------------------------------------------------------------- hole */

/* The descent reuses the Road's stage and reverses its two readings: the floor
   scrolls the other way, so you are going down rather than walking on, and the
   haze sits under you instead of ahead. */
.hole .roadfloor.down i.bricks { animation-direction: reverse; }
.hole .roadstage { --c: #c9553f; }
.hole .roadhaze {
  background: linear-gradient(180deg, rgba(6, 6, 9, .82) 0 12%, transparent 42%, rgba(20, 6, 6, .5) 100%);
}
.hole .roadwhere b { color: #e07a5f; }
.hole .roadmarks .pay {
  border-color: var(--gold); color: var(--gold);
}
/* What the descent is carrying, under the button where the hand would be. */
.threats.carrying { justify-content: center; margin-top: 8px; }
.threats.carrying .held {
  font: 700 9px/1 var(--ui); letter-spacing: .12em; text-transform: uppercase;
  border: 1px solid var(--gold); color: var(--gold);
  padding: 5px 7px; border-radius: 2px;
}

/* --------------------------------------------------------------- season */

/* The cast is a SEASON, not "the characters". Saying so makes the set finite
   and collectable, and leaves a shape for the next one to arrive in. */
.seasonhead {
  margin: 0 0 2px; display: flex; align-items: baseline; gap: 9px;
  font: 400 21px/1 var(--display); letter-spacing: .05em; text-transform: uppercase;
}
.seasonhead s {
  text-decoration: none; font: 700 8.5px/1 var(--mono);
  letter-spacing: .16em; text-transform: uppercase; color: var(--dim);
}
/* A gap you can see is a reason to come back. Dashed, not disabled-looking —
   it is a promise, not a broken button. */
.seasonnext {
  border: 1px dashed #2c2c36; border-radius: 8px; padding: 14px 15px;
  display: flex; flex-direction: column; gap: 4px; text-align: center;
}
.seasonnext b {
  font: 400 19px/1 var(--display); letter-spacing: .05em;
  text-transform: uppercase; color: var(--dim);
}
.seasonnext span { font: 400 12px/1.4 var(--ui); color: #6a6778; }

/* ------------------------------------------------------------ home strip */

/* What you have, always visible and always tappable. A currency you cannot see
   is a currency you do not spend, and a fraction you can see is a gap somebody
   wants to close. */
.hstrip {
  display: flex; gap: 6px; padding: calc(8px + env(safe-area-inset-top)) 10px 8px;
  position: relative; z-index: 2;
}
.hchip {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 7px 4px 6px; border-radius: 6px;
  background: rgba(10, 10, 14, .66); border: 1px solid #23232c;
  backdrop-filter: blur(6px);
}
.hchip b {
  font: 400 17px/1 var(--display); letter-spacing: .03em;
  font-variant-numeric: tabular-nums;
}
.hchip s {
  text-decoration: none; font: 700 7.5px/1 var(--mono);
  letter-spacing: .16em; text-transform: uppercase; color: var(--dim);
}
.hchip:active { transform: translateY(1px); }
.dustchip b { color: var(--gold); }

/* --------------------------------------------------------- home hero */

/* Your fighter, full bleed, in their own colour. This is the only place a coat
   you bought is visible without going to look for it. */
.hhero { position: relative; overflow: hidden; min-height: 0; }
.hfig {
  position: absolute; inset: 0;
  background-size: cover; background-position: 50% var(--foc, 12%);
  background-repeat: no-repeat;
}
/* Dark at the top for the wordmark and at the foot for the name and the
   buttons under it, clear through the middle where the face is. */
.hveil {
  position: absolute; inset: 0;
  background:
    linear-gradient(180deg, rgba(6,6,9,.55) 0 4%, rgba(6,6,9,.12) 14%,
      rgba(6,6,9,0) 26% 44%, rgba(6,6,9,.78) 74%, rgba(6,6,9,.97) 90%, #06070a 100%),
    radial-gradient(120% 62% at 50% 100%, color-mix(in srgb, var(--c) 26%, transparent), transparent 70%);
}
/* Brand and identity together at the foot, over the ground the veil already
   lays down — the top belongs to the face. */
.hplate {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 0 14px 8px; text-align: center;
  display: flex; flex-direction: column; align-items: center; gap: 1px;
}
.hplate h1 {
  margin: 0; font: 400 clamp(34px, 10.5vw, 52px)/.88 var(--display);
  letter-spacing: .02em; text-transform: uppercase; color: #f4f1ea;
  text-shadow: 0 3px 26px #000, 0 0 60px rgba(0, 0, 0, .9);
}
/* Tapping yourself changes who you are. */
.hwho {
  background: none; border: 0; padding: 4px 0 0;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
}
.hwho .nm {
  font: 400 clamp(22px, 6.4vw, 30px)/1 var(--display);
  letter-spacing: .03em; text-transform: uppercase;
  text-shadow: 0 2px 12px #000;
}
.hwho .ct {
  font: 700 8.5px/1 var(--mono); letter-spacing: .18em;
  text-transform: uppercase; color: var(--c);
  text-shadow: 0 1px 6px #000;
}
.hwho:active { transform: translateY(1px); }

/* The daily is the only thing here with a clock on it, so it is the only thing
   that gets a dot. */
.go.door.fresh { position: relative; }
.go.door.fresh::after {
  content: ''; position: absolute; right: 11px; top: 50%; margin-top: -4px;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--door); box-shadow: 0 0 10px var(--door);
}
@media (prefers-reduced-motion: no-preference) {
  .go.door.fresh::after { animation: freshPulse 1.9s ease-in-out infinite; }
}
@keyframes freshPulse { 50% { opacity: .35; transform: scale(.82) } }

/* ---------------------------------------------------------- the doors */

/* The home screen's doors. They were five identical 66px slabs and the art
   they sat on could barely be seen past them; these are shorter, and only the
   Road is filled. Identity comes from a rule down the leading edge in the
   mode's own colour — the same colour that door's sheet opens in — so the
   button and the thing it opens are visibly one object.

   No second line on any of them. What a mode is belongs in its sheet. */
.pad.doors { gap: 6px; padding-bottom: calc(10px + env(safe-area-inset-bottom)); }
.go.door {
  min-height: 54px; position: relative; overflow: hidden;
  border-color: #23232c; background: #101016;
}
.go.door::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
  background: var(--door, var(--line));
}
.go.door .big { font-size: clamp(19px, 5.6vw, 25px); }
.go.door.hit { background: var(--door); border-color: var(--door); color: #14131a; }
.go.door.hit::before { background: rgba(0, 0, 0, .32); }
.go.door:not(:disabled):active { transform: translateY(2px); }
.go.door:disabled { opacity: 1; color: var(--dim); border-color: #1b1b22; }
.go.door:disabled::before { opacity: .4; }

.d-quest { --door: #ff6f6a; }
.d-hole { --door: #e07a5f; }
.d-daily { --door: #e0a33c; }
.d-versus { --door: #9fc0dd; }

/* Lore is not a mode and should not look like one. */
.go.slim.quiet {
  min-height: 38px; background: none; border-color: #1e1e26; color: var(--dim);
  /* Sits under centred doors, so it centres too rather than hugging the left. */
  margin: 0 auto;
}
.go.slim.quiet .big {
  font: 700 11px/1 var(--ui); letter-spacing: .16em; text-transform: uppercase;
}

/* ----------------------------------------------------- the mode sheets */

/* A door explains itself before it opens. Anchored to the bottom and slid up,
   because on a phone that is where the thumb already is and the buttons that
   answer it end up under the same finger that asked. */
.slideveil {
  align-items: flex-end; padding: 0;
  background: rgba(4, 4, 6, .74);
  opacity: 0; transition: opacity .18s ease-out;
}
.slideveil.in { opacity: 1; }
.slideup {
  width: 100%; max-width: 540px; margin: 0 auto;
  background: #101016; border-top: 3px solid var(--p1, var(--gold));
  border-radius: 14px 14px 0 0;
  padding: 10px 18px calc(18px + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; gap: 10px;
  box-shadow: 0 -18px 44px rgba(0, 0, 0, .7);
  transform: translateY(100%);
  transition: transform .26s cubic-bezier(.2, .9, .3, 1);
}
.slideveil.in .slideup { transform: none; }
@media (prefers-reduced-motion: reduce) {
  .slideup { transition: none; transform: none; }
  .slideveil { transition: none; }
}

/* Each door keeps its own colour, so the sheet is recognisably the thing you
   just pressed rather than a generic dialog. */
.slideup.m-quest { --p1: #ff6f6a; }
.slideup.m-hole { --p1: #e07a5f; }
.slideup.m-daily { --p1: #e0a33c; }
.slideup.m-versus { --p1: #9fc0dd; }

.slideup .grab {
  width: 36px; height: 4px; border-radius: 2px; background: #33333f;
  align-self: center; margin-bottom: 2px;
}
.slideup .kicker {
  margin: 0; font: 700 9px/1 var(--mono); letter-spacing: .2em;
  text-transform: uppercase; color: var(--p1);
}
.slideup .mname {
  margin: 0; font: 400 clamp(24px, 7vw, 32px)/1 var(--display);
  letter-spacing: .03em; text-transform: uppercase;
}
.slideup .mbody { margin: 0; font-size: 13.5px; line-height: 1.5; color: var(--dim); }

/* The three slots every mode fills, in the same order every time — that
   repetition is what makes them comparable at a glance. */
.mfacts {
  margin: 4px 0 0; display: grid; grid-template-columns: auto 1fr;
  gap: 7px 14px; align-items: baseline;
}
.mfacts dt {
  font: 700 8.5px/1.3 var(--mono); letter-spacing: .13em;
  text-transform: uppercase; color: var(--dim); white-space: nowrap;
}
.mfacts dd { margin: 0; font-size: 13px; line-height: 1.4; }
.mstanding {
  margin: 2px 0 4px; padding-top: 9px; border-top: 1px solid var(--line);
  font: 700 10px/1.3 var(--mono); letter-spacing: .1em;
  text-transform: uppercase; color: var(--p1);
}

/* The cast and the wardrobe are two views of the same thing, so they are two
   tabs rather than one buried under the other. */
.loretabs {
  flex: 0 0 auto; display: flex; gap: 6px;
  padding: 0 12px 8px; border-bottom: 1px solid var(--line);
}
.ltab {
  flex: 1; min-height: 38px; border-radius: 5px;
  background: none; border: 1px solid var(--line); color: var(--dim);
  font: 400 15px/1 var(--display); letter-spacing: .05em; text-transform: uppercase;
  display: flex; align-items: center; justify-content: center; gap: 7px;
}
.ltab s {
  text-decoration: none; font: 700 8.5px/1 var(--mono);
  letter-spacing: .08em; color: var(--dim);
}
.ltab.on {
  color: var(--ink); border-color: var(--gold);
  background: color-mix(in srgb, var(--gold) 10%, transparent);
}
.ltab.on s { color: var(--gold); }

/* ------------------------------------------------------------- wardrobe */

/* Every coat in the game, grouped by who wears it. Coats for fighters you have
   not collected still show — a locked thing you can see is a reason to keep
   playing; a locked thing you cannot see is not in the game at all. */
.wardrobebody {
  flex: 1; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: 18px;
  padding: 12px 12px calc(24px + env(safe-area-inset-bottom));
}
.rack { display: flex; flex-direction: column; gap: 8px; }
.rackname {
  margin: 0; display: flex; align-items: baseline; gap: 8px;
  font: 400 19px/1 var(--display); letter-spacing: .04em; text-transform: uppercase;
  border-bottom: 1px solid var(--line); padding-bottom: 6px;
}
.rackname s {
  text-decoration: none; margin-left: auto;
  font: 700 8.5px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--dim);
}
.rackrow { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 8px; }

/* The swatch IS the coat — the printed portrait, cropped to the head, with the
   name and what a press would do sitting on it. */
.coatcell {
  position: relative; aspect-ratio: 4/5; border-radius: 6px; overflow: hidden;
  border: 2px solid var(--line); padding: 0;
  background-size: cover; background-position: 50% 8%; background-color: #16161d;
  display: flex; flex-direction: column; justify-content: flex-end;
  text-align: left;
}
.coatcell::before {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, transparent 30%, rgba(6, 6, 9, .92) 88%);
}
.coatcell .cn, .coatcell .cs { position: relative; padding: 0 6px; }
.coatcell .cn {
  font: 400 13px/1.02 var(--display); letter-spacing: .02em; text-transform: uppercase;
  text-shadow: 0 1px 3px #000;
}
.coatcell .cs {
  font: 700 7.5px/1.25 var(--mono); letter-spacing: .08em; text-transform: uppercase;
  color: var(--dim); padding-bottom: 6px; padding-top: 2px;
}
/* Owned reads warm, worn reads lit, everything else sits back. */
.coatcell.mine { border-color: var(--gold); }
.coatcell.mine .cs { color: var(--gold); }
.coatcell.on { border-color: var(--c); }
.coatcell.on .cs { color: var(--c); }
.coatcell:disabled { filter: grayscale(.55) brightness(.66); }
.coatcell.on:disabled, .coatcell.mine:disabled { filter: none; }
.coatcell:not(:disabled):active { transform: translateY(2px); }
.coatcell.bare { background-position: 50% 6%; }

/* ---------------------------------------------------------------- forge */

/* Where Dust goes. Two shelves — permanent upgrades that only the Hole reads,
   and coats that cost the same Dust, which is what makes choosing between a
   new coat and one fewer Bomb a real decision. */
.forge .forgebody {
  flex: 1; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: 12px;
  padding: 12px 12px calc(24px + env(safe-area-inset-bottom));
}

/* Who you are spending on. Switching also switches the shelf below it. */
/* flex: 0 0 auto, or the column parent shrinks a 96px row of portraits down to
   a 10px strip — the tiles size themselves and the row must not be squeezed. */
.forgewho {
  flex: 0 0 auto; display: flex; gap: 8px;
  overflow-x: auto; scrollbar-width: none; padding-bottom: 2px;
}
.forgewho::-webkit-scrollbar { display: none; }
.whotile {
  flex: 0 0 auto; width: 78px; height: 96px; position: relative;
  border-radius: 6px; border: 2px solid var(--line); overflow: hidden;
  background-size: cover; background-position: 50% var(--foc, 10%);
  background-color: #16161d; padding: 0;
  display: flex; flex-direction: column; justify-content: flex-end;
  filter: grayscale(.7) brightness(.62);
}
.whotile.on { border-color: var(--c); filter: none; }
.whotile .nm {
  position: relative; font: 400 12px/1.05 var(--display); letter-spacing: .02em;
  text-transform: uppercase; padding: 4px 5px;
  background: linear-gradient(180deg, transparent, rgba(6, 6, 9, .92) 60%);
  text-shadow: 0 1px 3px #000;
}
.whotile .rank {
  position: absolute; top: 4px; right: 4px;
  font: 700 8px/1 var(--mono); letter-spacing: .06em;
  background: var(--gold); color: #14131a; padding: 3px 4px; border-radius: 2px;
}

.forgehead {
  margin: 6px 0 0; font: 400 clamp(18px, 5.4vw, 23px)/1 var(--display);
  letter-spacing: .04em; text-transform: uppercase;
}
.forgenote { margin: -4px 0 0; font: 400 12px/1.45 var(--ui); color: var(--dim); }
.forgeerr { margin: 0; font: 700 11px/1 var(--ui); letter-spacing: .1em; text-transform: uppercase; color: var(--danger); }

.upgrades, .coats { flex: 0 0 auto; display: flex; flex-direction: column; gap: 8px; }

.up, .coat {
  text-align: left; padding: 11px 12px; border-radius: 6px;
  background: #14141b; border: 2px solid var(--line);
  display: grid; grid-template-columns: 1fr auto; gap: 2px 12px;
  align-items: baseline;
}
.up .t, .coat .t {
  grid-column: 1; font: 400 17px/1 var(--display);
  letter-spacing: .03em; text-transform: uppercase;
}
.up .cost, .coat .cost {
  grid-column: 2; grid-row: 1 / span 2; align-self: center;
  font: 700 13px/1 var(--mono); color: var(--gold); white-space: nowrap;
}
.up .p, .coat .p { grid-column: 1; font: 400 11.5px/1.35 var(--ui); color: var(--dim); }

/* Levels as pips, so how far up a track you are reads without a number. */
.up .pips { grid-column: 1; display: flex; gap: 4px; margin-top: 6px; }
.up .pips i {
  width: 16px; height: 5px; border-radius: 1px;
  background: #2a2a34; border: 1px solid var(--line);
}
.up .pips i.on { background: var(--c, var(--gold)); border-color: transparent; }
.up .now {
  grid-column: 1; font: 700 9px/1 var(--mono); letter-spacing: .1em;
  text-transform: uppercase; color: var(--dim); margin-top: 5px;
}

/* A coat is a colourway, so the shelf shows the colour rather than naming it —
   the swatch IS the printed portrait. These rules have to sit AFTER the shared
   `.up .t, .coat .t` block above: same specificity, so order decides, and put
   any earlier they lose and every coat collapses into one 44px column. */
.coat { grid-template-columns: 44px 1fr auto; align-items: center; }
.coat .swatch {
  grid-column: 1; grid-row: 1 / span 2; align-self: center;
  width: 44px; height: 52px; border-radius: 4px;
  background-size: cover; background-position: 50% 10%;
  border: 1px solid rgba(0, 0, 0, .5);
}
.coat .t { grid-column: 2; grid-row: 1; }
.coat .p { grid-column: 2; grid-row: 2; }
.coat .cost { grid-column: 3; grid-row: 1 / span 2; align-self: center; }
.coat.on { border-color: var(--c); background: color-mix(in srgb, var(--c) 10%, #14141b); }
.coat.on .cost { color: var(--c); }

.up.can, .coat.can { border-color: var(--gold); }
.up.can:active, .coat.can:active { transform: translateY(2px); }
.up.cant, .coat.cant { opacity: .5; }
.up.cant .cost, .coat.cant .cost { color: var(--danger); }
.up.maxed { opacity: .72; border-style: dashed; }
.up.maxed .cost { color: var(--dim); }
.coat.have { border-color: var(--c); opacity: .85; }
.coat.have .cost { color: var(--c); }
/* Earned, not sold — shown so the condition is visible, never pressable. */
.coat.locked { border-style: dashed; opacity: .6; }
.coat.locked .cost { color: var(--dim); font-size: 10px; }

/* ---------------------------------------------------------------- quest */

.head .dust { margin-left: auto; font: 400 12px/1 var(--mono); color: var(--gold); }
.head .dust b { font: 400 17px/1 var(--display); }

.wavemark { text-align: center; display: flex; flex-direction: column; align-items: center; gap: 2px; }
.wavemark s {
  text-decoration: none; font: 700 9px/1 var(--mono); letter-spacing: .24em;
  text-transform: uppercase; color: var(--dim);
}
.wavemark b { font: 400 clamp(46px,15vw,78px)/.86 var(--display); }
.wavemark i {
  font-style: normal; font: 700 8px/1 var(--ui); letter-spacing: .2em; text-transform: uppercase;
  background: var(--danger); color: #fff; padding: 4px 7px; border-radius: 2px;
}

/* 3:2, not 16:9. These portraits are 4:5 and the subject is the top half of
   the painting; a 16:9 letterbox of one is a horizontal slice of a face. And
   the crop runs bottom-up rather than left-to-right: with `cover` on a box
   wider than the source, width binds and the horizontal position does nothing,
   so a side gradient only ever darkened the art, never cleared a space. */
.foecard {
  width: 100%; max-width: 340px; aspect-ratio: 3/2; border-radius: 6px;
  border: 2px solid var(--line); background-size: cover;
  background-position: 50% var(--foc, 22%);
  position: relative; overflow: hidden; text-align: left;
  display: flex; flex-direction: column; justify-content: flex-end; gap: 1px; padding: 10px 12px;
}
.foecard::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(6,6,9,.05) 0 38%, rgba(6,6,9,.84) 78%, rgba(6,6,9,.96) 100%);
}
.foecard > * { position: relative; z-index: 1; }
/* Heraldry is drawn square; stretched across a 3:2 banner it reads as a crop of
   something else. Height binds, width follows, centred. */
.foecard .crest { position: absolute; inset: 7% 0 20%; opacity: .45; z-index: 0; display: grid; place-items: center; }
.foecard .crest svg { height: 100%; width: auto; max-width: 100%; }

/* You: a row, so the portrait keeps its own proportions. */
.youcard {
  width: 100%; max-width: 340px; border-radius: 6px; border: 2px solid var(--c);
  background: rgba(14,14,19,.9); text-align: left; overflow: hidden;
  display: flex; align-items: stretch; gap: 10px; padding: 0;
}
.youcard .mug {
  flex: 0 0 auto; width: 58px; aspect-ratio: 4/5; background-size: cover;
  background-position: 50% var(--foc, 18%); border-right: 1px solid var(--line);
}
.youcard .mug.bare { display: grid; place-items: center; padding: 7px; }
.youcard .mug.bare svg { width: 100%; height: 100%; }
.youcard .txt {
  display: flex; flex-direction: column; justify-content: center; gap: 2px;
  padding: 8px 4px 8px 0; min-width: 0;
}
.foecard .lab, .youcard .lab {
  font: 700 8px/1 var(--mono); letter-spacing: .2em; text-transform: uppercase; color: var(--dim);
}
.foecard .nm, .youcard .nm {
  font: 400 22px/1 var(--display); letter-spacing: .03em; text-transform: uppercase;
}
.foecard .ar, .youcard .ar {
  font: 700 8.5px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--c);
}

.threats { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; }
.threats span {
  font: 700 9px/1 var(--mono); letter-spacing: .08em; text-transform: uppercase;
  color: var(--danger); border: 1px solid color-mix(in srgb, var(--danger) 45%, transparent);
  border-radius: 2px; padding: 6px 8px;
}
.homebody p b { color: var(--gold); font-weight: 400; font-family: var(--display); font-size: 15px; }

/* Screens that carry a header use .pick's three rows; only the home screen
   drops the header and takes the two-row template. */
.draft .homebody { justify-content: center; gap: 14px; }
.forge .homebody { justify-content: flex-start; gap: 10px; }
.swap { display: flex; gap: 5px; flex-wrap: wrap; justify-content: center; }
.swap .pip {
  width: 44px; aspect-ratio: 3/4; border-radius: 4px; border: 2px solid var(--line);
  background-size: cover; background-position: 50% 14%; opacity: .5;
}
.swap .pip.on { border-color: var(--c); opacity: 1; }
h4.sect {
  align-self: stretch; margin: 6px 0 0; font: 700 9px/1 var(--mono);
  letter-spacing: .22em; text-transform: uppercase; color: var(--gold); text-align: left;
}
.forgelist { display: flex; flex-direction: column; gap: 6px; width: 100%; }
.frow {
  display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; text-align: left;
  background: #14141b; border: 1px solid var(--line); border-radius: 4px; padding: 9px 11px;
}
.frow .t { font: 400 17px/1 var(--display); letter-spacing: .03em; text-transform: uppercase; }
.frow .p { grid-column: 1; font: 400 11.5px/1.35 var(--ui); color: var(--dim); }
.frow .at { grid-column: 1; font: 700 8.5px/1 var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--c); }
.frow .c {
  grid-row: 1 / span 3; grid-column: 2; align-self: center;
  font: 400 15px/1 var(--display); color: var(--gold); white-space: nowrap;
}
.frow.can { border-color: color-mix(in srgb, var(--gold) 45%, var(--line)); }
.frow:disabled { opacity: .45; }
.frow.excl { border-style: dashed; }
.frow.excl .c { color: var(--dim); font: 700 8.5px/1 var(--mono); letter-spacing: .1em; text-transform: uppercase; }
.frow.got { border-color: var(--c); opacity: 1; }
.frow.got .c { color: var(--c); }

.payout { display: flex; align-items: baseline; gap: 6px; justify-content: center; margin-top: 8px; }
.payout b { font: 400 40px/1 var(--display); color: var(--gold); }
.payout s { text-decoration: none; font: 700 9px/1 var(--mono); letter-spacing: .2em; text-transform: uppercase; color: var(--dim); }
.earned {
  display: flex; flex-direction: column; gap: 2px; margin-top: 8px;
  border: 1px solid var(--gold); border-radius: 4px; padding: 8px 10px;
}
.earned b { font: 400 18px/1 var(--display); letter-spacing: .03em; text-transform: uppercase; color: var(--gold); }
.earned s { text-decoration: none; font: 400 11px/1.3 var(--ui); color: var(--dim); }
.threats.carry span.held {
  color: var(--gold); border-color: color-mix(in srgb, var(--gold) 45%, transparent);
}
.draft .forgelist .frow { padding: 14px 13px; }
.draft .frow .t { font-size: 21px; }
.versus .homebody h1 {
  margin: 0; font: 400 clamp(38px,12vw,64px)/.88 var(--display);
  letter-spacing: .03em; text-transform: uppercase;
}

/* --------------------------------------------------------------- the map */

/* Bottom-to-top, because you are climbing it. Only the next handful is drawn —
   the Road does not end and pretending otherwise would be a lie. */
/* ------------------------------------------------------------- the road */

/* One place, not a menu you can wander. The road is the same seamless
   Midjourney tile, laid flat with a perspective rotation so it actually
   recedes, and scrolled forever so you are always walking up it. Whoever is
   next stands at the far end of it in the dark.

   The tile does the heavy lifting; everything else here is light and haze. */
.pick.quest { display: flex; flex-direction: column; grid-template-rows: none; }
.pick.quest .head, .pick.quest .pad { flex: 0 0 auto; }

.roadstage {
  position: relative; flex: 1; min-height: 0; overflow: hidden;
  background: #06060a;
  perspective: 300px; perspective-origin: 50% 34%;
}

/* The floor, tipped away from you. It runs from under your feet to a horizon
   at about a third up, where it dissolves into fog rather than stopping — a
   road with a visible far edge reads as a rug. */
.roadfloor {
  position: absolute; left: 50%; bottom: -6%; width: 300%; height: 150%;
  transform: translateX(-50%) rotateX(75deg); transform-origin: 50% 100%;
}
.roadfloor .bricks {
  position: absolute; inset: 0;
  background: url('art/key/road-tile.jpg') repeat 50% 0 / 34% auto;
  animation: walk 11s linear infinite;
  -webkit-mask-image: radial-gradient(46% 92% at 50% 100%, #000 4%, rgba(0,0,0,.55) 42%, transparent 82%);
  mask-image: radial-gradient(46% 92% at 50% 100%, #000 4%, rgba(0,0,0,.55) 42%, transparent 82%);
}
@keyframes walk { to { background-position: 50% 512px } }

/* Distance. The far end of a road is darker and hazier than the near end, and
   without this the bricks are as bright at the horizon as they are at your
   feet, which is what made it read as a flat yellow slab. */
.roadhaze {
  position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(180deg, #06060a 0 26%, rgba(6,6,10,.86) 40%, rgba(6,6,10,.30) 56%, rgba(6,6,10,0) 74%),
    radial-gradient(52% 26% at 50% 44%, rgba(242,177,60,.20), transparent 72%),
    radial-gradient(120% 70% at 50% 108%, transparent 30%, rgba(6,6,10,.72) 100%);
}

/* Whoever is standing at the end of it. Lit from behind, so they read as a
   silhouette on the road rather than a portrait pasted over it. */
/* Standing at the horizon, feet in the fog. Masked from the bottom up rather
   than in a circle — a radial cut every side and made them look like a sticker
   dropped on the dark. */
.roadfoe {
  position: absolute; left: 50%; top: -4%; width: 62%; aspect-ratio: 4/5;
  transform: translateX(-50%);
  background-size: cover; background-position: 50% var(--foc, 12%);
  /* Faded on every edge, not just the bottom. These portraits are painted on
     pale grounds, so a hard rectangle of light grey sat on the dark and read as
     a sticker. Wide radial rather than a circle, or the fade itself is the
     shape you notice. */
  -webkit-mask-image: radial-gradient(52% 58% at 50% 34%, #000 8%, rgba(0,0,0,.5) 46%, transparent 76%);
  mask-image: radial-gradient(52% 58% at 50% 34%, #000 8%, rgba(0,0,0,.5) 46%, transparent 76%);
  filter: brightness(.5) contrast(1.3) saturate(.8);
  animation: loom 5.5s ease-in-out infinite alternate, arrive .7s cubic-bezier(.2,.8,.3,1) both;
}
.roadfoe .crest { position: absolute; inset: 14%; opacity: .5; }
.roadfoe .crest svg { width: 100%; height: 100%; }
@keyframes loom  { from { transform: translateX(-50%) scale(1) } to { transform: translateX(-50%) scale(1.045) } }
@keyframes arrive { from { opacity: 0; filter: brightness(0) blur(7px) } }
/* A boss is lit red and does not wait quietly. */
.bossrun .roadfoe { filter: brightness(.7) contrast(1.24) saturate(1.1) hue-rotate(-8deg); }
.bossrun .roadhaze {
  background:
    radial-gradient(80% 46% at 50% 30%, rgba(255,59,69,.22), transparent 70%),
    linear-gradient(180deg, #07070a 0 12%, rgba(7,7,10,.35) 34%, rgba(7,7,10,0) 62%);
}

/* Clear of the portrait, with its own ground — a name laid over a face reads as
   a mistake, and the bricks behind it are the brightest thing on the screen. */
.roadplate {
  position: absolute; left: 6%; right: 6%; top: 40%; z-index: 3;
  display: flex; flex-direction: column; align-items: center; gap: 3px; text-align: center;
  padding: 9px 10px 11px; border-radius: 6px;
  background: linear-gradient(180deg, rgba(6,6,10,0), rgba(6,6,10,.9) 22%, rgba(6,6,10,.9) 78%, rgba(6,6,10,0));
  animation: arriveUp .6s .2s ease-out both;
}
@keyframes arriveUp { from { opacity: 0; transform: translateY(14px) } }
.roadplate .lab {
  font: 700 7.5px/1 var(--mono); letter-spacing: .22em; text-transform: uppercase;
  color: color-mix(in srgb, var(--c) 70%, #fff);
}
.roadplate .nm {
  font: 400 clamp(26px,8.4vw,40px)/.94 var(--display); text-transform: uppercase;
  letter-spacing: .02em; text-shadow: 0 4px 22px #000, 0 0 46px rgba(0,0,0,.9);
}
.roadplate .ar {
  font: 700 8px/1 var(--mono); letter-spacing: .18em; text-transform: uppercase; color: var(--c);
}
.bossrun .roadplate .lab { color: var(--danger); }

/* Where you are standing, bottom of the road, big. */
/* The brightest part of the tile is directly under this, so it gets a plate of
   its own rather than a text-shadow that loses. */
/* Where you are and what it pays, as one object at your feet. Both were loose
   lines of text before; the payout in particular had no reason to be sitting
   between the road and the button. */
.roadwhere {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 10px; z-index: 3;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 7px 20px 9px; border-radius: 8px;
  background: rgba(7,7,10,.82); border: 1px solid rgba(242,177,60,.32);
  box-shadow: 0 10px 30px rgba(0,0,0,.6);
}
.roadwhere b {
  font: 400 clamp(38px,12vw,58px)/.82 var(--display); color: var(--gold);
}
.roadwhere .lab {
  font: 700 8px/1 var(--mono); letter-spacing: .26em; text-transform: uppercase; color: var(--dim);
}
.roadwhere .pay {
  margin-top: 3px; font: 700 8px/1 var(--mono); letter-spacing: .1em;
  text-transform: uppercase; color: var(--dim);
}
.roadwhere .pay i { font-style: normal; color: var(--gold); }

/* What is different about this fighter, hung under their name. It used to
   float in the gap between the stretch number and the button, which is the one
   part of the screen that should hold nothing but the decision. */
.roadmarks { display: flex; flex-wrap: wrap; gap: 5px; justify-content: center; margin-top: 7px; }
.roadmarks span {
  font: 700 8px/1 var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--danger); background: rgba(60,8,12,.72);
  border: 1px solid color-mix(in srgb, var(--danger) 40%, transparent);
  border-radius: 2px; padding: 5px 6px;
}
/* Boons you are carrying up the road, low and quiet. */
.roadheld {
  position: absolute; left: 0; right: 0; bottom: 78px; z-index: 3;
  display: flex; flex-wrap: wrap; gap: 5px; justify-content: center; padding: 0 12px;
}
.roadheld span {
  font: 700 8px/1 var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--gold); background: rgba(8,8,11,.8);
  border: 1px solid color-mix(in srgb, var(--gold) 36%, transparent);
  border-radius: 2px; padding: 5px 6px;
}

/* Leave sits in the header, away from the thing you came here to press. */
.roadhead { display: flex; align-items: center; gap: 10px; }
.roadhead .side { flex: 1; }
.roadhead .leave {
  order: 3; font: 700 8px/1 var(--mono); letter-spacing: .18em; text-transform: uppercase;
  color: var(--dim); border: 1px solid var(--line); border-radius: 3px; padding: 6px 8px;
}

/* One row, and the only thing on the screen to press. */
.walkrow { display: flex; gap: 8px; align-items: stretch; }
.youtile {
  flex: 0 0 auto; width: 58px; border-radius: 6px; padding: 0;
  border: 2px solid var(--c); background-size: cover;
  background-position: 50% var(--foc, 16%); background-color: #16161d;
  position: relative; overflow: hidden;
}
.youtile .bare { position: absolute; inset: 12%; display: grid; place-items: center; }
.youtile .bare svg { width: 100%; height: 100%; }
.youtile .rank {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 4px 0 5px;
  background: linear-gradient(180deg, transparent, rgba(6,6,9,.92) 55%);
  font: 700 8px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase; color: var(--c);
}
.youtile:active { transform: translateY(2px); }
.walkon { flex: 1; min-height: 72px; }
.walkon .sub { opacity: .72; }

@media (prefers-reduced-motion: reduce) {
  .roadfloor .bricks, .roadfoe, .roadplate { animation: none }
}

/* Your fighter, reduced to a strip in the action bar. */
.youstrip { display: flex; align-items: center; gap: 9px; text-align: left; padding-left: 8px; }
.youstrip .mug {
  flex: 0 0 auto; width: 30px; aspect-ratio: 4/5; border-radius: 3px;
  background-size: cover; background-position: 50% var(--foc, 18%);
  border: 1px solid var(--line);
}
.youstrip .mug.bare { display: grid; place-items: center; padding: 3px; }
.youstrip .mug.bare svg { width: 100%; height: 100%; }
.youstrip .big, .youstrip .sub { text-align: left; }


/* Your fighter, reduced to a strip in the action bar. */
.youstrip { display: flex; align-items: center; gap: 9px; text-align: left; padding-left: 8px; }
.youstrip .mug {
  flex: 0 0 auto; width: 30px; aspect-ratio: 4/5; border-radius: 3px;
  background-size: cover; background-position: 50% var(--foc, 18%);
  border: 1px solid var(--line);
}
.youstrip .mug.bare { display: grid; place-items: center; padding: 3px; }
.youstrip .mug.bare svg { width: 100%; height: 100%; }
.youstrip .big, .youstrip .sub { text-align: left; }

/* ------------------------------------------------------------- the coin */

/* The signature token, as an object rather than a 9px text chip in a row of
   seven other 9px text chips. Same print language as the sigils: flat fills,
   thick strokes, no gradients — the only lighting is a rim glow when it is
   ready to spend, because that is the one state worth interrupting you for. */
.coinface { display: block; width: 100%; height: 100%; }
.coinface .edge { fill: #14131a; stroke: rgba(255,255,255,.16); stroke-width: 3; }
.coinface .field { fill: rgba(255,255,255,.05); }
.coinface .glyph { fill: rgba(255,255,255,.34); stroke: rgba(255,255,255,.34); stroke-width: 0; }
.coinface .glyph [fill="none"] { stroke: rgba(255,255,255,.34); }
.coinface .pip { fill: rgba(255,255,255,.12); }
.coinface .arc { stroke: var(--gold); }
/* Charged. */
.on .coinface .edge { stroke: var(--gold); }
.on .coinface .glyph { fill: var(--gold); }
.on .coinface .glyph [fill="none"] { stroke: var(--gold); }
.on .coinface .pip.lit { fill: var(--gold); }
.full .coinface .field { fill: rgba(242,177,60,.14); }

/* In the health bar, beside the Coin count. */
.minicoin { display: inline-flex; align-items: center; gap: 4px; }
.minicoin .coinface { width: 22px; height: 22px; }
.minicoin b { font: 700 11px/1 var(--mono); color: var(--dim); }
.minicoin.on b { color: var(--gold); }

/* Under your thumb. Bottom-right of the stage so it never shares a press with
   Flip or Bank, which sit directly below it. */
.coinbtn {
  position: absolute; right: 10px; bottom: 10px; z-index: 5;
  width: 72px; padding: 0; background: none; border: 0;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  filter: drop-shadow(0 6px 14px rgba(0,0,0,.7));
}
.coinbtn .coinface { width: 64px; height: 64px; }
.coinbtn .nm {
  display: flex; align-items: baseline; gap: 4px;
  font: 700 7.5px/1 var(--mono); letter-spacing: .16em; text-transform: uppercase;
  color: var(--dim); background: rgba(8,8,11,.86); padding: 4px 6px; border-radius: 2px;
}
.coinbtn .nm b { font: 400 15px/1 var(--display); color: var(--dim); letter-spacing: 0; }
.coinbtn.on .nm b { color: var(--gold); }
.coinbtn.on .nm { color: var(--ink); }
.coinbtn .cta {
  max-width: 132px; text-align: center;
  font: 700 8px/1.25 var(--mono); letter-spacing: .06em; text-transform: uppercase;
  color: #14131a; background: var(--gold); padding: 4px 5px; border-radius: 2px;
}
/* Ready to spend: lift, and keep breathing until it is used. */
.coinbtn.ready { animation: coinReady 1.5s ease-in-out infinite; }
.coinbtn.ready .coinface .edge { stroke: var(--gold); stroke-width: 5; }
@keyframes coinReady {
  0%, 100% { transform: translateY(0) scale(1) }
  50%      { transform: translateY(-3px) scale(1.045) }
}
.coinbtn:active { transform: scale(.94); }
/* And a struck coin when it charges. */
.coinbtn.bump .coinface { animation: coinBump .5s cubic-bezier(.2,.9,.25,1.3); }
@keyframes coinBump {
  0%   { transform: rotateY(0) scale(1) }
  45%  { transform: rotateY(180deg) scale(1.28) }
  100% { transform: rotateY(360deg) scale(1) }
}
@media (prefers-reduced-motion: reduce) {
  .coinbtn.ready, .coinbtn.bump .coinface { animation: none }
}

/* The sheet you get for tapping a coin you cannot spend yet. */
.coinbig { position: relative; align-self: center; width: 104px; height: 104px; }
.coinbig .coinface { width: 104px; height: 104px; }
.coinbig .amt {
  position: absolute; inset: 0; display: grid; place-items: center;
  font: 400 40px/1 var(--display); color: var(--dim);
}
.coinbig.on .amt { color: var(--gold); }
.tokenbody { display: flex; flex-direction: column; gap: 6px; text-align: center; align-items: center; }
.tokenbody .lab { font: 700 8px/1 var(--mono); letter-spacing: .2em; text-transform: uppercase; color: var(--dim); }
.tokenbody h3 { margin: 0; }
.tokenbody p { margin: 0; font: 400 13.5px/1.5 var(--ui); color: var(--ink); max-width: 34ch; }

/* ---------------------------------------------------------- the faceoff */

/* The beat where the fight gets announced. Fighting-game grammar on purpose:
   the two of you arrive from opposite sides, the VS lands between you, and the
   whole thing clears itself. It is short — 2.4s — because it plays before every
   stretch of the Road and a splash you cannot skip stops being exciting the
   fourth time you see it. Tap anywhere to cut it. */
.faceoff {
  position: fixed; inset: 0; z-index: 60; overflow: hidden;
  background:
    radial-gradient(120% 80% at 50% 50%, rgba(30,20,12,.9) 0%, #050507 70%),
    #050507;
  display: grid; place-items: center;
  animation: foIn .18s ease-out;
}
@keyframes foIn { from { opacity: 0 } }
.fostage {
  position: relative; width: 100%; max-width: 540px; height: 100%;
  overflow: hidden; display: grid; place-items: center;
}

/* Diagonal split behind them, so the two halves read as opposed. */
.fostage::before {
  content: ''; position: absolute; inset: -10%;
  background: linear-gradient(104deg,
    color-mix(in srgb, var(--p1) 26%, transparent) 0 46%,
    rgba(0,0,0,0) 46% 54%,
    color-mix(in srgb, var(--p2) 26%, transparent) 54% 100%);
  animation: foSplit .5s cubic-bezier(.2,.9,.25,1) both;
}
@keyframes foSplit { from { transform: scaleY(0); opacity: 0 } }

.fostage .fighter { position: absolute; top: 0; bottom: 0; width: 58%; overflow: hidden; }
.faceoff .fighter .por {
  position: absolute; inset: 0; background-size: cover;
  background-position: 50% var(--foc, 16%);
  filter: contrast(1.08) saturate(1.05);
}
.faceoff .fighter .por svg { width: 100%; height: 100%; opacity: .5; }

/* Flying in, and skewed — a straight rectangle reads as a photo, a sheared one
   reads as an impact. */
.fostage .fighter.left  { left: -8%; transform-origin: left center;
  -webkit-mask-image: linear-gradient(74deg, #000 0 56%, transparent 76%);
  mask-image: linear-gradient(74deg, #000 0 56%, transparent 76%);
  animation: flyL .55s cubic-bezier(.16,.9,.24,1.03) both; }
.fostage .fighter.right { right: -8%; transform-origin: right center;
  -webkit-mask-image: linear-gradient(-74deg, #000 0 56%, transparent 76%);
  mask-image: linear-gradient(-74deg, #000 0 56%, transparent 76%);
  animation: flyR .55s cubic-bezier(.16,.9,.24,1.03) both; }
.fostage .fighter.right .por { transform: scaleX(-1); }
@keyframes flyL { from { transform: translateX(-64%) skewX(9deg); opacity: 0 } }
@keyframes flyR { from { transform: translateX(64%) skewX(-9deg); opacity: 0 } }

/* Names sit on the stage, so they are always inside the column. */
.fotag {
  position: absolute; bottom: 13%; max-width: 46%; z-index: 4;
  display: flex; flex-direction: column; gap: 3px;
  animation: foTag .4s .42s ease-out both;
}
.fotag.left  { left: 14px;  align-items: flex-start; text-align: left; }
.fotag.right { right: 14px; align-items: flex-end;  text-align: right; bottom: 25%; }
@keyframes foTag { from { opacity: 0; transform: translateY(10px) } }
.fotag .lab {
  font: 700 8px/1 var(--mono); letter-spacing: .2em; text-transform: uppercase; color: var(--dim);
}
.fotag .nm {
  font: 400 clamp(17px,5vw,24px)/.98 var(--display); text-transform: uppercase;
  letter-spacing: .02em; text-shadow: 0 3px 18px #000, 0 0 40px rgba(0,0,0,.9);
  overflow-wrap: anywhere;
}
.fotag .ar {
  font: 700 8px/1 var(--mono); letter-spacing: .18em; text-transform: uppercase; color: var(--c);
}

/* The VS lands last and lands hard. */
.faceoff .vs {
  position: relative; z-index: 3; display: grid; place-items: center;
  width: 128px; height: 128px;
}
.faceoff .vs span {
  font: 400 74px/1 var(--display); letter-spacing: -.03em; color: #fff;
  text-shadow: 0 0 26px rgba(0,0,0,.95), 0 6px 0 rgba(0,0,0,.55);
  animation: vsSlam .42s .34s cubic-bezier(.2,1.3,.3,1) both;
}
/* The shockwave it lands with. */
.faceoff .vs::after {
  content: ''; position: absolute; inset: 0; border-radius: 50%;
  border: 3px solid rgba(255,255,255,.8);
  animation: vsRing .55s .38s cubic-bezier(.2,.8,.3,1) both;
}
@keyframes vsSlam {
  from { transform: scale(3.4) rotate(-14deg); opacity: 0; filter: blur(6px) }
  60%  { transform: scale(.88) rotate(2deg);   opacity: 1; filter: blur(0) }
  to   { transform: scale(1) rotate(0) }
}
@keyframes vsRing {
  from { transform: scale(.2); opacity: .95 }
  to   { transform: scale(2.4); opacity: 0 }
}

.faceoff .stretch {
  position: absolute; left: 0; right: 0; bottom: calc(22px + env(safe-area-inset-bottom));
  text-align: center; z-index: 3;
  font: 700 9px/1 var(--mono); letter-spacing: .24em; text-transform: uppercase; color: var(--gold);
  animation: foTag .4s .6s ease-out both;
}

@media (prefers-reduced-motion: reduce) {
  .faceoff, .fostage::before, .fostage .fighter,
  .fotag, .faceoff .vs span, .faceoff .vs::after, .faceoff .stretch { animation: none }
  .faceoff .vs::after { display: none }
}

/* ---------------------------------------------------------- the coin toss */

/* Who goes first, decided in front of you. Both fighters ride the coin, one on
   each face, so the spin reads as a contest rather than as a spinner. */
.toss {
  position: fixed; inset: 0; z-index: 62; display: grid; place-items: center;
  align-content: center; gap: 26px;
  background: radial-gradient(120% 80% at 50% 45%, rgba(26,20,10,.94), #050507 72%), #050507;
  animation: foIn .2s ease-out;
}
.tosslede {
  font: 700 9px/1 var(--mono); letter-spacing: .3em; text-transform: uppercase; color: var(--dim);
  animation: riseIn .4s ease-out both;
}
.tosscoin {
  width: min(56vw, 200px); aspect-ratio: 1; position: relative;
  transform-style: preserve-3d;
  animation: tossSpin 1.5s cubic-bezier(.15,.6,.25,1) both;
}
/* Landing tails is the same flight plus a half turn. */
.tosscoin.tails { animation-name: tossSpinTails; }
@keyframes tossSpin {
  0%   { transform: translateY(46px) rotateY(0) scale(.7); opacity: 0 }
  12%  { opacity: 1 }
  100% { transform: translateY(0) rotateY(1800deg) scale(1) }
}
@keyframes tossSpinTails {
  0%   { transform: translateY(46px) rotateY(0) scale(.7); opacity: 0 }
  12%  { opacity: 1 }
  100% { transform: translateY(0) rotateY(1980deg) scale(1) }
}
/* No overflow:hidden here. It flattens the 3D context, which kills
   backface-visibility, and the coin then shows the wrong fighter face-up — it
   announced the Lion while showing the Queen. The portrait clips itself. */
.tface {
  position: absolute; inset: 0; border-radius: 50%;
  backface-visibility: hidden; -webkit-backface-visibility: hidden;
  display: grid; place-items: center;
  background: #14131a; border: 4px solid var(--c);
  box-shadow: 0 0 0 3px rgba(0,0,0,.6), 0 16px 40px rgba(0,0,0,.8);
}
.tface.tails { transform: rotateY(180deg); }
/* Zoomed to the head. Shown whole, a full-figure painting inside a 200px disc
   is a two-centimetre person, and two of those going past at speed read as the
   same picture twice — which is exactly what a coin must not look like. */
.tface .por {
  position: absolute; inset: 0; border-radius: 50%;
  background-size: 215% auto; background-position: 50% var(--foc, 12%);
  filter: contrast(1.1) saturate(1.05);
}
/* And each side keeps its own fighter's colour on the rim, so even a blur has
   two identities in it. */
.tface.heads { border-width: 5px; }
.tface.tails { border-width: 5px; }
.tface svg { width: 74%; height: 74%; opacity: .85; }
/* The rim catches the light as it turns. */
.tosscoin::after {
  content: ''; position: absolute; inset: -8%; border-radius: 50%;
  border: 2px solid rgba(242,177,60,.35); pointer-events: none;
  animation: tossRing .6s 1.35s cubic-bezier(.2,.8,.3,1) both;
}
@keyframes tossRing {
  from { transform: scale(.7); opacity: .9 }
  to   { transform: scale(1.5); opacity: 0 }
}

.tosscall { text-align: center; animation: tossCall .45s 1.5s cubic-bezier(.2,1.2,.3,1) both; }
.tosscall .nm {
  font: 400 clamp(24px,7.5vw,36px)/1 var(--display); text-transform: uppercase;
  letter-spacing: .03em; color: var(--gold);
}
@keyframes tossCall { from { opacity: 0; transform: scale(1.5) } }

@media (prefers-reduced-motion: reduce) {
  .tosscoin, .tosscoin::after, .tosscall, .tosslede { animation: none }
  .tosscoin { transform: none }
}

/* -------------------------------------------------------- your turn, said */

/* Control used to pass to you in silence. Sits over the table for a beat and
   goes; it is an announcement, not a dialog, so it never takes a tap. */
/* Covers the table for the beat it is up. Laid over the turn recap without a
   ground of its own, it simply collided with it — two sets of white words in
   the same place. It dims what is underneath instead, which is what an
   announcement does. */
.yourturn {
  position: absolute; inset: 0; z-index: 6; pointer-events: none;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  animation: turnHold 1.15s ease-out both;
}
.yourturn::before {
  content: ''; position: absolute; inset: 0;
  background: radial-gradient(80% 46% at 50% 50%, rgba(6,6,9,.86), rgba(6,6,9,.62));
}
.yourturn span, .yourturn i { position: relative; }
.yourturn span { animation: turnIn 1.15s cubic-bezier(.2,.9,.25,1) both; }
@keyframes turnHold { 0%,88% { opacity: 1 } 100% { opacity: 0 } }
.yourturn span {
  font: 400 clamp(30px,10vw,48px)/1 var(--display); text-transform: uppercase;
  letter-spacing: .04em; color: #fff; text-shadow: 0 4px 26px #000, 0 0 60px rgba(0,0,0,.95);
}
.yourturn i {
  font-style: normal; font: 700 8px/1 var(--mono); letter-spacing: .26em;
  text-transform: uppercase; color: var(--gold);
}
@keyframes turnIn {
  0%   { opacity: 0; transform: translateX(-40px) skewX(-10deg) }
  18%  { opacity: 1; transform: none }
  72%  { opacity: 1; transform: none }
  100% { opacity: 0; transform: translateX(30px) skewX(6deg) }
}
@media (prefers-reduced-motion: reduce) { .yourturn, .yourturn span { animation: none; opacity: .001 } }

/* ------------------------------------------------------------------ lore */

/* Who you have met, and what they are made of. Locked fighters are shown
   rather than hidden — a collection you cannot see the shape of is not a
   collection, it is a surprise. */
.pick.lore { display: flex; flex-direction: column; grid-template-rows: none; }
.pick.lore .head { flex: 0 0 auto; }
.lorebody { flex: 1; min-height: 0; overflow-y: auto; padding: 10px 12px 20px; }

.loregrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 9px; }
.lorecell {
  position: relative; aspect-ratio: 4/5; border-radius: 7px; overflow: hidden;
  border: 2px solid var(--c); background-size: cover; background-position: 50% var(--foc, 14%);
  background-color: #16161d; padding: 0; text-align: left;
  display: flex; flex-direction: column; justify-content: flex-end; gap: 1px;
}
.lorecell::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, transparent 40%, rgba(6,6,9,.9) 84%);
}
.lorecell > * { position: relative; z-index: 1; padding: 0 9px; }
.lorecell > *:last-child { padding-bottom: 9px; }
.lorecell .nm { font: 400 18px/1 var(--display); text-transform: uppercase; letter-spacing: .02em; }
.lorecell .ar { font: 700 7.5px/1 var(--mono); letter-spacing: .16em; text-transform: uppercase; color: var(--c); }
/* Not yet yours: a silhouette and the stretch that hands them over, so the
   lock points somewhere instead of just saying no. */
.lorecell.locked { border-color: var(--line); background-image: none; background: #101014; }
.lorecell.locked .q {
  position: absolute; inset: 0; display: grid; place-items: center; padding: 0;
  font: 400 54px/1 var(--display); color: rgba(255,255,255,.09); font-style: normal;
}
.lorecell.locked .nm { color: var(--dim); }
.lorecell.locked .ar { color: var(--gold); }

.loresheet { display: flex; flex-direction: column; gap: 10px; }
.lorehero {
  width: 100%; aspect-ratio: 4/5; max-height: 40dvh; border-radius: 8px;
  border: 2px solid var(--c); background-size: cover; background-position: 50% var(--foc, 12%);
  /* overflow, so the loop laid over the still is clipped by the same radius. */
  background-color: #16161d; position: relative; overflow: hidden;
}
.lorehero .crest { position: absolute; inset: 16%; opacity: .55; }
.lorehero .crest svg { width: 100%; height: 100%; }
.lorename { display: flex; flex-direction: column; gap: 2px; }
.lorename .nm { font: 400 clamp(26px,8vw,36px)/1 var(--display); text-transform: uppercase; }
.lorename .ti { font: 400 13px/1.3 var(--ui); color: var(--dim); }
.lorename .ar {
  align-self: flex-start; margin-top: 4px; font: 700 8px/1 var(--mono); letter-spacing: .16em;
  text-transform: uppercase; background: var(--c); color: #14131a; padding: 5px 7px; border-radius: 2px;
}
.lorebl { margin: 0; font: 400 13px/1.5 var(--ui); color: var(--ink); }

/* The deck as numbers you can act on. */
.loredeck { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.dstat {
  border: 1px solid var(--line); border-top: 3px solid var(--mk, var(--line));
  border-radius: 4px; padding: 7px 6px; text-align: center;
  display: flex; flex-direction: column; gap: 2px;
}
.dstat b { font: 400 20px/1 var(--display); color: var(--ink); }
.dstat span { font: 700 7px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase; color: var(--dim); }
.dstat i { font-style: normal; font: 700 7px/1 var(--mono); color: var(--mk, var(--dim)); }
.dstat.s-c { --mk: #cfd3da; } .dstat.s-h { --mk: #e2606a; }
.dstat.s-d { --mk: #d5a53c; } .dstat.s-s { --mk: #7a7a86; }

.loretok { border: 1px solid var(--line); border-left: 3px solid var(--gold); border-radius: 4px; padding: 9px 10px; }
.loretok .lab { font: 700 8px/1 var(--mono); letter-spacing: .18em; text-transform: uppercase; color: var(--gold); }
.loretok p { margin: 5px 0 0; font: 400 12px/1.45 var(--ui); color: var(--dim); }

.lorepowers { display: flex; flex-direction: column; gap: 6px; }
.lorepow {
  display: grid; grid-template-columns: auto 1fr auto; gap: 6px; align-items: baseline;
  border: 1px solid var(--line); border-radius: 4px; padding: 8px 9px;
}
.lorepow .k { font: 700 7px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase; background: var(--gold); color: #14131a; padding: 4px 5px; }
.lorepow .n { font: 700 13px/1.1 var(--ui); }
.lorepow .c { font: 700 12px/1 var(--mono); color: var(--gold); }
.lorepow p { grid-column: 1 / -1; margin: 0; font: 400 11.5px/1.4 var(--ui); color: var(--dim); }

/* ----------------------------------------------------------------- coach */

/* The Road saying what just happened. Sits over the table, dims it, and waits
   — a lesson on a timer is a lesson half-read. Deliberately not a sheet: it is
   not a decision, and it should not look like one. */
.coach {
  position: absolute; inset: 0; z-index: 30; display: grid; place-items: center;
  padding: 22px; background: rgba(5,5,8,.82);
  animation: foIn .2s ease-out;
}
.coach .note {
  max-width: 320px; text-align: center;
  display: flex; flex-direction: column; align-items: center; gap: 7px;
  border: 1px solid color-mix(in srgb, var(--gold) 40%, transparent);
  border-radius: 8px; padding: 18px 16px 14px; background: rgba(12,11,8,.96);
  box-shadow: 0 18px 50px rgba(0,0,0,.7);
  animation: coachIn .34s cubic-bezier(.2,.9,.25,1.02) both;
}
@keyframes coachIn { from { opacity: 0; transform: translateY(14px) scale(.97) } }
.coach .lab {
  font: 700 7.5px/1 var(--mono); letter-spacing: .28em; text-transform: uppercase; color: var(--gold);
}
.coach h3 {
  margin: 0; font: 400 clamp(21px,6.4vw,27px)/1.02 var(--display);
  text-transform: uppercase; letter-spacing: .02em;
}
.coach p { margin: 0; font: 400 13px/1.5 var(--ui); color: var(--ink); }
.coach .tap {
  margin-top: 4px; font: 700 7.5px/1 var(--mono); letter-spacing: .2em;
  text-transform: uppercase; color: var(--dim);
}
@media (prefers-reduced-motion: reduce) { .coach, .coach .note { animation: none } }
.lorerules { margin-top: 10px; }
