/* ============================================================
   BLOCK PARTY — front-end UI
   Two faces and nothing else: FinkHeavy (the Animal Crossing logo
   face) for the title, and Animal Crossing's own in-game bitmap font,
   set from the game's glyph sheet, for every other word. See
   web/src/bitmap.ts.

   Screens are being rebuilt one at a time, starting with the title.
   The others run on placeholder.css until their turn.
   ============================================================ */

@font-face {
  font-family: 'Fink';
  src: url('fonts/FinkHeavy.ttf') format('truetype');
  font-display: block;
}

:root {
  --paper: #f3e9d2;
  --gold: #ffd94a;
  --gold-deep: #e8a92c;
  --bark: #4d2e0c;
  --orange: #ff9226;
  --leaf: #2e6b2a;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;
  background: #f3e9d2;
  font-family: system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}

#bp-stage { position: fixed; inset: 0; }

#unity-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  background: #8fd0f0;
}

/* The living title backdrop: Unity drifts a camera around a freshly
   generated arena behind the sign, and this is the depth-of-field that
   keeps the title readable over it. 2px measures free in Chrome on a
   full-screen canvas; anything more starts to cost frames. */
#unity-canvas.title-live { filter: blur(2px) saturate(1.05); }

/* Impact frames (match.ts impact). A plain hit: a comic smack at the point
   of contact — a jagged white star with an ink rim and strokes flying off —
   popping in and gone in a sixth of a second. A home run: that, bigger, and
   a beat of the 3D in stark black and white with thin speed lines (just
   over the canvas, under the HUD). */
#unity-canvas.is-impact-b { filter: grayscale(1) contrast(4); }
.bp-smack {
  position: absolute;
  pointer-events: none;
  animation: bp-smack var(--ms, 170ms) cubic-bezier(.2, 1.4, .4, 1) both;
}
.bp-smack svg, .bp-speed svg { display: block; }
.bp-smack-star { fill: #fffdf4; stroke: #2b1706; stroke-linejoin: round; }
.bp-smack-strokes line { stroke: #2b1706; stroke-linecap: round; }
@keyframes bp-smack {
  0% { scale: .5; opacity: 1; }
  45% { scale: 1.08; opacity: 1; }
  100% { scale: 1; opacity: 0; }
}
.bp-speed { position: absolute; inset: 0; pointer-events: none; }
.bp-speed polygon { fill: rgba(20, 12, 4, .7); }

/* The hit marker (match.ts hitMark): four ticks round where the hit landed,
   snapped on big and settling, then gone. Each tick is turned onto its
   diagonal and pushed out along it, so the middle stays open. */
.bp-hitmark {
  position: absolute;
  width: 0; height: 0;
  pointer-events: none;
  z-index: 6;
  animation: bp-hitmark .55s ease-out both;
}
.bp-hitmark i {
  position: absolute;
  left: -8px; top: -2.5px;
  width: 16px; height: 5px;
  border-radius: 2px;
  background: #fffdf4;
  box-shadow: 0 0 0 2px #2b1706;
}
.bp-hitmark-ne { transform: rotate(-45deg) translateX(17px); }
.bp-hitmark-se { transform: rotate(45deg) translateX(17px); }
.bp-hitmark-sw { transform: rotate(135deg) translateX(17px); }
.bp-hitmark-nw { transform: rotate(-135deg) translateX(17px); }
.bp-hitmark.is-ko i { background: #ff3b2f; }
@keyframes bp-hitmark {
  0%   { scale: 1.6; opacity: 1; }
  14%  { scale: 1; opacity: 1; }
  70%  { scale: 1; opacity: 1; }
  100% { scale: .92; opacity: 0; }
}

/* The MLG callout (match.ts streak): slammed in tilted, shaking, and from a
   triple up cycling through every colour, then away up the screen. */
.bp-streak {
  position: fixed; left: 50%; top: 28%; z-index: 61;
  pointer-events: none;
  transform: translate(-50%, -50%) rotate(-7deg);
  animation: bp-streak 2.3s cubic-bezier(.2, 1.5, .4, 1) both;
}
.bp-streak > .bf { animation: bp-streak-shake .09s steps(2) infinite; }
.bp-streak.is-big > .bf { animation: bp-streak-shake .07s steps(2) infinite, bp-streak-hue .6s linear infinite; }
.bp-streak.is-calm > .bf { animation: none; }
@keyframes bp-streak {
  0%   { transform: translate(-50%, -50%) rotate(-16deg) scale(3.2); opacity: 0; }
  10%  { transform: translate(-50%, -50%) rotate(-5deg) scale(.92); opacity: 1; }
  16%  { transform: translate(-50%, -50%) rotate(-7deg) scale(1); }
  82%  { transform: translate(-50%, -58%) rotate(-7deg) scale(1); opacity: 1; }
  100% { transform: translate(-50%, -80%) rotate(-7deg) scale(.86); opacity: 0; }
}
@keyframes bp-streak-shake {
  0%   { translate: 2px -2px; }
  50%  { translate: -2px 2px; }
}
@keyframes bp-streak-hue {
  from { filter: hue-rotate(0deg); }
  to   { filter: hue-rotate(360deg); }
}

/* The cut to a new map behind the title: a slow dip to black over the 3D
   layer only — z-index 1 puts it above the canvas and below every 2D page
   (z-index 20), so the sign and the menu never blink. The camera flies
   between shots on one map; only a new map needs hiding. */
#bp-dip {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: #0b0704;
  opacity: 0;
  pointer-events: none;
}
#bp-dip.is-cut { animation: bp-dip 1.4s ease-in-out; }
@keyframes bp-dip { 0% { opacity: 0; } 42%, 58% { opacity: 1; } 100% { opacity: 0; } }

/* Full-presentation veil for scene changes. Unlike the showcase dip, this
   sits above the DOM UI as well as the Unity canvas. */
#bp-scene-fade {
  position: absolute;
  inset: 0;
  z-index: 100;
  background: #000;
  opacity: 0;
  pointer-events: none;
  transition: opacity .42s ease-in-out;
}
#bp-scene-fade.is-active { opacity: 1; pointer-events: auto; }

/* The overlay never eats input unless a control asks for it. Its size is
   the player's Interface size (screen.ts sets --ui-zoom; Auto shrinks it on
   a small screen). The victory page sizes itself to the window. */
#bp-ui {
  position: absolute;
  /* Clear of a phone's notch and rounded corners (viewport-fit=cover lets
     the 3D run under them; the words stay out). 0 everywhere else. */
  inset: env(safe-area-inset-top, 0) env(safe-area-inset-right, 0) env(safe-area-inset-bottom, 0) env(safe-area-inset-left, 0);
  pointer-events: none;
  overflow: hidden;
  zoom: var(--ui-zoom, 1);
}
#bp-ui > * { pointer-events: auto; }

/* ============================================================
   THE BITMAP FONT
   One box per glyph, the atlas masked down to that glyph and filled
   with the current colour. --px is device pixels per sheet pixel; the
   atlas is baked at 4x so px=4 is pixel-exact.
   ============================================================ */

.bf {
  position: relative;
  display: inline-block;
  height: calc(16px * var(--px));
  width: calc(var(--w) * 1px * var(--px));
  line-height: 0;
  vertical-align: top;
  white-space: nowrap;
}
.bf-run {
  position: absolute;
  left: 0; top: 0;
  height: 100%;
  display: flex;
  color: inherit;
  transform: translate(calc(var(--dx, 0) * 1px * var(--px)), calc(var(--dy, 0) * 1px * var(--px)));
}
.bf-g {
  display: block;
  height: 100%;
  width: calc(var(--gw) * 1px * var(--px));
  margin-right: calc(var(--t) * 1px * var(--px));
  background: currentColor;
  -webkit-mask-image: url('art/ui-font.png');
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-size: calc(var(--aw) * 1px * var(--px)) calc(var(--ah) * 1px * var(--px));
  -webkit-mask-position: calc(var(--gx) * -1px * var(--px)) calc(var(--gy) * -1px * var(--px));
  mask-image: url('art/ui-font.png');
  mask-repeat: no-repeat;
  mask-size: calc(var(--aw) * 1px * var(--px)) calc(var(--ah) * 1px * var(--px));
  mask-position: calc(var(--gx) * -1px * var(--px)) calc(var(--gy) * -1px * var(--px));
}

/* ============================================================
   TITLE SCREEN — a blank page: the name, and Press START!
   ============================================================ */

.title {
  position: absolute;
  inset: 0;
  z-index: 20;
  cursor: pointer;
  overflow: hidden;
  /* The sign and the menu share the page's height (cq units: the page's
     own, whatever the interface size): the sign gives up size on a short
     screen so the menu always has its room under it — a Steam Deck's 800
     lost the last row. On a tall one it is the sign it always was. */
  container-type: size;
  --logo-top: clamp(40px, 14cqh, 120px);
  /* The menu's own height and a margin under it. */
  --menu-room: 336px;
  --sign-h: max(120px, min(506px, 48cqh, 44cqw, calc(100cqh - var(--logo-top) - var(--menu-room) - 12px)));
}

/* The drawn backdrop. It is the page until the live arena has been
   generated (and the whole page whenever there is no 3D at all), then it
   fades out from under the sign. */
.title-paper {
  position: absolute;
  inset: 0;
  background: radial-gradient(90% 70% at 50% 38%, #fbf5e6 0%, var(--paper) 60%, #e9dcc0 100%);
  transition: opacity .8s ease;
  pointer-events: none;
}
.title.is-live .title-paper { opacity: 0; }

/* Live: a soft vignette so the corners settle down, and a dark bloom
   under the sign so the painted wood keeps its edges over grass. */
.title.is-live {
  background:
    radial-gradient(120% 95% at 50% 42%, rgba(26, 15, 4, 0) 42%, rgba(26, 15, 4, .42) 100%);
}
.title.is-live .title-logo {
  background: radial-gradient(52% 78% at 50% 50%, rgba(24, 14, 3, .46), rgba(24, 14, 3, 0) 72%);
}
.title.is-live .title-sign { filter: drop-shadow(0 6px 10px rgba(20, 11, 2, .55)); }
.title.is-live .title-face { text-shadow: 0 4px 10px rgba(20, 11, 2, .6); }

/* --- the name: the painted sign, or FinkHeavy ------------------- */

.title-sign {
  display: block;
  /* 1.62:1 image, as tall as the page can spare (--sign-h, on .title). */
  height: var(--sign-h);
  width: auto;
  user-select: none;
}

.title-logo {
  position: absolute;
  left: 0; right: 0;
  top: var(--logo-top);
  display: flex;
  flex-direction: column;
  align-items: center;
  pointer-events: none;
}
.title-logo.is-fresh { animation: title-logo-in .8s cubic-bezier(.2, 1.3, .4, 1) both; }
@keyframes title-logo-in { from { translate: 0 -20px; scale: .9; opacity: 0; } }
.title-line {
  position: relative;
  font-family: 'Fink', sans-serif;
  font-size: clamp(76px, 15vw, 176px);
  line-height: .9;
  letter-spacing: .01em;
}
.title-line + .title-line { margin-top: .08em; }
.title-word { display: flex; white-space: nowrap; }
/* Two copies on one spot: the bark outline under the gold face (a
   stroke painted under a fill is unreliable in browsers). */
.title-bark {
  position: absolute; left: 0; top: 0;
  color: var(--bark);
  -webkit-text-stroke: .12em var(--bark);
  text-shadow: 0 .07em 0 #2b1706;
}
.title-face { position: relative; color: var(--gold); }
.title-letter {
  display: inline-block;
  transform: rotate(calc((var(--i) - 2.5) * 1.2deg)) translateY(calc((var(--i) % 2) * .03em));
}

/* --- prompt and menu ------------------------------------------ */

.title-prompt {
  position: absolute;
  left: 0; right: 0;
  margin: 0 auto;
  width: fit-content;
  top: clamp(calc(var(--logo-top) + var(--sign-h) + 24px), 68cqh, calc(100cqh - 90px));
  pointer-events: none;
  /* A soft pulse, not a hard blink: never caught fully off. */
  animation: title-blink 1.4s ease-in-out infinite;
}
@keyframes title-blink { 50% { opacity: .35; } }

.title-menu {
  position: absolute;
  left: 0; right: 0;
  margin: 0 auto;
  width: fit-content;
  /* Under the sign, where it always sat on a tall screen (64%), but never
     so low its last row leaves the page. */
  top: clamp(calc(var(--logo-top) + var(--sign-h) + 12px), 64cqh, calc(100cqh - var(--menu-room)));
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* The pointer's column: every row keeps it, only the chosen row shows it,
   so moving the choice never changes the menu's width. */
.title-mark { visibility: hidden; }
.title-item.is-on .title-mark { visibility: visible; }
.title-item {
  display: block;
  background: none;
  border: 0;
  padding: 4px 10px;
  cursor: pointer;
  text-align: left;
}
.title-item.is-on { animation: title-nudge .9s ease-in-out infinite; }
@keyframes title-nudge { 50% { transform: translateX(6px); } }

/* --- how to play ----------------------------------------------- */

.title-help {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: rgba(60, 40, 16, .28);
  cursor: default;
}
.title-help-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 26px 30px 22px;
  background: #fffbf0;
  border: 3px solid var(--bark);
  border-radius: 14px;
  box-shadow: 0 10px 0 rgba(77, 46, 12, .22);
  max-width: 94vw;
}

/* ============================================================
   LOCAL PLAY SETUP — player count, then one page per team
   The live showcase remains visible through a translucent paper wash;
   cards stay opaque where text needs a clean reading surface.
   ============================================================ */

.su {
  position: absolute;
  inset: 0;
  z-index: 20;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  background: radial-gradient(90% 70% at 50% 38%, rgba(251, 245, 230, .78) 0%, rgba(243, 233, 210, .68) 60%, rgba(233, 220, 192, .72) 100%);
}
/* The scaling box: see fit() in setup.ts. */
.su-fit {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* The page says where its content sits vertically (the face-off centres
     itself); the scaling box must not overrule it. */
  justify-content: inherit;
  width: 100%;
  height: 100%;
  /* The key hints sit over the foot of the page, out of the flow: keep the
     page's own content clear of them (a Teams card, nine rows long, ran its
     Done! button in under the hint line). */
  box-sizing: border-box;
  padding-bottom: 62px;
}
/* While the page is being measured nothing moves: an element mid-flight
   is not part of how much room the page needs. */
.su-fit.is-measuring, .su-fit.is-measuring * {
  animation: none !important;
  transition: none !important;
  transform: none !important;
}
.su-head {
  margin-top: clamp(28px, 7vh, 64px);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}
.su-sub { opacity: .85; }
@keyframes su-in { from { translate: 0 10px; opacity: 0; } }

.su-hint {
  position: absolute;
  left: 0; right: 0;
  bottom: 22px;
  margin: 0 auto;
  width: fit-content;
}

/* count page */
.su-list {
  margin-top: clamp(24px, 6vh, 56px);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.su-item, .su-done {
  display: block;
  background: none;
  border: 0;
  padding: 4px 10px;
  cursor: pointer;
  text-align: left;
}
.su-item.is-on, .su-done.is-on { animation: title-nudge .9s ease-in-out infinite; }

/* team page */
.su-body {
  margin-top: clamp(18px, 4vh, 36px);
  display: flex;
  align-items: stretch;
  gap: clamp(20px, 4vw, 56px);
  max-width: 96vw;
}
.su-card {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 20px 22px 16px;
  background: #fffbf0;
  border: 3px solid var(--bark);
  border-radius: 16px;
  box-shadow: 0 8px 0 rgba(77, 46, 12, .2);
  /* Sized to what is on it: the rows are set in whole glyph boxes that
     cannot be squeezed, so the card grows to hold them and the page's
     own fit() shrinks everything if the window is small. */
  width: max-content;
  min-width: min(520px, 56vw);
  max-width: min(760px, 66vw);
}
/* The map card carries nine rows; at the team card's spacing it runs off
   the bottom of a laptop screen. */
/* The map page: card and preview share the 96vw body, 38% : 58% with the
   gap between, so nothing ever overflows or slides. */
.su-card.is-long { padding: 12px 18px 8px; width: min(520px, 38vw); }
.su-card.is-long .su-row { min-height: clamp(38px, 5.6vh, 50px); padding: 2px 10px; }
.su-card.is-long .su-tag { flex: 0 0 6.5em; }
.su-card.is-long .su-val { min-width: 0; gap: 8px; }
/* Six digits at most; the Random button sits after them, never on them. */
.su-card.is-long .su-val > .su-name { flex: 0 1 auto; min-width: 0; }
.su-card.is-long .su-random { flex: 0 0 auto; padding: 4px 8px; }
.su-card.is-long .su-done { margin: 4px auto 0; }
.su-card.is-long .su-row { min-height: 50px; }
.su-card.is-long .su-done { margin: 8px auto 0; }
.su-card.is-long .su-pip { height: 18px; }
.su-card.is-long .su-caret { height: 28px; }

.su-rule {
  height: 0;
  margin: 6px 6px 8px;
  border-top: 3px dotted rgba(77, 46, 12, .28);
}
.su-row {
  display: flex;
  align-items: center;
  gap: 18px;
  min-height: 62px;
  padding: 4px 12px;
  border-radius: 12px;
  cursor: pointer;
  border: 2px solid transparent;
}
/* Teams: nine rows (two players' names, four fighters), a little closer set. */
.su-card.is-pairs .su-row { min-height: 44px; padding: 1px 12px; }
.su-card.is-pairs .su-done { margin-top: 2px; }
.su-row.is-on {
  background: var(--gold);
  border-color: var(--bark);
}
.su-tag { flex: 0 0 8.5em; }
.su-val {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}
.su-val > .su-class, .su-val > .su-name { flex: 1; min-width: 0; }
.su-val > .su-class > .bf { flex: 0 0 auto; }
/* The class, and beside it the face of whoever plays him. Neither may be
   squeezed: the glyph runs inside a .bf are absolutely positioned, so a
   shrunken box does not shorten the word, it just slides the face over
   the top of it. */
.su-class { display: flex; align-items: center; justify-content: center; }
.su-class > * { flex: 0 0 auto; }

/* Every fighter's face sits in its own column at the end of his row, so
   the badges line up however long the class name is. */
.su-who {
  flex: 0 0 auto;
  width: 48px; height: 48px;
  margin-left: 10px;
  padding: 0;
  border-radius: 50%;
  border: 3px solid var(--bark);
  background: #fffdf4;
  box-shadow: 0 2px 0 rgba(77, 46, 12, .25);
  line-height: 0;
  cursor: pointer;
  overflow: hidden;
}
.su-who:hover { translate: 0 -2px; }
.su-who:active { translate: 0 1px; box-shadow: none; }
.su-who.is-only { cursor: default; }
.su-who.is-only:hover { translate: none; }
/* The renders carry shoulders; the badge wants the face, so the picture
   is zoomed and pulled up inside its circle. */
.su-who-face {
  width: 132%; height: 132%; display: block;
  margin: -14% 0 0 -16%;
  object-fit: cover;
}
.su-row.is-on .su-who { border-color: #7a4a17; background: #fff; }

/* Which war this squad is in: two pills with a face on each. They live
   inside the value column, so they are sized to sit in it rather than
   push past the edge of the card. */
.su-sides {
  flex: 1;
  display: flex; align-items: center; justify-content: center; gap: 12px;
}
.su-team {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 7px;
  padding: 4px 12px 4px 4px;
  border-radius: 999px;
  border: 3px solid var(--bark);
  background: #fffdf4;
  box-shadow: 0 2px 0 rgba(77, 46, 12, .25);
  cursor: pointer;
  line-height: 0;
}
.su-team:hover { translate: 0 -2px; }
.su-team:active { translate: 0 1px; box-shadow: none; }
.su-team.is-on { background: var(--gold); border-color: #7a4a17; }
/* A word set in glyph boxes cannot be squeezed — a narrower box does not
   shorten it, it just lets the next thing sit on top — so nothing inside
   a pill may shrink, and the row gives the pills more room instead. */
.su-team > .bf { flex: 0 0 auto; }
.su-row-wide .su-tag { flex: 0 0 6.6em; }
.su-team-face {
  flex: 0 0 auto;
  width: 34px; height: 34px; display: block;
  border-radius: 50%;
  border: 2px solid var(--bark);
  background: #f0e3c6;
  object-fit: cover;
  object-position: 50% 22%;
}
/* Fixed width on purpose. The blurb changes with every row you land on,
   and a column that resizes with its text drags the whole centred row
   sideways — the card visibly jumped as you moved down it. */
.su-side {
  width: min(46vw, 560px);
  min-width: 0;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  gap: 14px;
}
/* Three rows, the outer two equal: the map sits dead centre and the
   blurb hangs directly under it. */
.su-side.is-mid {
  display: grid;
  grid-template-rows: 1fr auto 1fr;
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  gap: 0;
  /* The map page gives the preview the room: a wider column, a narrower card. */
  width: min(52vw, 900px);
}
.su-side.is-mid .su-blurb { width: 100%; max-width: 100%; }
.su-side.is-mid .su-map-frame { width: 100%; }
.su-side.is-mid .su-blurb { align-self: start; margin-top: 14px; }
/* Two lines' worth, always. The caption changes with every row you land
   on and a one-line/two-line flip was shoving the art above it around. */
.su-blurb { text-align: center; width: 100%; min-height: 72px; }
.su-blurb .bf { max-width: 100%; }
/* Word spans wrap inside the column; the column never grows for a caption. */
.bf-para { display: flex; flex-wrap: wrap; justify-content: center; column-gap: 0.55em; row-gap: 6px; width: 100%; }
.su-arrow {
  flex: 0 0 auto;
  background: none;
  border: 0;
  padding: 2px 4px;
  cursor: pointer;
  visibility: hidden;
}
.su-row.is-on .su-arrow:not(.is-mute) { visibility: visible; }
/* Held open for spacing only, on the rows with nothing to spin. */
.su-arrow.is-mute { pointer-events: none; }
.su-arrow:hover { translate: 0 -1px; }

.su-name { position: relative; display: flex; align-items: center; }
.su-ghost {
  position: absolute;
  /* Stood off by the width of the cursor, so the bar sits in front of
     the stand-in name instead of on top of its first letter. */
  left: 9px; top: 0; bottom: 0;
  display: flex;
  align-items: center;
  pointer-events: none;
}
.su-caret {
  width: 4px;
  height: 34px;
  flex: 0 0 auto;
  background: var(--bark);
  animation: su-caret 1.06s steps(1) infinite;
}
@keyframes su-caret { 50% { opacity: 0; } }
.su-done { margin: 14px auto 2px; }

/* the three of them */
.su-stage {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: clamp(6px, 1.5vw, 22px);
  padding: 0 8px 14px;
  border-bottom: 3px solid rgba(77, 46, 12, .35);
}
.su-guy {
  width: clamp(96px, 12vw, 150px);
  aspect-ratio: 1;
  cursor: pointer;
  transition: translate .15s ease-out;
}
.su-guy.is-on { translate: 0 -8px; }
.su-art { position: relative; width: 100%; height: 100%; }
.su-art img { position: absolute; user-select: none; -webkit-user-drag: none; }
/* The class, beside him at his feet (everyone is drawn the same height). */
.su-badge {
  position: absolute;
  right: 2%;
  bottom: 4%;
  line-height: 0;
  filter: drop-shadow(0 2px 0 rgba(43, 23, 6, .35));
  pointer-events: none;
}
.su-art .su-badge img { position: static; }

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

.su-slider {
  display: flex;
  gap: 4px;
  align-items: center;
  height: 34px;
}
.su-pip {
  width: 16px;
  height: 22px;
  border: 2px solid var(--bark);
  border-radius: 3px;
  background: #fffbf0;
  cursor: pointer;
}
.su-pip.is-full { background: var(--gold-deep); }
.su-row.is-on .su-pip { background: #fffbf0; }
.su-row.is-on .su-pip.is-full { background: var(--bark); }

.su-random {
  flex: 0 0 auto;
  padding: 5px 10px;
  background: #fffbf0;
  border: 2px solid var(--bark);
  border-radius: 9px;
  box-shadow: 0 3px 0 rgba(77, 46, 12, .25);
  cursor: pointer;
}
.su-random:hover { translate: 0 -1px; }
.su-random:active { translate: 0 2px; box-shadow: 0 1px 0 rgba(77, 46, 12, .25); }
.su-row.is-on .su-random { background: var(--gold); }

/* The preview is a map from above now, not a 113x41 cross-section strip:
   it fills the column and takes its height from the grid's own w:h, so a
   26x10 Small map and a 45x10 Large one both sit centred in the side. */
.su-map-frame {
  border: 3px solid var(--bark);
  border-radius: 12px;
  box-shadow: 0 6px 0 rgba(77, 46, 12, .2);
  overflow: hidden;
  /* Open water, so the frame matches the sea while the next map generates. */
  background: #7fb4e8;
  /* A definite width, matching the side column: as a grid item a
     percentage would resolve against an auto track, and the canvas's own
     aspect would then let the frame shrink to whatever height the blurb
     left over — the map visibly resized as you moved down the rows. */
  width: min(46vw, 560px);
  line-height: 0;
}
.su-map-canvas {
  display: block;
  width: 100%;
  height: auto;
  /* A generated map is a grid of cells; keep the cells as cells. */
  image-rendering: pixelated;
}

/* The Build list: what the Structures slider may build, a switch each.
   It takes the preview's place (and its width) while it is open, so
   nothing beside it moves. */
.su-kinds {
  width: min(46vw, 560px);
  box-sizing: border-box;
  background: #fffbf0;
  border: 3px solid var(--bark);
  border-radius: 12px;
  box-shadow: 0 6px 0 rgba(77, 46, 12, .2);
  padding: 12px 14px 10px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
}
.su-side.is-mid .su-kinds { width: 100%; }
.su-kinds-title { display: flex; justify-content: center; margin-bottom: 6px; }
/* Two columns: the wood (built unless switched off) and the metal (built
   only when switched on, and never broken). */
.su-kinds-cols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 0 14px; align-items: start; }
.su-kinds-head { display: flex; flex-direction: column; align-items: center; gap: 3px; padding: 2px 0 6px; }
.su-kinds-list { display: grid; grid-template-columns: minmax(0, 1fr); gap: 2px; }
.su-kind {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 38px;
  padding: 2px 12px;
  background: none;
  border: 2px solid transparent;
  border-radius: 10px;
  cursor: pointer;
}
.su-kind.is-on { background: var(--gold); border-color: var(--bark); }
.su-check {
  flex: 0 0 auto;
  position: relative;
  box-sizing: border-box;
  width: 24px;
  height: 24px;
  border: 3px solid var(--bark);
  border-radius: 6px;
  background: #fffbf0;
}
.su-check.is-ticked { background: var(--leaf); }
.su-check.is-ticked::after {
  content: '';
  position: absolute;
  left: 5px;
  top: 0;
  width: 6px;
  height: 12px;
  border: solid #fffbf0;
  border-width: 0 3px 3px 0;
  transform: rotate(45deg);
}
.su-kinds .su-kinds-done { margin: 8px auto 2px; }

/* --- the face-off ----------------------------------------------- */

.su-vs-page { cursor: pointer; justify-content: center; }
.su-vs {
  position: relative;
  display: grid;
  gap: clamp(14px, 3vh, 28px) clamp(18px, 4vw, 48px);
  width: min(88vw, 940px);
}
.su-vs-2 { grid-template-columns: 1fr 1fr; column-gap: clamp(60px, 7vw, 96px); }
.su-vs-3, .su-vs-4 { grid-template-columns: 1fr 1fr; }
/* three teams: the third sits centred under the pair, same width */
.su-vs-3 .su-panel:nth-child(3) { grid-column: 1 / 3; width: calc(50% - clamp(9px, 2vw, 24px)); justify-self: center; }
/* two rows: the VS sits in the gap, so the top row wears its band on
   top and both rows keep their names clear of it */
.su-vs-3, .su-vs-4 { row-gap: clamp(44px, 8vh, 72px); }
.su-vs-3 .su-panel:nth-child(-n+2) .su-panel-band,
.su-vs-4 .su-panel:nth-child(-n+2) .su-panel-band { top: 0; bottom: auto; border-top: 0; border-bottom: 3px solid var(--bark); }
.su-vs-3 .su-panel:nth-child(-n+2) .su-panel-art,
.su-vs-4 .su-panel:nth-child(-n+2) .su-panel-art { top: 19%; }

.su-panel {
  position: relative;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  border: 4px solid var(--bark);
  border-radius: 14px;
  box-shadow: 0 8px 0 rgba(77, 46, 12, .22);
  background: var(--team);
}
.su-panel-art {
  position: absolute;
  left: 50%;
  top: 3%;
  height: 78%;
  aspect-ratio: 1;
  translate: -50% 0;
}
.su-panel-art img { position: absolute; user-select: none; -webkit-user-drag: none; }
/* A portrait fills its card, corner to corner, with the name band over
   the bottom of it. */
.su-panel-art.is-hero {
  left: 0; top: 0; translate: none;
  width: 100%; height: 100%; aspect-ratio: auto;
}
.su-hero {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  /* The art is cut to the card's own 3:2, so it fills corner to corner
     without losing a head. */
  object-fit: cover;
  object-position: 50% 45%;
}
.su-panel-art.is-hero { background: #130e09; }
.su-panel-band {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 19%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 0 14px;
  background: rgba(77, 46, 12, .88);
  border-top: 3px solid var(--bark);
}
.su-panel-name { min-width: 0; overflow: hidden; }

.su-vs-mark, .su-vs-flash {
  position: absolute;
  left: 50%; top: 50%;
  translate: -50% -50%;
  pointer-events: none;
}
.su-vs-flash {
  width: 0; height: 0;
  border-radius: 50%;
  background: radial-gradient(circle, #fffbf0 0%, rgba(255, 217, 74, .9) 40%, rgba(255, 146, 38, 0) 70%);
  opacity: 0;
}

/* The clash. Left-hand panels charge in from the left, right-hand
   ones from the right (a lone third from below). At contact everything
   freezes for an impact frame — a negative on ink, radial speed lines,
   the cards squashed into each other — then releases: the page jolts,
   a flash bursts at the point of impact and the VS pops out of it.
   Runs once, on the first paint of the page.

   Timeline (of an .8s beat): 0–.4s charge · .4–.56s impact frame ·
   .56s release. */
.su-vs { z-index: 1; }
.su-impact {
  position: absolute;
  inset: 0;
  z-index: 0;
  opacity: 0;
  pointer-events: none;
  background: #2b1706;
}
.su-impact::before {
  content: '';
  position: absolute;
  inset: 0;
  background: repeating-conic-gradient(from 0deg at 50% 50%, #fffbf0 0deg 2deg, transparent 2deg 7deg);
  -webkit-mask-image: radial-gradient(circle at 50% 50%, transparent 16%, #000 40%);
  mask-image: radial-gradient(circle at 50% 50%, transparent 16%, #000 40%);
}
.su-vs.is-fresh + .su-impact { animation: su-impact .8s steps(1, end) both; }
@keyframes su-impact { 0%, 49% { opacity: 0; } 50%, 69% { opacity: 1; } 70%, 100% { opacity: 0; } }

.su-vs.is-fresh {
  animation:
    su-negative .8s steps(1, end) both,
    su-jolt .4s ease-out .56s both;
}
@keyframes su-negative {
  0%, 49% { filter: none; }
  50%, 69% { filter: invert(1) grayscale(1) contrast(1.7); }
  70%, 100% { filter: none; }
}
@keyframes su-jolt {
  0%, 100% { translate: 0 0; }
  15% { translate: -18px 8px; }
  35% { translate: 16px -6px; }
  55% { translate: -10px 4px; }
  75% { translate: 5px -2px; }
}
.su-vs.is-fresh .su-panel { animation: su-charge-l .8s both; }
.su-vs.is-fresh .su-panel:nth-child(even) { animation-name: su-charge-r; }
.su-vs-3.is-fresh .su-panel:nth-child(3) { animation-name: su-charge-b; }
@keyframes su-charge-l {
  0% { translate: -110vw 0; scale: 1; animation-timing-function: cubic-bezier(.4, 0, .8, .4); }
  50%, 70% { translate: 8% 0; scale: 1.1 .9; animation-timing-function: ease-out; }
  85% { translate: -2% 0; scale: .97 1.03; }
  100% { translate: 0 0; scale: 1; }
}
@keyframes su-charge-r {
  0% { translate: 110vw 0; scale: 1; animation-timing-function: cubic-bezier(.4, 0, .8, .4); }
  50%, 70% { translate: -8% 0; scale: 1.1 .9; animation-timing-function: ease-out; }
  85% { translate: 2% 0; scale: .97 1.03; }
  100% { translate: 0 0; scale: 1; }
}
@keyframes su-charge-b {
  0% { translate: 0 110vh; scale: 1; animation-timing-function: cubic-bezier(.4, 0, .8, .4); }
  50%, 70% { translate: 0 -8%; scale: .9 1.1; animation-timing-function: ease-out; }
  85% { translate: 0 2%; scale: 1.03 .97; }
  100% { translate: 0 0; scale: 1; }
}
.su-vs.is-fresh .su-vs-flash { animation: su-flash .5s ease-out .56s both; }
@keyframes su-flash {
  0% { width: 0; height: 0; opacity: 1; }
  100% { width: 80vmin; height: 80vmin; opacity: 0; }
}
.su-vs.is-fresh .su-vs-mark { animation: su-vs-in .45s cubic-bezier(.2, 1.5, .4, 1) .56s both; }
@keyframes su-vs-in { from { scale: 3; opacity: 0; } }

/* The explosion: 200px of pixel art blown up over most of the screen,
   so it keeps its hard edges instead of turning to soup. */
.su-boom {
  position: absolute;
  /* left/top are set from the cards' meeting point when it is shown.
     The fire's mass sits low in its own canvas (it billows upward), so
     nudge it so the peak lands on the impact rather than below it. */
  translate: calc(-50% + 2%) calc(-50% - 8%);
  width: min(112vw, 104vh);
  z-index: 3;
  image-rendering: pixelated;
  pointer-events: none;
}

.su-go {
  margin-top: clamp(18px, 4vh, 36px);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}
.su-go > .bf { animation: title-blink 1.4s ease-in-out infinite; }
.su-vs.is-fresh + .su-impact + .su-go { animation: su-in .3s ease-out 1.15s both; }

/* ============================================================
   BOOT + METERS
   ============================================================ */

#bp-boot {
  position: absolute;
  inset: 0;
  z-index: 60;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 18px;
  background: var(--paper);
  transition: opacity .4s ease;
}
#bp-boot.gone { opacity: 0; pointer-events: none; }
#bp-boot p { font: 700 18px/1.4 system-ui, sans-serif; color: var(--bark); margin: 0; }
.bp-boot-blocks { display: flex; gap: 12px; }
.bp-boot-blocks i {
  display: block; width: 22px; height: 22px; border-radius: 5px; background: var(--gold);
  animation: boot-hop 1.1s ease-in-out infinite;
}
.bp-boot-blocks i:nth-child(2) { animation-delay: .14s; }
.bp-boot-blocks i:nth-child(3) { animation-delay: .28s; }
@keyframes boot-hop { 0%, 55%, 100% { transform: translateY(0); } 25% { transform: translateY(-12px); } }

/* The money: a render of the Bell model, drawn smooth (the class badges are pixel art),
   deepened a touch and ringed in the font's ink so it reads on the gold price chips. */
.bell-icon {
  display: inline-block;
  vertical-align: middle;
  image-rendering: auto;
  flex: none;
  filter: saturate(1.35) brightness(.96)
    drop-shadow(1px 0 0 #2b1706) drop-shadow(-1px 0 0 #2b1706)
    drop-shadow(0 1px 0 #2b1706) drop-shadow(0 -1px 0 #2b1706);
}

/* ============================================================
   THE OPENING CUTSCENE (web/src/intro.ts) — over everything, the
   boot bar and the burrow included, letterboxed on black.
   ============================================================ */

#bp-intro {
  position: absolute;
  inset: 0;
  z-index: 1000;
  background: #000;
  cursor: none;
  transition: opacity .65s ease;
}
#bp-intro.is-leaving { opacity: 0; pointer-events: none; }
#bp-intro video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;   /* the dialogue boxes sit low: never crop them */
  background: #000;
}
/* Autoplay with sound refused: the press that allows it. */
.in-gate {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: #000;
  cursor: pointer;
  line-height: 0;
}
.in-gate > * { animation: in-breathe 1.8s ease-in-out infinite; }
@keyframes in-breathe { 0%, 100% { opacity: .45; } 50% { opacity: 1; } }
.in-skip {
  position: absolute;
  right: clamp(16px, 3vw, 36px);
  bottom: clamp(14px, 3vh, 32px);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border: 0;
  background: rgba(0, 0, 0, .35);
  border-radius: 8px;
  line-height: 0;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity .35s ease;
}
.in-skip.is-on { opacity: .85; pointer-events: auto; }
.in-skip:hover { opacity: 1; }
.in-key { background: var(--cream, #f3e3c3); border-radius: 4px; padding: 4px 5px; }
@media (prefers-reduced-motion: reduce) {
  .in-gate > * { animation: none; }
}

.bp-fps {
  position: fixed; top: 6px; right: 8px; z-index: 90;
  font: 800 13px/1.4 monospace; color: #fff; background: rgba(0, 0, 0, .5);
  border-radius: 6px; padding: 1px 8px; pointer-events: none;
}

/* ============================================================
   POWER METER — how hard the grenade goes
   Worms' charge bar, drawn the way the GameCube would: a fat
   outlined track of twelve blocks above the weapon slot, cold
   and empty the moment you take aim, filling yellow -> orange
   -> red as you hold, flashing when there is nothing left to
   give. Built in web/src/match.ts (mountMeter/paintMeter) and
   fed frame by frame from Unity's `charge` message.
   ============================================================ */

.ph-power {
  position: absolute;
  left: 50%;
  bottom: 108px;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  pointer-events: none;
}

.ph-power-track {
  display: flex;
  gap: 3px;
  padding: 5px;
  border: 3px solid #241a0c;
  border-radius: 7px;
  background: rgba(38, 28, 16, .72);
  box-shadow: 0 3px 0 rgba(0, 0, 0, .35), inset 0 2px 0 rgba(255, 255, 255, .08);
}

.ph-power-block {
  display: block;
  width: 15px;
  height: 26px;
  border-radius: 3px;
  /* Empty: a dark slot, so twelve of them read as a bar to fill. */
  background: rgba(255, 244, 214, .12);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .35);
  transition: background .06s linear;
}

.ph-power-block.is-on {
  /* --hot is set per block in match.ts: yellow at the left, red at the right. */
  background: var(--hot, #ffd23f);
  box-shadow: inset 0 -3px 0 rgba(0, 0, 0, .22), 0 0 7px var(--hot, #ffd23f);
}

/* The bat's last block is the home run: ringed in gold, so there is a target. */
.ph-power.is-swing .ph-power-block:last-child { box-shadow: inset 0 0 0 2px #ffd23f; }
.ph-power.is-swing .ph-power-block.is-on:last-child { box-shadow: inset 0 -3px 0 rgba(0, 0, 0, .22), 0 0 10px #ffd23f; }

/* Maxed out: the whole track throbs, so you can feel the ceiling. */
.ph-power.is-full .ph-power-track { animation: ph-power-throb .22s steps(1) infinite; }
@keyframes ph-power-throb {
  0% { background: rgba(38, 28, 16, .72); transform: scale(1); }
  50% { background: rgba(255, 96, 48, .55); transform: scale(1.05); }
}

/* ---- WHOSE TURN, AND WHEN IT ENDS (match.ts) ---------------------- */

/* The last ten seconds, and the retreat: the clock, big, top middle. */
.ph-topclock {
  position: absolute;
  left: 50%;
  top: 12px;
  translate: -50% 0;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 12px 6px;
  border: 3px solid #241a0c;
  border-radius: 12px;
  background: rgba(38, 28, 16, .6);
  box-shadow: 0 3px 0 rgba(0, 0, 0, .3);
  line-height: 0;
  pointer-events: none;
}
/* Online the top middle already has whose turn it is and, when watching,
   whose screen: the clock goes under both. */
.ph-hud.is-online .ph-topclock { top: 100px; }
.ph-topclock.is-retreat { background: rgba(190, 58, 38, .85); }
.ph-topclock.is-tick { animation: ph-topclock-tick .45s cubic-bezier(.2, 1.6, .4, 1); }
@keyframes ph-topclock-tick { from { scale: 1.22; } to { scale: 1; } }

/* A turn starting: a band across the screen in the squad's colour. */
.ph-turnbanner {
  position: fixed;
  left: -4vw;
  right: -4vw;
  top: 34%;
  z-index: 55;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 20px 0 18px;
  background: repeating-linear-gradient(-45deg,
    var(--team) 0 28px, color-mix(in srgb, var(--team) 80%, #1a0d03) 28px 56px);
  border-top: 5px solid #241a0c;
  border-bottom: 5px solid #241a0c;
  box-shadow: 0 8px 0 rgba(0, 0, 0, .25);
  rotate: -3deg;
  line-height: 0;
  pointer-events: none;
  animation: ph-turnbanner 2s cubic-bezier(.2, 1.2, .3, 1) both;
}
.ph-turnbanner > span { animation: ph-turnbanner-text 2s cubic-bezier(.2, 1.5, .3, 1) both; }
/* Local Teams: whose squad, under the player's name. */
.ph-turnbanner.has-sub { flex-direction: column; gap: 12px; }
@keyframes ph-turnbanner {
  0% { translate: -110% 0; }
  16% { translate: 0 0; }
  84% { translate: 0 0; }
  100% { translate: 110% 0; }
}
@keyframes ph-turnbanner-text {
  0%, 10% { scale: .4; }
  24% { scale: 1.12; }
  32%, 100% { scale: 1; }
}

/* Control gone: TURN OVER, stamped. */
.ph-turnover {
  position: fixed;
  left: 50%;
  top: 22%;
  z-index: 55;
  translate: -50% -50%;
  rotate: -6deg;
  padding: 10px 22px 8px;
  border: 4px solid #241a0c;
  border-radius: 12px;
  background: #c62828;
  box-shadow: 0 5px 0 rgba(0, 0, 0, .3);
  line-height: 0;
  pointer-events: none;
  animation: ph-turnover 1.3s cubic-bezier(.2, 1.6, .4, 1) both;
}
@keyframes ph-turnover {
  0% { scale: 2.4; opacity: 0; }
  14% { scale: 1; opacity: 1; }
  78% { scale: 1; opacity: 1; }
  100% { scale: .9; opacity: 0; }
}

/* A purse that just reached something new: bottom middle, over the power meter. */
.ph-shopnote {
  position: fixed;
  left: 50%;
  bottom: 196px;
  z-index: 30;
  translate: -50% 0;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 9px 7px 12px;
  border: 3px solid #241a0c;
  border-radius: 999px;
  background: #fffdf4;
  box-shadow: 0 4px 0 color-mix(in srgb, var(--team) 65%, #241a0c), inset 6px 0 0 var(--team);
  line-height: 0;
  pointer-events: none;
  animation: ph-shopnote 3.8s ease-out both;
}
.ph-shopnote-key {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 7px;
  background: #4d2e0c;
  box-shadow: 0 3px 0 #241a0c;
}
@keyframes ph-shopnote {
  0% { translate: -50% 40px; opacity: 0; }
  8% { translate: -50% -6px; opacity: 1; }
  12% { translate: -50% 0; }
  88% { translate: -50% 0; opacity: 1; }
  100% { translate: -50% 10px; opacity: 0; }
}

/* ============================================================
   THE BURROW — the dirt curtain between the VS page and the match
   (web/src/transition.ts), in the island's own grass and dirt textures
   (art/ground-*.webp, from Assets/Art/BattleBurrow/Ground). Seven slabs of earth slam down over the
   page; the matchup and a tip are written on them while Unity builds
   the arena behind; then the ground gives way, the middle first, onto
   the match. Only transforms and opacity move, so it stays smooth
   while Unity is busy on the same thread.
   ============================================================ */

#bp-burrow {
  position: absolute;
  inset: 0;
  z-index: 120;
  overflow: hidden;
  pointer-events: auto;
}
#bp-burrow.is-opening { pointer-events: none; }

.bb-slabs { position: absolute; inset: 0; }
#bp-burrow.is-down .bb-slabs { animation: bb-jolt .32s ease-out both; }

.bb-slab {
  position: absolute;
  top: -3%;
  bottom: -3%;
  left: calc(var(--i) * (100% / 7) - 1.2%);
  width: calc(100% / 7 + 2.4%);
  display: block;
  will-change: transform;
  /* The island's own dirt (what a crater shows), in shadow just under the
     lawn's fringe. Each slab starts the texture somewhere else. */
  background-color: #9e6f31;
  background-image:
    linear-gradient(180deg, transparent 9.5vh, rgba(38, 18, 4, .5) 9.5vh, rgba(38, 18, 4, 0) 14vh),
    url(art/ground-dirt.webp);
  background-size: 100% 100%, 300px 300px;
  background-position: 0 0, calc(var(--i) * 97px) calc(var(--i) * -41px);
  box-shadow: inset -4px 0 0 rgba(0, 0, 0, .28), inset 3px 0 0 rgba(255, 220, 160, .06);
  /* Ragged seams, so the slabs read as chunks of earth, not a wipe. */
  clip-path: polygon(3% 0, 100% 0, 97% 11%, 100% 26%, 96% 40%, 100% 57%, 97% 73%, 100% 87%,
    97% 100%, 0 100%, 3% 85%, 0 70%, 4% 54%, 0 37%, 3% 21%, 0 9%);
}
.bb-slab.is-odd {
  clip-path: polygon(0 0, 97% 0, 100% 14%, 96% 30%, 100% 45%, 97% 61%, 100% 78%, 96% 92%,
    100% 100%, 3% 100%, 0 90%, 4% 76%, 0 60%, 3% 44%, 0 28%, 4% 13%);
}
/* The lawn along the top of the screen: the island's own grass, hanging over
   the dirt in a ragged fringe the way Animal Crossing's turf overhangs its
   cliffs. */
.bb-slab::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 12vh;
  background: #1f8a4c url(art/ground-grass.webp) calc(var(--i) * -83px) 0 / 300px 300px;
  clip-path: polygon(0 0, 100% 0, 100% 80%, 95% 96%, 90% 79%, 84% 100%, 78% 81%, 71% 95%, 65% 78%,
    58% 99%, 52% 80%, 45% 93%, 39% 77%, 32% 100%, 26% 82%, 19% 94%, 13% 78%, 6% 97%, 0 81%);
}
.bb-slab.is-odd::before {
  clip-path: polygon(0 0, 100% 0, 100% 83%, 94% 99%, 88% 78%, 81% 94%, 75% 80%, 69% 100%, 62% 79%,
    56% 95%, 49% 77%, 43% 98%, 36% 81%, 30% 93%, 23% 78%, 17% 100%, 10% 80%, 5% 94%, 0 79%);
}

/* In: each slab falls from above the screen and lands with a bounce. */
#bp-burrow.is-burying .bb-slab { animation: bb-land .38s var(--land) both; }
@keyframes bb-land {
  0% { transform: translateY(-106%); animation-timing-function: cubic-bezier(.55, 0, 1, .45); }
  72% { transform: translateY(0); animation-timing-function: ease-out; }
  86% { transform: translateY(-2.2%); animation-timing-function: ease-in; }
  100% { transform: translateY(0); }
}
@keyframes bb-jolt {
  0%, 100% { transform: translate(0, 0); }
  20% { transform: translate(0, 10px); }
  45% { transform: translate(-4px, -4px); }
  70% { transform: translate(3px, 2px); }
}

/* Out: the ground gives way, the middle first, each slab tipping as it drops. */
#bp-burrow.is-opening .bb-slab { animation: bb-drop .62s var(--open) cubic-bezier(.5, 0, .9, .45) both; }
@keyframes bb-drop {
  0% { transform: translateY(0) rotate(0); }
  12% { transform: translateY(-1.5%) rotate(0); }
  100% { transform: translateY(118%) rotate(var(--tip)); }
}

.bb-card {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(14px, 3vh, 26px);
  padding: 0 16px;
  pointer-events: none;
  opacity: 0;
}
#bp-burrow.is-down .bb-card { animation: bb-card-in .3s cubic-bezier(.2, 1.4, .4, 1) both; }
#bp-burrow.is-opening .bb-card { animation: bb-card-out .14s ease-in both; }
@keyframes bb-card-in { from { opacity: 0; transform: scale(.8); } to { opacity: 1; transform: scale(1); } }
@keyframes bb-card-out { from { opacity: 1; } to { opacity: 0; transform: scale(.94); } }
.bb-title, .bb-vs, .bb-tip { line-height: 0; }
.bb-vs { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 10px 14px; }
.bb-tip {
  position: absolute;
  left: 16px;
  right: 16px;
  bottom: clamp(24px, 7vh, 64px);
  display: flex;
  justify-content: center;
}
.bb-dig { display: flex; gap: 12px; }
.bb-dig i {
  display: block;
  width: 16px;
  height: 16px;
  background: var(--gold);
  box-shadow: 0 0 0 3px #2b1706;
  animation: boot-hop 1.1s ease-in-out infinite;
}
.bb-dig i:nth-child(2) { animation-delay: .14s; }
.bb-dig i:nth-child(3) { animation-delay: .28s; }

@media (prefers-reduced-motion: reduce) {
  #bp-burrow.is-burying .bb-slab,
  #bp-burrow.is-opening .bb-slab,
  #bp-burrow.is-down .bb-slabs { animation: none; }
  #bp-burrow.is-opening { opacity: 0; transition: opacity .3s; }
}

/* ============================================================
   THE FACE-OFF — the VS page (web/src/faceoff.ts)
   A collage: a torn sheet of coloured paper per squad on a kraft
   board, each leader a die-cut sticker on it, names on masking tape,
   VS on Marco's crayon star. Paper grain is his paper.png. The entrance
   runs on the same .8s beat as the impact frame and the boom: 0–.4s the
   sheets charge in · .4–.56s the impact frame · .56s the star slaps
   down, the tape goes on. After that everything stuck down twitches on
   a stop-motion beat and the sheets keep shoving.
   ============================================================ */

.fo {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  /* The board underneath: dark kraft with the paper's grain in it. */
  background: #3a2615 url(art/paper.webp) center / cover;
  background-blend-mode: multiply;
}
.fo-sheet {
  position: absolute;
  inset: 0;
  /* Lifted off the board: the shadow falls along the torn edges. */
  filter: drop-shadow(0 4px 9px rgba(12, 6, 0, .55));
  will-change: transform;
}
.fo-rim, .fo-paper { position: absolute; inset: 0; }
/* The white core a tear shows. */
.fo-rim { background: #fbf6ea url(art/paper.webp) center / cover; }
/* The wallpaper itself (the pattern tile is drawn per sheet in faceoff.ts). */
.fo-paper {
  background-color: var(--team);
  background-size: 340px 340px;
  overflow: hidden;
}
.fo-sticker {
  position: absolute;
  width: auto;
  max-width: none;
  user-select: none;
  -webkit-user-drag: none;
  pointer-events: none;
  rotate: var(--tilt, 0deg);
  filter: drop-shadow(3px 6px 5px rgba(20, 10, 0, .4));
}
.fo-sticker.is-flip { scale: -1 1; }

/* Masking tape, torn off the roll at both ends. */
.fo-tape {
  position: absolute;
  z-index: 3;
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding: 14px 30px 16px;
  line-height: 0;
  background: rgba(238, 224, 186, .94) url(art/paper.webp) center / 600px auto;
  background-blend-mode: multiply;
  box-shadow: 0 2px 3px rgba(20, 10, 0, .25);
  clip-path: polygon(0 6%, 4% 0, 8% 9%, 13% 1%, 88% 3%, 92% 10%, 96% 0, 100% 7%, 97% 22%, 100% 38%,
    96% 55%, 100% 71%, 97% 88%, 100% 95%, 94% 100%, 89% 92%, 84% 99%, 12% 97%, 7% 90%, 3% 100%,
    0 93%, 3% 78%, 0 62%, 4% 46%, 0 30%, 3% 16%);
  rotate: var(--tilt, 0deg);
  pointer-events: none;
  animation: fo-boil .48s steps(1, end) infinite;
  animation-delay: calc(var(--n) * -.13s);
}
.fo-tape.at-tl, .fo-tape.at-bl { translate: 0 -50%; }
.fo-tape.at-tr, .fo-tape.at-br { translate: -100% -50%; align-items: flex-end; }
.fo-tape.at-tc { translate: -50% -50%; align-items: center; }

/* VS on the crayon star. */
.fo-star {
  position: absolute;
  z-index: 4;
  width: var(--size);
  aspect-ratio: 1;
  translate: -50% -50%;
  rotate: var(--tilt);
  pointer-events: none;
  filter: drop-shadow(4px 7px 0 rgba(20, 10, 0, .35));
  animation: fo-boil .48s steps(1, end) infinite;
}
.fo-star img { position: absolute; inset: 0; width: 100%; height: 100%; }
.fo-star-word {
  position: absolute;
  left: 50%;
  top: 54%;
  translate: -50% -50%;
  line-height: 0;
}
/* Stop-motion: stuck-down things shift a hair between frames, the way a
   hand-animated cut-out does. */
@keyframes fo-boil {
  0% { transform: translate(0, 0) rotate(0deg); }
  33% { transform: translate(1px, -1px) rotate(.6deg); }
  66% { transform: translate(-1px, 1px) rotate(-.5deg); }
}

/* START, on its own strip of tape. */
.fo ~ .su-go {
  position: absolute;
  left: 0;
  right: 0;
  bottom: clamp(18px, 5vh, 52px);
  z-index: 6;
  width: max-content;
  margin: 0 auto;
  padding: 14px 34px 16px;
  gap: 10px;
  background: rgba(238, 224, 186, .95) url(art/paper.webp) center / 600px auto;
  background-blend-mode: multiply;
  box-shadow: 0 2px 3px rgba(20, 10, 0, .3);
  clip-path: polygon(0 8%, 5% 0, 10% 7%, 90% 2%, 95% 9%, 100% 0, 98% 30%, 100% 60%, 97% 100%,
    92% 93%, 86% 100%, 14% 96%, 8% 100%, 2% 92%, 0 60%, 3% 30%);
  rotate: -1.5deg;
  pointer-events: none;
}

/* The sheets shove at each other, out of step. */
.fo-sheet { animation: fo-shove-r 1.7s ease-in-out infinite alternate; }
.fo-sheet.from-r, .fo-sheet.from-tr, .fo-sheet.from-br { animation-name: fo-shove-l; animation-delay: -.85s; }
.fo-sheet.from-b { animation: none; }
@keyframes fo-shove-r { from { translate: 0 0; } to { translate: .45% 0; } }
@keyframes fo-shove-l { from { translate: 0 0; } to { translate: -.45% 0; } }

/* --- the entrance --------------------------------------------------- */

.fo.is-fresh {
  animation:
    su-negative .8s steps(1, end) both,
    su-jolt .4s ease-out .56s both;
}
.fo.is-fresh .fo-sheet { animation: fo-in-l .8s both, fo-shove-r 1.7s ease-in-out .8s infinite alternate; }
.fo.is-fresh .fo-sheet.from-r { animation: fo-in-r .8s both, fo-shove-l 1.7s ease-in-out .8s infinite alternate; }
.fo.is-fresh .fo-sheet.from-b { animation: fo-in-b .8s both; }
.fo.is-fresh .fo-sheet.from-tl { animation: fo-in-tl .8s both, fo-shove-r 1.7s ease-in-out .8s infinite alternate; }
.fo.is-fresh .fo-sheet.from-tr { animation: fo-in-tr .8s both, fo-shove-l 1.7s ease-in-out .8s infinite alternate; }
.fo.is-fresh .fo-sheet.from-bl { animation: fo-in-bl .8s both, fo-shove-r 1.7s ease-in-out .8s infinite alternate; }
.fo.is-fresh .fo-sheet.from-br { animation: fo-in-br .8s both, fo-shove-l 1.7s ease-in-out .8s infinite alternate; }
@keyframes fo-in-l {
  0% { translate: -100% 0; rotate: -4deg; animation-timing-function: cubic-bezier(.4, 0, .8, .4); }
  50%, 70% { translate: 1.5% 0; rotate: .6deg; animation-timing-function: ease-out; }
  100% { translate: 0 0; rotate: 0deg; }
}
@keyframes fo-in-r {
  0% { translate: 100% 0; rotate: 4deg; animation-timing-function: cubic-bezier(.4, 0, .8, .4); }
  50%, 70% { translate: -1.5% 0; rotate: -.6deg; animation-timing-function: ease-out; }
  100% { translate: 0 0; rotate: 0deg; }
}
@keyframes fo-in-b {
  0% { translate: 0 100%; animation-timing-function: cubic-bezier(.4, 0, .8, .4); }
  50%, 70% { translate: 0 -1.5%; animation-timing-function: ease-out; }
  100% { translate: 0 0; }
}
@keyframes fo-in-tl { 0% { translate: -100% -100%; animation-timing-function: cubic-bezier(.4, 0, .8, .4); } 50%, 70% { translate: 1% 1%; } 100% { translate: 0 0; } }
@keyframes fo-in-tr { 0% { translate: 100% -100%; animation-timing-function: cubic-bezier(.4, 0, .8, .4); } 50%, 70% { translate: -1% 1%; } 100% { translate: 0 0; } }
@keyframes fo-in-bl { 0% { translate: -100% 100%; animation-timing-function: cubic-bezier(.4, 0, .8, .4); } 50%, 70% { translate: 1% -1%; } 100% { translate: 0 0; } }
@keyframes fo-in-br { 0% { translate: 100% 100%; animation-timing-function: cubic-bezier(.4, 0, .8, .4); } 50%, 70% { translate: -1% -1%; } 100% { translate: 0 0; } }

/* The impact frame, over the collage: it goes negative, speed lines strike across. */
.fo.is-fresh + .su-impact {
  z-index: 5;
  background: transparent;
  animation: su-impact .8s steps(1, end) both;
}
/* The star slapped down on the tear, the tape pressed on after it. */
.fo.is-fresh .fo-star {
  animation:
    fo-slap .3s cubic-bezier(.2, 1.5, .4, 1) calc(.58s + var(--n) * .09s) both,
    fo-boil .48s steps(1, end) 1s infinite;
}
@keyframes fo-slap { from { scale: 2.6; rotate: calc(var(--tilt) - 28deg); opacity: 0; } }
.fo.is-fresh .fo-tape {
  animation:
    fo-stick .22s ease-out calc(.72s + var(--n) * .08s) both,
    fo-boil .48s steps(1, end) 1.1s infinite;
}
@keyframes fo-stick { from { scale: 0 1; opacity: 0; } }
.fo.is-fresh + .su-impact + .su-go { animation: fo-drop .32s cubic-bezier(.2, 1.4, .4, 1) 1.1s both; }
@keyframes fo-drop { from { translate: 0 60px; opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
  .fo.is-fresh, .fo.is-fresh *, .fo-sheet, .fo-tape, .fo-star { animation: none !important; }
}

/* ============================================================
   THE VICTORY PAGE (victory.ts)
   GAME! slammed over the world while the winners celebrate, then the
   collage: their sheet slams up over the board (the impact frame again),
   VICTORY! slaps down, the squad goes on sticker by sticker, then the
   stamps and the star, the tape, the report card and the tickets. After
   that it boils like the face-off, and the confetti keeps coming.

   Timeline: 0–.5s the sheet · .4–.56s impact · .56s VICTORY! ·
   .72/.87/1.02s stickers · 1.15s stamps · 1.25s star, tape ·
   1.35s card · 1.5s tickets.
   ============================================================ */

/* Over the HUD, under the scene fade (100) and the dirt (120). */
#bp-victory { position: absolute; inset: 0; z-index: 30; overflow: hidden; }

/* --- GAME! --- */
.vc-game { position: absolute; inset: 0; display: grid; place-items: center; pointer-events: none; }
.vc-game-burst {
  position: absolute;
  inset: -25%;
  background: repeating-conic-gradient(from 0deg at 50% 50%, rgba(255, 251, 240, .9) 0deg 1.4deg, transparent 1.4deg 8deg);
  -webkit-mask-image: radial-gradient(circle at 50% 50%, transparent 16%, #000 44%);
  mask-image: radial-gradient(circle at 50% 50%, transparent 16%, #000 44%);
  opacity: 0;
  animation: vc-burst 1s ease-out .1s both;
}
@keyframes vc-burst {
  0% { opacity: .95; transform: scale(.7) rotate(0deg); }
  100% { opacity: 0; transform: scale(1.3) rotate(12deg); }
}
.vc-game-word {
  position: relative;
  line-height: 0;
  filter: drop-shadow(0 10px 0 rgba(20, 10, 0, .35));
  animation:
    vc-game-in .42s cubic-bezier(.2, 1.6, .4, 1) both,
    vc-game-out .24s ease-in 2.34s forwards;
}
@keyframes vc-game-in { from { transform: scale(3.6) rotate(-14deg); opacity: 0; } }
@keyframes vc-game-out { to { transform: scale(.4) rotate(8deg); opacity: 0; } }

/* --- the board and the paper --- */
.vc { position: absolute; inset: 0; overflow: hidden; }
/* The board underneath, as on the face-off: dark kraft. */
.vc::before {
  content: '';
  position: absolute;
  inset: 0;
  background: #3a2615 url(art/paper.webp) center / cover;
  background-blend-mode: multiply;
}
.vc-scrap, .vc-sheet { position: absolute; inset: 0; }
.vc-scrap { rotate: var(--tilt, 0deg); filter: drop-shadow(0 3px 6px rgba(12, 6, 0, .5)); }
.vc-sheet { rotate: -.5deg; filter: drop-shadow(0 6px 14px rgba(12, 6, 0, .6)); will-change: transform; }

/* The squad's stickers stand on the bottom of the sheet. */
.vc-sticker { transform-origin: 50% 100%; }
.vc-sticker.is-ko { filter: grayscale(1) brightness(.96) contrast(.9) drop-shadow(3px 6px 5px rgba(20, 10, 0, .4)); }

/* K.O., in red rubber, over whoever fell. */
.vc-stamp {
  position: absolute;
  z-index: 3;
  translate: -50% -50%;
  rotate: -13deg;
  padding: 1vmin 1.8vmin 1.2vmin;
  line-height: 0;
  border: .6vmin solid #d8342a;
  border-radius: 1.2vmin;
  box-shadow: inset 0 0 0 .3vmin rgba(216, 52, 42, .3);
  background: rgba(255, 246, 228, .2);
  mix-blend-mode: multiply;
  opacity: .92;
  animation: fo-boil .48s steps(1, end) infinite;
}

/* MVP, on the crayon star, stuck on his shoulder. */
.vc-mvp {
  --tilt: 12deg;
  position: absolute;
  z-index: 4;
  width: var(--size);
  aspect-ratio: 1;
  translate: -50% -50%;
  rotate: var(--tilt);
  pointer-events: none;
  filter: drop-shadow(4px 7px 0 rgba(20, 10, 0, .35));
  animation: fo-boil .48s steps(1, end) infinite;
}
.vc-mvp img { position: absolute; inset: 0; width: 100%; height: 100%; }
.vc-mvp-word { position: absolute; left: 50%; top: 56%; translate: -50% -50%; line-height: 0; }

/* --- the right-hand column: laid out at 720x1000, scaled by --k --- */
.vc-side {
  position: absolute;
  z-index: 6;
  right: 2.6vw;
  top: 50%;
  width: 720px;
  height: 1000px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  transform: translateY(-50%) scale(var(--k, 1));
  transform-origin: 100% 50%;
}
.vc-side .vc-tape {
  position: relative;
  align-items: center;
  padding: 16px 40px 18px;
}

/* VICTORY!, across the top of the squad photo, with Marco's rainbow under it. */
.vc-headline {
  position: absolute;
  z-index: 5;
  left: 31%;
  top: 3.5%;
  translate: -50% 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  pointer-events: none;
}
.vc-title {
  line-height: 0;
  rotate: -3deg;
  filter: drop-shadow(0 8px 0 rgba(20, 10, 0, .35));
}

/* The squad report: an index card, ruled, taped on. */
.vc-card {
  position: relative;
  width: 660px;
  margin-top: 30px;
  padding: 30px 30px 24px 72px;
  background:
    linear-gradient(90deg, transparent 54px, rgba(214, 70, 60, .55) 54px 57px, transparent 57px),
    repeating-linear-gradient(180deg, transparent 0 43px, rgba(88, 140, 205, .3) 43px 45px),
    #fffdf4;
  background-position: 0 0, 0 20px, 0 0;
  box-shadow: 0 4px 10px rgba(20, 10, 0, .4);
  rotate: 1.2deg;
}
.vc-card::before {
  content: '';
  position: absolute;
  left: 50%;
  top: -20px;
  width: 180px;
  height: 42px;
  translate: -50% 0;
  rotate: -3deg;
  background: rgba(238, 224, 186, .92) url(art/paper.webp) center / 600px auto;
  background-blend-mode: multiply;
  clip-path: polygon(0 10%, 5% 0, 95% 4%, 100% 0, 97% 50%, 100% 100%, 5% 94%, 0 100%, 3% 50%);
}
.vc-card-head { line-height: 0; margin-bottom: 8px; }
.vc-rows {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr) auto auto auto;
  column-gap: 18px;
  align-items: center;
}
.vc-row { display: contents; }
.vc-row img { width: 42px; height: 42px; object-fit: contain; }
.vc-row > div { height: 45px; display: flex; align-items: center; line-height: 0; overflow: hidden; }
.vc-row.is-ko img { filter: grayscale(1); opacity: .55; }
.vc-num { justify-content: flex-end; }
.vc-card-line { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; margin-top: 12px; line-height: 0; }
.vc-card-line img { width: 40px; height: 40px; margin-left: -6px; }

/* The tickets out: strips of tape, the arrow on the one picked. */
.vc-picks { display: flex; flex-direction: column; align-items: center; gap: 16px; margin-top: 30px; }
/* Online: the vote in the same torn-tape tickets, everybody's say under them. */
.vc-olvote { gap: 14px; }
.vc-olrow { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px 18px; }
.vc-olvote .vc-ticket { min-width: 0; }
.vc-oltally { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; max-width: 640px; }
.vc-olchip {
  padding: 5px 10px 4px;
  border-radius: 999px;
  border: 2px solid var(--team);
  background: rgba(255, 253, 244, .92);
  line-height: 0;
}
.vc-olchip:not(.is-in) { border-style: dashed; }
.vc-olline { line-height: 0; }
.vc-pick.is-picked .vc-ticket { background-color: rgba(191, 232, 169, .98); }
.vc-picks.is-row {
  position: absolute;
  z-index: 6;
  left: 0;
  right: 0;
  bottom: clamp(18px, 5vh, 52px);
  flex-direction: row;
  justify-content: center;
  gap: clamp(14px, 3vw, 40px);
  margin: 0;
}
.vc-pick {
  position: relative;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  line-height: 0;
  rotate: var(--tilt, 0deg);
  filter: drop-shadow(0 3px 2px rgba(20, 10, 0, .35));
  transition: scale .12s ease-out;
}
.vc-ticket {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 26px;
  min-width: 420px;
  padding: 16px 34px 18px;
  background: rgba(238, 224, 186, .96) url(art/paper.webp) center / 600px auto;
  background-blend-mode: multiply;
  clip-path: polygon(0 8%, 5% 0, 10% 7%, 90% 2%, 95% 9%, 100% 0, 98% 30%, 100% 60%, 97% 100%,
    92% 93%, 86% 100%, 14% 96%, 8% 100%, 2% 92%, 0 60%, 3% 30%);
}
.vc-picks.is-row .vc-ticket { min-width: 0; }
.vc-pick.is-on { scale: 1.08; }
.vc-pick.is-on .vc-ticket { background-color: rgba(255, 226, 140, .98); }
.vc-pick.is-on .vc-ticket > .bf:first-child { color: #e8491a !important; }
.vc-arrow {
  position: absolute;
  right: 100%;
  top: 50%;
  width: 86px;
  margin-right: 8px;
  translate: 0 -50%;
  opacity: 0;
  pointer-events: none;
}
.vc-pick.is-on .vc-arrow { opacity: 1; animation: vc-poke .7s ease-in-out infinite; }
@keyframes vc-poke { 50% { transform: translateX(12px); } }
.vc-picks.is-row .vc-arrow {
  right: auto;
  top: auto;
  left: 50%;
  bottom: 100%;
  width: 64px;
  margin: 0 0 4px;
  translate: -50% 0;
  rotate: 90deg;
}
.vc-picks.is-row .vc-pick.is-on .vc-arrow { animation-name: vc-poke-down; }
@keyframes vc-poke-down { 50% { transform: translateX(10px); } }

/* --- confetti --- */
.vc-confetti { position: absolute; inset: 0; z-index: 7; overflow: hidden; pointer-events: none; }
.vc-confetti i {
  position: absolute;
  top: -6vh;
  width: var(--w);
  height: var(--h);
  animation: vc-fall linear infinite both;
}
.vc-confetti i.is-tri { width: calc(var(--w) * 1.3); height: calc(var(--w) * 1.15); clip-path: polygon(50% 0, 100% 100%, 0 100%); }
.vc-confetti i.is-dot { height: var(--w); border-radius: 50%; }
@keyframes vc-fall {
  0% { transform: translate3d(0, 0, 0) rotate(0deg) scaleX(1); }
  25% { transform: translate3d(calc(var(--drift) * .3), 30vh, 0) rotate(calc(var(--spin) * .25)) scaleX(.25); }
  50% { transform: translate3d(calc(var(--drift) * .6), 60vh, 0) rotate(calc(var(--spin) * .5)) scaleX(1); }
  75% { transform: translate3d(calc(var(--drift) * .85), 90vh, 0) rotate(calc(var(--spin) * .75)) scaleX(.25); }
  100% { transform: translate3d(var(--drift), 118vh, 0) rotate(var(--spin)) scaleX(1); }
}

/* --- a draw: the face-off again, washed out --- */
.vc-draw .fo-paper { filter: saturate(.35) brightness(.92); }
.vc-draw .fo-sticker { filter: grayscale(1) contrast(.95) drop-shadow(3px 6px 5px rgba(20, 10, 0, .4)); }

/* --- the entrance --- */
.vc.is-fresh {
  animation:
    su-negative .8s steps(1, end) both,
    su-jolt .4s ease-out .56s both;
}
/* A draw's face-off already goes negative on its own: twice would cancel out. */
.vc.vc-draw.is-fresh { animation: none; }
.vc.is-fresh::before { animation: vc-board .22s ease-out both; }
@keyframes vc-board { from { opacity: 0; } }
.vc.is-fresh .vc-scrap { animation: vc-board .3s ease-out .08s both; }
.vc.is-fresh .vc-sheet { animation: vc-sheet-in .8s both; }
@keyframes vc-sheet-in {
  0% { translate: 0 105%; rotate: 3deg; animation-timing-function: cubic-bezier(.4, 0, .8, .4); }
  50%, 70% { translate: 0 -1.2%; rotate: -.9deg; animation-timing-function: ease-out; }
  100% { translate: 0 0; rotate: -.5deg; }
}
.vc.is-fresh + .su-impact {
  z-index: 5;
  background: transparent;
  animation: su-impact .8s steps(1, end) both;
}
.vc.is-fresh .vc-title { animation: vc-slap .36s cubic-bezier(.2, 1.5, .4, 1) .56s both; }
@keyframes vc-slap { from { transform: scale(2.8) rotate(-18deg); opacity: 0; } }
.vc.is-fresh .vc-sticker { animation: vc-stick .34s cubic-bezier(.2, 1.5, .4, 1) calc(.72s + var(--n) * .15s) both; }
@keyframes vc-stick { from { transform: translateY(-5%) scale(1.45) rotate(-6deg); opacity: 0; } }
.vc.is-fresh .vc-stamp {
  animation:
    vc-stamp .2s ease-in calc(1.15s + var(--n) * .08s) both,
    fo-boil .48s steps(1, end) 1.6s infinite;
}
@keyframes vc-stamp { from { transform: scale(2.4); opacity: 0; } }
.vc.is-fresh .vc-mvp {
  animation:
    fo-slap .3s cubic-bezier(.2, 1.5, .4, 1) 1.25s both,
    fo-boil .48s steps(1, end) 1.6s infinite;
}
.vc.is-fresh .vc-tape {
  animation:
    fo-stick .22s ease-out 1.25s both,
    fo-boil .48s steps(1, end) 1.5s infinite;
}
.vc.is-fresh .vc-card { animation: vc-drop .34s cubic-bezier(.2, 1.4, .4, 1) 1.35s both; }
@keyframes vc-drop { from { transform: translateY(80px) rotate(-4deg); opacity: 0; } }
.vc.is-fresh .vc-pick { animation: vc-drop .3s cubic-bezier(.2, 1.4, .4, 1) calc(1.5s + var(--n) * .08s) both; }

@media (prefers-reduced-motion: reduce) {
  .vc-game *, .vc.is-fresh, .vc.is-fresh *, .vc.is-fresh::before, .vc-stamp, .vc-mvp, .vc-confetti i { animation: none !important; }
}

/* ============================================================
   THE WEAPONS PAGE (setup.ts rulesPage, arsenal.ts): the scheme card
   beside the arsenal, every weapon a card in its rarity's colour.
   ============================================================ */
.su-arsenal {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: clamp(6px, 1vw, 12px);
  width: min(46vw, 560px);
  padding: clamp(10px, 1.4vw, 16px);
  background: #fffbf0;
  border: 3px solid var(--bark);
  border-radius: 14px;
  box-shadow: 0 6px 0 rgba(77, 46, 12, .2);
}
.su-weapon {
  position: relative;
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  padding: 8% 8% 16%;
  border: 3px solid color-mix(in srgb, var(--rarity) 72%, #4d2e0c);
  border-radius: 10px;
  background: color-mix(in srgb, var(--rarity) 16%, #fffbf0);
  cursor: pointer;
  line-height: 0;
  transition: scale .1s ease-out;
}
.su-weapon-art { width: 100%; height: 100%; display: grid; place-items: center; }
.su-weapon-art img, .su-weapon-art svg { width: 100%; height: 100%; object-fit: contain; }
/* The rarity, as a band along the bottom. */
.su-weapon-band {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 14%;
  background: var(--rarity);
  border-radius: 0 0 6px 6px;
}
.su-weapon-count { position: absolute; top: 4%; right: 6%; line-height: 0; }
.su-inf { width: 26px; height: 13px; display: block; }
.su-weapon.is-fixed { cursor: default; }
.su-weapon.is-off .su-weapon-art { filter: grayscale(1); opacity: .35; }
/* Banned from the roll: a red cross through it. */
.su-weapon.is-banned::after {
  content: '';
  position: absolute;
  inset: 16%;
  background:
    linear-gradient(45deg, transparent 44%, #d8342a 44% 56%, transparent 56%),
    linear-gradient(-45deg, transparent 44%, #d8342a 44% 56%, transparent 56%);
  pointer-events: none;
}
.su-arsenal:not(.is-live) .su-weapon:not(.is-off) { opacity: .95; }
.su-weapon.is-on {
  outline: 4px solid #f7c948;
  outline-offset: 2px;
  scale: 1.07;
  z-index: 1;
}
.su-side.is-rules { justify-content: center; gap: 12px; }
.su-side.is-rules .su-blurb { min-height: 64px; }

/* Rarity in the match's inventory and shop: the cell's rim in its colour. */
.ph-cell[data-rarity='common'], .ph-slot[data-rarity='common'] { box-shadow: inset 0 0 0 3px #8f9aa6; }
.ph-cell[data-rarity='rare'], .ph-slot[data-rarity='rare'] { box-shadow: inset 0 0 0 3px #3f8ef0; }
.ph-cell[data-rarity='epic'], .ph-slot[data-rarity='epic'] { box-shadow: inset 0 0 0 3px #a35ae0; }
.ph-cell[data-rarity='legendary'], .ph-slot[data-rarity='legendary'] { box-shadow: inset 0 0 0 3px #f2a81d; }

/* The title's Settings card: How to Play's card, with rows to change. */
.title-settings { min-width: min(560px, 90vw); gap: 10px; }
.title-setting {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  padding: 6px 12px;
  border-radius: 10px;
  line-height: 0;
}
.title-setting.is-on { background: #ffe07a; box-shadow: inset 0 0 0 3px var(--bark); }
.title-setting-value { display: flex; align-items: center; gap: 10px; }
.title-setting-text { min-width: 12em; display: flex; justify-content: center; }
.title-setting-arrow { border: 0; background: none; padding: 2px 4px; cursor: pointer; line-height: 0; }
.title-setting-note { max-width: 520px; min-height: 58px; align-self: center; text-align: center; }

/* Who each of the squad is: every face his side fields, his own ringed
   in gold. Click one to have him; C walks through them. */
.su-fighters { flex: 0 0 auto; display: flex; align-items: center; gap: 5px; margin-left: 10px; }
.su-chip {
  width: 38px;
  height: 38px;
  padding: 0;
  border-radius: 50%;
  border: 2px solid rgba(77, 46, 12, .35);
  background: #fffdf4;
  overflow: hidden;
  line-height: 0;
  cursor: pointer;
  opacity: .5;
  filter: saturate(.4);
  transition: opacity .1s, filter .1s, scale .1s;
}
.su-chip:hover { opacity: .85; filter: none; }
.su-chip.is-picked {
  opacity: 1;
  filter: none;
  scale: 1.12;
  border: 3px solid var(--bark);
  box-shadow: 0 0 0 3px #f7c948, 0 2px 0 3px rgba(77, 46, 12, .35);
}
.su-chip-face { width: 132%; height: 132%; display: block; margin: -14% 0 0 -16%; object-fit: cover; }
.su-side.is-rules { position: relative; }
.su-caption {
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  margin-top: 12px;
  display: flex;
  justify-content: center;
  line-height: 0;
}
.su-lead { width: 24px; height: 24px; margin-left: 6px; vertical-align: middle; }
.su-tag { display: flex; align-items: center; }
.su-guy { cursor: pointer; }
.su-guy-name { display: flex; justify-content: center; margin-top: 6px; line-height: 0; opacity: .75; }
.su-guy.is-on .su-guy-name { opacity: 1; }

/* How to Play's pages: tabs across the top, the page under them. */
/* No height cap: a card taller than the window shrinks whole to fit it
   (screen.ts fit()), rather than spilling its words out of the bottom. */
.title-help-card.is-tabbed { width: min(900px, 94vw); align-items: center; gap: 10px; padding: 20px 28px 16px; }
.title-tabs { display: flex; gap: 22px; line-height: 0; }
.title-tab { border: 0; background: none; padding: 4px 6px; cursor: pointer; line-height: 0; }
.title-help-page {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
  width: 100%;
  min-height: 330px;
  padding: 10px 8px 2px;
  border-top: 2px dashed rgba(77, 46, 12, .25);
  line-height: 0;
}
.title-classes { display: grid; grid-template-columns: 1fr 1fr; gap: 16px 26px; width: 100%; }
.title-class { display: flex; flex-direction: column; gap: 8px; line-height: 0; min-width: 0; }
.title-class .bf-para { width: 100%; }
.title-class-head { display: flex; align-items: baseline; gap: 12px; }
.title-rarities { display: grid; grid-template-columns: auto 1fr; gap: 8px 14px; align-items: center; width: 100%; }
.title-rarity { background: var(--rarity); border: 2px solid var(--bark); border-radius: 8px; padding: 5px 10px; line-height: 0; justify-self: start; }
.title-schemes { display: grid; grid-template-columns: auto 1fr; gap: 8px 14px; align-items: center; width: 100%; }
.title-weapons { display: grid; grid-template-columns: 1fr 1fr; gap: 28px; width: 100%; }
.title-weapons-col { display: flex; flex-direction: column; gap: 12px; min-width: 0; line-height: 0; }
.title-weapons-col .bf-para { width: 100%; }
.title-help-page .bf-para { justify-content: flex-start; }

/* Class badges on the squad cards and the "who is up" panel: the same
   shield / helmet / flask / winged boot that floats over each head. */
.ph-tclass { display: flex; gap: 4px; padding: 0 3px 3px; }
.ph-tclass .ph-badge { flex: 1; display: flex; justify-content: center; }
.ph-badge { line-height: 0; }
.ph-badge.is-out { opacity: .35; filter: grayscale(1); }
.ph-face-who { display: flex; align-items: center; gap: 6px; line-height: 0; }
/* Pixel art: square pixels at a whole multiple, never smoothed. */
.class-icon { display: block; image-rendering: pixelated; image-rendering: crisp-edges; }

/* ============================================================
   THE SHOP (match.ts shop): shelves by rarity, a card per weapon.
   ============================================================ */
.ph-panel.ph-shop { position: relative; width: min(1080px, 97vw); max-height: 96vh; overflow: auto; gap: 12px; }
.ph-shop-purse {
  position: absolute;
  top: 18px;
  right: 22px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 16px 6px 10px;
  border: 3px solid var(--ink);
  border-radius: 999px;
  background: #fff4c8;
  box-shadow: 0 3px 0 var(--shade);
  line-height: 0;
}
.ph-shelves {
  display: grid;
  grid-template-columns: max-content max-content;
  justify-content: center;
  gap: 12px;
}
.ph-shelf {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  padding: 8px 10px 12px;
  border-radius: 16px;
  background: color-mix(in srgb, var(--rarity) 14%, #fffbf0);
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--rarity) 55%, transparent);
}
.ph-shelf-tag {
  padding: 4px 9px;
  border: 2px solid var(--ink);
  border-radius: 8px;
  background: var(--rarity);
  line-height: 0;
}
/* Every card on a shelf the same height: the prices line up along the bottom. */
.ph-shelf-row { display: flex; gap: 10px; align-items: stretch; }
.ph-ware {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  width: 132px;
  padding: 12px 8px 10px;
  border: 3px solid var(--ink);
  border-radius: 16px;
  background: var(--card);
  box-shadow: 0 4px 0 var(--shade);
  color: var(--ink);
  cursor: pointer;
  line-height: 0;
  overflow: hidden;
  transition: translate .12s ease, box-shadow .12s ease;
}
/* The rarity on the card itself: a ribbon across its top. */
.ph-ware::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 6px;
  background: var(--rarity);
}
.ph-ware:hover, .ph-ware:focus-visible { translate: 0 -3px; box-shadow: 0 7px 0 var(--shade); outline: none; }
.ph-ware:active { translate: 0 2px; box-shadow: 0 2px 0 var(--shade); }
/* The render, big, on a spotlight of its rarity's colour. */
.ph-ware-art {
  position: relative;
  display: block;
  width: 86px;
  height: 86px;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 44%, #fffdf6 0%, color-mix(in srgb, var(--rarity) 26%, #fff6e0) 56%,
    color-mix(in srgb, var(--rarity) 55%, #f3e2bd) 100%);
  box-shadow: inset 0 -5px 0 color-mix(in srgb, var(--rarity) 30%, transparent),
    0 0 0 2px color-mix(in srgb, var(--rarity) 65%, var(--card-edge));
}
.ph-ware-art > svg, .ph-ware-art > img {
  width: 100%;
  height: 100%;
  display: block;
  transform: scale(1.22);
  filter: drop-shadow(0 3px 2px rgba(43, 23, 6, .28));
  transition: transform .15s ease;
}
.ph-ware:hover .ph-ware-art > img, .ph-ware:focus-visible .ph-ware-art > img,
.ph-ware:hover .ph-ware-art > svg, .ph-ware:focus-visible .ph-ware-art > svg { transform: scale(1.34) rotate(-6deg); }
/* How many you have: a badge on the art, only when you have some. */
.ph-ware-have {
  position: absolute;
  top: -2px;
  right: -8px;
  padding: 4px 6px;
  border: 2px solid var(--ink);
  border-radius: 999px;
  background: color-mix(in srgb, var(--rarity) 80%, #2b1706);
  line-height: 0;
}
/* Two lines when it must ("Cluster Grenade"): the card grows, and the shelf's
   stretch keeps its neighbours' prices level with it. */
.ph-ware-name { width: 100%; min-height: 42px; display: flex; align-items: center; justify-content: center; }
.ph-ware-name .bf-para { justify-content: center; row-gap: 3px; }
.ph-ware-does {
  padding: 3px 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--rarity) 18%, #fffdf4);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--rarity) 45%, transparent);
  line-height: 0;
}
/* The price is the button. */
.ph-ware-price {
  margin-top: auto;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 14px;
  border: 3px solid var(--ink);
  border-radius: 999px;
  background: var(--gold);
  box-shadow: 0 3px 0 var(--gold-deep);
  line-height: 0;
}
.ph-ware:hover .ph-ware-price { background: color-mix(in srgb, var(--gold) 85%, #fff); }
.ph-ware.is-broke { cursor: not-allowed; }
.ph-ware.is-broke:hover, .ph-ware.is-broke:active { translate: 0 -1px; box-shadow: 0 5px 0 var(--shade); }
.ph-ware.is-broke .ph-ware-price { background: #ece2cb; border-color: #b9a98a; box-shadow: none; }
.ph-ware.is-broke .ph-ware-art > img, .ph-ware.is-broke .ph-ware-art > svg {
  filter: grayscale(.45) opacity(.8) drop-shadow(0 3px 2px rgba(43, 23, 6, .2));
}
/* Legendary: a slow shine sweeping across the card. */
.ph-ware.is-legendary::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(115deg, transparent 42%, rgba(255, 255, 255, .42) 50%, transparent 58%);
  background-size: 260% 100%;
  animation: ph-ware-shine 3.4s ease-in-out infinite;
  pointer-events: none;
}
@keyframes ph-ware-shine { 0% { background-position: 150% 0; } 55%, 100% { background-position: -110% 0; } }
@media (prefers-reduced-motion: reduce) { .ph-ware.is-legendary::after { animation: none; opacity: 0; } }
.ph-shop-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 4px; min-height: 52px; line-height: 0; }
/* What the card under the pointer does (or how Bells are earned). */
.ph-shop-info { display: flex; flex-direction: column; align-items: flex-start; gap: 7px; line-height: 0; }
/* A short window: the whole sheet a size smaller, rather than a scrollbar. */
@media (max-height: 980px) and (min-height: 821px) {
  .ph-panel.ph-shop { zoom: .9; max-height: 106vh; }
}
@media (max-height: 820px) {
  .ph-panel.ph-shop { zoom: .78; max-height: 122vh; }
}

/* ------------------------------------------------------------------ online
   Online play's own pages (web/src/online): the hub, join, connecting, the
   room, the face-off's YOU, the match's online layer and the online
   victory. The game's paper, bark ink and gold, on layouts of their own. */
.ol-page {
  position: absolute;
  inset: 0;
  z-index: 20;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(10px, 2vh, 20px);
  padding: clamp(18px, 4vh, 40px) 16px 60px;
  background: radial-gradient(90% 70% at 50% 38%, rgba(251, 245, 230, .8) 0%, rgba(243, 233, 210, .72) 60%, rgba(233, 220, 192, .76) 100%);
}
.ol-head { display: flex; flex-direction: column; align-items: center; gap: 10px; line-height: 0; }
.ol-sub { line-height: 0; }
.ol-hint { position: absolute; left: 0; right: 0; bottom: 18px; margin: 0 auto; width: fit-content; line-height: 0; }
.ol-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 16px 20px;
  background: #fffbf0;
  border: 3px solid var(--bark);
  border-radius: 16px;
  box-shadow: 0 8px 0 rgba(77, 46, 12, .2);
  line-height: 0;
}
.ol-card-title { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; line-height: 0; margin-bottom: 4px; }
.ol-card-note { margin-top: auto; padding-top: 8px; line-height: 0; }
.ol-btn {
  padding: 8px 16px 5px;
  line-height: 0;
  border-radius: 999px;
  border: 3px solid var(--bark);
  background: var(--gold);
  box-shadow: 0 3px 0 var(--gold-deep);
  cursor: pointer;
}
.ol-btn.is-cream { background: #fffdf4; box-shadow: 0 3px 0 rgba(77, 46, 12, .25); }
.ol-btn.is-off { background: #efe6d2; box-shadow: none; cursor: not-allowed; }
.ol-btn:hover { translate: 0 -1px; }
.ol-btn:active { translate: 0 2px; box-shadow: none; }
.ol-error {
  max-width: min(560px, 90vw);
  padding: 10px 14px;
  border: 3px dashed #d8342a;
  border-radius: 12px;
  background: rgba(255, 240, 232, .9);
  line-height: 0;
}
.ol-field {
  min-width: 0;
  width: 13em;
  padding: 8px 12px;
  border-radius: 10px;
  border: 3px solid var(--bark);
  background: #fff;
  color: var(--bark);
  font: 700 22px system-ui, sans-serif;
}
.ol-field.is-small { width: 9em; font-size: 18px; padding: 5px 10px; }

/* faces and line-ups */
.ol-face {
  width: 46px;
  height: 46px;
  border-radius: 50%;
  border: 2px solid rgba(77, 46, 12, .45);
  background: #fffdf4;
  object-fit: cover;
  display: block;
}
.ol-face.is-small { width: 30px; height: 30px; }
.ol-face.is-chip { width: 34px; height: 34px; border: 0; }
.ol-fighter { position: relative; line-height: 0; }
.ol-badge { position: absolute; right: -6px; bottom: -4px; line-height: 0; filter: drop-shadow(0 1px 0 rgba(43, 23, 6, .5)); }
.ol-lineup { display: flex; gap: 10px; line-height: 0; }
.ol-lineup.is-big .ol-face { width: 64px; height: 64px; }

/* the hub */
.ol-player { width: min(780px, 94vw); gap: 16px; }
.ol-player.is-on { box-shadow: 0 0 0 4px var(--gold), 0 8px 0 rgba(77, 46, 12, .2); }
.ol-namerow { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; line-height: 0; }
.ol-label { flex: 0 0 auto; line-height: 0; }
.ol-name { flex: 1 1 auto; min-width: 0; background: none; border: 0; padding: 4px 6px; cursor: text; line-height: 0; text-align: left; }

/* A name being typed (online/common.ts nameField): the game's own letters
   over an invisible input, a blinking caret where the input's is. The same
   size as the name it replaces, so nothing round it moves. */
.ol-bfield {
  position: relative;
  display: flex;
  align-items: center;
  flex: 1 1 auto;
  min-width: 0;
  /* The name button it replaces is its letters plus 4px above and below. */
  height: calc(16px * var(--px) + 8px);
  padding: 0 8px;
  border-radius: 10px;
  background: #fff7df;
  box-shadow: inset 0 0 0 3px rgba(77, 46, 12, .22);
  overflow: hidden;
  cursor: text;
  line-height: 0;
}
.ol-bfield.is-focus { box-shadow: inset 0 0 0 3px var(--bark); }
.ol-bfield-words { display: block; white-space: nowrap; pointer-events: none; }
.ol-bfield-caret {
  position: absolute;
  top: 6px;
  bottom: 6px;
  width: max(3px, calc(1px * var(--px)));
  background: var(--bark);
  pointer-events: none;
  animation: su-caret 1.06s steps(1) infinite;
}
.ol-bfield:not(.is-focus) .ol-bfield-caret { display: none; }
.ol-bfield-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  opacity: 0;
  /* 16px: a phone does not zoom the page in on it. */
  font-size: 16px;
  cursor: text;
}

/* A menu line whose ">" keeps a column of its own (bitmap.ts pointed). */
.bf-pointed { display: inline-flex; line-height: 0; }
.ol-squadrow { display: flex; align-items: center; gap: 18px; padding-top: 12px; border-top: 3px dashed rgba(77, 46, 12, .2); }
.ol-squadwords { display: flex; flex-direction: column; gap: 9px; line-height: 0; }
.ol-menu { display: flex; flex-direction: column; gap: 4px; }
.ol-item { display: block; background: none; border: 0; padding: 4px 10px; cursor: pointer; text-align: left; line-height: 0; }
.ol-item.is-on { animation: title-nudge .9s ease-in-out infinite; }

/* join */
.ol-codecard { align-items: center; gap: 22px; padding: 26px 34px; }
.ol-tiles { display: flex; gap: 14px; }
.ol-tile {
  position: relative;
  width: 100px;
  padding: 8px 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  line-height: 0;
  border-radius: 18px;
  border: 4px solid var(--bark);
  background: #fff;
  box-shadow: 0 5px 0 rgba(77, 46, 12, .25);
  cursor: pointer;
}
.ol-tile > * { flex-shrink: 0; }
.ol-tile.is-on { background: #fff7cf; box-shadow: 0 0 0 4px var(--gold), 0 5px 0 var(--gold-deep); }
.ol-tile-turn { line-height: 0; padding: 2px 8px; opacity: 0; }
.ol-tile.is-on .ol-tile-turn { opacity: 1; }
.ol-actions { display: flex; gap: 16px; align-items: center; justify-content: center; }

/* connecting */
.ol-waitcard { align-items: center; gap: 18px; padding: 24px 36px; }
.ol-status { line-height: 0; }
.ol-spinner {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 6px solid rgba(77, 46, 12, .15);
  border-top-color: var(--gold-deep);
  animation: ol-spin .9s linear infinite;
}
@keyframes ol-spin { to { rotate: 360deg; } }

/* the room */
.ol-room { gap: clamp(8px, 1.2vh, 12px); padding-top: clamp(10px, 2vh, 20px); }
.ol-roomtop { display: flex; align-items: center; gap: 22px; line-height: 0; }
.ol-code { display: flex; align-items: center; gap: 14px; line-height: 0; }
.ol-codedigits { display: flex; gap: 8px; }
.ol-codetile {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 54px;
  height: 64px;
  line-height: 0;
  border-radius: 12px;
  border: 3px solid var(--bark);
  background: #fff;
  box-shadow: 0 4px 0 rgba(77, 46, 12, .25);
}
.ol-share { line-height: 0; }
.ol-seats { display: grid; grid-template-columns: repeat(4, minmax(0, 220px)); gap: 12px; max-width: 96vw; }
.ol-seat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 0 10px 8px;
  border: 3px solid var(--bark);
  border-radius: 14px;
  background: #fffdf4;
  box-shadow: 0 5px 0 rgba(77, 46, 12, .18);
  overflow: hidden;
  line-height: 0;
}
.ol-seat.is-you { box-shadow: 0 0 0 4px var(--gold), 0 5px 0 rgba(77, 46, 12, .18); }
.ol-seat.is-ready { background: #f1fbe9; }
.ol-seat.is-empty {
  justify-content: center;
  border: 3px dashed color-mix(in srgb, var(--team) 50%, rgba(77, 46, 12, .35));
  background: rgba(255, 253, 244, .45);
  box-shadow: none;
}
.ol-seat-band {
  align-self: stretch;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin: 0 -10px;
  padding: 9px 8px 8px;
  background: var(--team);
  border-bottom: 3px solid var(--bark);
}
.ol-seat-band .bf-para { flex: 0 1 auto; width: auto; min-width: 0; }
.ol-you, .ol-host { flex: 0 0 auto; padding: 3px 5px; border-radius: 6px; background: #fffdf4; box-shadow: inset 0 0 0 2px var(--bark); line-height: 0; }
.ol-seat-squad, .ol-seat-wait, .ol-seat-state { line-height: 0; }
.ol-roombody { display: flex; align-items: stretch; gap: 16px; max-width: 96vw; }
.ol-squadcard { width: min(500px, 46vw); gap: 4px; border-top: 8px solid var(--team); }
.ol-matchcard { width: min(420px, 40vw); gap: 4px; }
.ol-row {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 36px;
  padding: 1px 8px;
  border-radius: 10px;
  border: 2px solid transparent;
  cursor: pointer;
}
.ol-row.is-on { border-color: var(--bark); background: #fff4c4; }
.ol-row-tag { flex: 0 0 5.6em; line-height: 0; }
.ol-row-val { display: flex; align-items: center; gap: 8px; min-width: 0; line-height: 0; }
.ol-row-text { display: flex; align-items: center; gap: 10px; line-height: 0; cursor: pointer; }
.ol-arrow { background: none; border: 0; padding: 2px 4px; cursor: pointer; line-height: 0; }
.ol-sides { display: flex; gap: 8px; }
.ol-sidepick {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 10px 4px 5px;
  border-radius: 999px;
  border: 2px solid rgba(77, 46, 12, .3);
  background: #fffdf4;
  opacity: .55;
  cursor: pointer;
  line-height: 0;
}
.ol-sidepick.is-on { opacity: 1; border-color: var(--bark); background: #fff4c4; }
.ol-member { display: flex; align-items: center; gap: 12px; }
.ol-faces { display: flex; gap: 4px; }
.ol-chip {
  width: 38px;
  height: 38px;
  padding: 0;
  border-radius: 50%;
  border: 2px solid rgba(77, 46, 12, .35);
  background: #fffdf4;
  overflow: hidden;
  line-height: 0;
  cursor: pointer;
  opacity: .5;
  filter: saturate(.4);
}
.ol-chip.is-picked { opacity: 1; filter: none; border: 3px solid var(--bark); box-shadow: 0 0 0 3px var(--gold); }
.ol-class { display: flex; align-items: center; gap: 4px; line-height: 0; }
.ol-map { line-height: 0; display: flex; justify-content: center; }
.ol-map-canvas {
  max-width: 100%;
  max-height: 104px;
  image-rendering: pixelated;
  border: 3px solid var(--bark);
  border-radius: 8px;
}
.ol-roomactions { display: flex; gap: 16px; align-items: center; }
.ol-sitout { align-items: center; gap: 10px; padding: 12px 22px; background: #fff4c4; }
.ol-bigbtn {
  padding: 10px 24px 8px;
  line-height: 0;
  border-radius: 999px;
  border: 4px solid var(--bark);
  background: #fffdf4;
  box-shadow: 0 5px 0 rgba(77, 46, 12, .3);
  cursor: pointer;
}
.ol-bigbtn.is-ready { background: #bfe8a9; box-shadow: 0 5px 0 #6fae52; }
.ol-bigbtn.is-start { background: var(--gold); box-shadow: 0 5px 0 var(--gold-deep); }
.ol-bigbtn.is-off { background: #efe6d2; box-shadow: none; cursor: not-allowed; }
.ol-bigbtn.is-on { outline: 4px solid var(--gold); outline-offset: 3px; }
.ol-bigbtn.is-leave { padding: 9px 18px 7px; }

/* the 3-2-1 */
.ol-countdown {
  position: absolute;
  inset: 0;
  z-index: 30;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  background: rgba(43, 23, 6, .35);
  line-height: 0;
}
.ol-count.is-fresh { animation: ol-slam .45s cubic-bezier(.2, 1.6, .4, 1) both; }
.ol-count-sub { line-height: 0; }
@keyframes ol-slam { from { scale: 2.4; opacity: 0; } 60% { opacity: 1; } }

/* the face-off's YOU, and the tapes clear of the top edge on a short screen */
.fo-label { display: flex; align-items: center; gap: 8px; line-height: 0; }
.fo-you {
  padding: 4px 7px;
  border-radius: 7px;
  background: var(--gold);
  box-shadow: inset 0 0 0 3px #1f1208;
  line-height: 0;
}
.ol-vs { cursor: default; }
.ol-vs .fo-tape.at-tl { translate: 0 -15%; }
.ol-vs .fo-tape.at-tr { translate: -100% -15%; }
.ol-vs .fo-tape.at-tc { translate: -50% -15%; }

/* the match's online layer */
.ol-hud { position: absolute; inset: 0; z-index: 21; }
/* It covers the arena: every click and drag must go through it to the
   game (#bp-ui > * would otherwise take them, and the mouse could not
   aim or fire). */
#bp-ui > .ol-hud { pointer-events: none; }
.ol-turnpill {
  position: absolute;
  left: 50%;
  top: 14px;
  translate: -50% 0;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px 5px;
  border-radius: 999px;
  border: 3px solid var(--bark);
  background: #fffdf4;
  line-height: 0;
}
.ol-turnpill.is-yours { background: #fff4c4; }
.ol-dot { width: 12px; height: 12px; border-radius: 50%; background: var(--team); box-shadow: inset 0 0 0 2px var(--bark); flex: 0 0 auto; }
.ol-watching {
  position: absolute;
  left: 50%;
  top: 62px;
  translate: -50% 0;
  padding: 5px 12px 4px;
  border-radius: 999px;
  background: rgba(43, 23, 6, .55);
  line-height: 0;
}
/* Top right, under the minimap: the squads' cards climb the left edge from
   the bottom, and four of them reach the top of a short screen. Unity draws
   the minimap's frame 159 GUI units tall at a scale of height/800, never
   under .75 (MatchHud.DrawMiniMap, CartoonSkin.Scale). */
.ol-players {
  position: absolute;
  right: 8px;
  top: calc(max(19.9vh, 119px) + 10px);
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 8px 10px;
  border-radius: 12px;
  border: 3px solid var(--bark);
  background: rgba(255, 253, 244, .88);
  line-height: 0;
}
.ol-player-row { display: flex; align-items: center; gap: 7px; line-height: 0; }
.ol-player-row.is-left .ol-dot { background: #b9ab93; }
.ol-player-row.is-struggling .ol-dot { background: #f0a030; animation: title-blink 1s ease-in-out infinite; }
.ol-player-state { line-height: 0; }
.ol-toast {
  position: absolute;
  left: 50%;
  top: 104px;
  translate: -50% 0;
  z-index: 40;
  padding: 9px 16px 7px;
  border-radius: 12px;
  border: 3px solid var(--bark);
  background: #fffdf4;
  box-shadow: 0 5px 0 rgba(77, 46, 12, .25);
  line-height: 0;
  animation: ol-toast 5s ease-in-out both;
}
.ol-toast:not(.is-fresh) { animation: none; }
@keyframes ol-toast {
  0% { opacity: 0; translate: -50% -14px; }
  6% { opacity: 1; translate: -50% 0; }
  88% { opacity: 1; }
  100% { opacity: 0; }
}
.ol-veil {
  position: absolute;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(43, 23, 6, .55);
}
.ol-veil-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  padding: 22px 30px;
  border-radius: 16px;
  border: 3px solid var(--bark);
  background: #fffbf0;
  line-height: 0;
}

.ol-room .ol-hint { position: static; margin: 0 auto; }

/* The room's match card: the match at a glance, and the way in to all of it. */
.ol-readout { min-height: 28px; cursor: default; }
.ol-setbtn { align-self: center; margin-top: 8px; padding: 8px 20px 6px; }
.ol-setbtn.is-on { outline: 3px solid var(--bark); outline-offset: 3px; }

/* The match settings (settings.ts): the island beside the weapons. The
   sliders and the weapon cards are the local setup pages' own. */
.ol-settings { gap: clamp(8px, 1.4vh, 14px); padding-top: clamp(12px, 2.4vh, 24px); }
.ol-setbody { display: flex; align-items: stretch; gap: 16px; max-width: 96vw; }
.ol-setcard { width: min(480px, 46vw); gap: 2px; }
.ol-setcard .ol-row { min-height: 34px; }
.ol-setcard .ol-row-tag { flex-basis: 8.4em; }
.ol-map.is-big { margin-bottom: 6px; }
/* The room's Build list has the island card to itself while it is open:
   a little tighter than the local page's, to sit inside the card. */
.ol-setcard .su-kinds { width: 100%; padding: 8px 10px 6px; box-shadow: none; }
.ol-setcard .su-kinds-cols { gap: 0 6px; }
.ol-setcard .su-kind { min-height: 32px; padding: 1px 6px; gap: 8px; }
.ol-setcard .su-kinds .su-kinds-done { margin: 6px auto 0; }
.ol-map.is-big .ol-map-canvas { max-height: 132px; }
.ol-slider { height: 26px; }
.ol-slider .su-pip { width: 15px; height: 18px; }
.ol-row.is-on .su-pip { background: #fffbf0; }
.ol-row.is-on .su-pip.is-full { background: var(--bark); }
.ol-settings.is-watching .su-pip,
.ol-settings.is-watching .su-weapon { cursor: default; }
.ol-seed { gap: 12px; }
.ol-seeddigits { display: flex; align-items: center; gap: 3px; line-height: 0; }
.ol-caret { width: 3px; height: 20px; background: var(--gold-deep); }
.ol-reroll { padding: 6px 12px 4px; }
.ol-arsenal { width: auto; margin-top: 8px; padding: 10px; gap: 8px; box-shadow: none; }
/* Smaller cards than the local page's: the art steps back from the count in the corner. */
.ol-arsenal .su-weapon { padding: 20% 16% 22%; }
.ol-setcaption { min-height: 30px; margin-top: auto; padding-top: 8px; display: flex; align-items: center; justify-content: center; gap: 8px; line-height: 0; }
.ol-rarity {
  padding: 4px 8px 3px;
  border-radius: 999px;
  border: 2px solid color-mix(in srgb, var(--rarity) 72%, #4d2e0c);
  background: color-mix(in srgb, var(--rarity) 22%, #fffbf0);
  line-height: 0;
}
.ol-settings .ol-hint { position: static; margin: 0 auto; }

/* The victory page's online news goes under it, not over the winners. */
#bp-victory .ol-toast { top: auto; bottom: 14px; z-index: 40; }
/* Short screens: the room and its settings shrink to fit rather than run off the bottom. */
@media (max-height: 880px) { .ol-room, .ol-settings { zoom: .94; } }
@media (max-height: 800px) { .ol-room, .ol-settings { zoom: .86; } .ol-room .ol-hint, .ol-settings .ol-hint { display: none; } }
@media (max-height: 720px) { .ol-room, .ol-settings { zoom: .78; } }
@media (max-height: 650px) { .ol-room, .ol-settings { zoom: .7; } }
@media (max-height: 580px) { .ol-room, .ol-settings { zoom: .62; } }
@media (max-height: 520px) { .ol-room, .ol-settings { zoom: .55; } }

/* ============================================================
   SETTINGS — Display, Graphics, Audio, Controls (options.ts)
   How to Play's card, with tabs across the top and rows to change;
   the same card from the title and from the pause menu.
   ============================================================ */

.opt-scrim {
  position: absolute;
  inset: 0;
  z-index: 40;
  display: grid;
  place-items: center;
  padding: 10px;
  background: rgba(60, 40, 16, .32);
  cursor: default;
}
.opt-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  width: min(780px, 94vw);
  padding: 20px 28px 14px;
  background: #fffbf0;
  border: 3px solid var(--bark);
  border-radius: 14px;
  box-shadow: 0 10px 0 rgba(77, 46, 12, .22);
}
.opt-tabs { display: flex; align-items: center; gap: 16px; line-height: 0; }
.opt-tab, .opt-bumper { border: 0; background: none; padding: 4px 6px; cursor: pointer; line-height: 0; }
.opt-bumper { border: 2px solid rgba(77, 46, 12, .28); border-radius: 8px; padding: 5px 8px; }
.opt-rows {
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: 100%;
  padding-top: 10px;
  border-top: 2px dashed rgba(77, 46, 12, .25);
}
.opt-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  min-height: 42px;
  padding: 4px 12px;
  border-radius: 10px;
  line-height: 0;
}
.opt-row.is-on { background: #ffe07a; box-shadow: inset 0 0 0 3px var(--bark); }
.opt-value { display: flex; align-items: center; gap: 8px; }
.opt-arrow { border: 0; background: none; padding: 2px 4px; cursor: pointer; line-height: 0; }
.opt-text {
  min-width: 13em;
  display: flex;
  justify-content: center;
  border: 0;
  background: none;
  padding: 4px 0;
  cursor: pointer;
  line-height: 0;
}
div.opt-text { cursor: default; }
.opt-bar {
  position: relative;
  width: 9em;
  height: 16px;
  border: 2px solid var(--bark);
  border-radius: 999px;
  background: #fff7df;
  overflow: hidden;
  cursor: pointer;
}
.opt-bar i { display: block; height: 100%; background: linear-gradient(#ffe27a, #e8a92c); }
/* Bar and number together are as wide as a text value, so every row's
   arrows line up. */
.opt-num { min-width: 3.5em; display: flex; justify-content: flex-end; }
.opt-note {
  display: flex;
  align-items: center;
  justify-content: center;
  width: min(640px, 100%);
  min-height: 52px;
  text-align: center;
}
.opt-foot { display: flex; gap: 30px; line-height: 0; }
.opt-foot-btn { border: 0; background: none; padding: 4px 10px; cursor: pointer; line-height: 0; }
.opt-foot-btn.is-on { animation: title-nudge .9s ease-in-out infinite; }
.opt-hint { line-height: 0; }

/* A controller's own buttons: two columns of actions, each with its button. */
.opt-pad-head { line-height: 0; padding-bottom: 6px; }
.opt-pad-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px 12px;
  width: 100%;
  padding-top: 10px;
  border-top: 2px dashed rgba(77, 46, 12, .25);
}
.opt-pad-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 6px 10px;
  border: 2px solid rgba(77, 46, 12, .18);
  border-radius: 10px;
  background: #fffdf6;
  cursor: pointer;
  line-height: 0;
}
.opt-pad-row.is-on { background: #ffe07a; border-color: var(--bark); }
.opt-chip { padding: 5px 8px; border-radius: 8px; border: 2px solid var(--bark); background: #fff; line-height: 0; }
.opt-pad-row.is-waiting .opt-chip { animation: ph-pad-wait .8s ease-in-out infinite; }

@media (max-width: 700px) {
  .opt-card { padding: 14px 12px 10px; }
  .opt-text { min-width: 8em; }
  .opt-bar { width: 7em; }
  .opt-pad-grid { grid-template-columns: 1fr; }
}

/* The title's full screen switch, top right: four corners out, or in. */
.title-fs {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 5;
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 3px solid var(--bark);
  border-radius: 12px;
  background: #fffbf0;
  box-shadow: 0 4px 0 rgba(77, 46, 12, .25);
  cursor: pointer;
}
.title-fs:hover { background: #ffe07a; }
.title-fs svg { width: 26px; height: 26px; }
/*
 * THE GAME UI — match HUD, inventory, shop, pause and party, dressed in
 * the same clothes as the menus: cream card stock, a thick bark outline,
 * gold for whatever is chosen, and the Fink face on every heading. The
 * class names are still ph-, so match.ts and party.ts emit exactly what
 * they did when this was grey placeholder chrome.
 */

:root {
  --ink: #4d2e0c;
  --card: #fffbf0;
  --card-edge: #e6d6b0;
  --shade: rgba(77, 46, 12, .28);
}

/* ---- Layout shells ---- */

.ph-page {
  position: absolute;
  inset: 0;
  z-index: 20;
  display: grid;
  place-items: center;
  padding: 16px;
  overflow: auto;
  font-family: system-ui, sans-serif;
  color: var(--ink);
  background: rgba(43, 23, 6, .45);
}
.ph-panel {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: min(560px, 100%);
  padding: 20px 22px;
  border-radius: 20px;
  background: var(--card);
  border: 3px solid var(--ink);
  box-shadow: 0 6px 0 var(--shade);
  color: var(--ink);
  font-family: system-ui, sans-serif;
}
.ph-panel-bar { width: 100%; flex-direction: row; align-items: center; flex-wrap: wrap; padding: 10px 16px; }
/* Every word on these surfaces is set in the game's own glyph sheet, the
   same as the menus, so nothing here carries a font of its own. The boxes
   only say where the words sit. */
.ph-title { line-height: 0; }
.ph-label { line-height: 0; }
.ph-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.ph-menu { display: flex; flex-direction: column; gap: 10px; }
.ph-menu .ph-btn { width: 100%; }
.ph-volume-row { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 12px; }
.ph-volume-row input { width: 100%; accent-color: var(--gold-deep); }
.ph-volume-value { min-width: 3.5em; text-align: right; font-weight: 800; }

/* ---- Controls ---- */

.ph-btn {
  padding: 8px 18px 4px;
  line-height: 0;
  border-radius: 999px;
  border: 3px solid var(--ink);
  background: var(--gold);
  color: var(--ink);
  box-shadow: 0 3px 0 var(--gold-deep);
  cursor: pointer;
}
.ph-btn:hover { translate: 0 -1px; background: #ffe680; }
.ph-btn:active { translate: 0 3px; box-shadow: 0 0 0 var(--gold-deep); }
.ph-btn-big { padding: 12px 24px 8px; }
.ph-btn-off { opacity: .45; box-shadow: none; }
.ph-field {
  padding: 10px 12px;
  border-radius: 12px;
  border: 3px solid var(--ink);
  background: #fff;
  color: var(--ink);
  font: 700 16px system-ui, sans-serif;
}
.ph-note-lead { margin: 0; justify-content: flex-start; }
.ph-empty { margin: 0; justify-content: flex-start; opacity: .8; }
.ph-digits { line-height: 0; }
.ph-wait { opacity: .65; }

/* ---- Slots (inventory, shop, class picker) ---- */

.ph-rack { display: flex; flex-wrap: wrap; gap: 12px; }
.ph-slot {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  min-width: 92px;
  padding: 10px 10px 8px;
  border-radius: 16px;
  border: 3px solid var(--ink);
  background: var(--card);
  box-shadow: 0 4px 0 var(--shade);
  color: var(--ink);
  cursor: pointer;
}
.ph-slot:hover { translate: 0 -2px; }
.ph-slot:active { translate: 0 2px; box-shadow: 0 2px 0 var(--shade); }
.ph-slot:disabled { opacity: .45; cursor: not-allowed; translate: none; }
.ph-slot-on { background: var(--gold); box-shadow: 0 4px 0 var(--gold-deep); }
.ph-slot-art {
  position: relative; display: block; width: 72px; height: 72px;
  border-radius: 12px;
  background: radial-gradient(circle at 50% 42%, #fff 0%, #f0e3c6 78%);
  border: 2px solid var(--card-edge);
}
.ph-slot-on .ph-slot-art { border-color: rgba(77, 46, 12, .35); }
.ph-slot-art > svg, .ph-slot-art > img { width: 100%; height: 100%; display: block; }
.ph-slot-img { object-fit: contain; padding: 3px; filter: drop-shadow(0 2px 1px rgba(77, 46, 12, .35)); }
.ph-slot-art > .ph-char { width: 100%; height: 100%; }
.ph-slot-key, .ph-slot-count {
  position: absolute;
  min-width: 22px; padding: 4px 6px 2px;
  border-radius: 999px; border: 2px solid var(--ink);
  line-height: 0; text-align: center;
}
.ph-slot-key { top: -9px; left: -9px; background: #fff; }
.ph-slot-count { bottom: -9px; right: -9px; background: var(--ink); }
.ph-slot-label, .ph-slot-price { line-height: 0; }

/* ---- Match HUD ---- */

.ph-hud { position: absolute; inset: 0; color: var(--ink); }
#bp-ui > .ph-hud { pointer-events: none; }
#bp-ui > .ph-hud > * { pointer-events: auto; }
/* Whose turn it is used to sit in a pill at the top; it lives in the
   portrait panel at the bottom right now. */
.ph-clock { line-height: 0; }

/* Squad bars, bottom left. Worms writes the team's name in the team's
   own colour and hangs a bar of blocks under it, one block per member,
   each draining as he is hurt. No card behind it: the bars ARE the card. */
.ph-cards {
  position: absolute; left: 20px; bottom: 20px;
  display: flex; flex-direction: column; gap: 10px;
  max-width: 34vw;
}
/* Each squad sits on its own little plank, the way Worms hangs its team
   bars on one. A hair of rotation, alternating, so they read as laid
   down by hand rather than snapped to a grid. */
.ph-card {
  min-width: 296px;
  padding: 5px 11px 8px;
  border-radius: 14px;
  border: 3px solid var(--ink);
  background: linear-gradient(#fffdf4 0%, var(--card) 55%, #f1e4c8 100%);
  box-shadow: 0 4px 0 var(--shade), inset 0 0 0 2px rgba(255, 255, 255, .55);
  rotate: -0.7deg;
}
.ph-cards > .ph-card:nth-child(even) { rotate: 0.6deg; }
.ph-card-mine { border-color: var(--gold); }
.ph-card-head { display: flex; align-items: center; gap: 8px; margin: 0 0 4px 1px; }
.ph-card-tag, .ph-card-coin { line-height: 0; }
.ph-card-coin { margin-left: auto; display: flex; align-items: center; gap: 3px; }

/* The bar: a dark trough with one block per member sunk into it. */
.ph-tbar {
  display: flex; gap: 4px;
  padding: 3px;
  border-radius: 7px;
  border: 3px solid var(--ink);
  background: #3b2409;
  box-shadow: inset 0 2px 3px rgba(0, 0, 0, .45);
}
.ph-seg {
  position: relative; flex: 1; height: 16px;
  border-radius: 3px;
  background: rgba(255, 251, 240, .14);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .25);
  overflow: hidden;
}
.ph-seg > i {
  position: absolute; left: 0; top: 0; height: 100%;
  border-right: 1px solid rgba(0, 0, 0, .35);
  background:
    linear-gradient(rgba(255, 255, 255, .5) 0 42%, rgba(255, 255, 255, .12) 42% 58%,
      rgba(0, 0, 0, .18) 58% 100%),
    var(--team, #56c25f);
  transition: width .18s ease-out;
}
.ph-seg-low > i {
  background:
    linear-gradient(rgba(255, 255, 255, .45) 0 42%, rgba(255, 255, 255, .1) 42% 58%,
      rgba(0, 0, 0, .2) 58% 100%),
    #e8503f;
}
/* Overheal: health past the maximum, a gold band over the full block. */
.ph-seg > b {
  position: absolute; left: 0; top: 0; height: 100%;
  border-right: 1px solid rgba(0, 0, 0, .35);
  background:
    linear-gradient(rgba(255, 255, 255, .55) 0 42%, rgba(255, 255, 255, .16) 42% 58%,
      rgba(0, 0, 0, .12) 58% 100%),
    #ffd34d;
  transition: width .18s ease-out;
}
/* Whoever is up gets the gold ring; a squadmate who is out leaves an
   empty hole rather than a grey one. */
.ph-seg-up { outline: 2px solid var(--gold); outline-offset: 1px; }
.ph-seg-out { opacity: .45; background: rgba(0, 0, 0, .35); }

/* What he is holding: dead centre along the bottom, between the squads
   and the clock. */
.ph-hotbar {
  position: absolute; left: 50%; bottom: 18px;
  transform: translateX(-50%);
}
/* Flying the free cam: how to fly it, where the weapon slot was. */
.ph-viewhint {
  position: absolute; left: 50%; bottom: 22px;
  transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 8px 16px;
  border-radius: 12px;
  background: rgba(43, 23, 6, .62);
  white-space: nowrap;
  pointer-events: none;
}
/* One card, not a card inside a card: the weapon sits straight on it. */
.ph-hotbar .ph-slot-art {
  width: 78px; height: 78px;
  background: none;
  border: 0;
}
/* Shells in the gun. Sits over the weapon slot, because the question it
   answers — can I still shoot? — is asked while looking at the weapon. */
.ph-shells {
  position: absolute; left: 50%; bottom: calc(100% + 8px);
  transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  pointer-events: none;
}
.ph-shell-row { display: flex; gap: 6px; }
.ph-shell {
  width: 12px; height: 26px; border-radius: 3px 3px 2px 2px;
  background: linear-gradient(#c8452e 0 62%, #e0b43c 62% 100%);
  border: 2px solid #4d2e0c;
  box-shadow: 0 2px 0 rgba(77, 46, 12, .35);
  transition: transform .12s ease, opacity .12s ease;
}
.ph-shell.is-spent {
  background: linear-gradient(#6f5a52 0 62%, #8d7c50 62% 100%);
  opacity: .5;
  transform: rotate(28deg) translateY(3px);
}
.ph-shell-note {
  background: #fdf6e3; border: 2px solid #4d2e0c; border-radius: 8px;
  padding: 2px 8px 3px;
  box-shadow: 0 2px 0 rgba(77, 46, 12, .35);
}

/* The crosshair: four short strokes and a dot, dead centre, in the
   HUD's ink with a cream halo so it reads over grass and sky alike. */
.ph-reticle {
  position: fixed; left: 50%; top: 50%;
  width: 34px; height: 34px; transform: translate(-50%, -50%);
  pointer-events: none;
}
.ph-reticle i, .ph-reticle b {
  position: absolute; background: #4d2e0c;
  box-shadow: 0 0 0 2px #fdf6e3;
  border-radius: 2px;
}
.ph-reticle-n, .ph-reticle-s { left: 15px; width: 4px; height: 10px; }
.ph-reticle-n { top: 0; }
.ph-reticle-s { bottom: 0; }
.ph-reticle-e, .ph-reticle-w { top: 15px; width: 10px; height: 4px; }
.ph-reticle-e { right: 0; }
.ph-reticle-w { left: 0; }
.ph-reticle-dot { left: 15px; top: 15px; width: 4px; height: 4px; border-radius: 50%; }

/* ---- SWING (the bat's timing meter) -----------------------------------
   The ordinary POWER meter (styles.css). Once he swings it stops where it
   was clicked and the lit blocks blink while the swing plays out. */
.ph-power.is-swing.is-locked .ph-power-block.is-on { animation: ph-swing-blink .16s steps(1) 6; }
@keyframes ph-swing-blink { 50% { background: #fff8dc; box-shadow: 0 0 10px #fff8dc; } }

/* A word thrown across the screen: HOME RUN! */
.ph-flash {
  position: fixed; left: 50%; top: 38%; z-index: 60;
  transform: translate(-50%, -50%);
  pointer-events: none;
  animation: ph-flash 1.9s cubic-bezier(.2, 1.5, .4, 1) both;
}
@keyframes ph-flash {
  0%   { transform: translate(-50%, -50%) scale(0) rotate(-10deg); opacity: 1; }
  18%  { transform: translate(-50%, -50%) scale(1.1) rotate(3deg); }
  26%  { transform: translate(-50%, -50%) scale(1) rotate(0deg); }
  80%  { transform: translate(-50%, -62%) scale(1); opacity: 1; }
  100% { transform: translate(-50%, -78%) scale(.9); opacity: 0; }
}

/* ---- CONTROLLERS (settings) ---------------------------------------- */
.ph-pads {
  display: flex; flex-direction: column; gap: 10px;
  max-height: 48vh; overflow-y: auto; padding-right: 4px;
  margin: 6px 0 4px;
}
.ph-pad {
  background: #fff; border: 3px solid #4d2e0c; border-radius: 14px;
  padding: 10px 12px; display: flex; flex-direction: column; gap: 8px;
}
.ph-pad-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }
.ph-pad-row {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 5px 8px; min-height: 34px;
  background: #fdf6e3; border: 2px solid rgba(77, 46, 12, .35); border-radius: 9px;
  cursor: pointer; font: inherit; text-align: left;
}
.ph-pad-row:hover, .ph-pad-row.is-pad-focus { border-color: #4d2e0c; background: #fff7d6; }
.ph-pad-chip {
  flex: 0 0 auto; padding: 3px 7px;
  background: #f7c948; border: 2px solid #4d2e0c; border-radius: 7px;
}
.ph-pad-row.is-waiting .ph-pad-chip { animation: ph-pad-wait .8s ease-in-out infinite; background: #fff; }
@keyframes ph-pad-wait { 50% { transform: scale(1.08); background: #f7c948; } }
.ph-pad-reset {
  align-self: flex-end; padding: 4px 10px; cursor: pointer; font: inherit;
  background: none; border: 2px dashed rgba(77, 46, 12, .45); border-radius: 9px;
}
@media (max-width: 620px) { .ph-pad-grid { grid-template-columns: 1fr; } }

/* A controller's cursor: whatever button the pad has landed on. */
.is-pad-focus { outline: 4px solid #f7c948; outline-offset: 3px; border-radius: 12px; }

.ph-purse { display: flex; align-items: center; gap: 10px; }

/* Who is up, bottom right. Worms hangs a big dial in the corner with the
   seconds on it in red; this is that dial, with his portrait and his
   health on the plank beside it. */
.ph-face {
  position: absolute; right: 20px; bottom: 20px;
  display: flex; align-items: center; gap: 14px;
  padding: 10px 14px;
  border-radius: 16px;
  border: 4px solid var(--ink);
  background: linear-gradient(#fffdf4 0%, var(--card) 45%, #f0e2c4 100%);
  box-shadow: 0 6px 0 var(--shade), inset 0 0 0 2px rgba(255, 255, 255, .6);
}
/* His arms, as a ring that drains: full is a closed gold circle, spent
   is a red sliver. It sits at the left end of the panel, in front of his
   face, and is only there while he is climbing or getting them back. */
.ph-arms {
  position: relative;
  width: 34px; height: 34px;
  border-radius: 50%;
  border: 3px solid var(--ink);
  background: #f0e3c6;
  --arms: 360deg;
}
.ph-arms-fill {
  position: absolute; inset: 2px;
  border-radius: 50%;
  background: conic-gradient(var(--gold-deep) 0 var(--arms), rgba(77, 46, 12, .18) var(--arms) 360deg);
  -webkit-mask: radial-gradient(circle, transparent 0 46%, #000 46% 100%);
  mask: radial-gradient(circle, transparent 0 46%, #000 46% 100%);
}
.ph-arms.is-spent .ph-arms-fill {
  background: conic-gradient(#d8392b 0 var(--arms), rgba(77, 46, 12, .18) var(--arms) 360deg);
}
.ph-face-art {
  width: 74px; height: 74px; display: block;
  border-radius: 10px;
  border: 4px solid var(--ink);
  background: var(--team, #f0e3c6);
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .5);
  object-fit: cover;
}
.ph-face-lines { display: flex; flex-direction: column; gap: 4px; min-width: 112px; }
.ph-face-name, .ph-face-num { line-height: 0; }

/* The clock: a ring that drains clockwise from noon over the turn, with
   the seconds left read large in the middle of it. No hand — a hand and
   a number in the same small circle only fight each other. */
.ph-timer {
  position: relative;
  display: grid; place-items: center;
  width: 92px; height: 92px;
  border-radius: 50%;
  border: 4px solid var(--ink);
  background: radial-gradient(circle at 50% 38%, #fffdf4 0%, #f6ecd4 72%, #eaddbd 100%);
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .6);
  --sweep: 360deg;
}
.ph-timer::before {
  content: "";
  position: absolute; inset: 5px;
  border-radius: 50%;
  background: conic-gradient(var(--gold-deep) 0 var(--sweep), rgba(77, 46, 12, .16) var(--sweep) 360deg);
  -webkit-mask: radial-gradient(circle, transparent 0 72%, #000 72% 100%);
  mask: radial-gradient(circle, transparent 0 72%, #000 72% 100%);
  transition: none;
}
.ph-timer-low::before {
  background: conic-gradient(#d8392b 0 var(--sweep), rgba(77, 46, 12, .16) var(--sweep) 360deg);
}
.ph-clock { position: relative; line-height: 0; }

.ph-power-head { display: flex; align-items: center; justify-content: center; gap: 10px; line-height: 0; }
.ph-power-num { line-height: 0; }

/* ---- The weapon sheet: a grid of pigeonholes ---- */

.ph-grid {
  display: grid;
  grid-template-columns: repeat(5, 76px);
  gap: 8px;
  justify-content: center;
  padding: 10px;
  border-radius: 14px;
  border: 3px solid var(--ink);
  background: #f0e3c6;
}
.ph-cell {
  position: relative;
  width: 76px; height: 76px;
  border-radius: 10px;
  border: 2px solid rgba(77, 46, 12, .35);
  background: var(--card);
  cursor: pointer;
}
.ph-cell:hover { translate: 0 -2px; }
.ph-cell-on { background: var(--gold); border-color: var(--ink); box-shadow: 0 0 0 3px var(--ink); }
.ph-cell-empty { background: rgba(77, 46, 12, .06); border-style: dashed; cursor: default; }
.ph-cell-art { position: absolute; inset: 6px; display: block; }
.ph-cell-art > svg, .ph-cell-art > img { width: 100%; height: 100%; display: block; object-fit: contain; }
.ph-cell-key, .ph-cell-count {
  position: absolute; line-height: 0;
  padding: 3px 5px 1px;
  border-radius: 999px; border: 2px solid var(--ink);
}
.ph-cell-key { top: -8px; left: -8px; background: #fff; }
.ph-cell-count { bottom: -8px; right: -8px; background: var(--ink); }
.ph-caption { line-height: 0; text-align: center; padding: 2px 0 4px; }

/* ---- Modal sheets (inventory, shop, match over) ---- */

.ph-scrim {
  position: absolute; inset: 0; z-index: 30;
  display: grid; place-items: center; padding: 16px;
  background: rgba(43, 23, 6, .5);
}
.ph-entering { animation: ph-fade .12s ease-out; }
@keyframes ph-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* ---- Party island bar ---- */

.ph-bar { position: absolute; left: 50%; bottom: 12px; transform: translateX(-50%); width: min(1100px, 95vw); z-index: 20; }
.ph-party-code { display: flex; align-items: center; gap: 8px; margin-right: 16px; }
.ph-roster { display: flex; gap: 8px; flex-wrap: wrap; flex: 1; }
.ph-mate {
  display: flex; align-items: center; gap: 6px;
  padding: 5px 10px; border-radius: 999px; border: 2px solid var(--ink);
  background: #fff; font-size: 13px; font-weight: 700;
}
.ph-mate-ready { background: #d6f2d1; }
.ph-mate-empty { opacity: .45; }
.ph-mate-dot { width: 10px; height: 10px; border-radius: 50%; }
.ph-mate-name { font-weight: 800; }
.ph-mate-state { color: #8a6a37; }
.ph-code { display: flex; gap: 8px; }
.ph-digit {
  width: 48px; height: 58px;
  border-radius: 14px; border: 3px solid var(--ink);
  background: #fff; color: var(--ink);
  font: 400 30px ui-monospace, monospace; cursor: pointer;
  box-shadow: 0 3px 0 var(--shade);
}
.ph-digit-on { background: var(--gold); box-shadow: 0 3px 0 var(--gold-deep); }
