/* =============================================================
   Rook, Paper, Scissors!
   ============================================================= */

:root {
  --bg:        #0c0a14;
  --bg-2:      #141020;
  --panel:     rgba(26, 20, 42, 0.88);
  --panel-2:   rgba(40, 32, 62, 0.78);
  --line:      rgba(150, 120, 220, 0.22);
  --line-hot:  rgba(180, 140, 255, 0.5);
  --ink:       #f0eef8;
  --ink-dim:   #b6afd4;
  --ink-faint: #8a83ad;

  --accent:    #a970ff;
  --accent-2:  #ff8a4c;
  --cyan:      #58d6ff;
  --good:      #8bd17c;
  --bad:       #ff6b6b;
  --gold:      #ffd166;

  --sq-light:  #e3ddf8;
  --sq-dark:   #64559b;
  --sq-light-2:#efeaff;
  --sq-dark-2: #7d6fb0;

  /* Type scale. Everything here is read off a compressed 720p stream on a
     phone, so nothing gets smaller than 12px and nothing thin gets used for
     information that matters. */
  --fs-xs:  12px;
  --fs-sm:  13px;
  --fs-md:  14.5px;
  --fs-lg:  16px;
  --fs-xl:  20px;

  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px;  --sp-4: 16px;  --sp-5: 24px;

  --radius: 14px;
  --r-sm: 9px;  --r-lg: 18px;

  --shadow:   0 18px 50px rgba(0, 0, 0, 0.55);
  --shadow-1: 0 2px 8px rgba(0, 0, 0, 0.3);
  --shadow-2: 0 10px 28px -8px rgba(0, 0, 0, 0.6);
}

* { box-sizing: border-box; }

/* Author `display` rules beat the UA stylesheet's [hidden] { display:none },
   so overlays like .banner and .modal-root need this or they stay on top of
   the board, invisible but swallowing every click. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  min-height: 100%;
}
/* The page is three stations in one scroll, and a slow flick settles on one
   of them; the sticky bar is what they are measured against. */
html {
  scroll-padding-top: var(--bar, 62px);
  /* the shell keeps the viewer's place itself when the table changes size
     out of sight; the browser's own anchoring would do it twice */
  overflow-anchor: none;
  scrollbar-width: thin;
  scrollbar-color: #3a2f5c #0c0a14;
}
html::-webkit-scrollbar { width: 9px; }
html::-webkit-scrollbar-thumb { background: #3a2f5c; border-radius: 5px; }
html::-webkit-scrollbar-track { background: #0c0a14; }

body {
  background:
    radial-gradient(1200px 700px at 78% -10%, var(--bg-glow-1, rgba(169, 112, 255, 0.20)), transparent 60%),
    radial-gradient(900px 600px at 8% 110%, var(--bg-glow-2, rgba(255, 138, 76, 0.14)), transparent 60%),
    var(--bg);
  transition: background-color .35s ease;
  color: var(--ink);
  font: var(--fs-md)/1.55 "Nunito", "Segoe UI", Inter, system-ui, -apple-system, sans-serif;
  overflow-x: hidden;
}

button, select, input { font: inherit; color: inherit; }

/* ---------------- shell ---------------- */

.app {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  --bar: 62px;
}

.topbar {
  /* above the end-of-game banner, which covers the whole viewport: the toolbar
     has to stay usable once a game is over or Undo cannot be reached */
  position: sticky;
  top: 0;
  z-index: 70;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 7px 18px;
  border-bottom: 1px solid var(--line);
  background: linear-gradient(180deg, rgba(30, 22, 50, 0.9), rgba(20, 16, 32, 0.5));
  backdrop-filter: blur(10px);
  flex: 0 0 auto;
}

.brand { display: flex; align-items: baseline; gap: 10px; }
.brand-glyph {
  font-size: 26px;
  line-height: 1;
  color: var(--accent);
  text-shadow: 0 0 18px rgba(169, 112, 255, 0.8);
}
.brand-text {
  font-weight: 800;
  letter-spacing: 3px;
  font-size: 18px;
  background: linear-gradient(92deg, #fff, var(--accent) 45%, var(--accent-2));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.brand-sub {
  font-size: 11px;
  letter-spacing: 2.5px;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.topbar-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* Buttons are glass: a highlight along the top, a little lift on hover, a
   press on click, and a ring for the keyboard. The plain one carries the
   accent; .ghost is neutral. */
.btn {
  position: relative;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 9px 16px;
  border-radius: 12px;
  border: 1px solid color-mix(in srgb, var(--accent) 50%, transparent);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .16), rgba(255, 255, 255, 0) 55%),
    linear-gradient(180deg, color-mix(in srgb, var(--accent) 80%, #fff), color-mix(in srgb, var(--accent) 70%, #000));
  color: #fff;
  font-weight: 600;
  letter-spacing: .01em;
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .18), 0 6px 18px -8px color-mix(in srgb, var(--accent) 80%, transparent);
  transition: transform .14s, box-shadow .14s, background .14s, border-color .14s, filter .14s;
}
.btn:hover {
  transform: translateY(-1px);
  filter: brightness(1.08);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .2), 0 10px 26px -10px color-mix(in srgb, var(--accent) 90%, transparent);
}
.btn:active { transform: translateY(0) scale(.985); filter: brightness(.97); box-shadow: inset 0 2px 6px rgba(0, 0, 0, .3); }
.btn:focus-visible { outline: 2px solid color-mix(in srgb, var(--accent) 60%, #fff); outline-offset: 2px; }
.btn.ghost {
  background: linear-gradient(180deg, rgba(255, 255, 255, .09), rgba(255, 255, 255, .035));
  border-color: rgba(255, 255, 255, .13);
  color: var(--ink);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .08), 0 1px 2px rgba(0, 0, 0, .35);
}
.btn.ghost:hover {
  background: linear-gradient(180deg, rgba(255, 255, 255, .15), rgba(255, 255, 255, .06));
  border-color: rgba(255, 255, 255, .26);
  filter: none;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12), 0 8px 18px -10px rgba(0, 0, 0, .7);
}
.btn.small { padding: 6px 11px; font-size: 13px; border-radius: 10px; }
.btn.on {
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .16), rgba(255, 255, 255, 0) 55%),
    linear-gradient(180deg, var(--accent), #7d4ad6);
  border-color: transparent;
  color: #fff;
}
.btn[disabled] { opacity: .35; cursor: not-allowed; transform: none; box-shadow: none; filter: none; }.btn[disabled] { opacity: .35; cursor: not-allowed; transform: none; box-shadow: none; }

/* ---------------- layout ---------------- */

.layout {
  flex: 1 1 auto;
  display: grid;
  /* two columns: the settings rail is a drawer now and takes none */
  grid-template-columns: minmax(0, 1fr) 330px;
  gap: 16px;
  padding: 16px 20px 18px;
  min-height: 0;
}

/* Either panel can be put away, and the board takes the room. Stream mode is
   a preset that puts the left one away and makes everything bigger; the two
   toggles work on their own afterwards. */
.app.no-left .rail:not(.right) { display: none; }
.app.no-right .rail.right { display: none; }
.app.no-left .layout  { grid-template-columns: minmax(0, 1fr) 330px; }
.app.no-right .layout { grid-template-columns: minmax(0, 1fr); }
.app.no-left.no-right .layout { grid-template-columns: minmax(0, 1fr); }
.app.stream.no-left .layout { grid-template-columns: minmax(0, 1fr) 370px; }
.app.stream.no-left.no-right .layout { grid-template-columns: minmax(0, 1fr); }

/* With both away the board is the only thing left, so it gets everything:
   the columns, the padding, and the height that the furniture round it was
   using. The board is limited by the window's HEIGHT rather than its width
   at any sensible size, so trimming the rows above and below it is the only
   thing that actually makes it bigger. */
.app.no-left.no-right .layout { padding: 8px 10px 10px; gap: 8px; }
.app.no-left.no-right .stage { gap: 6px; padding-bottom: 4px; }
.app.no-left.no-right .topbar { padding: 6px 20px; }
.app.no-left.no-right .brand-sub { display: none; }
.app.no-left.no-right .playerbar { padding: 5px 10px; }
.app.no-left.no-right .pb-avatar { font-size: 19px; }
.app.no-left.no-right .pb-graveyard { display: none; }
/* the hint slot stays - a floating tooltip over the chips it describes was
   a bug once - but one line of it is enough */
/* A FIXED height here too, and for the same reason as the rule above: a
   min-height would let a long description wrap and start the measuring loop
   the comment up there is about. One line, not two. */
.app.no-left.no-right .inv-hint {
  height: 30px;
  padding: 3px 10px;
  font-size: var(--fs-xs);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.app.no-left.no-right .inv-hint br { display: none; }

.topbar-actions .btn.icon {
  min-width: 34px;
  padding-left: 9px;
  padding-right: 9px;
  font-size: 15px;
  line-height: 1;
}
/* a lit topbar button is a filled pill, the same as Stream mode and Sound -
   the house style already says what "on" looks like */

/* Nobody operates the app in stream mode - they watch it on a compressed
   video feed, so everything that carries information gets larger. */
.app.stream { --fs-xs: 13px; --fs-sm: 14.5px; --fs-md: 16px; --fs-lg: 18px; }
.app.stream .pb-name { font-size: 21px; }
.app.stream .pb-avatar { font-size: 30px; }
.app.stream .pb-hearts { font-size: 19px; }
.app.stream .pb-score { font-size: 18px; }
.app.stream .card h2 { font-size: 13px; letter-spacing: 2.2px; }
.app.stream .vote-row .txt { font-size: 17px; }
.app.stream .movelog { font-size: 15px; max-height: 190px; }
.app.stream .topbar { padding: 10px 20px; }

.rail {
  display: flex;
  flex-direction: column;
  gap: 12px;
  overflow-y: auto;
  min-height: 0;
  padding-right: 4px;
  padding-bottom: 10px;
}

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 14px;
  backdrop-filter: blur(8px);
  box-shadow: var(--shadow);
}
.card.grow { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 120px; }

.card h2 {
  margin: 0 0 var(--sp-3);
  font-size: var(--fs-xs);
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 700;
}

/* ---------------- form bits ---------------- */

.field { display: block; margin-bottom: 10px; }
.field:last-child { margin-bottom: 0; }
.field-label {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: var(--fs-xs);
  color: var(--ink-dim);
  margin-bottom: 5px;
}
.field-label b { margin-left: auto; color: var(--ink); }
.field.inline { display: flex; gap: 8px; }
.field.inline input { flex: 1 1 auto; }

select, input[type="text"] {
  width: 100%;
  padding: 8px 10px;
  border-radius: 9px;
  border: 1px solid var(--line);
  background: rgba(10, 8, 18, 0.7);
  outline: none;
}
select:focus, input[type="text"]:focus { border-color: var(--accent); }

input[type="range"] { width: 100%; accent-color: var(--accent); }

.dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; border: 1px solid rgba(0,0,0,.4); }
.dot.white { background: #f2eeff; }
.dot.black { background: #241d38; }

.switch { display: flex; gap: 9px; align-items: flex-start; margin-bottom: 9px; cursor: pointer; }
.switch:last-child { margin-bottom: 0; }
.switch input { margin-top: 3px; accent-color: var(--accent); }
.switch small { display: block; color: var(--ink-faint); font-size: var(--fs-xs); }

.preset-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.chip {
  padding: 5px 9px;
  font-size: 11.5px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.04);
  cursor: pointer;
}
.chip:hover { border-color: var(--accent); color: #fff; }

.status-line { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--ink-dim); margin: 8px 0; }
.led { width: 8px; height: 8px; border-radius: 50%; background: var(--ink-faint); flex: 0 0 auto; }
.status-line.ok .led { background: var(--good); box-shadow: 0 0 10px var(--good); }
.status-line.wait .led { background: var(--gold); box-shadow: 0 0 10px var(--gold); animation: pulse 1s infinite; }
.status-line.err .led { background: var(--bad); box-shadow: 0 0 10px var(--bad); }
.status-line.demo .led { background: var(--cyan); box-shadow: 0 0 10px var(--cyan); }

@keyframes pulse { 50% { opacity: .3; } }

.hint { font-size: var(--fs-xs); color: var(--ink-faint); margin: var(--sp-3) 0 0; line-height: 1.5; }
.hint code {
  background: rgba(169, 112, 255, 0.16);
  padding: 1px 5px;
  border-radius: 5px;
  color: #d9c6ff;
  font-size: 11px;
}

/* ---------------- stage ---------------- */

.stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  min-height: 0;
  overflow-y: auto;             /* a short window scrolls the board column rather than cropping it */
  overflow-x: hidden;
  padding-bottom: 10px;
  scroll-padding-bottom: 10px;
}
/* Zoomed past the column, the board is dragged rather than scrolled: the bars
   would cover the board they are there to move, and there is nothing else in
   this column to scroll. */
.app.zoomed .stage { overflow: auto; scrollbar-width: none; -ms-overflow-style: none; }
.app.zoomed .stage::-webkit-scrollbar { width: 0; height: 0; }
.app.zoomed .board-shell { margin: 0 auto; }
.app.zoomed .board { cursor: grab; }
.board.panning, .app.zoomed .board.panning { cursor: grabbing; }
/* space held: the board is about to be dragged rather than played on */
.board.grabbing-ready { cursor: grab; }
.board.grabbing-ready.panning { cursor: grabbing; }
/* A flex column shrinks its children to fit by default, which silently squashed
   the power-up row until half the chips were hidden inside it. The board is the
   one part that gives: sizeBoard() measures the rest and hands it the remainder. */
.stage > * { flex: 0 0 auto; }

.playerbar {
  width: 100%;
  max-width: calc(var(--board-fit, 640px) + 20px);
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 6px 13px;
  border-radius: 12px;
  background: linear-gradient(180deg, rgba(255, 255, 255, .07), rgba(255, 255, 255, .025)), var(--panel-2);
  border: 1px solid rgba(255, 255, 255, .12);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .08);
  transition: border-color .2s, box-shadow .2s;
}
.playerbar.active {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px rgba(169, 112, 255, .35), 0 0 26px rgba(169, 112, 255, .22);
}
.pb-avatar { font-size: 26px; line-height: 1; }
.pb-name { font-weight: 800; font-size: var(--fs-lg); }
.pb-role { font-size: var(--fs-xs); color: var(--ink-faint); letter-spacing: 1.4px; text-transform: uppercase; }
.pb-hearts { color: #ff5f7e; letter-spacing: 1px; font-size: 15px; text-shadow: 0 0 10px rgba(255,95,126,.6); }
.pb-hearts i { color: rgba(255,255,255,.22); font-style: normal; text-shadow: none; }

.pb-graveyard { margin-left: auto; display: flex; gap: 1px; opacity: .8; flex-wrap: wrap; justify-content: flex-end; max-width: 45%; }
.pb-graveyard .grave { width: 19px; height: 19px; display: block; }
.pb-graveyard .grave .pc { stroke-width: 2.2; }
.pb-graveyard .grave.w .pc { fill: #efeaff; stroke: #2a2142; }
.pb-graveyard .grave.b .pc { fill: #2a2142; stroke: #b9addf; }
.pb-score { font-variant-numeric: tabular-nums; color: var(--gold); font-weight: 700; min-width: 32px; text-align: right; }
.pb-think { font-size: 11px; color: var(--accent); letter-spacing: 1px; }

.board-shell {
  position: relative;
  padding: 10px;
  border-radius: 22px;
  background: var(--frame, linear-gradient(158deg, #3c3162, #221a3e 60%, #1a1430));
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent),
    inset 0 1px 0 rgba(255, 255, 255, .08),
    inset 0 -1px 0 rgba(0, 0, 0, .4),
    0 30px 70px -22px rgba(0, 0, 0, .85);
}

.board {
  position: relative;
  /* The size itself lands instantly - transitioning a var()-driven width fights
     the measuring pass in sizeBoard(). sizeBoard plays a FLIP animation over the
     top instead: the layout jumps, the picture eases. */
  transform-origin: center center;
  width: var(--board-size, 640px);
  /* --board-size is the WIDTH; the height follows however tall the board is */
  height: calc(var(--board-size, 640px) * var(--board-rows, 8) / var(--board-cols, 8));
  display: grid;
  /* the board is however many squares across it has grown to be */
  grid-template-columns: repeat(var(--board-cols, 8), 1fr);
  grid-template-rows: repeat(var(--board-rows, 8), 1fr);
  border-radius: 13px;
  overflow: hidden;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .45), inset 0 2px 14px rgba(0, 0, 0, .35);
  user-select: none;
}

.sq {
  position: relative;
  display: flex;
  align-items: flex-end;
  justify-content: flex-start;
}
.sq.light { background: var(--sq-light); }
.sq.dark  { background: var(--sq-dark); }

.sq .coord {
  position: absolute;
  font-size: 10.5px;
  font-weight: 800;
  opacity: .6;
  pointer-events: none;
}
.sq .coord.file { right: 3px; bottom: 1px; }
.sq .coord.rank { left: 3px; top: 1px; }
.sq.light .coord { color: var(--sq-dark); }
.sq.dark .coord { color: var(--sq-light); }

.sq.last::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(255, 209, 102, .3);
  box-shadow: inset 0 0 0 3px rgba(255, 209, 102, .55);
  pointer-events: none;
}
.sq.sel::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(88, 214, 255, .3);
  box-shadow: inset 0 0 0 3px var(--cyan);
  pointer-events: none;
}
.sq.check::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(circle, rgba(255, 60, 60, .9), rgba(255, 60, 60, .25) 60%, transparent 75%);
  animation: checkPulse 1.1s ease-in-out infinite;
  pointer-events: none;
}
@keyframes checkPulse { 50% { opacity: .55; } }

.sq.move .hint-dot,
.sq.grab .hint-dot { position: absolute; inset: 0; display: grid; place-items: center; pointer-events: none; }
.sq.move .hint-dot::before {
  content: "";
  width: 30%;
  height: 30%;
  border-radius: 50%;
  background: rgba(18, 10, 36, .5);
  box-shadow: 0 0 0 2px rgba(255, 255, 255, .18);
}
.sq.grab .hint-dot::before {
  content: "";
  width: 86%;
  height: 86%;
  border-radius: 50%;
  border: 5px solid rgba(255, 107, 107, .78);
  box-shadow: inset 0 0 12px rgba(255, 107, 107, .35);
}
.sq.pick { cursor: crosshair; box-shadow: inset 0 0 0 3px rgba(255, 138, 76, .85); }

/* Reckless play: the move is offered, but the board says what it costs. */
.sq.doomed .hint-dot::before {
  background: rgba(255, 70, 70, .85) !important;
  border-color: rgba(255, 70, 70, .9) !important;
  box-shadow: 0 0 12px rgba(255, 70, 70, .9) !important;
}
.sq.doomed::before {
  content: "";
  position: absolute;
  inset: 0;
  box-shadow: inset 0 0 0 2px rgba(255, 70, 70, .55);
  pointer-events: none;
  z-index: 2;
}

/* The clock face. A ring that empties as the bank does, and the time in
   tabular figures so nothing jumps when a digit changes. Lit for the side
   that is spending; amber under thirty seconds; red and beating under ten. */
.pb-clock {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-left: 6px;
  padding: 4px 10px 4px 5px;
  border-radius: 999px;
  background: rgba(0, 0, 0, .28);
  border: 1px solid rgba(255, 255, 255, .08);
  color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  font-size: var(--fs-lg);
  letter-spacing: .02em;
  transition: color .2s, border-color .2s, background .2s, box-shadow .2s;
}
.pb-clock svg { width: 32px; height: 32px; flex: 0 0 auto; }
/* the arc starts at twelve; the face itself stays upright for the hand */
.pb-clock .ck-arc { transform: rotate(-90deg); transform-origin: 20px 20px; transform-box: view-box; }
.pb-clock .ck-tick { stroke: rgba(255, 255, 255, .28); stroke-width: 1.2; stroke-linecap: round; }
.pb-clock .ck-tick.big { stroke: rgba(255, 255, 255, .6); stroke-width: 1.6; }
.pb-clock .ck-hand { stroke: currentColor; stroke-width: 2; stroke-linecap: round; transition: transform .2s linear; }
.pb-clock .ck-pin { fill: currentColor; }
.pb-clock.live .ck-hand { stroke: #fff; filter: drop-shadow(0 0 3px rgba(255, 255, 255, .6)); }
.pb-clock.crit.live .ck-hand { transition: none; }
.pb-clock .ck-track { fill: none; stroke: rgba(255, 255, 255, .1); stroke-width: 3.5; }
.pb-clock .ck-arc {
  fill: none;
  stroke: currentColor;
  stroke-width: 3.5;
  stroke-linecap: round;
  transition: stroke-dashoffset .2s linear;
}
.pb-clock.live {
  color: var(--ink);
  border-color: color-mix(in srgb, var(--accent) 60%, transparent);
  background: color-mix(in srgb, var(--accent) 16%, rgba(0, 0, 0, .3));
  box-shadow: 0 0 18px -6px var(--accent);
}
.pb-clock.live .ck-arc { stroke: var(--accent); }
.pb-clock.low.live { color: #ffe3a3; border-color: rgba(255, 209, 102, .6); background: rgba(255, 209, 102, .12); box-shadow: 0 0 18px -6px var(--gold); }
.pb-clock.low.live .ck-arc { stroke: var(--gold); }
.pb-clock.crit.live {
  color: #fff;
  border-color: rgba(255, 95, 95, .8);
  background: rgba(255, 70, 70, .22);
  box-shadow: 0 0 22px -4px #ff5f5f;
  animation: clock-beat 1s ease-in-out infinite;
}
.pb-clock.crit.live .ck-arc { stroke: #ff5f5f; transition: none; }
.pb-clock.flag { color: #ff8a8a; text-decoration: line-through; animation: none; }
@keyframes clock-beat {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.06); }
}
@media (prefers-reduced-motion: reduce) { .pb-clock.crit.live { animation: none; } }
.app.stream .pb-clock { font-size: 22px; padding: 5px 13px 5px 6px; }
.app.stream .pb-clock svg { width: 38px; height: 38px; }

.pb-danger {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 1.6px;
  padding: 3px 8px;
  border-radius: 999px;
  color: #fff;
  background: linear-gradient(180deg, #ff5f5f, #c92f2f);
  box-shadow: 0 0 16px -3px #ff5f5f;
  animation: pulse 1.1s ease-in-out infinite;
}
.sq.blast::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(255, 123, 114, .35);
  pointer-events: none;
}
.sq.votehot::after {
  content: "";
  position: absolute;
  inset: 0;
  box-shadow: inset 0 0 0 3px rgba(169, 112, 255, .9);
  pointer-events: none;
}

/* A portal stays on the board for as long as the pair exists. */
.sq .portal-mark {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 62%;
  height: 62%;
  border-radius: 50%;
  pointer-events: none;
  z-index: 1;
  background:
    radial-gradient(circle, transparent 30%, rgba(199,155,255,.55) 44%, transparent 58%),
    radial-gradient(circle, transparent 58%, rgba(120,60,220,.45) 70%, transparent 82%);
  box-shadow: 0 0 16px -4px rgba(199,155,255,.9), inset 0 0 12px -4px rgba(199,155,255,.8);
  animation: portalSpin 5.5s linear infinite;
}
@keyframes portalSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .sq .portal-mark { animation: none; } }

.sq .tile {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 38%;
  height: 38%;
  display: grid;
  place-items: center;
  font-size: calc(var(--board-size, 640px) / var(--board-cols, 8) * 0.235);
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, color-mix(in srgb, currentColor 40%, rgba(14, 10, 26, .95)), rgba(10, 7, 20, .96));
  border: 2px solid color-mix(in srgb, currentColor 70%, transparent);
  box-shadow: 0 0 16px -2px currentColor, inset 0 0 10px -4px currentColor;
  animation: bob 2.4s ease-in-out infinite;
  pointer-events: none;
  z-index: 3;
}
@keyframes bob { 50% { transform: translateY(-9%) scale(1.06); } }

/* pieces */

.piece {
  position: absolute;
  /* one square of whatever the board has grown into */
  width: calc(100% / var(--board-cols, 8));
  height: calc(100% / var(--board-rows, 8));
  display: grid;
  place-items: center;
  font-family: "Segoe UI Symbol", "Apple Symbols", "Noto Sans Symbols 2", serif;
  font-size: calc(var(--board-size, 640px) / var(--board-cols, 8) * 0.77);
  line-height: 1;
  cursor: grab;
  z-index: 5;
  transition: left .19s cubic-bezier(.3, .9, .3, 1), top .19s cubic-bezier(.3, .9, .3, 1);
  will-change: left, top;
}
/* vector pieces: one sprite, colour driven entirely from here */
.pc { width: 100%; height: 100%; display: block; overflow: visible; }
.pc, .pc use { stroke-linejoin: round; stroke-linecap: round; }
.piece .pc { filter: drop-shadow(0 3px 3px rgba(0, 0, 0, .5)); }

.piece.w .pc { fill: var(--piece-w, #fbf8ff); stroke: var(--piece-w-line, #2a2142); stroke-width: 1.7; }
.piece.b .pc { fill: var(--piece-b, #1d1630); stroke: var(--piece-b-line, #d5cbf2); stroke-width: 1.7; }

/* A crystal shard belongs to neither side, so it takes neither side's colours.
   It reads as scenery: icy, lit from inside, and faintly breathing. */
.piece.n { cursor: default; }
.piece.n .pc {
  fill: var(--crystal, #79e4ff);
  stroke: var(--crystal-line, #0b2b3d);
  stroke-width: 1.5;
  animation: shardBreathe 3.4s ease-in-out infinite;
}
.piece.n .pc-mane { stroke: #eafcff; opacity: .55; stroke-width: 1.3; }
.piece.n .glyph {
  color: var(--crystal, #79e4ff);
  text-shadow: 0 0 14px rgba(121, 228, 255, .85), 0 1px 0 var(--crystal-line, #0b2b3d),
    0 -1px 0 var(--crystal-line, #0b2b3d), 1px 0 0 var(--crystal-line, #0b2b3d),
    -1px 0 0 var(--crystal-line, #0b2b3d);
}
@keyframes shardBreathe {
  0%, 100% { filter: drop-shadow(0 0 7px rgba(121, 228, 255, .5)) drop-shadow(0 3px 3px rgba(0, 0, 0, .5)); }
  50%      { filter: drop-shadow(0 0 15px rgba(121, 228, 255, .9)) drop-shadow(0 3px 3px rgba(0, 0, 0, .5)); }
}
@media (prefers-reduced-motion: reduce) { .piece.n .pc { animation: none; } }

/* Nobody's men. Each one gets its own palette so a zombie is never mistaken
   for a shard, and none of them are mistaken for a piece you can move. */
/* A tree is green, obviously, and it sways a little so the board reads as
   overgrown rather than decorated. */
.piece.n[data-type="xt"] .pc {
  fill: #5f9c4e; stroke: #24401b;
  animation: treeSway 5.2s ease-in-out infinite;
  transform-origin: 50% 92%;
}
.piece.n[data-type="xt"] .pc-mane { stroke: #a8e08a; opacity: .75; }
@keyframes treeSway {
  0%, 100% { transform: rotate(-1.6deg); }
  50%      { transform: rotate(1.6deg); }
}
@media (prefers-reduced-motion: reduce) { .piece.n[data-type="xt"] .pc { animation: none; } }

/* When the clipboard refuses, the story goes here instead. */
.story-out { margin-top: 10px; text-align: left; }
.story-out p { margin: 0 0 6px; font-size: 12px; color: #9fb0d0; }
.story-out textarea {
  width: 100%;
  box-sizing: border-box;
  resize: vertical;
  font: 12px/1.45 ui-monospace, "Cascadia Code", Consolas, monospace;
  color: #e8e6f0;
  background: rgba(0, 0, 0, .35);
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: 8px;
  padding: 8px 10px;
}

/* The roll of honour on the end screen: who this game will be remembered
   for, and who the night already remembers. */
.recap-roll, .recap-hall {
  list-style: none;
  margin: 6px 0 0;
  padding: 0;
  display: grid;
  gap: 4px;
}
.recap-roll li, .recap-hall li {
  display: grid;
  grid-template-columns: 18px minmax(0, auto) minmax(0, auto) 1fr auto;
  align-items: baseline;
  gap: 8px;
  padding: 5px 9px;
  border-radius: 8px;
  background: rgba(255, 255, 255, .04);
  border-left: 3px solid rgba(255, 209, 102, .55);
  font-size: 13px;
}
.recap-hall li { grid-template-columns: 18px minmax(0, auto) minmax(0, auto) auto; }
.recap-roll li.b { border-left-color: rgba(150, 190, 255, .55); }
.recap-roll li.fell { opacity: .72; }
.recap-roll li b, .recap-hall li b { color: #ffe9c9; font-weight: 700; white-space: nowrap; }
.recap-roll li i, .recap-hall li i {
  font-style: normal;
  color: #9fb0d0;
  text-transform: uppercase;
  letter-spacing: .08em;
  font-size: 10px;
  white-space: nowrap;
}
.rh-mark { font-size: 13px; line-height: 1; }
.rh-did {
  color: #c9c4dc;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rh-end {
  color: #8b93ad;
  font-size: 11px;
  white-space: nowrap;
}
@media (max-width: 560px) {
  .recap-roll li { grid-template-columns: 18px 1fr; }
  .recap-roll li i, .rh-did, .rh-end { grid-column: 2; }
}

/* A piece that earned a name wears it. The ribbon is the only thing renown
   puts on the board; everything else it does is on the piece itself. */
.bg-renown {
  color: #ffd166;
  text-shadow: 0 0 6px rgba(255, 209, 102, .8);
}
.bg-renown.legend {
  color: #fff3c4;
  text-shadow: 0 0 10px rgba(255, 226, 140, 1), 0 0 20px rgba(255, 180, 60, .7);
  animation: legend-glimmer 2.4s ease-in-out infinite;
}
@keyframes legend-glimmer {
  0%, 100% { opacity: .82; transform: scale(1); }
  50%      { opacity: 1;   transform: scale(1.16); }
}
@media (prefers-reduced-motion: reduce) { .bg-renown.legend { animation: none; } }

/* ============ the duel ============
   A fight, over the board, that decides nothing: the ability rolled the dice
   before any of this was built and the board behind it is already correct. */
.duel-stage {
  position: absolute;
  inset: 0;
  z-index: 40;
  overflow: hidden;
  border-radius: inherit;
  cursor: pointer;
  animation: duel-in .26s ease-out both;
}
.duel-stage.out { animation: duel-out .26s ease-in both; }
@keyframes duel-in  { from { opacity: 0; } to { opacity: 1; } }
@keyframes duel-out { from { opacity: 1; } to { opacity: 0; } }

.duel-dark {
  position: absolute; inset: 0;
  background:
    radial-gradient(ellipse 62% 46% at 50% 52%, rgba(10, 6, 16, .74), rgba(4, 3, 8, .97) 78%);
  backdrop-filter: blur(4px) saturate(.55);
}

/* the lit floor they are standing on */
.duel-ring {
  position: absolute; left: 50%; top: 62%;
  width: 74%; height: 26%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(ellipse at 50% 50%, rgba(255, 176, 102, .30), transparent 70%);
  box-shadow: inset 0 0 60px rgba(255, 140, 60, .18);
  animation: duel-floor 1.9s ease-in-out infinite;
}
@keyframes duel-floor {
  0%, 100% { opacity: .8; }
  50%      { opacity: 1; }
}

/* A crowd, out of two repeating gradients: heads along the bottom, bobbing.
   Cheap, and at this size nobody is counting them. */
.duel-crowd {
  position: absolute; left: -6%; right: -6%; bottom: 0;
  height: 15%;
  background-repeat: repeat-x;
  background-size: 5.2% 100%;
  background-image:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 26 40'><circle cx='13' cy='15' r='7' fill='%23161d33'/><path d='M2 40c0-9 5-15 11-15s11 6 11 15z' fill='%23161d33'/><path d='M13 8.4a7 7 0 0 1 5.6 2.8' stroke='%233b4a72' stroke-width='1.6' fill='none'/></svg>");
  animation: duel-bob 1.05s ease-in-out infinite;
  opacity: .95;
}
.duel-crowd.two {
  height: 11%;
  background-size: 4.1% 100%;
  background-position: 40% 0;
  opacity: .7;
  animation-duration: 1.35s;
  animation-direction: reverse;
}
@keyframes duel-bob {
  0%, 100% { transform: translateY(6%); }
  50%      { transform: translateY(-8%); }
}

.duel-bout {
  position: absolute; left: 0; right: 0; top: 50%;
  transform: translateY(-56%);
  display: flex; align-items: center; justify-content: center;
  gap: 15%;
}

.duel-fighter {
  position: relative;
  width: 30%;
  max-width: 210px;
  text-align: center;
  /* a rim as well as a shadow: a black piece on a near-black arena is an
     outline and nothing else without one */
  filter: drop-shadow(0 10px 18px rgba(0, 0, 0, .85))
          drop-shadow(0 0 10px rgba(255, 214, 160, .32));
}
/* and something lit to stand on */
.duel-fighter::before {
  content: "";
  position: absolute; left: 50%; top: 46%;
  width: 132%; height: 118%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(ellipse at 50% 50%,
    rgba(255, 196, 130, .34), rgba(255, 150, 90, .12) 52%, transparent 72%);
  z-index: -1;
}
.duel-fighter.right::before {
  background: radial-gradient(ellipse at 50% 50%,
    rgba(150, 200, 255, .38), rgba(110, 150, 255, .14) 52%, transparent 72%);
}
.duel-fighter.left  { animation: duel-enter-l .42s cubic-bezier(.2, .9, .3, 1.3) both; }
.duel-fighter.right { animation: duel-enter-r .42s cubic-bezier(.2, .9, .3, 1.3) both; }
@keyframes duel-enter-l { from { opacity: 0; transform: translateX(-90%) scale(.7); } }
@keyframes duel-enter-r { from { opacity: 0; transform: translateX(90%)  scale(.7); } }

/* the piece itself, borrowed straight off the sprite sheet so it is
   recognisably the one standing on the board - only much bigger */
.duel-fighter .piece {
  position: relative;
  width: 100%; height: auto;
  left: auto; top: auto;
  transform: none;
  aspect-ratio: 1;
}
.duel-fighter .piece .pc { width: 100%; height: 100%; }
.duel-fighter.right .piece { transform: scaleX(-1); }
.duel-name {
  margin-top: 2%;
  font: 600 clamp(9px, 1.5vw, 15px)/1.1 var(--font, system-ui), system-ui;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: #d8d3e8;
  opacity: .8;
}

/* the exchange: both go in, whoever lost it comes back out */
.duel-fighter.left.lunge  { animation: duel-lunge-l .2s ease-in both; }
.duel-fighter.right.lunge { animation: duel-lunge-r .2s ease-in both; }
@keyframes duel-lunge-l { to { transform: translateX(24%) rotate(9deg); } }
@keyframes duel-lunge-r { to { transform: translateX(-24%) rotate(-9deg); } }

.duel-fighter.left.hit  { animation: duel-hit-l .36s cubic-bezier(.3, -.4, .5, 1.4) both; }
.duel-fighter.right.hit { animation: duel-hit-r .36s cubic-bezier(.3, -.4, .5, 1.4) both; }
@keyframes duel-hit-l {
  0%   { transform: translateX(22%) rotate(8deg); filter: brightness(2.4); }
  100% { transform: translateX(-14%) rotate(-6deg); }
}
@keyframes duel-hit-r {
  0%   { transform: translateX(-22%) rotate(-8deg); filter: brightness(2.4); }
  100% { transform: translateX(14%) rotate(6deg); }
}

.duel-fighter.down { animation: duel-down .72s cubic-bezier(.4, 0, .7, 1) .3s both; }
@keyframes duel-down {
  0%   { opacity: 1; }
  100% { opacity: 0; transform: translateY(46%) rotate(78deg) scale(.82); }
}
.duel-fighter.held { animation: duel-held 1s ease-out .3s both; }
@keyframes duel-held {
  0%   { filter: drop-shadow(0 0 0 rgba(120, 210, 255, 0)); }
  25%  { filter: drop-shadow(0 0 26px rgba(120, 210, 255, 1)); }
  100% { filter: drop-shadow(0 0 12px rgba(120, 210, 255, .7)); }
}
.duel-fighter.won { animation: duel-won .8s ease-out .34s both; }
@keyframes duel-won {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.12) translateY(-6%); }
  100% { transform: scale(1.06); }
}

/* where they meet */
.duel-clash {
  position: absolute; left: 50%; top: 46%;
  width: 0; height: 0;
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.duel-clash.go::before {
  content: "";
  position: absolute; left: 50%; top: 50%;
  width: 26px; height: 26px; margin: -13px;
  border-radius: 50%;
  background: radial-gradient(circle, #fff, #ffd166 40%, transparent 72%);
  animation: duel-flash .34s ease-out both;
}
@keyframes duel-flash {
  0%   { transform: scale(.3); opacity: 1; }
  100% { transform: scale(6);  opacity: 0; }
}
.duel-spark {
  position: absolute; left: 0; top: 0;
  width: 5px; height: 5px;
  border-radius: 1px;
  animation: duel-spark .62s cubic-bezier(.2, .7, .4, 1) both;
}
@keyframes duel-spark {
  0%   { transform: translate(0, 0) scale(1); opacity: 1; }
  100% { transform: translate(var(--dx), calc(var(--dy) + 26px)) scale(.2); opacity: 0; }
}

.duel-banner {
  position: absolute; left: 0; right: 0; top: 8%;
  text-align: center;
  font: 800 clamp(13px, 2.6vw, 30px)/1.1 var(--font, system-ui), system-ui;
  letter-spacing: .2em;
  color: #ffe9c9;
  text-shadow: 0 0 18px rgba(255, 140, 60, .8), 0 3px 0 rgba(0, 0, 0, .6);
  animation: duel-banner .5s cubic-bezier(.2, .9, .3, 1.4) both;
}
@keyframes duel-banner { from { opacity: 0; transform: translateY(-40%) scale(.8); } }
.duel-banner.result {
  animation: duel-banner .42s cubic-bezier(.2, .9, .3, 1.6) both;
  color: #fff;
}
.duel-skip {
  position: absolute; right: 3%; bottom: 3%;
  font: 500 11px/1 var(--font, system-ui), system-ui;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: rgba(232, 230, 240, .42);
}

@media (prefers-reduced-motion: reduce) {
  .duel-stage, .duel-stage * { animation: none !important; }
}

/* A pirate hat. Cosmetic and nothing else: it sits on the piece and changes
   not one thing about what the piece can do.

   Most of them are caps, drawn rather than typed - a tricorn is not an emoji
   and the nearest one is a baseball cap. The few who carry colours get the
   real flag, and so does every ship. */
.pc-hat {
  position: absolute;
  top: -4%;
  left: 50%;
  width: 46%;
  transform: translateX(-50%) rotate(-11deg);
  line-height: 0;
  pointer-events: none;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .75));
  z-index: 3;
}
.pc-hat svg { width: 100%; height: auto; display: block; overflow: visible; }
/* red cloth, the knot at the right, both tails off the back of it, spots */
.pc-hat .hat-felt { fill: #b23a3a; stroke: #4a1414; stroke-width: .8; }
.pc-hat .hat-tail { fill: #962f2f; stroke: #4a1414; stroke-width: .7; stroke-linejoin: round; }
.pc-hat .hat-knot { fill: #c74a4a; stroke: #4a1414; stroke-width: .7; }
.pc-hat .hat-spot { fill: #f4e2d8; opacity: .85; }
.piece.b .pc-hat .hat-felt { fill: #2f4d86; }
.piece.b .pc-hat .hat-tail { fill: #27406f; }
.piece.b .pc-hat .hat-knot { fill: #3b60a4; }

/* The colours are carried, not worn: the staff runs down past the shoulder
   and into the body, and the cloth flies clear of the head on the side the
   bandana's tails are not on. */
.pc-flag {
  position: absolute;
  top: -16%;
  left: -4%;
  width: 36%;
  line-height: 0;
  pointer-events: none;
  transform: scaleX(-1);
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .75));
  z-index: 2;
}
.pc-flag svg { width: 100%; height: auto; display: block; overflow: visible; }
.pc-flag .flag-staff { fill: #6b4a28; stroke: #2e1d0c; stroke-width: .6; }
.pc-flag .flag-truck { fill: #d8b25e; stroke: #2e1d0c; stroke-width: .6; }
/* black cloth on both sides - it is a pirate flag - so the border carries
   the colour instead, and the sides stay tellable apart at a glance */
.pc-flag .flag-cloth { fill: #14161a; stroke: #b23a3a; stroke-width: 1; stroke-linejoin: round; }
.piece.b .pc-flag .flag-cloth { stroke: #2f4d86; }
.pc-flag .flag-bone { fill: #f2ece0; stroke: none; }
.pc-flag .flag-eye { fill: #14161a; stroke: none; }
.pc-flag .flag-bones { stroke: #f2ece0; stroke-width: 1.4; stroke-linecap: round; fill: none; opacity: .9; }

/* A ship flies hers from her own masthead, so she wants the cloth and not
   the pole. The sprite puts the tall mast about a third across and the truck
   of it a fifth of the way down. */
.piece[data-type="v"] .pc-flag {
  top: 17%;
  left: 28%;
  width: 30%;
  transform: none;
}
.piece[data-type="v"] .pc-flag .flag-staff,
.piece[data-type="v"] .pc-flag .flag-truck { display: none; }

.piece.moving .pc-hat { transform: translateX(-50%) rotate(-19deg); transition: transform .18s; }
.piece.moving .pc-flag { transform: scaleX(-1) rotate(7deg); transition: transform .18s; }
.piece.moving[data-type="v"] .pc-flag { transform: rotate(-4deg); }

/* Tall grass. A square that is empty as far as every rule is concerned and
   full of cover as far as anything overhead is concerned. */
/* Tall means tall. It has to read as something you could lie down in, or
   nobody believes it is hiding anybody. */
.sq.grass { background-image: linear-gradient(180deg, rgba(30, 78, 36, .12) 8%, rgba(30, 78, 36, .62) 100%); }
.sq.grass .blades,
.sq.grass .blades.two {
  position: absolute; left: 0; right: 0; bottom: 0; height: 96%;
  pointer-events: none;
  background-repeat: repeat-x;
  background-size: 17% 100%;
  background-image:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 60'><path d='M3.4 60C3.4 34 1.6 18 0 4c3.6 8 6 24 6 56zM11.4 60C11.4 30 9 14 6.6 0c4.8 9 8 27 8 60zM19 60c0-25 1.8-40 3.4-52-3.6 9-6 25-6 52z' fill='%233f8f47'/></svg>");
  animation: sway 5.5s ease-in-out infinite;
  transform-origin: 50% 100%;
  opacity: .92;
}
.sq.grass .blades.two {
  height: 72%;
  background-position: 47% 0;
  background-size: 21% 100%;
  opacity: .68;
  animation-duration: 7.5s;
  animation-direction: reverse;
}
/* somebody standing in it is standing IN it, not on top of it */
.sq.grass .blades { z-index: 4; }
.sq.grass .blades.two { z-index: 4; }
@keyframes sway {
  0%, 100% { transform: skewX(0deg); }
  50%      { transform: skewX(6deg); }
}
@media (prefers-reduced-motion: reduce) { .sq.grass .blades { animation: none; } }

/* The lights out. Nothing here changes a rule; it changes what you can see,
   which is the entire point of a night. */
.app.night .board-shell { background: #05070d; border-radius: 14px; }
.app.night .sq.light { background: #23304a; }
.app.night .sq.dark  { background: #16203a; }
.app.night .sq.grass { background-image: linear-gradient(180deg, rgba(20, 54, 26, 0) 34%, rgba(20, 54, 26, .75) 100%); }
.app.night .sq.grass .blades { filter: brightness(.55) saturate(.8); }
.app.night .board {
  box-shadow: 0 0 0 1px rgba(150, 190, 255, .12), 0 24px 70px rgba(0, 0, 0, .75);
}
.app.night .piece { filter: brightness(.86) contrast(1.05); }
.app.night .piece.n[data-type="e"] { filter: none; }

/* The three of them. A craft is metal and glass with the lights on under it,
   a cow is a cow, and the hive is lit from the inside. */
.piece.n[data-type="e"] .pc { fill: #8e9bb8; stroke: #232b40; animation: none; }
.piece.n[data-type="e"] {
  --pc-dome: #9fe8ff; --pc-hull: #6d7a96; --pc-lamp: #ffe36a; --pc-beam: #cdf6ff;
  filter: drop-shadow(0 0 12px rgba(120, 225, 255, .55));
}
/* on the ground it is a thing you can reach, and it should look like one */
.piece.n[data-type="e"].landed {
  --pc-lamp: #ff7b72;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, .7));
}
.piece.n[data-type="e"].landed .pc { fill: #6f7c96; }

.piece.n[data-type="d"] .pc { fill: #f2ede2; stroke: #3a3226; animation: none; }
.piece.n[data-type="d"] {
  --pc-patch: #3a3226; --pc-nose: #e8a0b4; --pc-pupil: #1c1c22;
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, .45));
}

.piece.n[data-type="w"] .pc { fill: #6fb84a; stroke: #1d3a12; animation: none; }
.piece.n[data-type="w"] {
  --pc-eye2: #0d1410; --pc-gun: #39435c; --pc-core: #b6ff6a;
  filter: drop-shadow(0 0 8px rgba(150, 255, 90, .4));
}

/* Somebody who went up in a beam and came back down on the other side. They
   keep the colours they went up wearing - the piece renders in its old
   side’s palette, because "that is my knight and it is theirs now" is the
   whole point - and carry a green light that says what happened. */
.piece.alien .pc {
  stroke: #4aff8a;
  stroke-width: 1.6;
}
.piece.alien {
  filter: drop-shadow(0 0 10px rgba(74, 255, 138, .65));
  animation: turned-pulse 2.4s ease-in-out infinite;
}
@keyframes turned-pulse {
  0%, 100% { filter: drop-shadow(0 0 7px rgba(74, 255, 138, .45)); }
  50%      { filter: drop-shadow(0 0 15px rgba(74, 255, 138, .85)); }
}
@media (prefers-reduced-motion: reduce) { .piece.alien { animation: none; } }

.piece.n[data-type="xh"] .pc { fill: #4a2f63; stroke: #1d1230; animation: none; }
.piece.n[data-type="xh"] {
  --pc-core: #b6ff6a;
  filter: drop-shadow(0 0 14px rgba(150, 255, 90, .5));
  animation: hive-throb 2.6s ease-in-out infinite;
}
@keyframes hive-throb {
  0%, 100% { filter: drop-shadow(0 0 10px rgba(150, 255, 90, .35)); }
  50%      { filter: drop-shadow(0 0 20px rgba(150, 255, 90, .7)); }
}
@media (prefers-reduced-motion: reduce) { .piece.n[data-type="xh"] { animation: none; } }

/* The island. None of these is drawn in the shard palette: a monkey the
   colour of ice is a monkey nobody reads as a monkey. Both animals are also
   drawn smaller than a fighting piece, because neither of them is one. */
.piece.n[data-type="a"] .pc,
.piece.n[data-type="i"] .pc { transform: scale(.74); transform-origin: 50% 58%; }

/* Every sprite is stamped out through <use>, and a class selector in this
   file cannot reach inside a <use>'s shadow tree - only inherited properties
   get in. So a second colour is passed as a custom property, which does
   inherit, and the shape carries the same value as its own fallback. */
.piece.n[data-type="a"] .pc { fill: #a3703f; stroke: #3b2412; animation: none; }
.piece.n[data-type="a"] {
  --pc-skin: #e2c096; --pc-sclera: #fff6e6; --pc-pupil: #2b1c0f;
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, .45));
}

.piece.n[data-type="i"] .pc { fill: #38ad57; stroke: #14401f; animation: none; }
.piece.n[data-type="i"] {
  --pc-cut: #f5b23f; --pc-belly: #9ee7a8; --pc-wing: #218040;
  --pc-sclera: #fffbe8; --pc-pupil: #12280c;
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, .45));
}

.piece.n[data-type="xn"] .pc { fill: #d8bd8c; stroke: #4a3820; animation: none; }
.piece.n[data-type="xn"] {
  --pc-thatch: #a8813f; --pc-door: #3a2c18; --pc-cut: #d9534f;
  filter: drop-shadow(0 0 9px rgba(210, 175, 110, .45));
}

/* The two huts. Somebody lives in each of them, so neither is drawn in the
   cyan the shards use - a hut in that palette reads as scenery, and the
   whole point of these is that you can go and knock. */
.piece.n[data-type="xd"] .pc { fill: #6f9a52; stroke: #263a18; animation: none; }
.piece.n[data-type="xd"] .pc-mane { stroke: #cbe8a6; opacity: .7; }
.piece.n[data-type="xd"] .pc-cut { fill: #22300f; stroke: none; }
.piece.n[data-type="xd"] .pc-eye { fill: #ffd76a; stroke: none; }
.piece.n[data-type="xd"] { filter: drop-shadow(0 0 9px rgba(140, 210, 110, .45)); }

.piece.n[data-type="xf"] .pc { fill: #a98a63; stroke: #33241a; animation: none; }
.piece.n[data-type="xf"] .pc-mane { stroke: #f0d9b4; opacity: .75; }
.piece.n[data-type="xf"] .pc-cut { fill: #2c2016; stroke: none; }
.piece.n[data-type="xf"] .pc-eye { fill: #ff9f4a; stroke: none; }
.piece.n[data-type="xf"] { filter: drop-shadow(0 0 9px rgba(230, 160, 80, .45)); }

/* A target does not sit still: it is asking to be shot at. */
.piece.n[data-type="xm"] .pc { fill: #e8e2d4; stroke: #2b2118; animation: targetTick 2.6s ease-in-out infinite; }
.piece.n[data-type="xm"] .pc-cut { fill: #d1483f; stroke: none; }
.piece.n[data-type="xm"] .pc-eye { fill: #f7f2e6; stroke: none; }
.piece.n[data-type="xm"] .pc-mane { stroke: #8b8274; opacity: .8; }
.piece.n[data-type="xm"] { filter: drop-shadow(0 0 10px rgba(240, 120, 100, .5)); }
@keyframes targetTick {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.06); }
}
@media (prefers-reduced-motion: reduce) { .piece.n[data-type="xm"] .pc { animation: none; } }

/* A tower is the part of a castle with an upstairs, so it is drawn lighter at
   the top and darker at the base and does not breathe like a shard: masonry
   that shimmered would read as ice. */
.piece.n[data-type="xo"] .pc { fill: #b3a894; stroke: #2f2820; animation: none; }
.piece.n[data-type="xo"] .pc-mane { stroke: #6f6455; opacity: .85; }
.piece.n[data-type="xo"] .pc-cut { fill: #3b3229; stroke: none; }
.piece.n[data-type="xo"] { filter: drop-shadow(0 3px 5px rgba(0, 0, 0, .55)); }
.piece.n[data-type="xw"] .pc { animation: none; }

/* Terrain that is stone rather than ice. The crystal palette is cyan on
   purpose; a boulder in it reads as an ice cube. */
.piece.n[data-type="xb"] .pc { fill: #9a8d7e; stroke: #3a3128; animation: none; }
.piece.n[data-type="xb"] .pc-mane { stroke: #6d6156; opacity: .9; }
.piece.n[data-type="xe"] .pc {
  fill: #a4877a; stroke: #3a2118; animation: none;
  filter: drop-shadow(0 0 9px rgba(255, 122, 42, .75)) drop-shadow(0 3px 4px rgba(0, 0, 0, .6));
}
.piece.n[data-type="xe"] .pc-mane { stroke: #7a5a49; opacity: .9; }
.piece.n[data-type="xe"] .pc-eye { fill: #ff8a2a; stroke: #ffd08a; stroke-width: 1.2; }

.piece.n.mob .pc { stroke-width: 1.7; animation: none; filter: drop-shadow(0 3px 4px rgba(0,0,0,.6)); }
.piece.n.mob .pc-eye { fill: #120a1e; stroke: none; }
.piece.n.mob-z .pc { fill: #8fc46a; stroke: #24401b; }
.piece.n.mob-g .pc { fill: #dff0ff; stroke: #4a6a8c; animation: ghostFloat 3.2s ease-in-out infinite; }
.piece.n.mob-g .pc-eye { fill: #4a6a8c; }
.piece.n.mob-l .pc { fill: #9a9384; stroke: #3a352c; }
.piece.n.mob-o .pc { fill: #c96a4a; stroke: #3d1c12; }
/* the cyclops: sunburnt hide, and the one eye is the only bright thing on it */
.piece.n.mob-u .pc { fill: #c2a26a; stroke: #4a3418; }
.piece.n.mob-u .pc-eye { fill: #fff3c4; stroke: none; }
.piece.n.mob-u .pc-mane { stroke: #6b4a1e; opacity: .95; }
.piece.n.mob .glyph { color: #c9f0a0; text-shadow: 0 2px 0 #24401b; }
.piece.n.mob-g .glyph { color: #dff0ff; text-shadow: 0 2px 0 #4a6a8c; }
.piece.n.mob-l .glyph { color: #9a9384; text-shadow: 0 2px 0 #3a352c; }
.piece.n.mob-u .glyph { color: #f0d79a; text-shadow: 0 2px 0 #4a3418; }
.piece.n.mob-o .glyph { color: #ff9c78; text-shadow: 0 2px 0 #3d1c12; }
@keyframes ghostFloat {
  0%, 100% { transform: translateY(0); opacity: .88; }
  50%      { transform: translateY(-6%); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .piece.n.mob-g .pc { animation: none; } }

.piece.n.void .pc {
  fill: #14001f;
  stroke: #b06bff;
  stroke-width: 2;
  animation: voidPull 2.4s ease-in-out infinite;
  filter: drop-shadow(0 0 16px rgba(176, 107, 255, .85));
}
.piece.n.void .pc-mane { stroke: #d7a8ff; opacity: .8; }
.piece.n.void .glyph { color: #b06bff; text-shadow: 0 0 18px rgba(176, 107, 255, .9); }
@keyframes voidPull {
  0%, 100% { transform: scale(.92) rotate(0deg); }
  50%      { transform: scale(1.06) rotate(180deg); }
}
@media (prefers-reduced-motion: reduce) { .piece.n.void .pc { animation: none; } }

/* Stone does not breathe and water does not glitter: same obstacle, different
   scenery, so the eye can tell a shard from a curtain wall from a river. */
.piece.n.wall .pc {
  fill: var(--wall, #b5ac9b);
  stroke: var(--wall-line, #423a30);
  animation: none;
  filter: drop-shadow(0 3px 3px rgba(0, 0, 0, .55));
}
.piece.n.wall .pc-mane { stroke: #6e6455; opacity: .7; }
.piece.n.wall .glyph { color: var(--wall, #b5ac9b); text-shadow: 0 2px 0 var(--wall-line, #423a30); }

.piece.n.water .pc {
  fill: var(--water, #5aa9e6);
  stroke: var(--water-line, #14456f);
  animation: waterBob 4.5s ease-in-out infinite;
  filter: drop-shadow(0 2px 4px rgba(0, 20, 40, .5));
}
.piece.n.water .pc-mane { stroke: #d8f0ff; opacity: .5; }
.piece.n.water .glyph { color: var(--water, #5aa9e6); text-shadow: 0 2px 0 var(--water-line, #14456f); }
@keyframes waterBob {
  0%, 100% { transform: translateY(0); opacity: .92; }
  50%      { transform: translateY(4%); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .piece.n.water .pc { animation: none; } }

.pc .pc-cut, .pc .pc-cross { fill: none; stroke-width: 2.4; }
.pc .pc-mane { fill: none; stroke-width: 1.6; opacity: .55; }
.piece.w .pc-eye { fill: var(--piece-w-line, #2a2142); stroke: none; }
.piece.b .pc-eye { fill: var(--piece-b-line, #d5cbf2); stroke: none; }

/* Classic packs render the old lettering instead of the vector set. */
.piece .glyph {
  font-family: "Segoe UI Symbol", "Apple Symbols", "Noto Sans Symbols 2", serif;
  font-size: calc(var(--board-size, 640px) / var(--board-cols, 8) * 0.78);
  line-height: 1;
  display: block;
}
.piece.w .glyph {
  color: var(--piece-w, #fbf8ff);
  text-shadow:
    0 1px 0 var(--piece-w-line), 0 -1px 0 var(--piece-w-line),
    1px 0 0 var(--piece-w-line), -1px 0 0 var(--piece-w-line),
    0 4px 8px rgba(0, 0, 0, .5);
}
.piece.b .glyph {
  color: var(--piece-b, #1d1630);
  text-shadow:
    0 1px 0 var(--piece-b-line), 0 -1px 0 var(--piece-b-line),
    1px 0 0 var(--piece-b-line), -1px 0 0 var(--piece-b-line),
    0 4px 8px rgba(0, 0, 0, .5);
}
.pb-graveyard .grave .glyph { font-size: 17px; }
.piece.dragging { z-index: 20; transition: none; cursor: grabbing; filter: drop-shadow(0 10px 14px rgba(0,0,0,.6)); }
/* the copy that walks out through the near edge of a Round World */
.piece.wrap-ghost { pointer-events: none; z-index: 4; }
.piece.frozen { filter: hue-rotate(-25deg) saturate(1.5) brightness(1.15); }
/* Nobody's talking to anybody. A gloomy piece is drained of colour and slouches. */
.piece.gloomy .pc { filter: grayscale(.75) brightness(.72) drop-shadow(0 3px 3px rgba(0,0,0,.5)); }
.piece.gloomy .glyph { filter: grayscale(.75) brightness(.72); }
.piece.gloomy { animation: sulk 3.4s ease-in-out infinite; }
@keyframes sulk {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(3%) rotate(-2deg); }
}
@media (prefers-reduced-motion: reduce) { .piece.gloomy { animation: none; } }

/* Status badges are real elements in a row, not pseudo-elements: there is only
   one ::before and one ::after per piece, and a piece can easily carry three
   statuses at once. */
.pc-badges {
  position: absolute;
  right: 1%;
  top: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
  pointer-events: none;
  z-index: 3;
  line-height: 1;
}
/* Damage, and only damage: a whole piece shows nothing. Sits under the piece
   rather than over it so it never covers the silhouette the board is read by. */
.pc-hp {
  position: absolute;
  left: 18%;
  right: 18%;
  bottom: 4%;
  height: max(3px, calc(var(--board-size, 640px) / 190));
  border-radius: 999px;
  background: rgba(8, 5, 16, .8);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .6), 0 1px 3px rgba(0, 0, 0, .7);
  overflow: hidden;
  pointer-events: none;
  z-index: 6;
}
.pc-hp i {
  display: block;
  width: var(--hp, 100%);
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, #ff5d5d, #ffb03a);
  box-shadow: 0 0 7px rgba(255, 93, 93, .9);
  transition: width .28s ease;
  animation: hpThrob 1.9s ease-in-out infinite;
}
@keyframes hpThrob { 0%, 100% { opacity: 1; } 50% { opacity: .62; } }
@media (prefers-reduced-motion: reduce) { .pc-hp i { animation: none; } }

.pc-badges i {
  font-style: normal;
  font-size: calc(var(--board-size, 640px) / var(--board-cols, 8) * 0.235);
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .9));
}
/* a berth: your own ship, lit as somewhere the selected piece can step into */
.sq.berth::after {
  content: "";
  position: absolute;
  inset: 8%;
  border-radius: 6px;
  border: 2px dashed rgba(120, 210, 255, .85);
  box-shadow: inset 0 0 14px rgba(90, 190, 255, .35);
  pointer-events: none;
}

.pc-badges .bg-crew { color: #9fe0ff; text-shadow: 0 0 8px rgba(90, 190, 255, .8); }
.pc-badges .bg-crew b { font-size: .8em; margin-left: 1px; }
.pc-badges .bg-frozen { color: var(--cyan); text-shadow: 0 0 8px var(--cyan); }
.pc-badges .bg-gloom { filter: drop-shadow(0 0 4px #6b7a99); }
.pc-badges .bg-revenge { filter: drop-shadow(0 0 5px #ff3b6b); animation: pulse 1.4s infinite; }
.pc-badges .bg-bond { color: #ff7bd5; text-shadow: 0 0 8px rgba(255, 123, 213, .9); }
.pc-badges .bg-ice { color: #8fe4ff; text-shadow: 0 0 9px rgba(143, 228, 255, .95); }
.pc-badges .bg-fire { color: #ff8a4c; text-shadow: 0 0 9px rgba(255, 138, 76, .95); animation: pulse 1.5s infinite; }
.pc-badges .bg-cheat { color: #ff4d9d; text-shadow: 0 0 9px rgba(255, 77, 157, .95); animation: pulse 1.8s infinite; }
.pc-badges .bg-named { filter: drop-shadow(0 0 5px #ff3b6b); }
.pc-badges .bg-thrall { filter: drop-shadow(0 0 5px #ff4d6d); }
.pc-badges .bg-obsess { filter: drop-shadow(0 0 5px var(--defect, #ff7bd5)); }
.pc-badges .bg-bomb { animation: pulse 1s infinite; filter: drop-shadow(0 0 4px var(--bad)); }
.pc-badges .bg-shield { filter: drop-shadow(0 0 4px var(--good)); }
.pc-badges .bg-defect { filter: drop-shadow(0 0 4px currentColor); }
.piece.shielded::before {
  content: "";
  position: absolute;
  inset: 8%;
  border-radius: 50%;
  border: 2px solid rgba(139, 209, 124, .9);
  box-shadow: 0 0 14px rgba(139, 209, 124, .7), inset 0 0 14px rgba(139, 209, 124, .35);
  animation: pulse 1.6s ease-in-out infinite;
}
.piece.pop { animation: pop .3s ease-out; }
@keyframes pop { 0% { transform: scale(.4); } 70% { transform: scale(1.12); } 100% { transform: scale(1); } }

/* ---------------- rule-card draft ---------------- */

.draft {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-4);
  background: rgba(8, 5, 16, .84);
  backdrop-filter: blur(7px);
  z-index: 70;
  padding: var(--sp-5);
  overflow-y: auto;              /* content decides the height, never the board */
  overscroll-behavior: contain;
  animation: draftIn .28s ease-out;
}
@keyframes draftIn { from { opacity: 0; transform: scale(.97); } }

.draft-head { text-align: center; }
/* What a piece can do instead of moving. */
/* ---------------- the ring ----------------
   Commands laid out round the piece they belong to rather than in a list beside
   it. Fixed position, because the board itself is zoomed and panned and the ring
   must not be dragged about with it. Nothing here scrolls: if the ring would run
   off an edge the whole thing is nudged back on. */
.ring-menu {
  position: fixed;
  width: 0;
  height: 0;
  z-index: 90;
  pointer-events: none;
  animation: ringIn .16s ease-out;
}
@keyframes ringIn {
  from { transform: scale(.82); opacity: 0; }
  to   { transform: scale(1); opacity: 1; }
}
.ring-menu > * { pointer-events: auto; }

.ring-menu .rm-hub {
  position: absolute;
  left: 50%; top: 50%;
  width: 92px; height: 92px;
  margin: -46px 0 0 -46px;
  border-radius: 50%;
  display: grid;
  place-content: center;
  gap: 1px;
  text-align: center;
  border: 1px solid rgba(255, 255, 255, .16);
  background: radial-gradient(circle at 50% 38%, rgba(30, 24, 54, .8), rgba(16, 12, 30, .82));
  backdrop-filter: blur(8px) saturate(1.1);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .55), inset 0 0 22px rgba(120, 90, 200, .22);
  color: var(--ink);
  cursor: pointer;
  font: inherit;
}
.ring-menu .rm-hub b { font-size: 12px; letter-spacing: .2px; }
.ring-menu .rm-hub i { font-style: normal; font-size: 11px; color: var(--accent-2); }
.ring-menu .rm-hub em {
  font-style: normal; font-size: 9px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-faint);
}
.ring-menu .rm-hub:hover { border-color: rgba(255, 255, 255, .34); }

.ring-menu .rm-cat,
.ring-menu .rm-act {
  position: absolute;
  border-radius: 50%;
  display: grid;
  place-content: center;
  gap: 2px;
  border: 1px solid currentColor;
  background: rgba(18, 14, 34, .74);
  backdrop-filter: blur(8px) saturate(1.1);
  box-shadow: 0 6px 18px rgba(0, 0, 0, .45);
  cursor: pointer;
  font: inherit;
  transition: transform .12s, box-shadow .12s, background .12s;
}
.ring-menu .rm-cat:hover,
.ring-menu .rm-act:hover { transform: scale(1.12); background: rgba(34, 26, 60, .94); }
.ring-menu .rm-cat.open {
  background: rgba(46, 34, 82, .98);
  box-shadow: 0 0 0 2px currentColor, 0 6px 20px rgba(0, 0, 0, .55);
}
.ring-menu .rm-icon { font-size: 20px; line-height: 1; }
.ring-menu .rm-act .rm-icon { font-size: 22px; }
.ring-menu .rm-label {
  font-size: 8.5px;
  line-height: 1.1;
  letter-spacing: .02em;
  color: var(--ink);
  max-width: 58px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ring-menu .rm-act { animation: ringOut .14s ease-out; }
@keyframes ringOut { from { opacity: 0; transform: scale(.6); } to { opacity: 1; transform: scale(1); } }

/* why there is nothing on offer, which used to be the board saying nothing */
.ring-menu .rm-none {
  position: absolute;
  left: 50%; top: 62px;
  width: 250px;
  margin-left: -125px;
  padding: 10px 12px;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: rgba(16, 12, 30, .82);
  backdrop-filter: blur(8px);
  box-shadow: 0 10px 28px rgba(0, 0, 0, .5);
  color: var(--ink-dim);
  font-size: var(--fs-xs);
  line-height: 1.45;
  text-align: center;
}

@media (prefers-reduced-motion: reduce) {
  .ring-menu, .ring-menu .rm-act { animation: none; }
  .ring-menu .rm-cat:hover, .ring-menu .rm-act:hover { transform: none; }
}

/* The card somebody just took, held up in the middle of the board. */
/* A card that came up and did nothing. Same panel, crossed out, so the answer
   arrives where the player was already looking rather than only in the feed. */
.card-reveal.dead-card {
  color: var(--ink-dim);
  border-color: rgba(255, 107, 107, .5);
  box-shadow: 0 18px 44px rgba(0, 0, 0, .55), 0 0 0 1px rgba(255, 107, 107, .18) inset;
}
.card-reveal.dead-card .cr-icon { filter: grayscale(1); opacity: .55; }
.card-reveal.dead-card .cr-name { text-decoration: line-through; text-decoration-thickness: 2px; }
.card-reveal.dead-card .cr-dead {
  margin-top: 6px;
  font-size: var(--fs-xs);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: #ff8a8a;
}

.card-reveal {
  position: absolute;
  left: 50%;
  top: 42%;
  transform: translate(-50%, -50%);
  z-index: 64;
  width: min(300px, 74%);
  padding: var(--sp-4);
  text-align: center;
  border-radius: var(--r-lg);
  background: linear-gradient(180deg, #2b2148, #171029);
  border: 2px solid currentColor;
  box-shadow: 0 0 40px -6px currentColor, var(--shadow);
  pointer-events: none;
  animation: cardReveal .5s cubic-bezier(.2, 1.3, .4, 1) both;
}
.card-reveal.leaving { animation: cardRevealOut .38s ease-in forwards; }
.card-reveal .cr-who {
  font-size: var(--fs-xs); letter-spacing: 1.6px; text-transform: uppercase;
  color: var(--ink-faint); margin-bottom: var(--sp-2);
}
.card-reveal .cr-icon { font-size: 46px; line-height: 1; filter: drop-shadow(0 0 16px currentColor); }
.card-reveal .cr-name {
  font-size: var(--fs-xl); font-weight: 800; color: currentColor;
  margin: var(--sp-2) 0 var(--sp-1);
}
.card-reveal .cr-desc { font-size: var(--fs-sm); color: var(--ink-dim); line-height: 1.45; }
.card-reveal.hidden-pick .cr-icon { filter: none; opacity: .7; }
@keyframes cardReveal {
  0%   { transform: translate(-50%, -50%) scale(.4) rotate(-12deg); opacity: 0; }
  55%  { transform: translate(-50%, -50%) scale(1.08) rotate(2deg); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1) rotate(0deg); opacity: 1; }
}
@keyframes cardRevealOut {
  to { transform: translate(-50%, -140%) scale(.8); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .card-reveal { animation: none; }
  .card-reveal.leaving { animation: none; opacity: 0; }
}

.draft-head h3 {
  margin: 0;
  font-size: 26px;
  letter-spacing: 2px;
  text-transform: uppercase;
  background: linear-gradient(92deg, #fff, var(--accent) 50%, var(--accent-2));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.draft-head p { margin: var(--sp-2) 0 0; color: var(--ink-dim); font-size: var(--fs-md); }

.draft-row {
  display: flex;
  gap: var(--sp-3);
  flex-wrap: wrap;
  justify-content: center;
  width: 100%;
  max-width: 720px;
  flex: 0 0 auto;
}

.rule-card {
  flex: 1 1 190px;
  width: clamp(160px, 30%, 216px);
  max-width: 216px;
  background:
    radial-gradient(120% 60% at 50% 0%, color-mix(in srgb, currentColor 16%, transparent), transparent 70%),
    linear-gradient(180deg, #2b2148, #191231);
  border: 1px solid color-mix(in srgb, currentColor 45%, var(--line));
  border-top: 3px solid currentColor;
  border-radius: var(--radius);
  padding: var(--sp-4) var(--sp-3) var(--sp-3);
  text-align: center;
  cursor: pointer;
  color: var(--ink);
  font: inherit;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  box-shadow: var(--shadow-2);
  transition: transform .14s, box-shadow .14s, border-color .14s;
}
.rule-card:hover:not(:disabled) {
  transform: translateY(-6px);
  border-color: currentColor;
  box-shadow: 0 14px 34px -10px currentColor;
}
.rule-card:disabled { cursor: default; opacity: .92; }
.rule-card .rc-icon {
  font-size: 26px;
  line-height: 1;
  width: 52px; height: 52px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: color-mix(in srgb, currentColor 20%, rgba(0, 0, 0, .35));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 45%, transparent),
              0 0 22px -6px currentColor;
}
.rule-card .rc-name {
  font-weight: 800; font-size: var(--fs-lg); letter-spacing: .2px;
  color: currentColor; line-height: 1.2;
}
.rule-card .rc-desc { font-size: var(--fs-sm); color: var(--ink); opacity: .92; line-height: 1.45; }
.rule-card .rc-flavor {
  font-size: var(--fs-xs); color: var(--ink-faint); font-style: italic;
  margin-top: auto; padding-top: var(--sp-2); line-height: 1.35;
}
.rule-card .rc-kind {
  font-size: 10px; letter-spacing: 2px; text-transform: uppercase; font-weight: 700;
  color: color-mix(in srgb, currentColor 75%, var(--ink-faint));
  border-top: 1px solid var(--line); padding-top: var(--sp-2);
  width: 100%; margin-top: var(--sp-1);
}
/* four rarities: the badge on the card says which, and a legendary one glows */
.rule-card .rc-rarity {
  font-style: normal; padding: 1px 8px; border-radius: 999px; margin-left: 2px;
  color: var(--rar); background: color-mix(in srgb, var(--rar) 22%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--rar) 55%, transparent);
}
.rule-card.rc-common { --rar: #9fd3a8; }
.rule-card.rc-rare { --rar: #6fb8ff; }
.rule-card.rc-epic { --rar: #c079ff; }
.rule-card.rc-legendary { --rar: #ffd166; box-shadow: var(--shadow-2), 0 0 28px -8px #ffd166; }
.rule-card.rc-legendary:hover:not(:disabled) { box-shadow: 0 14px 34px -10px currentColor, 0 0 34px -6px #ffd166; }
.rule-card.picked { border-color: var(--gold); box-shadow: 0 0 0 2px var(--gold), 0 0 30px -6px var(--gold); }

.line.card { border-left-color: var(--accent); background: rgba(169, 112, 255, .12); }
.movelog .mv.card { color: var(--accent); }

/* the bomb badge moved into .pc-badges, where it can share the piece with a
   snowflake and a shield instead of fighting them for one ::after */
/* a mine that cannot keep its mouth shut */
.sq.beep { animation: mineBeep .9s ease-out; }
@keyframes mineBeep {
  0%   { box-shadow: inset 0 0 0 0 rgba(255, 60, 60, 0); }
  22%  { box-shadow: inset 0 0 22px 3px rgba(255, 60, 60, .85); }
  55%  { box-shadow: inset 0 0 10px 1px rgba(255, 60, 60, .35); }
  75%  { box-shadow: inset 0 0 20px 2px rgba(255, 60, 60, .7); }
  100% { box-shadow: inset 0 0 0 0 rgba(255, 60, 60, 0); }
}
@media (prefers-reduced-motion: reduce) { .sq.beep { animation: none; } }

.sq.mine-known::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(circle, rgba(255, 138, 76, .5), transparent 65%);
  pointer-events: none;
}

/* Shown while the verdict is put away: small, out of the way, and the only
   thing on screen that brings it back. */
/* The one ending nobody gets by playing well. */
.banner.elopement > div {
  border-color: rgba(255, 123, 213, .55);
  box-shadow: 0 30px 80px -20px rgba(0, 0, 0, .9), 0 0 0 1px rgba(255, 123, 213, .2) inset,
              0 0 60px -20px rgba(255, 123, 213, .7);
}
.banner.elopement h3 {
  background: linear-gradient(96deg, #ff7bd5, #ffd166 55%, #ff7bd5);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.banner.elopement > div > p { color: #ffb8e6; }

.banner-peek {
  position: absolute;
  right: 14px;
  top: 14px;
  z-index: 30;
  padding: 7px 13px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: rgba(16, 12, 30, .92);
  color: var(--ink-dim);
  font: inherit;
  font-size: var(--fs-xs);
  letter-spacing: .02em;
  cursor: pointer;
  backdrop-filter: blur(6px);
  box-shadow: 0 6px 20px rgba(0, 0, 0, .45);
  transition: color .15s, border-color .15s, transform .15s;
}
.banner-peek:hover { color: var(--ink); border-color: var(--accent-2); transform: translateY(-1px); }

/* The end screen is glass over the board, not a lid on it: the position
   stays in view behind the verdict, and the card scrolls on its own. */
.banner {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: radial-gradient(70% 70% at 50% 45%, rgba(8, 5, 16, .3), rgba(8, 5, 16, .6));
  backdrop-filter: blur(2px);
  z-index: 68;
  text-align: center;
  padding: var(--sp-5);
  overflow-y: auto;
  animation: banner-in .35s ease-out both;
}
@keyframes banner-in { from { opacity: 0; } }
.banner > div {
  max-height: calc(100% - 40px);
  overflow-y: auto;
  scrollbar-width: thin;
  background: linear-gradient(180deg, rgba(46, 35, 78, .62), rgba(24, 17, 48, .72));
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: 22px;
  padding: var(--sp-5) 40px;
  box-shadow: 0 40px 90px -30px rgba(0, 0, 0, .9), inset 0 1px 0 rgba(255, 255, 255, .12);
  backdrop-filter: blur(18px) saturate(1.25);
  animation: banner-card .45s cubic-bezier(.2, .9, .3, 1.2) both;
}
@keyframes banner-card { from { opacity: 0; transform: translateY(18px) scale(.96); } }
.banner h3 {
  margin: 0 0 var(--sp-2);
  font-size: 38px;
  letter-spacing: 1px;
  background: linear-gradient(92deg, #fff, var(--accent) 55%, var(--accent-2));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.banner p { margin: 0 0 var(--sp-4); color: var(--ink-dim); font-size: var(--fs-lg); }
.banner > div { max-width: 560px; text-align: left; }
.banner h3, .banner p { text-align: center; }
.banner-actions {
  display: flex; gap: 10px; justify-content: center; flex-wrap: wrap;
  margin-top: var(--sp-4); padding-top: var(--sp-4);
  border-top: 1px solid rgba(255, 255, 255, .1);
}
.banner-actions .btn { padding: 11px 18px; }

.recap-stats {
  display: flex; flex-wrap: wrap; gap: var(--sp-2);
  justify-content: center; margin-bottom: var(--sp-4);
}
.recap-stats span {
  font-size: var(--fs-xs); color: var(--ink-dim);
  background: rgba(255, 255, 255, .06);
  border: 1px solid var(--line);
  padding: 4px 10px; border-radius: 999px;
}

.recap-h {
  margin: 0 0 var(--sp-2); font-size: var(--fs-xs); letter-spacing: 2px;
  text-transform: uppercase; color: var(--ink-faint); font-weight: 700; text-align: center;
}
.recap-list { list-style: none; margin: 0 0 var(--sp-4); padding: 0; display: flex; flex-direction: column; gap: 6px; }
.recap-list li {
  display: grid; grid-template-columns: 24px 34px 1fr; align-items: baseline; gap: var(--sp-2);
  font-size: var(--fs-sm); background: rgba(255, 255, 255, .04);
  border-left: 3px solid var(--accent); border-radius: var(--r-sm); padding: var(--sp-2) var(--sp-3);
}
.rm-icon { font-size: 16px; }
.rm-ply { color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.rm-text { color: var(--ink); }

.recap-series {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-3);
  flex-wrap: wrap; padding: var(--sp-3); margin-bottom: var(--sp-4);
  border-radius: var(--radius); background: rgba(169, 112, 255, .12);
  border: 1px solid var(--line-hot); font-size: var(--fs-lg);
  font-variant-numeric: tabular-nums;
}
.recap-series i { color: var(--ink-faint); font-style: normal; }
.rs-draw, .rs-games { font-size: var(--fs-xs); color: var(--ink-faint); }
.banner #bannerNew { display: block; margin: 0 auto; }

/* ---------------- theme ambience ----------------
   One fixed, pointer-transparent layer per pack. All of it is a couple of
   repeating gradients on a transform animation, so it costs no script and
   the compositor can do it on its own thread. */
.ambience {
  position: fixed;
  inset: -10% -10% -20%;
  pointer-events: none;
  z-index: 1;
  opacity: 0;
  transition: opacity .5s ease;
}
[data-ambience="none"] .ambience { display: none; }
[data-ambience="snow"] .ambience,
[data-ambience="rain"] .ambience,
[data-ambience="embers"] .ambience,
[data-ambience="fog"] .ambience { opacity: 1; }

[data-ambience="snow"] .ambience {
  background-image:
    radial-gradient(2.2px 2.2px at 20% 10%, rgba(255,255,255,.9), transparent),
    radial-gradient(1.8px 1.8px at 70% 30%, rgba(255,255,255,.75), transparent),
    radial-gradient(2.6px 2.6px at 40% 60%, rgba(255,255,255,.85), transparent),
    radial-gradient(1.6px 1.6px at 85% 75%, rgba(255,255,255,.7), transparent),
    radial-gradient(2px 2px at 10% 85%, rgba(255,255,255,.8), transparent);
  background-size: 340px 340px, 280px 280px, 420px 420px, 300px 300px, 380px 380px;
  animation: drift 26s linear infinite;
}
[data-ambience="embers"] .ambience {
  background-image:
    radial-gradient(2.4px 2.4px at 25% 90%, rgba(255,150,60,.9), transparent),
    radial-gradient(1.8px 1.8px at 65% 80%, rgba(255,90,30,.85), transparent),
    radial-gradient(3px 3px at 45% 95%, rgba(255,190,90,.8), transparent),
    radial-gradient(2px 2px at 85% 88%, rgba(255,120,40,.8), transparent);
  background-size: 300px 300px, 260px 260px, 380px 380px, 320px 320px;
  animation: rise 14s linear infinite;
}
[data-ambience="rain"] .ambience {
  background-image:
    repeating-linear-gradient(180deg, rgba(0,255,135,.16) 0 2px, transparent 2px 16px),
    repeating-linear-gradient(180deg, rgba(0,255,135,.10) 0 3px, transparent 3px 26px);
  background-size: 100% 220px, 100% 340px;
  animation: fall 3.4s linear infinite;
}
[data-ambience="fog"] .ambience {
  background-image:
    radial-gradient(60% 40% at 20% 80%, rgba(180, 130, 220, .16), transparent 70%),
    radial-gradient(50% 35% at 75% 65%, rgba(255, 140, 40, .12), transparent 70%);
  animation: breathe 12s ease-in-out infinite;
}
@keyframes drift   { to { background-position: 120px 340px, -90px 280px, 60px 420px, -140px 300px, 100px 380px; } }
@keyframes rise    { to { background-position: -40px -300px, 60px -260px, -80px -380px, 30px -320px; } }
@keyframes fall    { to { background-position: 0 220px, 0 340px; } }
@keyframes breathe { 50% { opacity: .55; transform: scale(1.06); } }

@media (prefers-reduced-motion: reduce) {
  .ambience { animation: none !important; }
}

/* ---------------- board effects ----------------
   Every node here is spawned by js/fx.js, animates once and removes
   itself. All of it is transform/opacity so the compositor handles it. */

/* Low ground mist that rolls across the board, in the spirit of the night
   levels in Plants vs Zombies. Two blurred bands at different speeds so it
   never reads as one repeating tile. Sits under the pieces, over the squares. */
.smoke-layer {
  position: absolute;
  inset: 10px;
  border-radius: 13px;
  overflow: hidden;
  pointer-events: none;
  z-index: 2;
  opacity: 0;
  transition: opacity .6s ease;
}
[data-smoke="on"] .smoke-layer { opacity: 1; }

.smoke-layer i {
  position: absolute;
  left: -60%;
  width: 220%;
  height: 46%;
  display: block;
  background:
    radial-gradient(closest-side at 12% 60%, rgba(255,255,255,.42), transparent 72%),
    radial-gradient(closest-side at 34% 45%, rgba(255,255,255,.34), transparent 70%),
    radial-gradient(closest-side at 56% 62%, rgba(255,255,255,.40), transparent 74%),
    radial-gradient(closest-side at 78% 48%, rgba(255,255,255,.30), transparent 70%);
  filter: blur(9px);
}
.smoke-layer i:nth-child(1) { bottom: -8%;  animation: smokeDrift 34s linear infinite; }
.smoke-layer i:nth-child(2) { bottom: 14%;  opacity: .7; transform: scaleX(-1);
                              animation: smokeDrift 52s linear infinite reverse; }
.smoke-layer i:nth-child(3) { bottom: 36%;  opacity: .45;
                              animation: smokeDrift 74s linear infinite; }
@keyframes smokeDrift { to { transform: translateX(46%); } }
.smoke-layer i:nth-child(2) { animation-name: smokeDriftFlip; }
@keyframes smokeDriftFlip { to { transform: scaleX(-1) translateX(46%); } }

@media (prefers-reduced-motion: reduce) {
  .smoke-layer i { animation: none !important; }
}

.fx-layer {
  position: absolute;
  inset: 10px;                 /* sit inside the bezel, over the board */
  pointer-events: none;
  overflow: hidden;
  border-radius: 13px;
  z-index: 26;
}
.fx { position: absolute; transform: translate(-50%, -50%); pointer-events: none; }

.board-shell.fx-shake { animation: fxShake .6s cubic-bezier(.36,.07,.19,.97); }
@keyframes fxShake {
  10%, 90% { transform: translate(calc(var(--shake) * -.3), 0); }
  20%, 80% { transform: translate(calc(var(--shake) * .5), calc(var(--shake) * -.2)); }
  30%, 50%, 70% { transform: translate(calc(var(--shake) * -1), calc(var(--shake) * .2)); }
  40%, 60% { transform: translate(var(--shake), 0); }
}

/* full-board wash */
.fx-tint {
  inset: 0; left: 0; top: 0; width: 100%; height: 100%;
  transform: none;
  animation: fxFade .7s ease-out forwards;
  mix-blend-mode: screen;
}
@keyframes fxFade { 0% { opacity: 0; } 25% { opacity: 1; } 100% { opacity: 0; } }

/* shockwave rings */
.fx-ring {
  width: calc(100% / var(--board-cols, 8)); height: calc(100% / var(--board-rows, 8));
  border-radius: 50%;
  border: 3px solid var(--ring, #ff9a4d);
  animation: fxRing .8s cubic-bezier(.2,.7,.3,1) forwards;
}
.fx-ring-2 { animation-duration: .9s; animation-delay: .1s; border-width: 2px; }
@keyframes fxRing {
  0%   { transform: translate(-50%, -50%) scale(.2); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(3.4); opacity: 0; }
}

/* the flash at the centre of a blast */
.fx-boom {
  width: calc(100% / var(--board-cols, 8)); height: calc(100% / var(--board-rows, 8));
  border-radius: 50%;
  background: radial-gradient(circle, #fff 0%, var(--boom, #ff7a2a) 40%, transparent 70%);
  animation: fxBoom .7s ease-out forwards;
}
@keyframes fxBoom {
  0%   { transform: translate(-50%, -50%) scale(.3); opacity: 1; }
  40%  { transform: translate(-50%, -50%) scale(1.9); opacity: .95; }
  100% { transform: translate(-50%, -50%) scale(2.6); opacity: 0; }
}

/* the cloud a captured piece leaves */
.fx-puff {
  width: 9%; height: 9%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(235,230,255,.85), rgba(200,190,230,.25) 60%, transparent 72%);
  filter: blur(1.5px);
  animation: fxPuff .8s ease-out forwards;
}
.fx-puff-s { width: 5.5%; height: 5.5%; animation-name: fxPuffS; }
@keyframes fxPuff {
  0%   { transform: translate(-50%, -50%) scale(.35); opacity: .95; }
  100% { transform: translate(-50%, -75%) scale(2.1); opacity: 0; }
}
@keyframes fxPuffS {
  0%   { transform: translate(-50%, -50%) scale(.4); opacity: .8; }
  100% { transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(1.5); opacity: 0; }
}

/* a portal mouth */
.fx-portal {
  width: 13%; height: 13%;
  border-radius: 50%;
  background: radial-gradient(circle, transparent 26%, rgba(199,155,255,.85) 42%, rgba(120,60,220,.5) 62%, transparent 76%);
  animation: fxPortal 1.4s ease-out forwards;
}
.fx-portal-b { animation-delay: .12s; }
@keyframes fxPortal {
  0%   { transform: translate(-50%, -50%) scale(.2) rotate(0deg); opacity: 0; }
  25%  { transform: translate(-50%, -50%) scale(1.15) rotate(120deg); opacity: 1; }
  70%  { transform: translate(-50%, -50%) scale(1) rotate(300deg); opacity: .9; }
  100% { transform: translate(-50%, -50%) scale(.85) rotate(420deg); opacity: 0; }
}

/* the whole board leaning */
.fx-gravity {
  left: 0; top: 0; width: 100%; height: 100%;
  transform: none;
  background: linear-gradient(90deg, transparent, rgba(169,112,255,.35) 60%, rgba(169,112,255,.6));
  animation: fxGrav 1.1s cubic-bezier(.4,0,.3,1) forwards;
}
.fx-gravity-l { transform: scaleX(-1); }
@keyframes fxGrav { 0% { opacity: 0; } 30% { opacity: 1; } 100% { opacity: 0; } }

/* a quarter turn */
.fx-twist {
  width: 38%; height: 38%;
  border-radius: 14%;
  border: 3px dashed rgba(255,209,102,.9);
  animation: fxTwist 1s cubic-bezier(.5,0,.3,1) forwards;
}
@keyframes fxTwist {
  0%   { transform: translate(-50%, -50%) scale(1.3) rotate(0deg); opacity: 0; }
  30%  { opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(.9) rotate(90deg); opacity: 0; }
}

/* thrown fragments */
.fx-shard {
  width: 1.6%; height: 1.6%;
  border-radius: 2px;
  animation: fxShard .9s cubic-bezier(.15,.7,.4,1) forwards;
}
@keyframes fxShard {
  0%   { transform: translate(-50%, -50%) scale(1) rotate(0deg); opacity: 1; }
  100% { transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(.3) rotate(var(--rot)); opacity: 0; }
}

/* a rock arriving from off-board */
/* a bomb lobbed across the board: the holder slides, the ball inside it arcs */
.fx-toss { width: 3.5%; height: 3.5%; animation: fxTossPath .5s ease-in forwards; }
.fx-toss::before {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  background: radial-gradient(circle at 40% 40%, #ffe0a0, #ff5a3c 55%, #5a1a00);
  box-shadow: 0 0 14px 4px rgba(255, 120, 60, .7);
  animation: fxTossArc .5s ease-out;
}
@keyframes fxTossPath { to { left: var(--x2); top: var(--y2); } }
@keyframes fxTossArc {
  0%, 100% { transform: translateY(0) scale(1); }
  50% { transform: translateY(-260%) scale(1.5); }
}
.fx-meteor {
  width: 3%; height: 3%;
  border-radius: 50%;
  background: radial-gradient(circle at 40% 40%, #fff2c4, #ff7a2a 55%, #7a2400);
  box-shadow: 0 0 26px 6px rgba(255, 140, 50, .8);
  animation: fxMeteor .45s cubic-bezier(.5,0,.9,.6) forwards;
}
.fx-meteor::after {
  content: "";
  position: absolute;
  right: 40%; top: 40%;
  width: 900%; height: 26%;
  transform-origin: right center;
  transform: rotate(-38deg);
  background: linear-gradient(90deg, transparent, rgba(255,150,60,.75));
  border-radius: 999px;
}
@keyframes fxMeteor {
  0%   { transform: translate(calc(-50% - 62vh), calc(-50% - 48vh)) scale(1.5); opacity: 0; }
  15%  { opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(.6); opacity: 1; }
}

/* eruption */
.fx-lava {
  width: calc(100% / var(--board-cols, 8)); height: calc(100% / var(--board-rows, 8));
  border-radius: 50% 50% 40% 40%;
  background: linear-gradient(0deg, #ff3b00, #ffb43b 60%, #fff3c4);
  filter: blur(1px);
  animation: fxLava 1.5s ease-out forwards;
}
@keyframes fxLava {
  0%   { transform: translate(-50%, -20%) scaleY(.1); opacity: 0; }
  25%  { transform: translate(-50%, -95%) scaleY(2.4); opacity: 1; }
  60%  { transform: translate(-50%, -70%) scaleY(1.6); opacity: .9; }
  100% { transform: translate(-50%, -40%) scaleY(.3); opacity: 0; }
}

.fx-glow {
  width: 22%; height: 22%;
  border-radius: 50%;
  background: radial-gradient(circle, var(--glow, #ffd166), transparent 68%);
  animation: fxGlow 1s ease-out forwards;
}
@keyframes fxGlow {
  0%   { transform: translate(-50%, -50%) scale(.3); opacity: 0; }
  30%  { transform: translate(-50%, -50%) scale(1.4); opacity: .95; }
  100% { transform: translate(-50%, -50%) scale(2.2); opacity: 0; }
}

/* fissures */
.fx-crack {
  width: 34%; height: 34%;
  animation: fxCrack 2.2s ease-out forwards;
}
.fx-crack svg { width: 100%; height: 100%; overflow: visible; }
.fx-crack polyline {
  fill: none;
  stroke: #1a0f08;
  stroke-width: 2.6;
  stroke-linecap: round;
  opacity: .85;
  stroke-dasharray: 160;
  stroke-dashoffset: 160;
  animation: fxDraw .5s ease-out forwards;
}
@keyframes fxDraw { to { stroke-dashoffset: 0; } }
@keyframes fxCrack { 0% { opacity: 1; } 70% { opacity: 1; } 100% { opacity: 0; } }

/* a wall of water crossing the board */
.fx-wave {
  left: 0; top: 0; width: 100%; height: 100%;
  transform: none;
  background:
    linear-gradient(180deg, rgba(255,255,255,.65) 0 3%, transparent 3%),
    linear-gradient(180deg, rgba(120, 200, 255, .0) 0%, rgba(40, 130, 220, .78) 45%, rgba(10, 70, 150, .9) 100%);
  animation: fxWave 1.7s cubic-bezier(.4,0,.5,1) forwards;
}
.fx-wave-up { animation-name: fxWaveUp; }
@keyframes fxWave   { 0% { transform: translateY(-115%); } 55% { transform: translateY(0); } 100% { transform: translateY(115%); } }
@keyframes fxWaveUp { 0% { transform: translateY(115%); } 55% { transform: translateY(0); } 100% { transform: translateY(-115%); } }

/* growth */
.fx-vine {
  width: 11%; height: 11%;
  border-radius: 60% 10% 60% 10%;
  background: radial-gradient(circle at 35% 65%, #9ee06a, #2f7d3a 70%);
  box-shadow: 0 0 14px -3px #7fd05a;
  animation: fxVine 1.6s cubic-bezier(.2,.9,.3,1) forwards;
}
@keyframes fxVine {
  0%   { transform: translate(-50%, -50%) scale(0) rotate(-40deg); opacity: 0; }
  35%  { transform: translate(-50%, -50%) scale(1.25) rotate(8deg); opacity: 1; }
  75%  { transform: translate(-50%, -50%) scale(1) rotate(0deg); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(.9); opacity: 0; }
}

/* A shard punching up through the board. Deliberately NOT called .fx-shard:
   that class belongs to the small fragments FX.debris throws, and naming this
   one the same quietly turned every explosion's confetti into glass slabs. */
.fx-spire {
  width: 13%; height: 15%;
  background: linear-gradient(160deg, rgba(234, 252, 255, .95), rgba(121, 228, 255, .75) 45%, rgba(30, 120, 190, .5));
  clip-path: polygon(50% 0%, 78% 38%, 66% 100%, 34% 100%, 22% 38%);
  box-shadow: 0 0 22px -4px #79e4ff;
  animation: fxSpire 1.4s cubic-bezier(.15, .95, .25, 1) forwards;
}
@keyframes fxSpire {
  0%   { transform: translate(-50%, -20%) scaleY(0) rotate(-6deg); opacity: 0; }
  30%  { transform: translate(-50%, -50%) scaleY(1.2) rotate(3deg); opacity: 1; }
  60%  { transform: translate(-50%, -50%) scaleY(1) rotate(0deg); opacity: .95; }
  100% { transform: translate(-50%, -50%) scaleY(1) rotate(0deg); opacity: 0; }
}
.fx-shatter {
  width: 16%; height: 16%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(234, 252, 255, .9), rgba(121, 228, 255, .35) 55%, transparent 72%);
  animation: fxShatter .7s ease-out forwards;
}
@keyframes fxShatter {
  0%   { transform: translate(-50%, -50%) scale(.4); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1.9); opacity: 0; }
}

/* ---------------- mist, pits, crowds, plates ---------------- */

/* The veil over an empty square while the mist is down. A child element rather
   than ::after, because half a dozen square states already own that. */
.sq .fog-veil {
  position: absolute;
  /* Bleeding past its own square is the whole point: the veils overlap their
     neighbours and the mask fades each one out at the rim, so a bank of fog has
     a soft edge instead of the staircase you get from squares of flat white. */
  inset: -22%;
  z-index: 2;
  pointer-events: none;
  background:
    radial-gradient(120% 100% at 30% 15%, rgba(228, 236, 248, .7), rgba(196, 208, 226, .58) 55%, rgba(176, 188, 208, .5));
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 34%, rgba(0, 0, 0, .78) 52%, transparent 70%);
  mask-image: radial-gradient(circle at 50% 50%, #000 34%, rgba(0, 0, 0, .78) 52%, transparent 70%);
  backdrop-filter: blur(3px) saturate(.6);
  animation: fogBreathe 7s ease-in-out infinite alternate;
}
.sq:nth-child(odd) .fog-veil { animation-duration: 9.5s; animation-delay: -3s; }
@keyframes fogBreathe {
  from { opacity: .78; transform: translateX(-2%) scale(1.04); }
  to   { opacity: 1; transform: translateX(2%) scale(1.08); }
}
/* wherever the player is allowed to go stays legible through it */
.sq.move .hint-dot, .sq.grab .hint-dot { z-index: 3; }
@media (prefers-reduced-motion: reduce) { .sq .fog-veil { animation: none; } }

/* a piece that has changed sides keeps the paint it changed into */
/* ---------------- goofy eyes ----------------
   Deliberately badly drawn: one eye larger than the other, the pair set at a
   slight angle that never changes for a given piece, and pupils that move with
   whatever is happening to it. */
.googly {
  position: absolute;
  /* Where a face goes depends entirely on what it is stuck to, so every piece
     names its own spot. The numbers below are read straight off the sprites:
     the drawing is a 45x45 viewBox filling the whole square, so a coordinate of
     17.4 is 39% across. */
  left: var(--eye-x, 50%);
  top: var(--eye-y, 26%);
  transform: translate(-50%, -50%) rotate(var(--wonk, 0deg));
  display: flex;
  align-items: center;
  gap: var(--eye-gap, 6%);
  width: var(--eye-w, 62%);
  height: var(--eye-h, 30%);
  pointer-events: none;
  z-index: 4;
}

/* Big and daft is the whole point, so these stay close to the old size and only
   move: what changes per piece is where the pair sits, not how small it is. */
/* a small round head, so they sit high and hang over the edges of it */
.piece[data-type="p"] { --eye-x: 50%; --eye-y: 28%; --eye-w: 56%; --eye-h: 28%; }
/* under the crown, above the collar */
.piece[data-type="k"] { --eye-x: 50%; --eye-y: 44%; --eye-w: 58%; --eye-h: 30%; }
/* below the coronet, on the gown, set wide */
.piece[data-type="q"] { --eye-x: 50%; --eye-y: 56%; --eye-w: 60%; --eye-h: 30%; }
/* a tower has no head: they go on the stonework, far apart, faintly appalled */
.piece[data-type="r"] { --eye-x: 50%; --eye-y: 54%; --eye-w: 60%; --eye-h: 28%; --eye-gap: 10%; }
/* the mitre is narrow, and the cut across it is the mouth */
.piece[data-type="b"] { --eye-x: 50%; --eye-y: 38%; --eye-w: 54%; --eye-h: 28%; }
/* the horse already has an eye drawn on: the bigger googly lands exactly on it
   (sprite circle at 17.4, 17.8 => 38.7% across, 39.6% down; the pair is centred
   at eye-x, and the big one sits 0.27 of the width to the left of that) */
.piece[data-type="n"] { --eye-x: 55%; --eye-y: 40%; --eye-w: 60%; --eye-h: 30%; --eye-gap: 4%; }
.piece[data-type="c"] { --eye-x: 54%; --eye-y: 39%; --eye-w: 60%; --eye-h: 30%; --eye-gap: 4%; }
/* the siege engine wears them on the frame, where the arm pivots
   (sprite eye at 22.5, 15.4 => 50% across, 34% down) */
.piece[data-type="m"] { --eye-x: 50%; --eye-y: 34%; --eye-w: 58%; --eye-h: 28%; --eye-gap: 8%; }
/* a ship's face is its gunports, low on the hull, not floating above the masts
   (sprite ports at cy 34.4 => 76% down) */
.piece[data-type="v"] { --eye-x: 50%; --eye-y: 74%; --eye-w: 54%; --eye-h: 21%; --eye-gap: 10%; }
/* the chariot's face is its wheel */
.piece[data-type="t"] { --eye-x: 47%; --eye-y: 66%; --eye-w: 50%; --eye-h: 26%; }
/* the hero has a very small head and a very large cape */
.piece[data-type="h"] { --eye-x: 50%; --eye-y: 24%; --eye-w: 44%; --eye-h: 24%; }
/* a sweet is nearly all face */
.piece[data-type="y"] { --eye-x: 50%; --eye-y: 50%; --eye-w: 56%; --eye-h: 30%; }
/* and what it grows into is rounder still */
.piece[data-type="j"] { --eye-x: 50%; --eye-y: 46%; --eye-w: 62%; --eye-h: 32%; }
.piece[data-type="s"] { --eye-x: 50%; --eye-y: 34%; --eye-w: 56%; --eye-h: 30%; }
/* the gobstopper has one drawn on too, at 17.6, 17.6 */
.piece[data-type="f"] { --eye-x: 55%; --eye-y: 39%; --eye-w: 60%; --eye-h: 30%; }
.googly i {
  position: relative;
  display: block;
  background: #fdfdff;
  border: 1.5px solid rgba(20, 12, 34, .85);
  border-radius: 50%;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .45);
}
.googly i:first-child { width: 46%; height: 82%; }
.googly i:last-child  { width: 36%; height: 66%; margin-top: var(--lean, 0%); }
.googly i::after {
  content: "";
  position: absolute;
  left: 52%;
  top: 58%;
  width: 46%;
  height: 46%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: #17102a;
  transition: transform .18s, background .18s, width .18s, height .18s;
}

/* frightened: pupils shrink and climb */
.googly.look-fear i::after { width: 30%; height: 30%; top: 34%; }
.googly.look-fear i { animation: eyeShake .28s linear infinite; }
/* miserable: half-lidded and looking down */
.googly.look-despair i::after { top: 76%; height: 34%; }
.googly.look-despair i { height: 46%; }
/* furious: pupils narrow into slits */
.googly.look-hate i::after { width: 62%; height: 22%; border-radius: 40%; background: #3a0d18; }
/* smitten: hearts, obviously */
.googly.look-love i::after {
  background: #ff4d9d;
  width: 54%; height: 54%;
  border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
  transform: translate(-50%, -50%) rotate(45deg);
}
/* On the move. A still eye on a sliding piece looks painted on, so the pair
   swings on its mounting and the pupils lag behind. Hearts are left alone -
   they are drawn by rotating the pupil, and the swing would flatten them. */
.piece.moving .googly { animation: eyeJiggle .34s ease-in-out; }
@keyframes eyeJiggle {
  0%   { transform: translate(-50%, -50%) rotate(var(--wonk, 0deg)); }
  30%  { transform: translate(-58%, -43%) rotate(calc(var(--wonk, 0deg) - 10deg)); }
  62%  { transform: translate(-43%, -57%) rotate(calc(var(--wonk, 0deg) + 8deg)); }
  100% { transform: translate(-50%, -50%) rotate(var(--wonk, 0deg)); }
}
.piece.moving .googly:not(.look-love) i::after { animation: eyeSwing .34s ease-in-out; }
@keyframes eyeSwing {
  0%   { transform: translate(-50%, -50%); }
  32%  { transform: translate(-12%, -38%); }
  68%  { transform: translate(-86%, -62%); }
  100% { transform: translate(-50%, -50%); }
}

/* pleased with itself: half-lidded from the top, pupils high and steady */
.googly.look-smug i { height: 52%; }
.googly.look-smug i::after { top: 32%; width: 44%; height: 44%; }
/* nothing happening: pupils drift wide apart and slowly */
.googly.look-bored i::after { animation: eyeWander 9s ease-in-out infinite; width: 42%; height: 42%; }
/* cornered: tiny pupils, hard shake, whites showing */
.googly.look-panic i::after { width: 22%; height: 22%; top: 30%; background: #1a0a14; }
.googly.look-panic i { animation: eyeShake .13s linear infinite; }
/* winning: pupils blown wide and bright */
.googly.look-triumph i::after { width: 66%; height: 66%; background: #2a1840; }
.googly.look-triumph i { animation: eyePop 1.1s ease-in-out infinite; }
@keyframes eyePop {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.14); }
}
/* caught out: looking anywhere but at you */
.googly.look-guilt i::after { top: 70%; left: 74%; width: 38%; height: 38%; }
.googly.look-guilt i { height: 58%; }

/* idle: a slow wander, so they never look dead *//* idle: a slow wander, so they never look dead */
.googly.look-idle i::after,
.googly.look-greet i::after { animation: eyeWander 5.5s ease-in-out infinite; }
@keyframes eyeWander {
  0%, 100% { transform: translate(-64%, -50%); }
  50%      { transform: translate(-36%, -44%); }
}
@keyframes eyeShake {
  0%, 100% { transform: translateX(-4%); }
  50%      { transform: translateX(4%); }
}
@media (prefers-reduced-motion: reduce) {
  .googly, .googly i, .googly i::after,
  .piece.moving .googly, .piece.moving .googly:not(.look-love) i::after { animation: none; }
}

/* ---------------- ice and fire ----------------
   A property of the piece, not a type: an ice king is still drawn as a king and
   still mates like one. Only the surface changes, so the silhouette a viewer
   reads the board by is left exactly as it was. */
.piece.el-ice .pc {
  filter: drop-shadow(0 0 7px rgba(143, 228, 255, .85)) drop-shadow(0 3px 3px rgba(0, 0, 0, .5));
}
.piece.el-ice .pc-mane, .piece.el-ice .pc-cut { stroke: #cdf3ff; }
.piece.el-ice::before {
  content: "";
  position: absolute;
  inset: 6%;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 42%, rgba(143, 228, 255, .28), transparent 68%);
  pointer-events: none;
  z-index: 1;
  animation: elBreathe 3.4s ease-in-out infinite;
}
.piece.el-fire .pc {
  filter: drop-shadow(0 0 8px rgba(255, 138, 76, .9)) drop-shadow(0 3px 3px rgba(0, 0, 0, .5));
}
.piece.el-fire .pc-mane, .piece.el-fire .pc-cut { stroke: #ffd9a8; }
.piece.el-fire::before {
  content: "";
  position: absolute;
  inset: 4%;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 62%, rgba(255, 120, 40, .34), transparent 66%);
  pointer-events: none;
  z-index: 1;
  animation: elBreathe 1.6s ease-in-out infinite;
}
@keyframes elBreathe {
  0%, 100% { opacity: .55; transform: scale(.94); }
  50%      { opacity: 1; transform: scale(1.06); }
}
@media (prefers-reduced-motion: reduce) {
  .piece.el-ice::before, .piece.el-fire::before { animation: none; }
}

/* a square a boulder can be dropped on: reachable, but not by walking */
.sq.lob::after {
  content: "";
  position: absolute;
  left: 50%; top: 50%;
  width: 42%; height: 42%;
  margin: -21% 0 0 -21%;
  border-radius: 50%;
  border: 2px dashed rgba(200, 162, 122, .95);
  box-shadow: 0 0 12px rgba(200, 162, 122, .5) inset;
  pointer-events: none;
  animation: lobSpin 7s linear infinite;
}
@keyframes lobSpin { to { transform: rotate(-360deg); } }
@media (prefers-reduced-motion: reduce) { .sq.lob::after { animation: none; } }

/* Nobody's piece any more, and it shows: its own colours, drained, circling. */
.piece.thrall .pc { opacity: .9; filter: saturate(.6) drop-shadow(0 0 8px rgba(255, 77, 109, .55)); }
.piece.thrall::after {
  content: "";
  position: absolute;
  inset: 4%;
  border-radius: 50%;
  border: 2px dotted rgba(255, 77, 109, .75);
  animation: defectSpin 4s linear infinite reverse;
}
@media (prefers-reduced-motion: reduce) { .piece.thrall::after { animation: none; } }

.piece.defected .pc { fill: var(--defect, #ff7bd5); }
.piece.defected .glyph { color: var(--defect, #ff7bd5); }
/* the king keeps its own half: left is the side it belongs to, right is the
   faction that came over to it */
.piece.defected .pc-half {
  position: absolute;
  inset: 0;
  clip-path: inset(0 0 0 50%);
  pointer-events: none;
}
.piece.defected.w .pc-half .pc { fill: var(--piece-w, #fbf8ff); stroke: var(--piece-w-line, #2a2142); }
.piece.defected.b .pc-half .pc { fill: var(--piece-b, #1d1630); stroke: var(--piece-b-line, #d5cbf2); }
.piece.defected::after {
  content: "";
  position: absolute;
  inset: 6%;
  border-radius: 50%;
  border: 2px dashed var(--defect, #ff7bd5);
  opacity: .55;
  animation: defectSpin 6s linear infinite;
}
@keyframes defectSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .piece.defected::before { animation: none; } }

/* fog rolling across the whole board */
.fx-mist {
  left: 0; top: 0; width: 100%; height: 100%;
  transform: none;
  background:
    radial-gradient(60% 40% at 20% 30%, rgba(235, 242, 252, .55), transparent 70%),
    radial-gradient(50% 45% at 75% 60%, rgba(220, 230, 245, .5), transparent 72%),
    linear-gradient(180deg, rgba(225, 235, 248, .3), rgba(225, 235, 248, .12));
  animation: fxMist 2.6s ease-out forwards;
}
@keyframes fxMist {
  0%   { opacity: 0; transform: translateX(-30%) scale(1.2); }
  35%  { opacity: 1; transform: translateX(0) scale(1); }
  100% { opacity: 0; transform: translateX(18%) scale(1.05); }
}

/* somebody saying something, where they are standing */
.speech {
  position: absolute;
  z-index: 30;
  transform: translate(-50%, -118%);
  max-width: min(240px, 46%);
  width: max-content;
  padding: 7px 11px;
  border-radius: 14px;
  /* Semi-transparent, with a blur behind it: a solid panel over the board hides
     the thing it is talking about, and a transparent one with no blur is
     unreadable over a chequerboard. */
  --bubble-bg: rgba(16, 11, 28, .72);
  background: var(--bubble-bg);
  backdrop-filter: blur(7px) saturate(1.15);
  border: 1px solid currentColor;
  box-shadow: 0 10px 28px -10px rgba(0, 0, 0, .8), 0 0 18px -8px currentColor;
  font-size: clamp(11px, calc(var(--board-size, 640px) / 44), 17px);
  line-height: 1.35;
  color: var(--ink);
  pointer-events: none;
  animation: speechIn .28s cubic-bezier(.2, 1.3, .4, 1) both;
}
.speech .sp-who { margin-right: 6px; }
/* a piece talking is smaller and quieter than a companion talking */
/* Last words. Quieter and slower than the ordinary chatter, and they hang
   around a beat longer so they are actually readable. */
.speech.last-words {
  font-style: italic;
  opacity: .96;
  letter-spacing: .01em;
  animation-duration: 3.4s;
}

.speech.piece-say {
  max-width: min(180px, 40%);
  padding: 4px 9px;
  border-radius: 11px;
  font-size: clamp(10px, calc(var(--board-size, 640px) / 52), 15px);
  font-style: italic;
  --bubble-bg: rgba(14, 10, 24, .68);
  background: var(--bubble-bg);
}
.speech::after {
  content: "";
  position: absolute;
  /* --tail is set when a bubble has been nudged off the edge of the board, so
     the point still lands over whoever is talking */
  left: var(--tail, 50%);
  bottom: -7px;
  margin-left: -6px;
  border: 6px solid transparent;
  border-top-color: var(--bubble-bg, rgba(16, 11, 28, .78));
}
.speech.below { transform: translate(-50%, 18%); }
.speech.below::after {
  bottom: auto; top: -7px;
  border-top-color: transparent;
  border-bottom-color: var(--bubble-bg, rgba(16, 11, 28, .78));
}
.speech.leaving { animation: speechOut .5s ease forwards; }
@keyframes speechIn { from { opacity: 0; transform: translate(-50%, -90%) scale(.85); } }
@keyframes speechOut { to { opacity: 0; transform: translate(-50%, -150%) scale(.95); } }
@media (prefers-reduced-motion: reduce) {
  .speech { animation: none; }
  .speech.leaving { animation: none; opacity: 0; }
}

.zoom-badge {
  position: absolute;
  right: 12px;
  top: 12px;
  z-index: 12;
  padding: 3px 8px;
  border-radius: 999px;
  font-size: var(--fs-xs);
  font-weight: 800;
  letter-spacing: .5px;
  color: var(--ink);
  background: rgba(12, 8, 22, .78);
  border: 1px solid var(--line-hot);
  cursor: pointer;
  opacity: 0;
}
.zoom-badge.fade { animation: zoomBadge 1.6s ease forwards; }
.zoom-badge.off.fade { animation: zoomBadge .9s ease forwards; }
@keyframes zoomBadge { 0% { opacity: 0; } 15% { opacity: 1; } 70% { opacity: 1; } 100% { opacity: 0; } }

/* the close-up: attention drawn to the two squares that matter */
.fx-focus {
  width: 15%; height: 15%;
  border-radius: 14%;
  border: 3px solid rgba(255, 255, 255, .9);
  box-shadow: 0 0 22px -2px rgba(255, 255, 255, .8), inset 0 0 18px -6px rgba(255, 255, 255, .7);
  animation: fxFocus 1.1s cubic-bezier(.2, .9, .3, 1) forwards;
}
.fx-focus-b { animation-delay: .1s; }
@keyframes fxFocus {
  0%   { transform: translate(-50%, -50%) scale(2.4); opacity: 0; }
  30%  { transform: translate(-50%, -50%) scale(1); opacity: 1; }
  70%  { transform: translate(-50%, -50%) scale(1.06); opacity: .85; }
  100% { transform: translate(-50%, -50%) scale(1.2); opacity: 0; }
}

/* an aircraft nobody ordered */
.fx-plane {
  left: -30%;
  width: 22%; height: 6%;
  transform: translate(0, -50%);
  background: linear-gradient(90deg, transparent, rgba(255, 220, 190, .35) 40%, rgba(255, 255, 255, .8));
  clip-path: polygon(0 45%, 74% 40%, 100% 50%, 74% 60%, 0 55%);
  filter: drop-shadow(0 0 10px rgba(255, 160, 120, .9));
  animation: fxPlane 1.5s linear forwards;
}
@keyframes fxPlane {
  0%   { transform: translate(0, -50%); opacity: 0; }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { transform: translate(700%, -50%); opacity: 0; }
}

/* the half second before one lands */
.fx-whistle {
  width: 4%; height: 34%;
  background: linear-gradient(180deg, transparent, rgba(255, 200, 150, .9));
  filter: blur(1px);
  animation: fxWhistle .46s ease-in forwards;
}
@keyframes fxWhistle {
  0%   { transform: translate(-50%, -320%) scaleY(.4); opacity: 0; }
  40%  { opacity: 1; }
  100% { transform: translate(-50%, -50%) scaleY(1); opacity: .9; }
}

/* something unrepeatable, in somebody's face */
.fx-cuss {
  width: auto; height: auto;
  font-weight: 900;
  font-size: clamp(12px, calc(var(--board-size, 640px) / 26), 26px);
  letter-spacing: 1px;
  color: #ffce4d;
  text-shadow: 0 2px 0 #7a2d00, 0 0 14px rgba(255, 138, 76, .9);
  animation: fxCuss 1.4s cubic-bezier(.2, .9, .3, 1) forwards;
}
@keyframes fxCuss {
  0%   { transform: translate(-50%, -50%) scale(.4) rotate(0deg); opacity: 0; }
  25%  { transform: translate(-50%, -95%) scale(1.15) rotate(var(--rot, 0deg)); opacity: 1; }
  100% { transform: translate(calc(-50% + var(--dx, 0px)), -230%) scale(.9) rotate(var(--rot, 0deg)); opacity: 0; }
}

/* a heart that is not doing well */
.fx-broken {
  width: auto; height: auto;
  line-height: 1;
  font-size: clamp(14px, calc(var(--board-size, 640px) / 22), 30px);
  animation: fxBroken 1.6s ease-out forwards;
}
.fx-broken::before { content: "\1F494"; }
@keyframes fxBroken {
  0%   { transform: translate(-50%, -50%) scale(.5); opacity: 0; }
  30%  { transform: translate(-50%, -80%) scale(1.15); opacity: 1; }
  100% { transform: translate(-50%, -190%) scale(.9); opacity: 0; }
}

/* love, in the general direction of somebody */
.fx-heart {
  width: auto; height: auto;
  line-height: 1;
  color: #ff7bd5;
  text-shadow: 0 0 12px rgba(255, 123, 213, .9);
  animation: fxHeart 1.5s cubic-bezier(.2, .8, .4, 1) forwards;
}
.fx-heart::before { content: "\2764"; }
@keyframes fxHeart {
  0%   { transform: translate(-50%, -50%) scale(.4) rotate(0deg); opacity: 0; }
  25%  { transform: translate(-50%, -80%) scale(1.1) rotate(var(--rot, 0deg)); opacity: 1; }
  100% { transform: translate(calc(-50% + var(--dx, 0px)), -260%) scale(.7) rotate(var(--rot, 0deg)); opacity: 0; }
}

/* a curse landing on somebody */
.fx-hex {
  width: 16%; height: 16%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(176, 107, 255, .85), rgba(90, 30, 160, .5) 55%, transparent 74%);
  box-shadow: 0 0 26px -4px #b06bff;
  animation: fxHexSpin 1.4s cubic-bezier(.2, .9, .3, 1) forwards;
}
@keyframes fxHexSpin {
  0%   { transform: translate(-50%, -50%) scale(.2) rotate(0deg); opacity: 0; }
  35%  { transform: translate(-50%, -50%) scale(1.15) rotate(200deg); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(.9) rotate(420deg); opacity: 0; }
}

/* everybody within earshot */
.fx-rally {
  width: 62%; height: 62%;
  border-radius: 50%;
  border: 3px solid rgba(255, 209, 102, .8);
  animation: fxRally 1.4s cubic-bezier(.15, .9, .3, 1) forwards;
}
@keyframes fxRally {
  0%   { transform: translate(-50%, -50%) scale(.1); opacity: 0; }
  25%  { opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1.6); opacity: 0; }
}

/* the ground opening to let something out */
.fx-rise {
  width: 14%; height: 14%;
  border-radius: 50% 50% 40% 40%;
  background: radial-gradient(circle at 50% 80%, #3d5c28, rgba(20,30,12,.85) 70%, transparent 78%);
  animation: fxRise 1.2s cubic-bezier(.2, 1, .3, 1) forwards;
}
@keyframes fxRise {
  0%   { transform: translate(-50%, 40%) scaleY(.1); opacity: 0; }
  35%  { transform: translate(-50%, -50%) scaleY(1.15); opacity: 1; }
  100% { transform: translate(-50%, -50%) scaleY(1); opacity: 0; }
}

/* one piece of a scattered burst */
.fx-spark {
  width: 5%; height: 5%;
  border-radius: 50%;
  background: radial-gradient(circle, #fff 20%, #ffb066 55%, transparent 72%);
  box-shadow: 0 0 14px -2px #ffb066;
  animation: fxSpark .52s ease-out forwards;
}
@keyframes fxSpark {
  0%   { transform: translate(-50%, -50%) scale(.2); opacity: 0; }
  30%  { transform: translate(-50%, -50%) scale(1.5); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(.8); opacity: 0; }
}

/* ---------------- open water ----------------
   A square you can wade into. It is not a piece and never was: the square
   stays empty so everything crosses it for free, and all of this is the
   board saying "that is water" loudly enough that nobody walks in by
   accident. Two waves at different speeds, because one wave reads as a
   pattern and two read as a surface. */
.sq.wet {
  background:
    linear-gradient(180deg, rgba(58, 148, 214, .34), rgba(24, 86, 150, .62)),
    var(--sq-dark, #4a3f7d);
  box-shadow: inset 0 0 0 1px rgba(150, 220, 255, .28), inset 0 8px 18px rgba(0, 20, 45, .5);
  overflow: hidden;
}
.sq.wet.light { background:
    linear-gradient(180deg, rgba(96, 180, 236, .32), rgba(38, 110, 180, .58)),
    var(--sq-light, #cdc7ee); }
.sq.wet .wave {
  position: absolute;
  left: -50%;
  width: 200%;
  height: 34%;
  bottom: 18%;
  pointer-events: none;
  background:
    radial-gradient(closest-side, rgba(196, 236, 255, .55), transparent 72%) 0 50% / 34% 100% repeat-x;
  animation: waveRun 5.5s linear infinite;
  opacity: .8;
}
.sq.wet .wave.two {
  bottom: 44%;
  height: 26%;
  opacity: .45;
  animation-duration: 8.5s;
  animation-direction: reverse;
}
@keyframes waveRun { to { transform: translateX(34%); } }
@media (prefers-reduced-motion: reduce) { .sq.wet .wave { animation: none; } }

/* deep water: the terrain kind, which nothing crosses. Same palette so the
   two read as the same sea, but solid and lit from above rather than open. */
.piece.n[data-type="xr"] .pc {
  fill: #3f8fd0;
  stroke: #113a63;
  animation: deepSwell 4.4s ease-in-out infinite;
  transform-origin: 50% 60%;
}
.piece.n[data-type="xr"] .pc-mane { stroke: #bfe8ff; opacity: .8; }
.piece.n[data-type="xr"] { filter: drop-shadow(0 0 10px rgba(70, 160, 230, .45)); }
@keyframes deepSwell {
  0%, 100% { transform: translateY(2%) scaleY(.97); }
  50%      { transform: translateY(-2%) scaleY(1.03); }
}
@media (prefers-reduced-motion: reduce) { .piece.n[data-type="xr"] .pc { animation: none; } }

/* ---------------- the coin ----------------
   Two faces back to back on one spinning disc. Which way up it comes down is
   decided in script and set as a class, so what you watch land is what
   actually happened rather than a coincidence of timing. */
.coin-toss {
  position: absolute;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  align-content: center;
  gap: 14px;
  pointer-events: none;
  background: radial-gradient(closest-side, rgba(8, 6, 18, .58), rgba(8, 6, 18, 0) 78%);
  animation: coinIn .22s ease-out;
}
.coin-toss.leaving { animation: coinOut .4s ease-in forwards; }
@keyframes coinIn  { from { opacity: 0; } to { opacity: 1; } }
@keyframes coinOut { to { opacity: 0; transform: scale(1.06); } }

.coin-toss .coin {
  position: relative;
  width: clamp(74px, 22%, 132px);
  aspect-ratio: 1;
  transform-style: preserve-3d;
  animation: coinSpin 1.5s cubic-bezier(.15, .55, .25, 1) forwards;
  filter: drop-shadow(0 10px 22px rgba(0, 0, 0, .6));
}
/* tails is the same spin plus a half turn, so it settles the other way up */
.coin-toss.tails .coin { animation-name: coinSpinTails; }

.coin-toss .coin-face {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  border-radius: 50%;
  backface-visibility: hidden;
  font-size: clamp(38px, 12vh, 68px);
  line-height: 1;
  box-shadow: inset 0 0 0 3px rgba(0, 0, 0, .25), inset 0 -6px 12px rgba(0, 0, 0, .3);
}
.coin-toss .coin-heads {
  background: linear-gradient(150deg, #fff6d8, #e8c86a 45%, #a8842c);
  color: #4a3708;
}
.coin-toss .coin-tails {
  background: linear-gradient(150deg, #cfd6e6, #7d879e 45%, #3b4256);
  color: #10131c;
  transform: rotateX(180deg);
}

@keyframes coinSpin {
  0%   { transform: translateY(0) rotateX(0deg) scale(.7); }
  35%  { transform: translateY(-42%) rotateX(1080deg) scale(1.08); }
  100% { transform: translateY(0) rotateX(2160deg) scale(1); }
}
@keyframes coinSpinTails {
  0%   { transform: translateY(0) rotateX(0deg) scale(.7); }
  35%  { transform: translateY(-42%) rotateX(1080deg) scale(1.08); }
  100% { transform: translateY(0) rotateX(2340deg) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .coin-toss .coin { animation-duration: .01s; }
}

.coin-toss .coin-say {
  font-weight: 800;
  letter-spacing: .3px;
  font-size: clamp(13px, 3.2vh, 19px);
  color: var(--ink, #efeaff);
  text-shadow: 0 2px 10px rgba(0, 0, 0, .8);
  opacity: 0;
  transform: translateY(6px);
}
.coin-toss .coin-say.in {
  opacity: 1;
  transform: none;
  transition: opacity .25s ease-out, transform .25s cubic-bezier(.2, 1.1, .3, 1);
}

/* water coming back in over the edge of the world */
.fx-tide {
  width: calc(100% / var(--board-cols, 8));
  height: calc(100% / var(--board-rows, 8));
  border-radius: 3px;
  background:
    linear-gradient(0deg, rgba(120, 200, 255, .9), rgba(46, 120, 190, .55) 65%, transparent);
  box-shadow: inset 0 0 0 1px rgba(180, 232, 255, .7), 0 0 16px rgba(90, 180, 240, .5);
  animation: fxTide 1s cubic-bezier(.3, .8, .3, 1) forwards;
}
@keyframes fxTide {
  0%   { transform: translate(-50%, -50%) scale(1.25); opacity: 0; }
  35%  { transform: translate(-50%, -50%) scale(1); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(.9); opacity: 0; }
}

/* ground that was not there a moment ago, rolling out */
.fx-newland {
  width: calc(100% / var(--board-cols, 8));
  height: calc(100% / var(--board-rows, 8));
  border-radius: 3px;
  background:
    linear-gradient(180deg, rgba(168, 232, 176, .85), rgba(74, 160, 106, .5) 70%, transparent);
  box-shadow: inset 0 0 0 1px rgba(190, 255, 205, .75), 0 0 18px rgba(120, 220, 150, .45);
  animation: fxNewLand .9s cubic-bezier(.2, .9, .3, 1) forwards;
}
@keyframes fxNewLand {
  0%   { transform: translate(-50%, -50%) scale(.2); opacity: 0; }
  30%  { transform: translate(-50%, -50%) scale(1.06); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1); opacity: 0; }
}

/* a block of wall arriving */
.fx-block {
  width: calc(100% / var(--board-cols, 8) * 1.04);
  height: calc(100% / var(--board-rows, 8) * 1.04);
  background: linear-gradient(180deg, #d8cfbc, #9c917c 60%, #6b6154);
  border-radius: 2px;
  box-shadow: 0 6px 14px rgba(0, 0, 0, .55);
  animation: fxBlock 1.1s cubic-bezier(.2, 1.1, .3, 1) forwards;
}
@keyframes fxBlock {
  0%   { transform: translate(-50%, -260%) scale(1.1); opacity: 0; }
  35%  { transform: translate(-50%, -50%) scale(1); opacity: 1; }
  70%  { transform: translate(-50%, -46%) scale(1); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1); opacity: 0; }
}

/* an argument, settled */
.fx-boot {
  width: 16%; height: 16%;
  background: linear-gradient(180deg, #8a6a45, #4b382a);
  clip-path: polygon(28% 0, 72% 0, 72% 62%, 100% 62%, 100% 100%, 28% 100%);
  animation: fxBoot .9s cubic-bezier(.6, 0, .3, 1) forwards;
}
@keyframes fxBoot {
  0%   { transform: translate(-190%, -50%) rotate(-30deg); opacity: 0; }
  30%  { transform: translate(-50%, -50%) rotate(0deg); opacity: 1; }
  55%  { transform: translate(-20%, -50%) rotate(10deg); opacity: 1; }
  100% { transform: translate(60%, -50%) rotate(20deg); opacity: 0; }
}

/* the floor giving way */
.fx-hole {
  width: 13%; height: 13%;
  border-radius: 50%;
  background: radial-gradient(circle, #000 30%, rgba(40, 26, 14, .9) 60%, transparent 78%);
  box-shadow: inset 0 -6px 10px rgba(0, 0, 0, .9);
  animation: fxHole 1.2s cubic-bezier(.2, .9, .3, 1) forwards;
}
@keyframes fxHole {
  0%   { transform: translate(-50%, -50%) scale(.1); opacity: 0; }
  40%  { transform: translate(-50%, -50%) scale(1.15); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1); opacity: 0; }
}

/* a crowd turning out, and then the blade */
/* The block itself: timber and a frame, up for as long as the condemned has. */
.fx-scaffold {
  width: 26%; height: 30%;
  background:
    linear-gradient(90deg, transparent 16%, #6b4a2a 16%, #6b4a2a 26%, transparent 26%),
    linear-gradient(90deg, transparent 74%, #6b4a2a 74%, #6b4a2a 84%, transparent 84%),
    linear-gradient(180deg, transparent 8%, #7d5730 8%, #7d5730 16%, transparent 16%),
    linear-gradient(180deg, transparent 86%, #4a3018 86%, #4a3018 100%, transparent 100%);
  filter: drop-shadow(0 4px 10px rgba(0, 0, 0, .7));
  animation: fxScaffold .5s cubic-bezier(.2, 1.4, .4, 1) both;
}
@keyframes fxScaffold {
  from { transform: translate(-50%, 10%) scaleY(0); opacity: 0; }
  to   { transform: translate(-50%, -30%) scaleY(1); opacity: 1; }
}

/* somebody in the crowd shouting something unhelpful */
.fx-jeer {
  width: 5%; height: 5%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255,209,102,.95), rgba(255,209,102,0) 70%);
  animation: fxJeer 1.1s ease-out forwards;
}
@keyframes fxJeer {
  0%   { transform: translate(-50%, -50%) scale(.3); opacity: 0; }
  25%  { opacity: .95; }
  100% { transform: translate(calc(-50% + var(--dx, 0px)), calc(-50% + var(--dy, -40px))) scale(1.1); opacity: 0; }
}

.fx-crowd {
  width: 30%; height: 12%;
  background:
    radial-gradient(circle at 10% 100%, #2b2140 42%, transparent 44%),
    radial-gradient(circle at 30% 100%, #3a2c55 42%, transparent 44%),
    radial-gradient(circle at 50% 100%, #241b38 42%, transparent 44%),
    radial-gradient(circle at 70% 100%, #3a2c55 42%, transparent 44%),
    radial-gradient(circle at 90% 100%, #2b2140 42%, transparent 44%);
  animation: fxCrowd 1.6s ease-out forwards;
}
@keyframes fxCrowd {
  0%   { transform: translate(-50%, 60%) scaleY(0); opacity: 0; }
  25%  { transform: translate(-50%, 40%) scaleY(1); opacity: 1; }
  80%  { transform: translate(-50%, 36%) scaleY(1); opacity: 1; }
  100% { transform: translate(-50%, 40%) scaleY(1); opacity: 0; }
}
.fx-blade {
  width: 15%; height: 34%;
  background: linear-gradient(180deg, #d9dde8, #8e97ad 60%, #5b6376);
  clip-path: polygon(0 0, 100% 0, 100% 62%, 0 100%);
  box-shadow: 0 6px 18px rgba(0, 0, 0, .6);
  animation: fxBlade .8s cubic-bezier(.7, 0, .9, .4) forwards;
}
@keyframes fxBlade {
  0%   { transform: translate(-50%, -190%); opacity: 1; }
  45%  { transform: translate(-50%, -50%); opacity: 1; }
  60%  { transform: translate(-50%, -44%); }
  100% { transform: translate(-50%, -46%); opacity: 0; }
}

/* steam off something too hot to drink */
.fx-steam {
  width: 3.5%; height: 3.5%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 250, 240, .85), transparent 70%);
  animation: fxSteam 2s ease-out forwards;
}
@keyframes fxSteam {
  0%   { transform: translate(-50%, -20%) scale(.5); opacity: 0; }
  25%  { opacity: .9; }
  100% { transform: translate(calc(-50% + var(--dx, 0px)), -220%) scale(2.4); opacity: 0; }
}

/* a flag going up on a square that has changed its mind */
.fx-flag {
  width: 9%; height: 22%;
  background:
    linear-gradient(90deg, #6b5a44 0 16%, transparent 16%),
    linear-gradient(180deg, #ff5a5a 0 46%, transparent 46%);
  transform-origin: 10% 100%;
  animation: fxFlag 1.5s cubic-bezier(.2, .9, .3, 1) forwards;
}
@keyframes fxFlag {
  0%   { transform: translate(-50%, -20%) rotate(-70deg); opacity: 0; }
  35%  { transform: translate(-50%, -60%) rotate(6deg); opacity: 1; }
  70%  { transform: translate(-50%, -60%) rotate(-6deg); opacity: 1; }
  100% { transform: translate(-50%, -60%) rotate(0deg); opacity: 0; }
}

/* iron coming up around somebody */
.fx-cage {
  width: 15%; height: 15%;
  background:
    repeating-linear-gradient(90deg, #aeb6c6 0 12%, transparent 12% 26%),
    linear-gradient(180deg, rgba(120,130,150,.5), rgba(60,66,82,.6));
  box-shadow: 0 0 20px -4px #aeb6c6;
  animation: fxCage 1.5s cubic-bezier(.15, 1, .3, 1) forwards;
}
@keyframes fxCage {
  0%   { transform: translate(-50%, 40%) scaleY(0); opacity: 0; }
  40%  { transform: translate(-50%, -50%) scaleY(1.1); opacity: 1; }
  70%  { transform: translate(-50%, -50%) scaleY(1); opacity: 1; }
  100% { transform: translate(-50%, -50%) scaleY(1); opacity: 0; }
}

/* the board coming apart in two directions at once */
.fx-drift {
  left: 0; top: 0; width: 100%; height: 100%;
  transform: none;
  background:
    linear-gradient(90deg, rgba(201,160,106,.34) 0 50%, transparent 50%),
    linear-gradient(270deg, rgba(120,90,160,.34) 0 50%, transparent 50%);
  animation: fxDrift 1.3s cubic-bezier(.3, 0, .2, 1) forwards;
}
.fx-drift-h { transform: rotate(90deg); }
@keyframes fxDrift {
  0%   { opacity: 0; }
  30%  { opacity: 1; }
  100% { opacity: 0; }
}

/* a crown arriving on somebody who was not expecting one */
.fx-crown {
  width: 16%; height: 16%;
  background: linear-gradient(180deg, #ffe9a8, var(--gold) 55%, #b98c17);
  clip-path: polygon(0 100%, 12% 30%, 30% 62%, 50% 8%, 70% 62%, 88% 30%, 100% 100%);
  filter: drop-shadow(0 0 12px rgba(255, 209, 102, .9));
  animation: fxCrown 1.7s cubic-bezier(.2, .9, .3, 1) forwards;
}
@keyframes fxCrown {
  0%   { transform: translate(-50%, -260%) scale(1.6) rotate(-12deg); opacity: 0; }
  35%  { transform: translate(-50%, -50%) scale(1) rotate(0deg); opacity: 1; }
  75%  { transform: translate(-50%, -55%) scale(1.05); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1); opacity: 0; }
}

/* something very large arriving */
.fx-stomp {
  width: 30%; height: 30%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(20,10,4,.75), rgba(20,10,4,.25) 60%, transparent 72%);
  animation: fxStomp .9s cubic-bezier(.6,0,.9,.4) forwards;
}
@keyframes fxStomp {
  0%   { transform: translate(-50%, -50%) scale(3.2); opacity: 0; }
  35%  { transform: translate(-50%, -50%) scale(1.6); opacity: .85; }
  40%  { transform: translate(-50%, -50%) scale(1); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1.1); opacity: 0; }
}

/* everything falling inward */
.fx-implode {
  width: 40%; height: 40%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(0,0,0,.9) 18%, rgba(169,112,255,.5) 42%, transparent 70%);
  animation: fxImplode 1s cubic-bezier(.6,0,.3,1) forwards;
}
@keyframes fxImplode {
  0%   { transform: translate(-50%, -50%) scale(1.8) rotate(0deg); opacity: 0; }
  35%  { transform: translate(-50%, -50%) scale(1) rotate(180deg); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(0) rotate(420deg); opacity: 0; }
}

/* a strike from above */
.fx-bolt {
  width: 4%; height: 60%;
  transform-origin: 50% 0;
  background: linear-gradient(180deg, rgba(255,255,255,.95), #9ad8ff 45%, transparent);
  clip-path: polygon(45% 0, 100% 42%, 60% 45%, 95% 100%, 0 52%, 42% 48%, 5% 44%);
  animation: fxBolt .7s steps(3, end) forwards;
}
@keyframes fxBolt {
  0%   { transform: translate(-50%, -100%) scaleY(.2); opacity: 0; }
  20%  { transform: translate(-50%, -100%) scaleY(1); opacity: 1; }
  60%  { opacity: .8; }
  100% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .fx, .board-shell.fx-shake { animation-duration: .01ms !important; animation-iteration-count: 1 !important; }
}

/* chaos flash */
.chaos-flash {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 60;
  display: grid;
  place-items: center;
  animation: chaosFade 1.5s ease-out forwards;
}
.chaos-flash span {
  font-size: 42px;
  font-weight: 800;
  letter-spacing: 3px;
  text-transform: uppercase;
  color: #fff;
  text-shadow: 0 0 30px var(--accent-2), 0 0 60px var(--accent);
}
@keyframes chaosFade {
  0% { opacity: 0; transform: scale(.9); }
  18% { opacity: 1; transform: scale(1); }
  70% { opacity: 1; }
  100% { opacity: 0; transform: scale(1.05); }
}

/* ---------------- inventory ---------------- */

.inventory {
  width: 100%;
  max-width: calc(var(--board-fit, 640px) + 20px);
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  min-height: 44px;
  max-height: 108px;          /* an unlimited bag scrolls rather than shoving the board */
  overflow-y: auto;
  align-items: center;
  align-content: flex-start;
}
.pu {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 12px;
  border-radius: 11px;
  border: 1px solid var(--line);
  background: var(--panel-2);
  cursor: pointer;
  transition: transform .12s, border-color .12s, box-shadow .12s;
}
.pu:hover:not([disabled]) { transform: translateY(-2px); border-color: currentColor; box-shadow: 0 0 20px -6px currentColor; }
.pu[disabled] { opacity: .38; cursor: default; }
.pu.armed { border-color: var(--accent-2); box-shadow: 0 0 0 1px var(--accent-2), 0 0 22px -4px var(--accent-2); }
.pu-icon { font-size: 17px; }
.pu-name { font-size: 12.5px; font-weight: 600; color: var(--ink); }
/* Does spending this cost me my move? */
.pu-cost {
  font-size: 9.5px; font-weight: 800; letter-spacing: .6px; text-transform: uppercase;
  padding: 1px 5px; border-radius: 4px;
  color: var(--accent-2); background: color-mix(in srgb, var(--accent-2) 20%, transparent);
}
.pu-cost.free { color: var(--good); background: color-mix(in srgb, var(--good) 22%, transparent); }
.inventory.compact .pu-cost { display: none; }
.hint-cost { font-style: normal; font-weight: 700; color: var(--accent-2); }
.hint-cost.free { color: var(--good); }

.pu-n {
  /* no colour of its own, so currentColor stays the chip's tint */
  font-size: 11px; font-weight: 800;
  background: color-mix(in srgb, currentColor 24%, transparent);
  border-radius: 999px; padding: 0 5px; min-width: 16px; text-align: center;
}
/* Icons only, for a bag too full to label. */
.inventory.compact .pu { padding: 7px 10px; gap: 5px; }
.inventory.compact .pu-name { display: none; }
.inventory.compact .pu-icon { font-size: 19px; }
.inv-empty { color: var(--ink-faint); font-size: var(--fs-xs); }

/* What a power-up does. This is a real row in the board column, and the room
   for it is reserved even when nothing is hovered, so the description can never
   come down on top of the chips it describes. Two lines of text fit. */
.inv-hint {
  width: 100%;
  max-width: calc(var(--board-fit, 640px) + 20px);
  /* A FIXED height, not a minimum. The slot's width follows the board, so a
     min-height let a long description wrap to a third line on a narrow board,
     which made the column taller, which made sizeBoard() choose a narrower
     board - the two measurements chased each other and settled on the floor. */
  height: 56px;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  align-items: center;
  padding: var(--sp-2) var(--sp-3);
  pointer-events: none;         /* never intercept a click meant for a chip */
  border-radius: var(--r-sm);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-left: 3px solid currentColor;
  font-size: var(--fs-sm);
  line-height: 1.4;
  color: var(--ink-dim);
}
.inv-hint b { color: currentColor; }
.inv-hint.idle { color: var(--ink-faint); border-left-color: var(--line); }

.prompt {
  width: 100%;
  max-width: calc(var(--board-fit, 640px) + 20px);
  min-height: 46px;            /* shares the hint's slot; keep the column steady */
  padding: 9px 14px;
  border-radius: 11px;
  background: rgba(255, 138, 76, .14);
  border: 1px solid rgba(255, 138, 76, .5);
  font-size: 13px;
  display: flex;
  align-items: center;
  gap: 12px;
}
.prompt button { margin-left: auto; }

/* ---------------- vote panel ---------------- */

.vote-card { border-color: var(--line-hot); box-shadow: 0 0 30px -10px var(--accent); }
.vote-timer {
  height: 6px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .08);
  overflow: hidden;
  margin-bottom: 6px;
}
.vote-timer-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--accent), var(--cyan));
  transition: width .1s linear;
}
.vote-meta {
  display: flex;
  justify-content: space-between;
  font-size: 11.5px;
  color: var(--ink-faint);
  margin-bottom: 10px;
  font-variant-numeric: tabular-nums;
}
.vote-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 5px; }
.vote-row { position: relative; padding: var(--sp-2) var(--sp-3); border-radius: var(--r-sm); overflow: hidden; background: rgba(255,255,255,.05); }
.vote-row .bar {
  position: absolute;
  inset: 0 auto 0 0;
  background: linear-gradient(90deg, rgba(169,112,255,.5), rgba(88,214,255,.28));
  transition: width .25s ease;
}
.vote-row .txt { position: relative; display: flex; justify-content: space-between; gap: 10px; font-size: var(--fs-md); font-weight: 600; }
.vote-row .txt b { font-variant-numeric: tabular-nums; }
.vote-row.lead { box-shadow: inset 0 0 0 2px var(--accent); }
.vote-row.lead .txt { color: #fff; }
.vote-empty { color: var(--ink-faint); font-size: 12px; padding: 6px 0; }
.vote-recent { margin-top: 9px; display: flex; flex-wrap: wrap; gap: 4px; max-height: 60px; overflow: hidden; }
.vote-recent span { font-size: 10.5px; padding: 2px 6px; border-radius: 999px; background: rgba(255,255,255,.05); }

/* ---------------- feed / log ---------------- */

.statuses { display: flex; flex-wrap: wrap; gap: 6px; }
.st {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-xs); font-weight: 600;
  padding: 4px 9px; border-radius: 999px;
  background: rgba(255, 255, 255, .05);
  border: 1px solid color-mix(in srgb, currentColor 45%, var(--line));
  color: var(--ink-dim);
}
.st b { color: currentColor; font-weight: 800; }
.st i { font-style: normal; opacity: .75; font-variant-numeric: tabular-nums; }
.st.w { color: #f0eef8; }
.st.b { color: #b6afd4; }
.st.hot { color: var(--accent-2); }
.st.cold { color: var(--cyan); }
.st.good { color: var(--good); }
.st.bad { color: var(--bad); }

.feed:empty::before {
  content: "The board is quiet. For now.";
  display: block;
  color: var(--ink-faint);
  font-size: var(--fs-sm);
  font-style: italic;
  padding: var(--sp-4) var(--sp-2);
  text-align: center;
}
.feed {
  flex: 1 1 auto;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-height: 0;
  padding-right: 4px;
}
.line { font-size: var(--fs-sm); padding: var(--sp-2) var(--sp-3); border-radius: var(--r-sm); background: rgba(255,255,255,.05); border-left: 3px solid transparent; line-height: 1.45; }
.line.system { color: var(--ink-faint); }
.line.result { border-left-color: var(--gold); color: var(--gold); font-weight: 600; }
.line.powerup { border-left-color: var(--cyan); }
.line.tile { border-left-color: var(--good); color: var(--ink-dim); }
.line.chaos { border-left-color: var(--accent-2); color: #ffd0b5; font-weight: 600; }
.line.vote { border-left-color: var(--accent); }
.line.say { background: rgba(169,112,255,.10); }
.line.say .who { font-weight: 700; margin-right: 6px; }

.movelog {
  max-height: 148px;
  overflow-y: auto;
  display: grid;
  grid-template-columns: 32px 1fr 1fr;
  gap: 3px 6px;
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  align-content: start;
}
.movelog .n { color: var(--ink-faint); }
.movelog .mv { padding: 1px 5px; border-radius: 5px; }
.movelog .mv.pu { color: var(--cyan); }
.movelog .mv.latest { background: rgba(169,112,255,.25); }

/* ---------------- modal ---------------- */

.modal-root { position: fixed; inset: 0; z-index: 100; display: grid; place-items: center; }
.modal-backdrop { position: absolute; inset: 0; background: rgba(6, 4, 12, .72); backdrop-filter: blur(4px); }
.modal {
  position: relative;
  max-width: 620px;
  width: min(92vw, 620px);
  /* max-height plus overflow is the whole mechanism: a min-height of min-content
     would out-rank the max and grow a long dialog straight off the top of the
     screen, where the close button and the first heading cannot be reached. The
     promotion dialog that this was added for was cropped for a different reason
     entirely - .promo-opt was inheriting `position: absolute` from .piece. */
  max-height: min(84vh, 84dvh);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: linear-gradient(180deg, #1e1734, #150f26);
  border: 1px solid var(--line-hot);
  border-radius: 18px;
  padding: 24px;
  box-shadow: var(--shadow);
}
.modal h2 { margin-top: 0; }
.modal h3 { margin: 18px 0 6px; color: var(--accent); font-size: 14px; letter-spacing: 1px; text-transform: uppercase; }
.modal ul { margin: 6px 0; padding-left: 18px; }
.modal li { margin-bottom: 5px; }
.modal code { background: rgba(169,112,255,.16); padding: 1px 5px; border-radius: 5px; color: #d9c6ff; }
.modal-close {
  position: sticky;             /* stays reachable however far the rules scroll */
  top: 0;
  float: right;
  margin: -6px -6px 0 var(--sp-3);
  z-index: 3;
  backdrop-filter: blur(6px);
}

/* The one dialog with no way out: two answers, and the board is waiting. */
.offer-row {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 14px;
}
.offer-row .btn { flex: 1 1 180px; padding: 11px 14px; font-size: 14px; }

.promo-row { display: flex; gap: 10px; justify-content: center; margin-top: 14px; }
.promo-opt {
  position: static;            /* .piece is a board rule and would float these out of flow */
  width: 74px; height: 74px;
  flex: 0 0 auto;
  display: grid; place-items: center;
  padding: 8px;
  border-radius: 14px;
  border: 1px solid var(--line);
  background: rgba(255,255,255,.05);
  cursor: pointer;
  transition: transform .12s, border-color .12s, background .12s;
}
.promo-opt .pc { filter: none; }
.promo-row { flex-wrap: wrap; }
.promo-opt:hover { border-color: var(--accent); background: rgba(169,112,255,.2); }

.pu-legend { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 8px; }
.pu-legend div { font-size: 12.5px; padding: 8px; border-radius: 9px; background: rgba(255,255,255,.04); }
.pu-legend b { display: block; }

/* ---------------- scrollbars ---------------- */

/* Not there at all. Everything still scrolls - by wheel, by drag, by key -
   there is simply no bar anywhere in the app.

   A scrollbar does do one useful thing, which is tell you the content
   continues past the edge, so that job goes to a soft fade at the bottom of
   anything that scrolls. It says the same thing and takes up no room. */
* { scrollbar-width: none; -ms-overflow-style: none; }
::-webkit-scrollbar { width: 0; height: 0; background: transparent; }
::-webkit-scrollbar-thumb, ::-webkit-scrollbar-track, ::-webkit-scrollbar-corner {
  background: transparent;
}

.rail, .movelog, .feed, .stage {
  -webkit-mask-image: linear-gradient(180deg, #000 0, #000 calc(100% - 26px), transparent 100%);
  mask-image: linear-gradient(180deg, #000 0, #000 calc(100% - 26px), transparent 100%);
}
/* the board must never be faded - it is the thing you are reading */
.stage { -webkit-mask-image: none; mask-image: none; }

/* The only bar left in the app, and it is one you are meant to grab: a
   hairline track and a small handle, nothing else. */
input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  height: 18px;
  background: transparent;
  cursor: pointer;
}
input[type="range"]::-webkit-slider-runnable-track {
  height: 3px;
  border-radius: 999px;
  background: rgba(169, 112, 255, .22);
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 13px; height: 13px;
  margin-top: -5px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid rgba(12, 8, 22, .9);
  box-shadow: 0 0 10px -2px var(--accent);
  transition: transform .12s;
}
input[type="range"]:hover::-webkit-slider-thumb { transform: scale(1.15); }
input[type="range"]::-moz-range-track { height: 3px; border-radius: 999px; background: rgba(169, 112, 255, .22); }
input[type="range"]::-moz-range-thumb {
  width: 13px; height: 13px; border-radius: 50%;
  background: var(--accent); border: 2px solid rgba(12, 8, 22, .9);
}

/* ---------------- responsive ---------------- */

@media (max-width: 1240px) {
  .layout { grid-template-columns: 250px minmax(0, 1fr) 290px; gap: 12px; padding: 12px; }
}
@media (max-width: 1040px) {
  body { overflow: auto; }
  .app { height: auto; }
  /* minmax(0,1fr), not 1fr: a plain 1fr track cannot shrink below its content,
     so the board inflated the column and then measured that inflated width. */
  .layout { grid-template-columns: minmax(0, 1fr); }
  .app.stream .layout { grid-template-columns: minmax(0, 1fr); }
  .rail { overflow: visible; max-height: none; }
  .stage { order: -1; }
}

/* ============================================================
   the shell: two screens, three doors, a drawer and a menu
   ============================================================ */

/* One scroll, three stations; the top bar carries the controls of whichever
   one is on screen. */
.app[data-screen="home"] .game-only { display: none !important; }
.app[data-screen="game"] .home-only { display: none !important; }

.station { position: relative; flex: 0 0 auto; }

/* The table is one viewport under the bar. Zoomed OUT it is four fifths of
   that, drawn at four fifths, so the doors show underneath; touching the
   board zooms it back in. The layout keeps its full size throughout - only
   the drawing scales - so the board is never measured against a station
   that is about to change. */
.table-station {
  height: calc(var(--vh, 100vh) - var(--bar));
  display: flex;
  flex-direction: column;
  transition: height .7s cubic-bezier(.2, .8, .2, 1);
}
.table-station .layout {
  flex: 1 1 auto;
  min-height: 0;
  padding-top: 26px;
  transform-origin: 50% 0;
  transition: transform .7s cubic-bezier(.2, .8, .2, 1);
}
@media (min-width: 1041px) {
  .app.table-out .table-station { height: calc((var(--vh, 100vh) - var(--bar)) * .6); }
  .app.table-out .table-station .layout {
    flex: none;
    height: calc(var(--vh, 100vh) - var(--bar));
    transform: scale(.6);
  }
}
.app.no-zoom-anim .table-station, .app.no-zoom-anim .table-station .layout { transition: none !important; }
.stage.landed { animation: tableLand .6s cubic-bezier(.2, .8, .2, 1); }
@keyframes tableLand { from { transform: scale(.96); opacity: .55; } to { transform: none; opacity: 1; } }

/* ---------------- the castle ---------------- */
/* Five layers, each moved by --p (how far the station has scrolled away, 0
   to 1) at its own rate, so the near things go faster than the far ones.
   Everything ambient runs all the time; the entrance runs under .play,
   which is put back on every time the station comes into view. */
.hero-station {
  height: calc(var(--vh, 100vh) - var(--bar));
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #05040c;
  --p: 0;
}
.scene, .layer { position: absolute; inset: 0; }
.layer { will-change: transform; }
.layer svg { position: absolute; left: 0; right: 0; bottom: 0; width: 100%; height: 100%; }
.layer.sky  { transform: translateY(calc(var(--p) * 30px)); }
.layer.far  { transform: translateY(calc(var(--p) * 90px)); }
.layer.mid  { transform: translateY(calc(var(--p) * 170px)); }
.layer.near { transform: translateY(calc(var(--p) * 280px)); }

.layer.sky {
  background:
    radial-gradient(1100px 560px at 62% 108%, rgba(214, 96, 140, .22), transparent 62%),
    radial-gradient(900px 500px at 20% 100%, rgba(120, 70, 190, .26), transparent 62%),
    linear-gradient(180deg, #03020a 0%, #0a0722 38%, #1c1240 68%, #3b1f52 88%, #6a3358 100%);
}
.stars {
  position: absolute; inset: 0;
  background-image:
    radial-gradient(1.8px 1.8px at 12% 18%, #fff, transparent),
    radial-gradient(1.4px 1.4px at 28% 42%, rgba(255,255,255,.9), transparent),
    radial-gradient(2.2px 2.2px at 44% 12%, #fff, transparent),
    radial-gradient(1.5px 1.5px at 58% 34%, rgba(255,255,255,.85), transparent),
    radial-gradient(1.9px 1.9px at 72% 20%, #fff, transparent),
    radial-gradient(1.3px 1.3px at 86% 44%, rgba(255,255,255,.8), transparent),
    radial-gradient(2px 2px at 92% 12%, #fff, transparent),
    radial-gradient(1.4px 1.4px at 6% 60%, rgba(255,255,255,.7), transparent);
  background-size: 520px 420px, 460px 380px, 610px 470px, 500px 410px, 560px 440px, 480px 400px, 640px 520px, 540px 450px;
  animation: twinkle 7s ease-in-out infinite alternate;
}
.stars.far {
  opacity: .55;
  background-size: 260px 210px, 230px 190px, 305px 235px, 250px 205px, 280px 220px, 240px 200px, 320px 260px, 270px 225px;
  animation: twinkleFar 11s ease-in-out infinite alternate-reverse;
}
@keyframes twinkle { from { opacity: .7; } to { opacity: 1; } }
@keyframes twinkleFar { from { opacity: .3; } to { opacity: .6; } }
.cloud {
  position: absolute;
  width: 46vw; height: 14vh;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(200, 170, 240, .16), rgba(200, 170, 240, 0));
  filter: blur(6px);
  animation: cloudDrift 110s linear infinite alternate;
}
.cloud.c1 { top: 12%; left: -10%; }
.cloud.c2 { top: 30%; left: 50%; width: 38vw; animation-duration: 150s; animation-direction: alternate-reverse; }
@keyframes cloudDrift { to { transform: translateX(22vw); } }
.moon {
  position: absolute; top: 10%; right: 14%;
  width: 96px; height: 96px; border-radius: 50%;
  background: radial-gradient(circle at 36% 34%, #fffbe9, #efe3bf 56%, #d3c39a);
  box-shadow: 0 0 40px 14px rgba(255, 244, 205, .26), 0 0 150px 60px rgba(255, 220, 170, .13);
  animation: moonDrift 140s linear infinite alternate;
}
@keyframes moonDrift { to { transform: translate(-40px, 18px); } }
/* a shooting star now and then, from the top right */
.shooter {
  position: absolute; top: 14%; left: 62%;
  width: 150px; height: 2px;
  background: linear-gradient(90deg, rgba(255,255,255,0), #fff 70%, rgba(255,255,255,0));
  transform: rotate(-32deg);
  opacity: 0;
  animation: shoot 12s ease-in infinite;
}
@keyframes shoot {
  0% { opacity: 0; transform: translate(0, 0) rotate(-32deg); }
  2% { opacity: 1; }
  9% { opacity: 0; transform: translate(-360px, 225px) rotate(-32deg); }
  100% { opacity: 0; transform: translate(-360px, 225px) rotate(-32deg); }
}
/* a bat across the moon, on its way somewhere */
.bat {
  position: absolute; top: 15%; left: -6%;
  width: 34px; height: 14px;
  animation: batFly 23s linear infinite;
}
.bat svg { width: 100%; height: 100%; fill: #05040a; animation: flap .22s ease-in-out infinite alternate; }
@keyframes batFly {
  0% { transform: translate(0, 0); }
  30% { transform: translate(38vw, -6vh); }
  60% { transform: translate(72vw, 4vh); }
  100% { transform: translate(112vw, -8vh); }
}
@keyframes flap { from { transform: scaleY(.55); } to { transform: scaleY(1.05); } }

/* far off: the castle on its ridge, paler for the distance */
.layer.far .ridge { fill: #16112f; }
.layer.far .castle path, .layer.far .castle rect { fill: #0f0b22; }
.layer.far .castle .roof { fill: #130e2a; }
.layer.far .castle .flag { fill: #b487ff; }
.layer.far .castle .gate { fill: #2a1610; }
.layer.far .castle .win { fill: #ffd28a; opacity: .85; animation: flicker 3.4s ease-in-out infinite; }
@keyframes flicker { 0%, 100% { opacity: .9; } 35% { opacity: .5; } 60% { opacity: 1; } 80% { opacity: .7; } }
/* nearer: the hills and their pines */
.layer.mid .hill-b { fill: #0c0919; }
.layer.mid .pines path { fill: #080614; }
.layer.mid .hill-a { fill: #07050f; }
/* nearest: the ground, lit by the fires */
.layer.near .ground { fill: #050409; }
.layer.near::before {
  content: "";
  position: absolute; left: 0; right: 0; bottom: 0; height: 26%;
  background:
    radial-gradient(40vw 14vh at 22% 100%, rgba(255, 150, 60, .28), transparent 70%),
    radial-gradient(28vw 10vh at 76% 100%, rgba(255, 140, 50, .18), transparent 70%);
  pointer-events: none;
}
.mist {
  position: absolute; left: -10%; right: -10%; bottom: 8%; height: 18%;
  background: linear-gradient(180deg, rgba(180, 140, 220, 0), rgba(190, 150, 230, .13) 50%, rgba(190, 150, 230, 0));
  filter: blur(10px);
  animation: mistDrift 30s ease-in-out infinite alternate;
}
@keyframes mistDrift { to { transform: translateX(6%) scaleY(1.2); } }

/* the campfires */
.fire { position: absolute; bottom: 5%; width: 60px; height: 80px; transform-origin: 50% 100%; }
.fire.f1 { left: 19%; }
.fire.f2 { left: 74%; transform: scale(.7); bottom: 7%; }
.fire .glow {
  position: absolute; left: 50%; bottom: -10px;
  width: 220px; height: 120px;
  transform: translateX(-50%);
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(255, 170, 70, .45), rgba(255, 120, 40, .12) 55%, transparent);
  animation: glowPulse 2.6s ease-in-out infinite;
}
@keyframes glowPulse { 50% { opacity: .7; transform: translateX(-50%) scale(1.08); } }
.fire .flame {
  position: absolute; left: 50%; bottom: 10px;
  transform: translateX(-50%);
  border-radius: 50% 50% 45% 45% / 62% 62% 38% 38%;
  filter: blur(.6px);
}
.fire .flame.a { width: 26px; height: 54px; background: linear-gradient(180deg, #ffb34a, #ff6a1e 65%, #c4300f); animation: flameA 1.1s ease-in-out infinite alternate; }
.fire .flame.b { width: 18px; height: 40px; background: linear-gradient(180deg, #ffe08a, #ff9a2e 70%, #ff5d1a); animation: flameB .85s ease-in-out infinite alternate; }
.fire .flame.c { width: 9px; height: 24px; background: linear-gradient(180deg, #fff9d6, #ffd36b); animation: flameC .7s ease-in-out infinite alternate; }
@keyframes flameA { from { transform: translateX(-50%) scaleY(.92) skewX(-4deg); } to { transform: translateX(-52%) scaleY(1.08) skewX(5deg); } }
@keyframes flameB { from { transform: translateX(-48%) scaleY(1.06) skewX(6deg); } to { transform: translateX(-52%) scaleY(.9) skewX(-5deg); } }
@keyframes flameC { from { transform: translateX(-50%) scaleY(.9); opacity: .85; } to { transform: translateX(-50%) scaleY(1.15); opacity: 1; } }
.fire .logs { position: absolute; left: 50%; bottom: 4px; width: 54px; height: 14px; transform: translateX(-50%); }
.fire .logs i {
  position: absolute; left: 0; right: 0; bottom: 0; height: 8px;
  border-radius: 4px; background: #17100c;
  transform: rotate(-12deg);
}
.fire .logs i + i { transform: rotate(14deg); background: #1c1410; }
.fire .ember {
  position: absolute; left: calc(50% + (var(--i) - 2.5) * 6px); bottom: 40px;
  width: 3px; height: 3px; border-radius: 50%;
  background: #ffc46a;
  box-shadow: 0 0 6px 1px rgba(255, 170, 70, .8);
  opacity: 0;
  animation: ember 2.4s ease-out infinite;
  animation-delay: calc(var(--i) * -.4s);
}
@keyframes ember {
  0% { opacity: 0; transform: translate(0, 0) scale(1); }
  10% { opacity: 1; }
  100% { opacity: 0; transform: translate(calc((var(--i) - 2.5) * 10px), -110px) scale(.4); }
}
/* the fireflies */
.flies { position: absolute; inset: 0; }
.flies i {
  position: absolute; left: var(--x); top: var(--y);
  width: 4px; height: 4px; border-radius: 50%;
  background: #f4ffa8;
  box-shadow: 0 0 7px 2px rgba(220, 255, 140, .7);
  animation: flyPath var(--d) ease-in-out infinite alternate, flyGlow var(--g) ease-in-out infinite;
  animation-delay: var(--dl), var(--dl);
}
@keyframes flyPath {
  0% { transform: translate(0, 0); }
  35% { transform: translate(22px, -18px); }
  65% { transform: translate(-14px, -34px); }
  100% { transform: translate(-28px, -8px); }
}
@keyframes flyGlow { 0%, 100% { opacity: .15; } 50% { opacity: 1; } }

/* the title: a wrapper that the scroll pulls apart, an inner that the
   entrance throws in */
.hero-station .hero {
  position: relative; z-index: 2;
  text-align: center;
  padding: 0 24px;
  transform: translateY(calc(-9vh - var(--p) * 160px));
  opacity: calc(1 - var(--p) * 1.25);
}
.hero-station .hero h1 { text-shadow: 0 10px 40px rgba(0, 0, 0, .7); animation: heroFloat 6s ease-in-out infinite; }
@keyframes heroFloat { 50% { transform: translateY(-5px); } }
.hero h1 .w { display: inline-block; }
.hero h1 .w b { display: inline-block; font-weight: inherit; -webkit-background-clip: text; background-clip: text; color: transparent;
  background-size: 220% 100%; animation: shimmer 5.5s linear infinite; }
.hero h1 .w1 b { background-image: linear-gradient(100deg, #fff, var(--accent) 35%, #fff 50%, var(--accent) 65%, #fff); }
.hero h1 .w2 b { background-image: linear-gradient(100deg, var(--cyan), #fff 35%, var(--cyan) 50%, #fff 65%, var(--cyan)); animation-delay: -1.8s; }
.hero h1 .w3 b { background-image: linear-gradient(100deg, var(--accent-2), #ffd166 35%, #fff 50%, #ffd166 65%, var(--accent-2)); animation-delay: -3.6s; }
@keyframes shimmer { from { background-position: 0% 0; } to { background-position: 200% 0; } }
.hero h1 .w1 { transform: translate(calc(var(--p) * -180px), calc(var(--p) * -50px)) rotate(calc(var(--p) * -8deg)); }
.hero h1 .w2 { transform: translate(0, calc(var(--p) * -130px)) scale(calc(1 + var(--p) * .3)); }
.hero h1 .w3 { transform: translate(calc(var(--p) * 180px), calc(var(--p) * -50px)) rotate(calc(var(--p) * 8deg)); }
.hero-station .tagline { color: #e2d6f5; text-shadow: 0 2px 18px rgba(0, 0, 0, .8); }
.hero .tagline span, .hero .go { display: block; }
.hero .flash, .hero .ring { position: absolute; left: 50%; top: 40%; pointer-events: none; opacity: 0; }
.hero .flash {
  width: 60vw; height: 40vh;
  transform: translate(-50%, -50%);
  background: radial-gradient(closest-side, rgba(255, 240, 220, .55), rgba(255, 200, 120, .15) 50%, transparent);
  filter: blur(8px);
}
.hero .ring {
  width: 40px; height: 40px; border-radius: 50%;
  border: 2px solid rgba(255, 230, 180, .8);
  box-shadow: 0 0 30px rgba(255, 200, 120, .6);
  transform: translate(-50%, -50%);
}
/* the entrance, under .play */
.hero-station.play .w b { opacity: 0; animation: wordIn .8s cubic-bezier(.2, 1.35, .3, 1) both, shimmer 5.5s linear infinite; }
.hero-station.play .w1 b { animation-delay: .35s, 0s; }
.hero-station.play .w2 b { animation-delay: .8s, -1.8s; }
.hero-station.play .w3 b { animation-delay: 1.25s, -3.6s; }
@keyframes wordIn {
  0% { opacity: 0; transform: scale(3.4) translateY(-30px); filter: blur(16px); }
  55% { opacity: 1; transform: scale(.94) translateY(0); filter: blur(0); }
  75% { transform: scale(1.05); }
  100% { opacity: 1; transform: scale(1); filter: blur(0); }
}
.hero-station.play .flash { animation: flare 1s ease-out 1.4s both; }
@keyframes flare { 0% { opacity: 0; transform: translate(-50%, -50%) scale(.5); } 30% { opacity: 1; transform: translate(-50%, -50%) scale(1.25); } 100% { opacity: 0; transform: translate(-50%, -50%) scale(1.6); } }
.hero-station.play .ring { animation: ripple 1.2s ease-out 1.45s both; }
@keyframes ripple { 0% { opacity: .9; transform: translate(-50%, -50%) scale(1); } 100% { opacity: 0; transform: translate(-50%, -50%) scale(28); border-width: 1px; } }
.hero-station.play .tagline span { animation: riseIn .8s ease-out 1.8s both; }
.hero-station.play .go { animation: riseIn .8s ease-out 2.15s both; }
@keyframes riseIn { from { opacity: 0; transform: translateY(26px); } to { opacity: 1; transform: none; } }
.hero-station.play .layer.far svg { animation: castleRise 1.4s cubic-bezier(.2, .8, .2, 1) both; }
@keyframes castleRise { from { transform: translateY(60px); opacity: 0; } to { transform: none; opacity: 1; } }
.hero-station.play .layer.mid svg { animation: castleRise 1.2s cubic-bezier(.2, .8, .2, 1) .15s both; }
.hero-station.play .fire { animation: ignite .9s cubic-bezier(.2, 1.4, .3, 1) .9s both; }
.hero-station.play .fire.f2 { animation-name: igniteSmall; animation-delay: 1.1s; }
@keyframes ignite { from { transform: scale(0); } to { transform: scale(1); } }
@keyframes igniteSmall { from { transform: scale(0); } to { transform: scale(.7); } }
.hero-station.play .moon { animation: moonIn 1.6s ease-out both, moonDrift 140s linear 1.6s infinite alternate; }
/* the whole scene eases back from a little too close, the way a title does */
.hero-station.play .scene { animation: sceneIn 3.2s cubic-bezier(.2, .7, .2, 1) both; transform-origin: 50% 70%; }
@keyframes sceneIn { from { transform: scale(1.1); } to { transform: none; } }
@keyframes moonIn { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: none; } }

.hero-go {
  margin-top: 22px;
  display: inline-flex; align-items: center; gap: 10px;
  padding: 12px 22px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, .22);
  background: rgba(20, 14, 36, .55);
  color: #fff;
  font-size: var(--fs-md);
  letter-spacing: .08em;
  cursor: pointer;
  backdrop-filter: blur(6px);
  transition: background .15s, transform .15s;
}
.hero-go:hover { background: rgba(169, 112, 255, .32); transform: translateY(-1px); }
/* until the page has been touched the browser keeps the sound; say so */
.hero-sound {
  position: absolute; left: 50%; bottom: -13vh;
  transform: translateX(-50%);
  display: inline-flex; align-items: center; gap: 9px;
  padding: 9px 18px 9px 14px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, .22);
  background: linear-gradient(180deg, rgba(40, 28, 66, .7), rgba(16, 11, 30, .7));
  color: #eee6ff;
  font-size: 12.5px;
  letter-spacing: .08em;
  cursor: pointer;
  backdrop-filter: blur(8px);
  box-shadow: 0 10px 30px -12px rgba(0, 0, 0, .8), inset 0 1px 0 rgba(255, 255, 255, .14);
  transition: background .15s, transform .15s;
}
.hero-sound i {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--accent, #a970ff);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent, #a970ff) 60%, transparent);
  animation: soundRing 2s ease-out infinite;
}
@keyframes soundRing { to { box-shadow: 0 0 0 12px transparent; } }
.hero-sound .ic { width: 15px; height: 15px; }
.hero-sound:hover { background: linear-gradient(180deg, rgba(169, 112, 255, .45), rgba(40, 28, 66, .8)); transform: translateX(-50%) translateY(-1px); }
.hero-go i { font-style: normal; display: inline-block; animation: bob 1.6s ease-in-out infinite; }
@keyframes bob { 50% { transform: translateY(5px); } }
@media (prefers-reduced-motion: reduce) {
  .hero-station *, .hero-station *::before { animation: none !important; }
  .hero-station.play .w b, .hero-station.play .tagline span, .hero-station.play .go, .hero-station.play .fire,
  .hero-station.play .layer svg, .hero-station.play .moon { opacity: 1; transform: none; }
  .hero-station.play .fire.f2 { transform: scale(.7); }
  .stage.landed { animation: none !important; }
  .table-station, .table-station .layout { transition: none; }
}
@media (max-width: 760px) {
  .moon { width: 60px; height: 60px; top: 7%; right: 9%; }
  .hero-station .hero { transform: translateY(calc(-4vh - var(--p) * 120px)); }
  .fire.f1 { left: 12%; }
  .fire.f2 { left: 78%; }
  .cloud { display: none; }
}
#btnNew, #btnLeft { display: none !important; }   /* the doors deal; the drawer has a gear */

.brand {
  background: none; border: 0; padding: 0; cursor: pointer; color: inherit; font: inherit;
  text-align: left;
}
.brand-text { white-space: nowrap; }
.topbar-live {
  flex: 1 1 auto;
  text-align: center;
  font-size: var(--fs-sm);
  letter-spacing: .06em;
  color: var(--ink-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------------- the front door ---------------- */
.home {
  flex: 0 0 auto;
  min-height: calc(100vh - var(--bar));
  overflow: visible;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 28px;
  padding: 10px 24px 64px;
}
.hero { text-align: center; max-width: 820px; }
.hero h1 {
  margin: 0 0 12px;
  font-size: clamp(34px, 6vw, 74px);
  line-height: 1;
  font-weight: 900;
  letter-spacing: .03em;
}
.hero h1 span { display: inline-block; -webkit-background-clip: text; background-clip: text; color: transparent; }
.hero h1 span:nth-child(1) { background-image: linear-gradient(92deg, #fff, var(--accent)); }
.hero h1 span:nth-child(2) { background-image: linear-gradient(92deg, var(--cyan), #fff); }
.hero h1 span:nth-child(3) { background-image: linear-gradient(92deg, var(--accent-2), #ffd166); }
.tagline { margin: 0 auto; max-width: 640px; color: var(--ink-dim); font-size: var(--fs-lg); line-height: 1.55; }

.doors {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 18px;
  width: 100%;
  max-width: 1180px;
}
.door {
  --door: var(--accent);
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 24px 22px 22px;
  border-radius: 22px;
  background: var(--panel);
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
  backdrop-filter: blur(10px);
  transition: transform .2s, border-color .2s, box-shadow .2s;
}
.door::before {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  background: radial-gradient(420px 220px at 50% -20%, color-mix(in srgb, var(--door) 22%, transparent), transparent 70%);
  pointer-events: none;
}
.door:hover { transform: translateY(-3px); border-color: color-mix(in srgb, var(--door) 55%, transparent); box-shadow: 0 24px 60px -20px color-mix(in srgb, var(--door) 45%, transparent), var(--shadow); }
.door-bots   { --door: var(--accent-2); }
.door-online { --door: var(--cyan); }
.door-chat   { --door: var(--accent); }
.door-royale { --door: var(--gold); }
/* three to a row: the chat door lives on the second row with the record and
   the badges, and the hall takes the third row to itself */
.home-cards .door { height: auto; }
.home-cards .hall-card { grid-column: 1 / -1; }
.door-icon { font-size: 40px; line-height: 1; filter: drop-shadow(0 0 16px color-mix(in srgb, var(--door) 70%, transparent)); }
.door h2 { margin: 0; font-size: 22px; font-weight: 800; letter-spacing: .01em; }
.door p { margin: 0; color: var(--ink-dim); font-size: var(--fs-md); line-height: 1.5; }
.door-opts { display: flex; flex-direction: column; gap: 10px; margin-top: 4px; }
.door .field { margin-bottom: 0; }
.door .status-line { margin: 0; }
.door .btn.big { margin-top: auto; }
.door-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: auto; }
.door-actions .btn.big { flex: 1 1 auto; margin-top: 0; }

.btn.big {
  width: 100%;
  padding: 13px 18px;
  font-size: var(--fs-lg);
  font-weight: 800;
  letter-spacing: .04em;
  background: linear-gradient(180deg, color-mix(in srgb, var(--door, var(--accent)) 85%, #fff), color-mix(in srgb, var(--door, var(--accent)) 70%, #000));
  border-color: transparent;
  box-shadow: 0 10px 28px -10px var(--door, var(--accent));
}
.btn.big:hover { box-shadow: 0 14px 34px -10px var(--door, var(--accent)); }
.btn.big[disabled] { background: rgba(255, 255, 255, .06); box-shadow: none; }

/* segmented pickers: one pill, one lit segment */
.seg {
  display: flex;
  gap: 4px;
  padding: 4px;
  border-radius: 13px;
  background: rgba(0, 0, 0, .28);
  border: 1px solid rgba(255, 255, 255, .06);
}
.seg.faded { opacity: .38; pointer-events: none; }
.seg-btn {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 9px 6px;
  border-radius: 10px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--ink-dim);
  font-weight: 700;
  font-size: 13px;
  cursor: pointer;
  transition: background .15s, color .15s, border-color .15s;
}
.seg-btn small { font-weight: 500; font-size: 11px; color: var(--ink-faint); }
.seg-btn:hover { color: var(--ink); background: rgba(255, 255, 255, .05); }
.seg-btn.on {
  color: var(--ink);
  background: color-mix(in srgb, var(--door, var(--accent)) 24%, transparent);
  border-color: color-mix(in srgb, var(--door, var(--accent)) 60%, transparent);
}
.seg.small .seg-btn { flex-direction: row; gap: 5px; padding: 7px 8px; font-size: 12.5px; }

.invite-out { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
/* signing in, on the online door */
.auth-line { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; font-size: 12px; color: var(--ink-dim); }
.auth-hint { flex: 1 1 100%; }
.auth-who { flex: 1 1 auto; }
.auth-who b { color: var(--ink); }
.auth-btn { display: inline-flex; align-items: center; gap: 6px; }
.auth-mark { display: inline-block; width: 10px; height: 10px; border-radius: 3px; }
.auth-mark.twitch { background: #9146ff; box-shadow: 0 0 8px rgba(145, 70, 255, .7); }
.auth-mark.google { background: linear-gradient(135deg, #4285f4, #34a853 50%, #fbbc05 75%, #ea4335); }
.auth-btn.twitch { border-color: rgba(145, 70, 255, .55); }
.auth-btn.google { border-color: rgba(66, 133, 244, .45); }
/* the leaderboard under the online door: your row, then five */
.door-board { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--line); font-size: 12px; }
.board-me { color: var(--ink-dim); margin-bottom: 7px; }
.board-me b { color: #ffe9c9; font-size: 14px; font-variant-numeric: tabular-nums; }
.board-top { list-style: none; margin: 0; padding: 0; display: grid; gap: 3px; }
.board-top li {
  display: grid; grid-template-columns: 16px minmax(0, 1fr) auto auto; gap: 8px; align-items: baseline;
  padding: 4px 8px; border-radius: 8px; background: rgba(255, 255, 255, .03);
}
.board-top li.me { background: rgba(255, 214, 130, .1); box-shadow: inset 0 0 0 1px rgba(255, 214, 130, .25); }
.board-top li i { font-style: normal; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.board-top li b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.board-top li span { color: #ffe9c9; font-weight: 700; font-variant-numeric: tabular-nums; }
.board-top li small { color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.invite-out input { flex: 1 1 180px; }
.invite-out .hint { width: 100%; margin: 0; }

.home-cards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
  width: 100%;
  max-width: 1180px;
}
.rec-name { font-size: var(--fs-xl); font-weight: 800; margin-bottom: 8px; }
.rec-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; margin-bottom: 6px; }
.rec-stat { text-align: center; padding: 8px 4px; border-radius: 10px; background: rgba(255, 255, 255, .04); }
.rec-stat b { display: block; font-size: 20px; font-variant-numeric: tabular-nums; }
.rec-stat span { font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-faint); }
.badges { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; }
.badge {
  display: flex; gap: 9px; align-items: center;
  padding: 8px 10px;
  border-radius: 11px;
  background: rgba(255, 255, 255, .04);
  border: 1px solid var(--line);
}
.badge-icon { font-size: 20px; line-height: 1; }
.badge b { display: block; font-size: 12.5px; }
.badge small { display: block; color: var(--ink-faint); font-size: 11px; line-height: 1.3; }
.badge.locked { opacity: .4; filter: grayscale(1); }
.foot { color: var(--ink-faint); font-size: var(--fs-xs); text-align: center; max-width: 620px; margin: 0; }

/* ---------------- the drawer ---------------- */
/* The old setup rail, behind a gear. It slides over the left of whatever
   screen is up rather than taking a column, so the board keeps the room. */
.drawer {
  position: fixed;
  top: 0; bottom: 0; left: 0;
  width: min(380px, 94vw);
  z-index: 90;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px 14px 30px;
  overflow-y: auto;
  background: linear-gradient(180deg, #18122b, #110c1f);
  border-right: 1px solid var(--line-hot);
  box-shadow: 24px 0 70px rgba(0, 0, 0, .6);
  transform: translateX(-104%);
  transition: transform .26s cubic-bezier(.2, .8, .3, 1);
}
.app.drawer-open .drawer { transform: none; }
.rail-backdrop {
  position: fixed; inset: 0;
  z-index: 89;
  background: rgba(6, 4, 12, .55);
  backdrop-filter: blur(2px);
  opacity: 0;
  pointer-events: none;
  transition: opacity .26s;
}
.app.drawer-open .rail-backdrop { opacity: 1; pointer-events: auto; }
.rail-head { display: flex; align-items: center; justify-content: space-between; }
.rail-head h3 { margin: 0; font-size: 13px; letter-spacing: .16em; text-transform: uppercase; color: var(--ink-dim); }

/* the rules card, shut while a game is on */
.lock-note { font-size: 10px; letter-spacing: .06em; color: var(--gold); margin-left: 8px; text-transform: none; font-weight: 600; }
.card.locked > *:not(h2) { opacity: .42; }
.card.locked { border-color: rgba(255, 209, 102, .3); }

/* the layout no longer has a left column: the drawer is over it */
.layout { grid-template-columns: minmax(0, 1fr) 330px; }
.app.no-left .layout { grid-template-columns: minmax(0, 1fr) 330px; }
.app.no-right .layout, .app.no-left.no-right .layout { grid-template-columns: minmax(0, 1fr); }
.app.stream .layout, .app.stream.no-left .layout { grid-template-columns: minmax(0, 1fr) 370px; }
.app.stream.no-right .layout, .app.stream.no-left.no-right .layout { grid-template-columns: minmax(0, 1fr); }

/* ---------------- the menu ---------------- */
/* The menu is a glass card hanging off its button, with a caret to say so. */
.gamemenu {
  position: fixed;
  top: 60px; right: 18px;
  z-index: 95;
  min-width: 250px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px;
  border-radius: 16px;
  background: linear-gradient(180deg, rgba(34, 25, 60, .86), rgba(20, 14, 38, .92));
  border: 1px solid rgba(255, 255, 255, .13);
  box-shadow: 0 30px 60px -20px rgba(0, 0, 0, .8), inset 0 1px 0 rgba(255, 255, 255, .08);
  backdrop-filter: blur(18px) saturate(1.3);
  animation: menu-in .18s cubic-bezier(.2, .8, .2, 1) both;
}
.gamemenu::before {
  content: "";
  position: absolute; top: -7px; right: 28px;
  width: 12px; height: 12px;
  transform: rotate(45deg);
  background: rgba(34, 25, 60, .96);
  border-left: 1px solid rgba(255, 255, 255, .13);
  border-top: 1px solid rgba(255, 255, 255, .13);
}
@keyframes menu-in { from { opacity: 0; transform: translateY(-8px) scale(.98); } }
.gamemenu hr { border: 0; border-top: 1px solid rgba(255, 255, 255, .1); margin: 6px 6px; }
.gm-item {
  display: flex; align-items: center; gap: 10px;
  text-align: left;
  padding: 11px 14px;
  border-radius: 11px;
  border: 0;
  background: transparent;
  color: var(--ink);
  font-weight: 600;
  cursor: pointer;
  transition: background .12s, transform .12s, color .12s;
}
.gm-item:hover:not([disabled]) { background: rgba(255, 255, 255, .09); transform: translateX(2px); }
.gm-item[disabled] { opacity: .35; cursor: default; }
.gm-item.danger { color: #ff9a9a; }
.gm-item.danger:hover:not([disabled]) { background: rgba(255, 95, 95, .16); }
.gm-item.armed { background: rgba(255, 95, 95, .22); color: #ffc2c2; }

/* ---------------- toasts ---------------- */
.toasts {
  position: fixed;
  left: 50%; bottom: 26px;
  transform: translateX(-50%);
  z-index: 96;
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;
}
.toast {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto;
  column-gap: 12px;
  align-items: center;
  padding: 10px 16px;
  border-radius: 14px;
  background: linear-gradient(180deg, rgba(42, 32, 70, .82), rgba(27, 20, 48, .9));
  border: 1px solid rgba(255, 255, 255, .13);
  box-shadow: 0 20px 50px -20px rgba(0, 0, 0, .8), inset 0 1px 0 rgba(255, 255, 255, .08);
  backdrop-filter: blur(14px);
  animation: toast-in .3s cubic-bezier(.2, .9, .3, 1.3) both;
}
.toast.out { animation: toast-out .3s ease-in both; }
.toast .toast-icon { grid-row: 1 / 3; font-size: 26px; line-height: 1; }
.toast b { font-size: var(--fs-md); }
.toast small { color: var(--ink-dim); font-size: var(--fs-xs); }
@keyframes toast-in  { from { opacity: 0; transform: translateY(16px) scale(.94); } }
@keyframes toast-out { to   { opacity: 0; transform: translateY(10px); } }

/* ---------------- narrow ---------------- */
@media (max-width: 980px) {
  .doors, .home-cards { grid-template-columns: 1fr; }
  .home { padding: 22px 14px 48px; gap: 20px; }
  .topbar-live { display: none; }
  .brand-sub { display: none; }
}
@media (max-width: 1040px) {
  .layout { grid-template-columns: minmax(0, 1fr); }
  /* one column: the board fills a screen and the commentary rail follows it
     down the page, so the table station grows rather than clipping it */
  .table-station { height: auto; min-height: calc(var(--vh, 100vh) - var(--bar)); }
  .table-station .layout { flex: 0 0 auto; }
  .stage { overflow: visible; }
}

/* In a game the top bar carries the game, not the pitch: the strapline goes,
   Rules moves into the menu, and what is left fits on one row. */
.app[data-screen="game"] .brand-sub { display: none; }
.app[data-screen="game"] .brand-text { font-size: 15px; letter-spacing: 2px; }
.app[data-screen="game"] #btnRules { display: none; }
.topbar-live { min-width: 140px; }
.topbar-actions { flex-wrap: nowrap; }
@media (max-width: 1100px) {
  .app[data-screen="game"] #btnFocus, .app[data-screen="game"] #btnStream { display: none; }
}
@media (max-width: 760px) {
  .topbar-actions { flex-wrap: wrap; }
  .app[data-screen="game"] #btnRight, .app[data-screen="game"] #btnFlip { display: none; }
}

/* On a phone the bar is one row or it is a third of the screen: the glyph
   stands for the name, and the actions stay on the line. */
@media (max-width: 760px) {
  .brand-text, .brand-sub { display: none; }
  .brand-glyph { font-size: 30px; }
  .topbar { padding: 8px 12px; gap: 8px; }
  .topbar-actions { flex-wrap: nowrap; gap: 6px; }
  .topbar-actions .btn { padding: 7px 10px; font-size: 13px; white-space: nowrap; }
}

/* the pickers must not wrap a two-word label onto two lines */
.seg.small .seg-btn { white-space: nowrap; padding-left: 6px; padding-right: 6px; }

/* ============================================================
   decoration: petals, the ribbon, the vines (after the Olexify page)
   ============================================================ */
html {
  --petal-a: #a8283a; --petal-b: #c9455a; --petal-c: #6e1424;
  --ribbon: linear-gradient(90deg, #c9982f, #e8b84b 50%, #c9982f);
}
.petals { position: fixed; inset: 0; z-index: 1; pointer-events: none; overflow: hidden; }
.petals > div { position: absolute; top: 0; will-change: transform; }
.petals > div > div { will-change: transform; }
[data-petals="off"] .petals, [data-theme-petals="off"] .petals { display: none; }
@keyframes petalFall { 0% { transform: translate3d(0, -10vh, 0) rotate(0deg); } 100% { transform: translate3d(0, 112vh, 0) rotate(420deg); } }
@keyframes petalSway { 0%, 100% { transform: translateX(-22px) rotate(-14deg); } 50% { transform: translateX(22px) rotate(16deg); } }

.ribbon {
  position: relative; z-index: 3;
  align-self: stretch;
  margin: 6px -24px 10px;
  transform: rotate(-1.2deg) scale(1.02);
  overflow: hidden;
  background: var(--ribbon);
  border-top: 1px solid rgba(0, 0, 0, .28);
  border-bottom: 1px solid rgba(0, 0, 0, .28);
  box-shadow: 0 14px 34px -16px rgba(0, 0, 0, .8), inset 0 1px 0 rgba(255, 255, 255, .35);
  padding: 13px 0;
  cursor: grab; touch-action: pan-y; user-select: none;
}
.ribbon:active { cursor: grabbing; }
.ribbon-shine { position: absolute; inset: 0; pointer-events: none; background: radial-gradient(60% 120% at 50% 50%, rgba(255, 243, 214, .32), transparent 70%); }
.ribbon-shift { will-change: transform; }
.ribbon-track { display: flex; width: max-content; animation: ribbonRun 46s linear infinite; will-change: transform; }
.ribbon:hover .ribbon-track { animation-play-state: paused; }
.ribbon.held .ribbon-track { animation: none; }
.ribbon-run { display: flex; align-items: center; gap: 34px; padding-right: 34px; font-family: "Cinzel", "Nunito", serif; font-size: 14px; font-weight: 800; letter-spacing: .18em; text-transform: uppercase; color: #140d08; white-space: nowrap; }
.ribbon-run i { font-style: normal; color: rgba(70, 45, 10, .75); }
@keyframes ribbonRun { to { transform: translate3d(-50%, 0, 0); } }

.vine { position: absolute; top: 40px; height: min(62vh, 580px); width: clamp(90px, 18vw, 200px); z-index: 0; opacity: .92; pointer-events: none; overflow: visible; }
.vine-left { left: -16px; }
.vine-right { right: -16px; transform: scaleX(-1); }
.vine .rose { transform-box: fill-box; transform-origin: 50% 88%; animation: roseSway 12s ease-in-out infinite; pointer-events: auto; transition: filter .35s; cursor: pointer; }
.vine g:nth-of-type(2n) .rose { animation-duration: 14.5s; animation-delay: -3s; }
.vine g:nth-of-type(3n) .rose { animation-duration: 17s; animation-delay: -6s; }
.vine .rose:hover { filter: brightness(1.3) drop-shadow(0 0 14px rgba(201, 69, 90, .75)); }
@keyframes roseSway { 0%, 100% { transform: rotate(-2.5deg); } 50% { transform: rotate(2.5deg); } }
@media (max-width: 1100px) { .vine { display: none; } }

/* ---------------- panels that answer the hand ---------------- */
.door {
  transform: perspective(900px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
  transition: transform .18s ease-out, box-shadow .25s, border-color .25s;
  will-change: transform;
}
.door::after {
  content: "";
  position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background: radial-gradient(420px circle at var(--mx, 50%) var(--my, 50%), rgba(255, 255, 255, .11), transparent 60%);
  opacity: 0; transition: opacity .25s;
}
.door:hover::after { opacity: 1; }
.door:hover {
  box-shadow: 0 30px 60px -28px color-mix(in srgb, var(--door, var(--accent)) 70%, transparent),
    0 0 0 1px color-mix(in srgb, var(--door, var(--accent)) 40%, transparent);
}
.card { transition: border-color .2s, box-shadow .2s; }
.card:hover { border-color: var(--line-hot); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06), 0 12px 30px -22px rgba(0, 0, 0, .8); }
/* coming in as you scroll: the class is taken off again once it is in, so
   the door's own transitions are its own from then on */
.reveal { opacity: 0; transition: opacity .7s ease, transform .7s cubic-bezier(.2, .8, .2, 1); transition-delay: var(--d, 0s); }
.reveal:not(.in) { transform: translateY(26px) !important; }
.reveal.in { opacity: 1; }
.ripple {
  position: absolute; width: 12px; height: 12px; margin: -6px 0 0 -6px;
  border-radius: 50%; background: rgba(255, 255, 255, .42);
  transform: scale(0); pointer-events: none;
  animation: pressRing .55s ease-out forwards;
}
@keyframes pressRing { to { transform: scale(26); opacity: 0; } }
.ic { width: 17px; height: 17px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; flex: none; }
.btn.icon .ic { width: 18px; height: 18px; }
@media (prefers-reduced-motion: reduce) {
  .petals { display: none; }
  .door { transform: none !important; transition: none; }
  .reveal, .reveal:not(.in) { opacity: 1; transform: none !important; }
  .ripple, .vine .rose, .ribbon-track { animation: none !important; }
}

/* Decoration only moves where it can be seen. The castle's scene pauses
   once you have scrolled away from it, the ribbon and the roses wait for
   the doors, and the petals thin out over the table. */
.app:not([data-station="hero"]) .hero-station .stars,
.app:not([data-station="hero"]) .hero-station .cloud,
.app:not([data-station="hero"]) .hero-station .moon,
.app:not([data-station="hero"]) .hero-station .shooter,
.app:not([data-station="hero"]) .hero-station .bat,
.app:not([data-station="hero"]) .hero-station .bat svg,
.app:not([data-station="hero"]) .hero-station .mist,
.app:not([data-station="hero"]) .hero-station .fire .glow,
.app:not([data-station="hero"]) .hero-station .fire .flame,
.app:not([data-station="hero"]) .hero-station .fire .ember,
.app:not([data-station="hero"]) .hero-station .flies i,
.app:not([data-station="hero"]) .hero-station .win,
.app:not([data-station="hero"]) .hero-station h1,
.app:not([data-station="hero"]) .hero-station .w b { animation-play-state: paused !important; }
.app:not([data-station="home"]) .ribbon-track,
.app:not([data-station="home"]) .vine .rose { animation-play-state: paused !important; }
html[data-station="game"] .petals { opacity: .4; }
.petals { transition: opacity .6s; }

/* ---------------- the curtain ---------------- */
/* Black until a touch. A browser plays nothing before one, so the opening
   waits for it and then plays whole, with sound. It lifts on its own after
   a while for anybody who would rather watch. */
.curtain {
  position: fixed; inset: 0; z-index: 300;
  display: grid; place-items: center;
  background: radial-gradient(70% 60% at 50% 45%, #14101f, #05040a 70%);
  cursor: pointer;
  user-select: none;
  transition: opacity .7s ease;
}
.curtain.out { opacity: 0; pointer-events: none; }
.curtain-in { text-align: center; color: #cbbfe6; animation: curtainIn 1.2s ease-out both; }
@keyframes curtainIn { from { opacity: 0; transform: translateY(10px); } }
.curtain-glyph {
  display: block; font-size: clamp(64px, 11vmin, 190px); line-height: 1; margin-bottom: clamp(18px, 3vmin, 48px);
  color: var(--accent, #a970ff);
  text-shadow: 0 0 30px color-mix(in srgb, var(--accent, #a970ff) 70%, transparent);
  animation: curtainBreathe 2.8s ease-in-out infinite;
}
@keyframes curtainBreathe { 50% { transform: translateY(-4px) scale(1.04); text-shadow: 0 0 44px var(--accent, #a970ff); } }
.curtain h2 { margin: 0 0 clamp(22px, 3.4vmin, 52px); font-size: clamp(22px, 3.6vmin, 64px); letter-spacing: .22em; font-weight: 800; }
.curtain h2 span { -webkit-background-clip: text; background-clip: text; color: transparent; }
.curtain h2 span:nth-child(1) { background-image: linear-gradient(92deg, #fff, var(--accent, #a970ff)); }
.curtain h2 span:nth-child(2) { background-image: linear-gradient(92deg, var(--cyan, #5ee0ff), #fff); }
.curtain h2 span:nth-child(3) { background-image: linear-gradient(92deg, var(--accent-2, #ff8a4c), #ffd166); }
.curtain-go { margin: 0; font-size: clamp(14px, 2vmin, 34px); letter-spacing: .24em; text-transform: uppercase; color: #fff; animation: curtainPulse 2.2s ease-in-out infinite; }
@keyframes curtainPulse { 50% { opacity: .45; } }
@media (prefers-reduced-motion: reduce) { .curtain-in, .curtain-glyph, .curtain-go { animation: none; } }

/* ---------------- type ---------------- */
/* Cinzel where something is being announced; Nunito for everything read. */
.hero h1, .brand-text, .curtain h2, .door h2, .banner h3, .rec-name, .draft-head h3, .pb-name { font-family: "Cinzel", "Nunito", serif; }
.hero h1 { letter-spacing: .06em; }
.brand-text { letter-spacing: 2.4px; }
.pb-name { letter-spacing: .02em; }
.door h2 { letter-spacing: .04em; }
.card h2 { letter-spacing: .12em; }

/* the bar's buttons on one line, the doors and the cards never wider than the
   page, and nothing - a rotated ribbon included - scrolling it sideways */
.topbar-actions .btn { white-space: nowrap; }
.topbar-live { min-width: 0; }
.doors, .home-cards, .foot { min-width: 0; max-width: min(1180px, 100%); }
.home { overflow-x: clip; }   /* the rotated ribbon must not widen the page; clip is not a scroll container, so sticky stays sticky */
.ribbon + .vine { top: 90px; }

/* the shared art lives in an svg with no size, but an inline svg still gets a line of its own above the app */
.defs { position: absolute; width: 0; height: 0; overflow: hidden; }

/* ---------------- battle royale ---------------- */
.royale {
  width: 100%;
  max-width: calc(var(--board-fit, 640px) + 20px);
  padding: 9px 14px 10px;
  border-radius: 11px;
  background: color-mix(in srgb, var(--gold) 12%, rgba(0, 0, 0, .25));
  border: 1px solid color-mix(in srgb, var(--gold) 45%, transparent);
  font-size: 13px;
  display: grid; gap: 6px;
}
.royale .ry-top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.royale .ry-top b { font-family: var(--font-display, inherit); letter-spacing: .5px; color: var(--gold); }
.royale .ry-count { font-variant-numeric: tabular-nums; min-width: 9ch; }
.royale .ry-orders { color: var(--ink-faint); }
.royale .ry-top .btn { margin-left: auto; }
.royale .ry-bar { height: 5px; border-radius: 999px; background: rgba(255, 255, 255, .08); overflow: hidden; }
.royale .ry-bar i { display: block; height: 100%; width: 100%; background: linear-gradient(90deg, var(--gold), var(--accent-2)); transform-origin: left; }
.royale .ry-bar.hot i { background: linear-gradient(90deg, var(--bad), var(--gold)); }
.royale .ry-chat { color: var(--ink-faint); font-size: 12px; display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.royale .ry-chat b { color: var(--ink); font-weight: 700; }
.royale .ry-who { display: inline-flex; align-items: center; gap: 3px; padding: 1px 7px; border-radius: 999px; background: rgba(255, 255, 255, .06); }
.royale .ry-who i { font-style: normal; opacity: .8; }
/* the streak of a move: a bar from the old square, turned to point at the new */
.fx-dash {
  height: 5px; transform-origin: left center; transform: translate(0, -50%) rotate(var(--ang));
  border-radius: 999px; background: linear-gradient(90deg, transparent, var(--dash)); opacity: 0;
  box-shadow: 0 0 10px var(--dash); animation: fxDash .5s ease-out forwards;
}
@keyframes fxDash { 0% { opacity: 0; clip-path: inset(0 100% 0 0); } 30% { opacity: 1; clip-path: inset(0 0 0 0); } 100% { opacity: 0; } }
/* the square a piece has just landed on */
.sq.landed::after { content: ""; position: absolute; inset: 0; border-radius: 6px; box-shadow: inset 0 0 0 3px var(--gold); animation: ryLanded .7s ease-out forwards; pointer-events: none; z-index: 3; }
.sq.landed.took::after { box-shadow: inset 0 0 0 3px var(--bad); }
@keyframes ryLanded { 0% { opacity: 1; transform: scale(1.06); } 100% { opacity: 0; transform: scale(1); } }
/* solo: the piece that is you */
.sq.me::before { content: ""; position: absolute; inset: 6%; border-radius: 50%; border: 3px solid var(--cyan); box-shadow: 0 0 14px -2px var(--cyan); pointer-events: none; z-index: 2; animation: ryMe 1.6s ease-in-out infinite; }
@keyframes ryMe { 50% { opacity: .45; } }
.sq.spent { filter: saturate(.6) brightness(.85); }
.royale .ry-kills { display: flex; gap: 6px; flex-wrap: wrap; font-size: 12px; color: var(--ink-faint); }
.royale .ry-kills b { color: var(--gold); }
.royale .ry-last { font-size: 12px; color: var(--ink); }
.royale.rang { animation: ryRang .5s ease-out; }
@keyframes ryRang { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--gold) 70%, transparent); } 100% { box-shadow: 0 0 0 14px transparent; } }
/* an order on the board: the piece that holds one, and where it is going */
.sq.ordered { box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--gold) 75%, transparent); }
.sq.order-to::before {
  content: ""; position: absolute; inset: 28%; border-radius: 50%;
  border: 3px solid var(--gold); box-shadow: 0 0 10px -2px var(--gold); pointer-events: none; z-index: 3;
}
.sq.order-to.grab::before, .sq.order-to.move::before { inset: 22%; }

/* ---------------- what a unit wears ---------------- */
.pc-cos { position: absolute; top: -7%; left: 50%; width: 52%; transform: translateX(-50%); line-height: 0; pointer-events: none; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .7)); z-index: 3; }
.pc-cos svg { width: 100%; height: auto; display: block; overflow: visible; }
.jy-cos { display: inline-block; line-height: 0; vertical-align: middle; }
.jy-cos svg { width: 100%; height: auto; }

/* ---------------- the quest card, on the front door ---------------- */
.quest {
  display: grid; grid-template-columns: 120px 1fr auto; gap: 20px; align-items: center;
  width: 100%; max-width: min(1180px, 100%); margin: 6px 0 18px; padding: 20px 22px;
  border-radius: 22px; position: relative; overflow: hidden;
  background: linear-gradient(135deg, rgba(64, 44, 18, .55), rgba(26, 20, 42, .9));
  border: 1px solid color-mix(in srgb, var(--gold) 45%, transparent);
  box-shadow: var(--shadow), 0 0 40px -14px var(--gold);
  backdrop-filter: blur(10px);
}
.quest::before { content: ""; position: absolute; inset: -40% auto auto -10%; width: 60%; height: 180%; background: radial-gradient(circle, color-mix(in srgb, var(--gold) 22%, transparent), transparent 60%); pointer-events: none; }
.quest-art { width: 120px; height: 120px; border-radius: 50%; display: grid; place-items: center; font-size: 60px; background: radial-gradient(circle at 40% 35%, #3a2d14, #1b1530 70%); box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--gold) 50%, transparent), 0 10px 30px -10px #000; }
.quest-body { position: relative; min-width: 0; }
.quest-body small { letter-spacing: 3px; text-transform: uppercase; font-size: 11px; color: var(--gold); }
.quest-body h2 { margin: 2px 0 6px; font-family: "Cinzel", "Nunito", serif; font-size: 28px; letter-spacing: 1px; }
.quest-body p { margin: 0 0 10px; color: var(--ink-dim); max-width: 70ch; line-height: 1.5; }
.quest-progress { height: 8px; border-radius: 999px; background: rgba(255, 255, 255, .08); overflow: hidden; max-width: 460px; }
.quest-progress i { display: block; height: 100%; background: linear-gradient(90deg, var(--accent-2), var(--gold)); transition: width .6s; }
.quest-purse { margin-top: 8px; font-size: 13px; color: var(--ink-faint); }
.quest-actions { display: flex; flex-direction: column; gap: 10px; min-width: 200px; position: relative; }
.quest-actions .btn.big, .jy-actions .btn.big { padding: 12px 20px; font-size: 15px; font-weight: 800; }

/* ---------------- the journey: a page over the page ---------------- */
.journey { position: fixed; inset: 0; z-index: 200; display: grid; grid-template-rows: 58px 1fr; color: var(--ink); background: radial-gradient(120% 80% at 50% 0%, #1a1230, #0c0a14 70%); animation: banner-in .25s ease-out both; outline: none; }
html.journey-open { overflow: hidden; }
.jy-bar { display: flex; align-items: center; gap: 14px; padding: 0 16px; border-bottom: 1px solid var(--line); background: rgba(12, 10, 20, .72); backdrop-filter: blur(10px); }
.jy-title { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
.jy-title b { font-family: "Cinzel", "Nunito", serif; font-size: 18px; letter-spacing: 1px; color: var(--gold); white-space: nowrap; }
.jy-title span { font-size: 12px; color: var(--ink-faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.jy-tabs { display: flex; gap: 4px; margin-left: auto; }
.jy-tab { padding: 7px 14px; border-radius: 999px; border: 1px solid var(--line); background: transparent; color: var(--ink-dim); cursor: pointer; font: inherit; font-weight: 700; white-space: nowrap; }
.jy-tab:hover { color: var(--ink); border-color: var(--line-hot); }
.jy-tab.on { background: color-mix(in srgb, var(--gold) 18%, transparent); color: var(--ink); border-color: color-mix(in srgb, var(--gold) 55%, transparent); }
.jy-purse { display: flex; gap: 10px; font-variant-numeric: tabular-nums; color: var(--gold); font-weight: 800; white-space: nowrap; }
.jy-body { position: relative; min-height: 0; }
.jy-map, .jy-army, .jy-vault { position: absolute; inset: 0; overflow: auto; overscroll-behavior: contain; }
.jy-army, .jy-vault { padding: 24px max(24px, calc((100% - 1180px) / 2)) 60px; }
.jy-svg { display: block; width: min(100%, 1100px); height: auto; margin: 0 auto; }
.jy-ch-num { font: 800 14px "Nunito", sans-serif; letter-spacing: 3px; text-transform: uppercase; fill: rgba(255, 255, 255, .55); }
.jy-ch-name { font: 700 34px "Cinzel", "Nunito", serif; fill: #fff; paint-order: stroke; stroke: rgba(0, 0, 0, .45); stroke-width: 6px; }
.jy-ch-region { font: 15px "Nunito", sans-serif; fill: rgba(255, 255, 255, .62); }
.jy-ch-icon { font-size: 24px; }
.jy-chapter.locked { opacity: .35; }
.jy-fog { fill: #07060c; opacity: .8; }
.jy-fog-text { fill: rgba(255, 255, 255, .3); font: 700 28px "Cinzel", serif; text-anchor: middle; letter-spacing: 6px; }
.jy-road { fill: none; stroke: rgba(255, 255, 255, .26); stroke-width: 7; stroke-dasharray: 10 14; stroke-linecap: round; }
.jy-road-done { fill: none; stroke: var(--gold); stroke-width: 7; stroke-linecap: round; filter: url(#jy-glow); }
.jy-node { cursor: pointer; outline: none; }
.jy-node .jy-ring { fill: #1b1530; stroke: rgba(255, 255, 255, .35); stroke-width: 3; transition: stroke-width .15s; }
.jy-node.open .jy-ring { stroke: var(--gold); fill: #2a2040; }
.jy-node.won .jy-ring { stroke: var(--good); fill: #14261b; }
.jy-node.locked { opacity: .55; cursor: default; }
.jy-node.locked .jy-ring { stroke: rgba(255, 255, 255, .2); fill: #120f1c; }
.jy-node:not(.locked):hover .jy-ring, .jy-node:focus-visible .jy-ring { stroke-width: 5; filter: url(#jy-glow); }
.jy-icon { font-size: 28px; text-anchor: middle; }
.jy-node.boss .jy-icon { font-size: 36px; }
.jy-crown { font-size: 22px; text-anchor: middle; }
.jy-label { font: 800 15px "Nunito", sans-serif; fill: #fff; text-anchor: middle; paint-order: stroke; stroke: rgba(0, 0, 0, .75); stroke-width: 4px; }
.jy-stars { font-size: 15px; fill: var(--gold); text-anchor: middle; letter-spacing: 2px; }
.jy-pulse { fill: none; stroke: var(--gold); stroke-width: 3; transform-box: fill-box; transform-origin: center; animation: jyPulse 1.8s ease-out infinite; }
@keyframes jyPulse { 0% { opacity: .9; transform: scale(.8); } 100% { opacity: 0; transform: scale(1.6); } }
.jy-token-ring { fill: var(--cyan); stroke: #fff; stroke-width: 3; filter: url(#jy-glow); }
.jy-token-glyph { font-size: 22px; text-anchor: middle; }
.jy-panel { position: absolute; top: 58px; right: 0; bottom: 0; width: min(580px, 100%); overflow: auto; overscroll-behavior: contain; z-index: 2; background: linear-gradient(180deg, rgba(34, 25, 60, .96), rgba(20, 14, 38, .985)); border-left: 1px solid var(--line-hot); box-shadow: -30px 0 60px -30px #000; animation: jyIn .25s cubic-bezier(.2, .8, .2, 1) both; }
@keyframes jyIn { from { transform: translateX(30px); opacity: 0; } }
.jy-brief { padding: 22px 24px 44px; }
.jy-head { display: flex; gap: 16px; align-items: center; }
.jy-foe { flex: none; width: 72px; height: 72px; border-radius: 50%; display: grid; place-items: center; font-size: 36px; background: color-mix(in srgb, var(--c) 22%, #1b1530); box-shadow: 0 0 0 3px color-mix(in srgb, var(--c) 60%, transparent), 0 0 30px -6px var(--c); }
.jy-head h2 { margin: 2px 0; font-family: "Cinzel", "Nunito", serif; font-size: 24px; line-height: 1.15; }
.jy-head small { letter-spacing: 2px; text-transform: uppercase; font-size: 11px; color: var(--ink-faint); }
.jy-head p { margin: 0; color: var(--ink-dim); }
.jy-dialogue { display: grid; gap: 8px; margin: 18px 0; }
.jy-line { display: grid; grid-template-columns: 34px 1fr; column-gap: 10px; align-items: start; padding: 10px 12px; border-radius: 12px; background: rgba(255, 255, 255, .05); border-left: 3px solid var(--c); animation: jyLine .4s ease-out both; animation-delay: calc(var(--i) * .16s); }
.jy-line i { font-style: normal; font-size: 22px; grid-row: 1 / 3; line-height: 1.2; }
.jy-line b { color: var(--c); font-size: 11px; letter-spacing: 1.5px; text-transform: uppercase; }
.jy-line span { grid-column: 2; font-size: 15px; line-height: 1.45; }
@keyframes jyLine { from { opacity: 0; transform: translateY(6px); } }
.jy-facts { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 14px 0; }
.jy-facts > div { padding: 10px 12px; border-radius: 10px; background: rgba(0, 0, 0, .25); border: 1px solid var(--line); min-width: 0; }
.jy-facts small { display: block; font-size: 11px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--ink-faint); margin-bottom: 2px; }
.jy-facts b { font-size: 14px; font-weight: 700; }
.jy-diff { color: var(--accent-2); letter-spacing: 2px; }
.jy-brief h3 { font-family: "Cinzel", "Nunito", serif; font-size: 15px; letter-spacing: 1px; margin: 16px 0 8px; color: var(--gold); }
.jy-edges { list-style: none; padding: 0; margin: 0; display: grid; gap: 6px; }
.jy-edges li { padding: 8px 12px 8px 34px; position: relative; background: color-mix(in srgb, var(--good) 10%, transparent); border: 1px solid color-mix(in srgb, var(--good) 35%, transparent); border-radius: 10px; font-size: 14px; }
.jy-edges li::before { content: "✦"; position: absolute; left: 12px; color: var(--good); }
.jy-prize li { background: color-mix(in srgb, var(--gold) 10%, transparent); border-color: color-mix(in srgb, var(--gold) 40%, transparent); display: flex; align-items: center; gap: 6px; }
.jy-prize li::before { color: var(--gold); }
.jy-none { color: var(--ink-faint); font-style: italic; }
.jy-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 20px; }
.jy-starline { font-size: 32px; color: var(--gold); letter-spacing: 4px; display: flex; align-items: baseline; gap: 12px; margin: 12px 0 4px; }
.jy-starline small { font-size: 13px; color: var(--ink-faint); letter-spacing: 0; }
.jy-outcome.won .jy-head h2 { color: var(--gold); }
.jy-outcome.lost .jy-head h2 { color: var(--bad); }
.jy-army-line { color: var(--ink-dim); margin: 10px 0 0; }
.jy-army-head { display: flex; justify-content: space-between; gap: 20px; align-items: flex-end; flex-wrap: wrap; margin-bottom: 18px; }
.jy-army-head h2 { font-family: "Cinzel", "Nunito", serif; font-size: 26px; margin: 0 0 6px; color: var(--gold); }
.jy-army-head p { margin: 0; color: var(--ink-dim); max-width: 64ch; }
.jy-army-stats { display: flex; gap: 10px; flex-wrap: wrap; }
.jy-army-stats span { padding: 8px 12px; border-radius: 10px; background: rgba(0, 0, 0, .25); border: 1px solid var(--line); font-size: 13px; color: var(--ink-dim); white-space: nowrap; }
.jy-army-stats b { color: var(--ink); font-size: 16px; margin-right: 4px; }
.jy-units { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: 12px; }
.unit { display: grid; grid-template-columns: 72px 1fr; gap: 12px; padding: 12px; border-radius: 14px; background: var(--panel); border: 1px solid var(--line); box-shadow: var(--shadow-2); min-width: 0; }
.unit.named { border-color: color-mix(in srgb, var(--gold) 45%, transparent); }
.unit.shamed { border-color: color-mix(in srgb, var(--bad) 40%, transparent); }
.unit-art.piece { position: relative; width: 72px; height: 72px; font-size: 54px; border-radius: 12px; background: radial-gradient(circle at 50% 60%, rgba(255, 255, 255, .08), transparent 70%); }
.unit-art.piece .pc { width: 80%; height: 80%; }
.unit-body { min-width: 0; }
.unit-body h3 { margin: 0; font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.unit-body small { color: var(--ink-faint); font-size: 12px; }
.unit-bar { height: 5px; border-radius: 999px; background: rgba(255, 255, 255, .08); margin: 6px 0; overflow: hidden; }
.unit-bar i { display: block; height: 100%; background: linear-gradient(90deg, var(--accent), var(--gold)); }
.unit-rec { margin: 4px 0; font-size: 12px; color: var(--ink-dim); }
.unit-nums { display: flex; gap: 10px; font-size: 12px; color: var(--ink-faint); }
.unit-tools { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-top: 6px; }
.unit-tools select, .unit-tools input { min-width: 0; width: 100%; font-size: 12px; padding: 5px 7px; }
.jy-chest { display: grid; grid-template-columns: 96px 1fr; gap: 16px; align-items: center; padding: 16px; border-radius: 16px; background: var(--panel); border: 1px solid var(--line); margin-bottom: 18px; }
.jy-chest-art { font-size: 64px; text-align: center; filter: grayscale(.8); opacity: .6; }
.jy-chest-art.has { filter: none; opacity: 1; animation: jyWobble 2.2s ease-in-out infinite; }
@keyframes jyWobble { 0%, 100% { transform: rotate(-3deg); } 50% { transform: rotate(3deg); } }
.jy-chest h3 { margin: 0 0 4px; }
.jy-chest p { margin: 0 0 10px; color: var(--ink-dim); }
.jy-reveal { grid-column: 1 / -1; }
.jy-reveal-in { display: flex; align-items: center; gap: 14px; padding: 12px 16px; border-radius: 12px; background: rgba(0, 0, 0, .3); border: 1px solid var(--line-hot); animation: toast-in .4s cubic-bezier(.2, .9, .3, 1.3) both; }
.jy-reveal-in b { font-size: 18px; }
.jy-reveal-in small { color: var(--ink-faint); }
.jy-reveal-in.legendary { box-shadow: 0 0 40px -8px var(--gold); border-color: var(--gold); }
.jy-h { font-family: "Cinzel", "Nunito", serif; color: var(--gold); letter-spacing: 1px; margin: 18px 0 10px; }
.jy-cats { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 12px; }
.jy-cat { display: grid; grid-template-columns: 56px 1fr; gap: 12px; padding: 12px; border-radius: 14px; background: var(--panel); border: 1px solid var(--line); opacity: .7; }
.jy-cat.owned { opacity: 1; border-color: color-mix(in srgb, var(--rar) 50%, transparent); }
.jy-cat h4 { margin: 0; font-size: 14px; }
.jy-cat .rar { font-size: 11px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--rar); }
.jy-cat.common { --rar: #9fd3a8; } .jy-cat.rare { --rar: #6fb8ff; } .jy-cat.epic { --rar: #c079ff; } .jy-cat.legendary { --rar: #ffd166; }
.jy-cat p { margin: 4px 0 0; font-size: 12px; color: var(--ink-dim); }
.jy-cat .jy-cat-state { color: var(--ink-faint); }
.jy-note { color: var(--ink-faint); font-size: 12px; margin-top: 14px; max-width: 80ch; }
@media (max-width: 860px) {
  .quest { grid-template-columns: 1fr; }
  .quest-art { display: none; }
  .jy-panel { width: 100%; }
  .jy-facts { grid-template-columns: 1fr; }
  .jy-title span { display: none; }
}

/* ---------------- the last ten seconds, on the board ---------------- */
.countdown {
  position: absolute; inset: 0; display: grid; place-items: center; pointer-events: none; z-index: 60;
  font-family: "Cinzel", "Nunito", serif; font-weight: 900; font-size: clamp(110px, 28vmin, 260px); line-height: 1;
  color: #fff; opacity: 0;
  text-shadow: 0 0 40px rgba(255, 100, 100, .9), 0 0 90px rgba(255, 60, 60, .5), 0 4px 0 rgba(0, 0, 0, .45);
}
.countdown.pop { animation: cdPop 1s cubic-bezier(.2, .9, .3, 1) both; }
@keyframes cdPop {
  0%   { opacity: 0; transform: scale(1.7); }
  14%  { opacity: .95; transform: scale(1); }
  70%  { opacity: .8; transform: scale(.98); }
  100% { opacity: 0; transform: scale(.9); }
}
.countdown.last { color: #ffb8b8; text-shadow: 0 0 60px #ff3b3b, 0 0 120px rgba(255, 59, 59, .6), 0 4px 0 rgba(0, 0, 0, .5); }
@media (prefers-reduced-motion: reduce) {
  .countdown.pop { animation: cdFade 1s linear both; }
}
@keyframes cdFade { 0% { opacity: .9; } 100% { opacity: 0; } }
