/* Bone Bros — canvas + DOM HUD (WebGL draws the world; text lives in DOM). */
/* Compact engine stage cards: retain names, descriptions and vote behavior. */
.mm-stagegrid .mm-stageshot { aspect-ratio: auto; height: auto; min-height: 0; background: none; padding: 12px 12px 0; }
.mm-stagegrid .mm-stageshot img, .mm-stagegrid .mm-stageshot::after { display: none; }
.mm-stagegrid .mm-stagelbl { position: static; display: block; padding: 0; background: none; text-shadow: none; }
.mm-stagegrid .mm-stagedesc { padding: 6px 12px 12px; }
.bb-wrap { position: relative; width: 100%; height: 100%; overflow: hidden; background: #0b0c10; }
.bb-canvas { display: block; cursor: crosshair; }
.bb-hud { position: absolute; inset: 0; pointer-events: none; font-family: system-ui, sans-serif; color: #e6ebf5;
  /* engine chrome safe insets (chat, settings, leaderboard corners) */
  --safe-t: var(--mm-safe-top, 0px); --safe-r: var(--mm-safe-right, 0px);
  --safe-b: var(--mm-safe-bottom, 0px); --safe-l: var(--mm-safe-left, 0px);
  /* the house arrival curve: fast out of the gate, long settle. Everything that
     ARRIVES uses it, so the panels, their cards and the overlays share one
     sense of weight. Named here because it was written out twelve times. */
  --bb-out: cubic-bezier(0.16, 1, 0.3, 1);
  /* v123: THE MOTION SCALE. style.css carried thirty distinct durations, eight
     of them (0.12 0.13 0.14 0.15 0.16 0.18 0.20 0.22) inside one band doing one
     job — so two controls a player operates in the same gesture animated at
     speeds 30% apart for no reason anyone could state. Four steps now, and
     `tools/motioncheck.js` fails the build on a fifth.

       snap   a state flip the finger is still on — hover, key, chip
       base   the default for anything that changes under the cursor
       panel  something arriving or leaving that owns the screen
       slow   the same, when it also travels

     ONE-SHOT AUTHORED FLOURISHES ARE NOT ON IT and must not be: the altar's
     coin flight, seal and ring, the loading fade, the ambient pulses. Their
     timing is the design, and a scale would flatten exactly the moments that
     are supposed to feel different. motioncheck names them as a category
     rather than treating each as an exception. */
  --bb-t-snap: 0.12s; --bb-t-base: 0.16s; --bb-t-panel: 0.22s; --bb-t-slow: 0.34s; }

/* top-centre head: team score pill (teams mode) stacked over the clock */
.bb-tophead { position: absolute; top: calc(8px + var(--safe-t)); left: 50%; transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 3px; z-index: 1; }
.bb-teampill { display: flex; align-items: center; gap: 9px; padding: 5px 16px;
  background: rgba(8, 10, 14, 0.78); border: 1px solid #2c3242; border-radius: 999px;
  box-shadow: 0 3px 14px #0009; font-weight: 800; white-space: nowrap; }
.bb-teampill:empty { display: none; }
.bb-tp-n { font-size: 12px; letter-spacing: 0.14em; text-transform: uppercase; text-shadow: 0 1px 3px #000; }
.bb-tp-s { font-size: 24px; min-width: 34px; text-align: center; background: rgba(255,255,255,0.06);
  border-radius: 10px; padding: 0 7px; text-shadow: 0 2px 6px #000; }
.bb-tp-d { opacity: 0.4; font-size: 16px; }
.bb-timer { font-size: 26px; font-weight: 800; letter-spacing: 0.06em; text-shadow: 0 2px 8px #000c;
  background: rgba(8, 10, 14, 0.55); padding: 1px 15px 3px; border-radius: 11px; }
/* v132: the slot is empty for the whole of a timed round now that the engine's
   own chip draws the clock (view().clock) — so an empty one is NOTHING, not a
   small blank plate hanging under it. Same rule .bb-teampill already uses. */
.bb-timer:empty { display: none; }
.bb-timer.bb-low { color: #ff6b6b; animation: bbPulse 1s infinite; }
.bb-timer.bb-ot { color: #ff5252; font-size: 21px; animation: bbPulse 0.8s infinite; }
@keyframes bbPulse { 50% { transform: scale(1.08); } }
/* v104: the slot changes what it is SAYING — phase, wave, clock — and the swap
   gets a beat. Last in the file's order so it wins the `animation` property
   over the two pulses above while it is on; render/hud.js drops it on a timer
   for the same reason. Kind only, never a tick. */
.bb-timer.bb-flip { animation: bbTimerFlip var(--bb-t-panel) var(--bb-out); }
@keyframes bbTimerFlip { from { opacity: 0.15; transform: translateY(-4px) scale(0.96); }
                         to   { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .bb-timer.bb-low, .bb-timer.bb-ot, .bb-timer.bb-flip { animation: none; } }

/* v93: the readouts sit ABOVE the floating damage numbers (z-index 1 against
   the number layer's auto), so a fight in the top-left corner puts its "14"s
   behind the board instead of across it. Same for the feed, the clock and the
   bottom readout. */
.bb-score { position: absolute; top: calc(40px + var(--safe-t)); left: calc(14px + var(--safe-l)); z-index: 1;
  font-size: 16px; font-weight: 700; background: #0d121bef; border: 1px solid #4a556b;
  border-radius: 12px; padding: 10px; width: 310px; max-width: calc(100% - 28px - var(--safe-l) - var(--safe-r));
  box-sizing: border-box; box-shadow: 0 3px 14px #0007; }
.bb-score:empty { display: none; }
.bb-srow { display: grid; grid-template-columns: 20px minmax(0, 1fr) auto 26px; gap: 8px; align-items: center;
  padding: 5px 7px; border-left: 4px solid var(--bb-player-color, #e6ebf5); border-radius: 4px; margin: 3px 0; }
.bb-srow b { text-align: right; font-size: 19px; color: #f4f7ff; font-variant-numeric: tabular-nums; }
.bb-sname { color: color-mix(in srgb, var(--bb-player-color, #e6ebf5) 55%, white);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bb-sbadge { font-size: 10px; font-weight: 900; color: #d9e1ee; letter-spacing: .04em; }
.bb-revenge { display: inline-block; margin-right: 5px; font-size: 18px; vertical-align: -1px; }
.bb-srow.bb-sme { background: color-mix(in srgb, var(--bb-player-color, #e6ebf5) 16%, transparent); }
/* v92: your row appended below the top three, the gap in rank made visible */
.bb-srow.bb-sfar { border-top: 1px dotted rgba(255, 209, 102, 0.35); margin-top: 2px; padding-top: 3px; }
.bb-rank { color: #b6c3d9; font-size: 13px; font-weight: 800; }

/* v172: the kill feed's rows, its slide-in and its own corner are gone — the
   engine draws the feed now (SDK v205 §7i), under the top-right chrome row,
   with its own fade and its own reduced-motion handling. */

.bb-status { position: absolute; left: 0; right: 0; bottom: calc(14px + var(--safe-b)); z-index: 1; }
.bb-me { position: relative; left: 50%; transform: translateX(-50%);
  width: min(720px, calc(100% - 32px)); box-sizing: border-box; padding: 16px 18px;
  background: linear-gradient(110deg, color-mix(in srgb, var(--bb-player-color, #e6ebf5) 16%, transparent), transparent 75%), #10151ef7;
  border: 1px solid color-mix(in srgb, var(--bb-player-color, #e6ebf5) 55%, #566177);
  border-top: 4px solid var(--bb-player-color, #e6ebf5); border-radius: 12px;
  box-shadow: 0 4px 16px #0008; }
.bb-me:empty { display: none; }
.bb-hpwrap { height: 32px; background: #080c12; border-radius: 7px; overflow: hidden;
  border: 1px solid #000a; box-shadow: 0 2px 10px #0008, inset 0 1px 0 #ffffff14; }
.bb-hp { height: 100%; transition: width var(--bb-t-base) ease; box-shadow: inset 0 -3px 0 #0003, inset 0 2px 0 #ffffff2e; }
.bb-hpnum { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-size: 18px; font-weight: 900; letter-spacing: 0.03em; color: #fff;
  text-shadow: 0 1px 1px #000, 1px 0 1px #000, -1px 0 1px #000; pointer-events: none; }
.bb-merow { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; gap: 12px; margin-top: 9px;
  font-size: 20px; font-weight: 800; text-shadow: 0 1px 1px #000; white-space: nowrap; }
.bb-wpnic { width: 32px; height: 32px; vertical-align: -8px; margin-right: 8px;
  filter: drop-shadow(0 1px 2px #000c); }
.bb-kills { display: inline-flex; align-items: baseline; gap: 7px; font-size: 28px; color: #f1f5ff; }
/* the purse (v28) — banked for the between-round shop */
.bb-coins { display: inline-flex; align-items: baseline; gap: 7px; font-size: 28px; color: #ffd166; }
.bb-coins.bb-cpop { animation: bbCoinPop var(--bb-t-panel) ease-out; }
@keyframes bbCoinPop { 30% { transform: scale(1.22); color: #ffe9a8; } }
@media (prefers-reduced-motion: reduce) { .bb-coins.bb-cpop { animation: none; } }
.bb-coins i { font-style: normal; font-size: 13px; font-weight: 800; letter-spacing: 0.06em;
  color: #e1c886; text-transform: uppercase; }
.bb-kills i { font-style: normal; font-size: 13px; font-weight: 800; letter-spacing: 0.06em;
  color: #c6d0e2; text-transform: uppercase; }
.bb-merow > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.bb-wpn { color: color-mix(in srgb, var(--bb-player-color, #e6ebf5) 55%, white); text-transform: uppercase; letter-spacing: 0.03em; }
.bb-cls { font-style: normal; color: #cfd8e8; opacity: 0.75; font-size: 12px; }
.bb-cls::after { content: '·'; padding: 0 2px 0 4px; opacity: 0.5; }
.bb-resp { color: #ff8585; grid-column: 1 / -1; text-align: center; }

.bb-hpwrap { position: relative; }
.bb-shbar { position: absolute; inset: 0 auto 0 0; height: 100%;
  background: repeating-linear-gradient(115deg, #ffd166 0 6px, #ffe9a8 6px 12px);
  opacity: 0.92; box-shadow: 0 0 8px #ffd16688; transition: width var(--bb-t-base) ease; }
.bb-pills { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; justify-content: center; }
.bb-pill { font-size: 12px; font-weight: 800; letter-spacing: 0.06em; padding: 3px 8px;
  border-radius: 999px; text-shadow: none; }
/* the prize colours are the registry's (PUPS.col): render/hud.js sets --bb-pup-<id> and its -glow on the HUD root (v101) */
.bb-quad { background: var(--bb-pup-quad); color: #2a0000; box-shadow: 0 0 10px var(--bb-pup-quad-glow); }
.bb-haste { background: var(--bb-pup-haste); color: #002233; box-shadow: 0 0 10px var(--bb-pup-haste-glow); }
.bb-shield { background: var(--bb-pup-shield); color: #2a2000; box-shadow: 0 0 10px var(--bb-pup-shield-glow); }
.bb-rapid { background: var(--bb-pup-rapid); color: #1c0033; box-shadow: 0 0 10px var(--bb-pup-rapid-glow); }
.bb-regen { background: var(--bb-pup-regen); color: #05270b; box-shadow: 0 0 10px var(--bb-pup-regen-glow); }
/* burning is a DEBUFF pill — outlined, not filled, so it never reads as a prize */
.bb-burn { background: #2a1206; color: #ff9d4d; border: 1px solid #ff7a1a;
  box-shadow: 0 0 10px #ff7a1a66; animation: bbBurnPulse 0.6s ease-in-out infinite alternate; }
@keyframes bbBurnPulse { from { opacity: 0.7; } to { opacity: 1; } }
/* v53: an altar boon is a THIRD kind of pill. The powerup pills are filled —
   they are a prize with a clock on it. A boon has no clock; it is what you paid
   for at the start of the round, and it reads as carried rather than ticking:
   outlined in its own colour with its sigil in front of the name. */
.bb-pill.bb-boon { background: #12151d; color: var(--bc); border: 1px solid var(--bc);
  box-shadow: 0 0 9px -2px var(--bc); letter-spacing: 0.12em; }

/* magazine pips: one per round, dark while reloading, next to the weapon name */
.bb-ammo { display: inline-flex; gap: 3px; margin-left: 7px; vertical-align: middle; }
.bb-ammo u { display: block; width: 5px; height: 14px; border-radius: 1px;
  background: #4a5163; box-shadow: inset 0 0 0 1px #00000055; }
.bb-ammo u.bb-on { background: #ffd166; box-shadow: 0 0 5px #ffd16688; }
.bb-ammo.bb-reload u { animation: bbReload 0.5s ease-in-out infinite alternate; }
@keyframes bbReload { from { background: #4a5163; } to { background: #8a6a2a; } }
@media (prefers-reduced-motion: reduce) {
  .bb-burn, .bb-ammo.bb-reload u { animation: none; }
}

/* command rail — bottom RIGHT (v24). These are reference, not status: they
   belong out of the one row a player reads mid-fight. */
.bb-rail { position: absolute; right: calc(14px + var(--safe-r)); bottom: calc(100% + 10px);
  display: flex; gap: 7px; }
.bb-rail:empty { display: none; }
@media (max-width: 620px) { .bb-rail { transform: scale(0.85); transform-origin: 100% 100%; } }
@media (min-width: 621px) and (max-width: 1040px) {
  .bb-me { width: calc(100% - 170px); left: 16px; transform: none; }
  .bb-merow { font-size: 18px; gap: 8px; }
}
@media (max-width: 620px) {
  .bb-me { padding: 12px 14px; }
  .bb-merow { grid-template-columns: 1fr auto; font-size: 18px; gap: 5px 12px; }
  .bb-wpn { grid-column: 1 / -1; }
  .bb-rail { bottom: calc(100% + 10px); }
}
/* v181: the SHORT-SCREEN status panel. The panel above was sized on a 1080p
   monitor, where 720px x 110px is a strip along the bottom. A school Chromebook
   is 1366x768 with ~650px of page left under the browser chrome, and there the
   same panel is half the screen wide and a sixth of it tall — it read as a
   fault on the PowerVR lab machine ("eats up the whole bottom of the screen").
   Height, not width, is the tell: a 1366-wide laptop with a 900px viewport is
   fine. So under 760px tall everything on the bottom edge drops ~35%: the
   panel to 520px, the bar 32 -> 20px, the text 20/28 -> 15/20, the ability
   tiles 56 -> 46px. Same layout, same order, less floor covered. */
@media (max-height: 760px) {
  /* second pass, on the designer's read of the first: a further fifth off, and
     lower — the panel is centred, the engine's bottom chrome lives in the corners,
     so it sits on half the bottom reserve instead of all of it. */
  .bb-me { width: min(420px, calc(100% - 32px)); padding: 7px 10px; border-top-width: 3px; border-radius: 8px; }
  .bb-hpwrap { height: 16px; border-radius: 4px; }
  .bb-hpnum { font-size: 11px; }
  .bb-merow { font-size: 12px; gap: 8px; margin-top: 5px; }
  .bb-wpnic { width: 18px; height: 18px; vertical-align: -4px; margin-right: 5px; }
  .bb-kills, .bb-coins { font-size: 16px; gap: 4px; }
  .bb-kills i, .bb-coins i { font-size: 9.5px; }
  .bb-cls { font-size: 10px; }
  .bb-pills { margin-top: 3px; }
  .bb-pill { font-size: 10px; padding: 2px 6px; }
  .bb-ammo u { height: 9px; width: 3px; }
  .bb-abil { width: 38px; height: 38px; border-radius: 9px; }
  .bb-abil img { width: 26px; height: 26px; }
  .bb-abil b { font-size: 17px; }
  .bb-abil i { font-size: 8.5px; padding: 0 4px 1px; }
  .bb-abnm { font-size: 8.5px; }
  .bb-status { bottom: calc(6px + var(--safe-b) / 2); }
  .bb-rail { bottom: calc(100% + 6px); }
}
.bb-abils { display: flex; gap: 7px; justify-content: center; margin-top: 6px; }
/* v77: 44px -> 56px, and the keycap pill 8px -> 10.5px. The old chip was
   legible on a design mock at 100% and not in a real match on a real screen,
   which is the only test that counts. Each tile is now a column: the key on
   the chip, the NAME of what it does underneath. */
.bb-abilw { display: flex; flex-direction: column; align-items: center; gap: 3px; }
.bb-abil { position: relative; width: 56px; height: 56px; border-radius: 12px; overflow: hidden;
  background: rgba(10, 13, 19, 0.85); border: 1px solid #333a48; box-shadow: 0 3px 10px #0008;
  display: flex; align-items: center; justify-content: center; }
.bb-abil img { width: 40px; height: 40px; image-rendering: auto; filter: drop-shadow(0 1px 2px #000c); }
.bb-abil b { font-size: 26px; color: #efe6cf; text-shadow: 0 1px 3px #000; font-weight: 400; }
.bb-abil i { position: absolute; left: 50%; bottom: -1px; transform: translateX(-50%);
  font-style: normal; font-size: 10.5px; font-weight: 800; letter-spacing: 0.08em; color: #0c0e13;
  background: #8b93a7; border-radius: 5px 5px 0 0; padding: 0 6px 1px; line-height: 1.5;
  white-space: nowrap; }
.bb-abnm { font-style: normal; font-size: 10px; font-weight: 800; letter-spacing: 0.09em;
  text-transform: uppercase; color: #cfd6e4; text-shadow: 0 1px 3px #000, 0 0 6px #000a;
  white-space: nowrap; }
/* dash cooldown fills the tile from the floor; ready flips it gold */
.bb-ab-dash u { position: absolute; left: 0; right: 0; bottom: 0; background: #7fb2d94d;
  transition: height var(--bb-t-snap) linear; }
.bb-ab-dash.bb-ready { border-color: #ffd166; box-shadow: 0 0 10px #ffd16655, 0 3px 10px #0008; }
.bb-ab-dash.bb-ready u { background: #ffd16633; }
.bb-ab-dash.bb-ready i { background: #ffd166; }

/* v67: z-index 9. The toast had none, so it lost the stacking contest to the
   intermission slab (z-index 7) that is later in the DOM — which meant "THE
   TYRANT FALLS" was drawn behind the altar card that opens on the same edge,
   and nobody ever saw it. */
.bb-toast { position: absolute; left: 50%; top: 34%; z-index: 9; transform: translate(-50%, -50%) scale(0.8);
  font-size: 34px; font-weight: 900; letter-spacing: 0.08em; color: #ffd166;
  text-shadow: 0 3px 10px #000c, 0 0 22px #ffb34766; opacity: 0; transition: opacity var(--bb-t-snap), transform var(--bb-t-snap); }
.bb-toast.bb-on { opacity: 1; transform: translate(-50%, -50%) scale(1); }
.bb-toast i { display: block; margin-top: 3px; font-style: normal; font-size: 13px;
  font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: #e6ebf5;
  opacity: 0.82; text-shadow: 0 2px 6px #000c; }

.bb-ambient { position: absolute; inset: 0;
  background: radial-gradient(ellipse at center, transparent 52%, rgba(5, 6, 10, 0.52) 100%); }
.bb-ambient.bb-lowhp { animation: bbLowHp 1.1s infinite;
  background: radial-gradient(ellipse at center, transparent 55%, rgba(160, 20, 20, 0.35) 100%); }
@keyframes bbLowHp { 50% { opacity: 0.45; } }

.bb-help { position: absolute; bottom: calc(100% + 100px); right: calc(14px + var(--safe-r));
  background: rgba(8, 10, 14, 0.72); border: 1px solid #3a4152; border-radius: 12px;
  padding: 10px 14px; font-size: 13px; line-height: 1.75; max-width: 240px;
  max-height: max(100px, calc(100vh - 330px - var(--safe-b) - var(--safe-t))); overflow: auto; pointer-events: none;
  opacity: 0; transform: translateY(8px); transition: opacity 0.5s, transform 0.5s; }
.bb-help.bb-on { opacity: 1; transform: none; pointer-events: auto; }
.bb-help b { color: #ffd166; font-size: 11px; letter-spacing: 0.14em; display: block; margin-bottom: 3px; }
.bb-help div span { display: inline-block; min-width: 52px; color: #ffd166; font-weight: 700; font-size: 11px; letter-spacing: 0.06em; }
.bb-help .bb-bro { color: #cfd8e8; font-size: 12px; line-height: 1.45;
  border-bottom: 1px solid #3a4152; padding-bottom: 7px; margin-bottom: 7px; }
.bb-help .bb-bro b { color: #9fe8ff; font-size: 13px; letter-spacing: 0.08em; text-transform: uppercase; margin-bottom: 1px; }

/* respawn bro picker — the only HUD piece that takes pointer events */
/* v43: bigger, and it ARRIVES rather than appearing. The two directions get
   different curves on purpose — coming in it eases hard out of the gate over
   0.3s so the panel has weight, going out it is 0.13s and linear so dismissing
   never feels like waiting. Same trick as the F1 card. */
/* v181: THE ENGINE'S TOUCH PAD TAKES EVERY TAP. On a touch device (a touchscreen
   Chromebook counts) the engine mounts two full-height zones over the whole stage
   — left 42% for the stick, right 58% for aim — at z-index 30 with pointer events
   on, so a game dialog under them cannot be tapped OR clicked: the bro picker's
   portraits and START, the altar, the F1 sheet, the lab's results box ("I can't
   tap to start"). While one of those is up (render/picker.js dialogSync puts
   `bb-dialog` on the body) the zones stop taking input. Nothing is lost: every one
   of those screens holds the player still. A stopgap for
   engine-proposals/game-modal-slot.md, keyed on an engine class name — if the
   engine renames it this quietly goes back to keys-only, never to a broken pad. */
body.bb-dialog .mm-tp-zone { pointer-events: none; }
/* v181: ...AND IT TAKES THE MOUSE. The engine reads mouse aim and the left button on
   the CANVAS (v260 pointer.bind: mousemove/mousedown on the canvas element), and the
   pad's zones stand between the mouse and that canvas the moment a device reports a
   touch point — every touchscreen Chromebook. Result on the Acer: aim froze and
   "LMB doesn't fire", with a keyboard and trackpad right there. Where a fine
   pointer (mouse or trackpad) is the PRIMARY input the zones let everything through;
   a touch on the canvas still aims and fires through the engine's own canvas touch
   handling, only the thumb stick is lost — and a phone or a convertible in tablet
   mode reports a coarse primary pointer and keeps its pad. Stopgap for
   engine-proposals/touch-pad-takes-the-mouse.md. */
@media (pointer: fine) { .mm-tp-zone { pointer-events: none; } }
.bb-swap { position: absolute; left: 50%; top: 50%; box-sizing: border-box;
  transform: translate(-50%, -50%) scale(0.96) translateY(12px);
  background: rgba(9, 11, 16, 0.975); border: 1px solid #3a4152; border-radius: 18px;
  padding: 18px 22px 20px; opacity: 0; pointer-events: none;
  transition: opacity var(--bb-t-snap) ease-in, transform var(--bb-t-snap) ease-in;
  width: min(1040px, 96vw); max-height: calc(100% - 32px); overflow-y: auto;
  box-shadow: 0 22px 60px #000c, 0 0 0 1px #0006;
  /* above the help card, which overlaps it on a wide viewport and was reading
     straight through the panel */
  z-index: 6; }
.bb-swap.bb-on { opacity: 1; transform: translate(-50%, -50%) scale(1) translateY(0); pointer-events: auto;
  transition: opacity var(--bb-t-panel) ease-out, transform var(--bb-t-slow) var(--bb-out); }
/* the cards land in sequence rather than all at once — 26ms apart is under the
   threshold where it reads as a delay and over the one where it reads as a
   single block appearing. Entry only: on the way out they all leave together. */
/* fill-mode BACKWARDS, not `both`. `both` keeps the animation's final
   `transform: none` applied for the life of the element, and an animated
   property beats a plain declaration in the cascade — so the entrance stagger
   silently disabled the hover lift on every card. `backwards` still holds the
   opening frame through the delay, and lets go once the card has landed. */
/* THE DEAL — one rule for all three lists, keyed on `.bb-in` and `--i` (v104).
   `.bb-in` is a ONE-SHOT: `.bb-on` means the panel is open and lasts as long
   as it is, so anything inserted under an open panel matched the old selector
   and played the entrance again — which is what re-dealt all eight offerings
   under the celebration playing on the card you had just bought. The index
   comes from the code that built the card, because the roster and the
   offerings are registries: a `:nth-child` ladder encodes 4 heroes + 3
   skeletons in CSS and drops an eighth class onto the first beat. Per-list
   duration, lead and step below. CHANGELOG v104 */
.bb-swap.bb-in .bb-swap-c,
.bb-inter.bb-in > *,
.bb-inter.bb-in .bb-int-bst {
  animation: bbCardIn var(--bb-deal, var(--bb-t-slow)) var(--bb-out) backwards;
  animation-delay: calc(var(--bb-lead, 0ms) + var(--i, 0) * var(--bb-step, 26ms)); }
.bb-swap.bb-in .bb-swap-c { --bb-deal: var(--bb-t-slow); --bb-step: 26ms; }
@keyframes bbCardIn { from { opacity: 0; transform: translateY(9px) scale(0.97); }
                      to   { opacity: 1; transform: none; } }
.bb-swap-h { font-size: 15px; letter-spacing: 0.16em; color: #ffd166; font-weight: 800; text-align: center;
  padding-bottom: 11px; margin-bottom: 13px; border-bottom: 1px solid #262d3b; }
.bb-swap-h i { color: #a7b0c4; font-style: normal; font-weight: 600; letter-spacing: 0.04em; }
/* Counterparts share a column; the one-off Barbarian sits between the rows. */
.bb-swap-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 9px; }
.bb-swap-heroes { grid-row: 1; }
.bb-swap-skeletons { grid-row: 3; }
.bb-swap-c[data-cls="mage"] { grid-column: 1; grid-row: 2; }
.bb-swap-c[data-cls="rogue"] { grid-column: 2; grid-row: 2; }
.bb-swap-c[data-cls="knight"] { grid-column: 3; grid-row: 2; }
.bb-swap-c[data-cls="skeleton-mage"] { grid-column: 1; grid-row: 4; }
.bb-swap-c[data-cls="skeleton-minion"] { grid-column: 2; grid-row: 4; }
.bb-swap-c[data-cls="skeleton-warrior"] { grid-column: 3; grid-row: 4; }
.bb-swap-c[data-cls="barbarian"] { grid-column: 4; grid-row: 2 / 5; align-self: center; }
/* section rules: a label plus a hairline that runs to the edge */
.bb-swap-sec { grid-column: 1 / 4; display: flex; align-items: center; gap: 9px;
  font-size: 10.5px; font-weight: 800; letter-spacing: 0.16em; text-transform: uppercase;
  color: #98a3ba; margin-top: 3px; }
.bb-swap-sec:first-child { margin-top: 0; }
.bb-swap-sec::after { content: ""; flex: 1; height: 1px; background: #262d3b; }
.bb-swap-c { position: relative; min-width: 0; display: flex; align-items: center; gap: 10px; text-align: left; cursor: pointer;
  background: #151922; border: 1px solid #2c3342; border-radius: 12px; padding: 10px 11px;
  color: #e6ebf5; font: inherit; overflow: hidden;
  transition: background var(--bb-t-base), border-color var(--bb-t-base), transform var(--bb-t-base) var(--bb-out), box-shadow var(--bb-t-base); }
/* v43: hover reads as a lift, not a tint change — the card rises, the portrait
   pushes in slightly, and a soft gold wash crosses the
   whole tile so the pointer is unmistakable at a glance during a 4s window. */
.bb-swap-c::before { content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(105deg, transparent 30%, #ffd16614 50%, transparent 70%);
  opacity: 0; transition: opacity var(--bb-t-panel); }
.bb-swap-c:hover { background: #1d2330; border-color: #5a667e; transform: translateY(-3px);
  box-shadow: 0 8px 20px #0008; }
.bb-swap-c:hover::before { opacity: 1; }
.bb-swap-c:active { transform: translateY(-1px); }
.bb-swap-c:focus-visible { outline: 2px solid #ffd166; outline-offset: 2px; }
.bb-swap-c:hover .bb-swap-face { transform: scale(1.06); }
.bb-swap-c:hover .bb-swap-m s { filter: brightness(1.18); }
/* The picked card has to be unmistakable — with the engine's 3-2-1 gone (v32)
   this screen is the first thing a player sees, and a 1px border change was not
   saying "this one is yours". */
.bb-swap-c.bb-on { border-color: #ffd166; background: #2b2413;
  box-shadow: 0 0 0 2px #ffd16655, 0 6px 16px #0007; transform: translateY(-2px); }
.bb-swap-c.bb-on::after { content: "✓"; position: absolute; top: -7px; right: -7px;
  width: 19px; height: 19px; border-radius: 50%; background: #ffd166; color: #1a1509;
  font-size: 12px; font-weight: 900; line-height: 19px; text-align: center;
  box-shadow: 0 2px 6px #000a; }
.bb-swap-c.bb-on b { color: #ffe9b0; }
/* The cached portrait is larger without rendering another frame. A
   gradient stands in if the portrait pass ever fails */
.bb-swap-face { flex: 0 0 auto; width: 80px; height: 80px; border-radius: 10px; overflow: hidden;
  background: linear-gradient(#28324a, #0b0e15); box-shadow: inset 0 0 0 1px #0008;
  display: block; position: relative;
  transition: transform var(--bb-t-base) var(--bb-out), box-shadow var(--bb-t-base); }
.bb-swap-face canvas { display: block; width: 100%; height: 100%; }
.bb-swap-c.bb-on .bb-swap-face { box-shadow: inset 0 0 0 1px #ffd16688, 0 0 10px #ffd16633; }
.bb-swap-txt { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 2px; }
.bb-swap-c b { font-size: 15px; letter-spacing: 0.01em; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
.bb-swap-c em { font-size: 11px; font-style: normal; font-weight: 700; letter-spacing: 0.05em;
  text-transform: uppercase; color: #9aa5bd;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* HP and speed as bars, not a sentence: the shapes are read at a glance and
   they say what the perk line used to spell out. Gold = bulk, cyan = legs —
   the same two colours the powerup HUD already uses for shield and haste. */
.bb-swap-m { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 5px; }
.bb-swap-m i { display: block; height: 4px; border-radius: 2px; background: #232838; overflow: hidden; }
.bb-swap-m s { display: block; height: 100%; text-decoration: none; border-radius: 2px;
  transition: filter var(--bb-t-base); }
.bb-swap-m u { text-decoration: none; font-size: 11.5px; font-weight: 800; letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums; }
.bb-swap-mhp s { background: linear-gradient(90deg, #d98b3a, #ffd166); }
.bb-swap-mhp u { color: #ffd166; }
.bb-swap-msp s { background: linear-gradient(90deg, #3d8fbd, #5bc8ff); }
.bb-swap-msp u { color: #5bc8ff; }
/* "you are this one right now", which the gold pick highlight cannot say by
   itself — before this the bro you were playing and the bro you had chosen
   looked identical */
.bb-swap-now { display: none; position: absolute; left: 11px; bottom: 10px; width: 80px;
  padding: 2px 0; border-radius: 0 0 8px 8px; font-size: 8.5px; text-align: center;
  font-weight: 800; letter-spacing: 0.08em; color: #0b0c10; background: #b9c2d4; }
.bb-swap-c.bb-now .bb-swap-now { display: block; }
.bb-swap-c.bb-on.bb-now .bb-swap-now { background: #ffd166; }
/* The respawn control (v27). You are not put back automatically any more, so
   this is a real button: dead while the wait runs, alive when it ends. It keeps
   the clock inside it — the thing you press is the thing you were waiting on. */
.bb-swap-go { display: block; width: 320px; max-width: 100%; margin: 17px auto 0;
  padding: 12px 18px 11px; border-radius: 12px; font: inherit; cursor: pointer;
  background: #1a1f2b; border: 1px solid #2f3646; color: #e6ebf5; position: relative; overflow: hidden;
  transition: background var(--bb-t-base), border-color var(--bb-t-base), transform var(--bb-t-snap) var(--bb-out), box-shadow var(--bb-t-base); }
.bb-swap-go.bb-wait { cursor: default; }
.bb-swap-go.bb-live { background: #2d2513; border-color: #ffd166;
  box-shadow: 0 0 0 3px #ffd16622, 0 6px 18px #0007;
  animation: bbGoPulse 1.5s ease-in-out infinite; }
.bb-swap-go.bb-live:hover { background: #40340f; transform: translateY(-2px);
  box-shadow: 0 0 0 4px #ffd16633, 0 10px 24px #0009; }
.bb-swap-go.bb-live:active { transform: translateY(0); }
/* a light sweeps across the live button — it is the one thing on screen you
   are meant to press, and it is only live for a moment */
/* v174: the sweep rides `transform`, not `left` — the same travel (-45% to
   105% of the button is -112.5% to 262.5% of the 40%-wide light) without a
   layout pass per animation frame; a transform runs on the compositor. */
.bb-swap-go.bb-live::after { content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 40%;
  background: linear-gradient(100deg, transparent, #ffd1661f, transparent);
  animation: bbGoSweep 2.1s linear infinite; pointer-events: none; will-change: transform; }
@keyframes bbGoSweep { from { transform: translateX(-112.5%); } to { transform: translateX(262.5%); } }
.bb-swap-go:focus-visible { outline: 2px solid #ffd166; outline-offset: 2px; }
@keyframes bbGoPulse { 0%, 100% { box-shadow: 0 0 0 3px #ffd16622, 0 6px 18px #0007; }
                       50%      { box-shadow: 0 0 0 6px #ffd16611, 0 6px 18px #0007; } }
@media (prefers-reduced-motion: reduce) {
  .bb-swap-go.bb-live { animation: none; }
  .bb-swap-go.bb-live::after { animation: none; display: none; }
  .bb-swap.bb-in .bb-swap-c { animation: none; }
  .bb-swap, .bb-swap.bb-on { transition: opacity var(--bb-t-snap); transform: translate(-50%, -50%); }
  .bb-swap-c, .bb-swap-c:hover, .bb-swap-face, .bb-swap-go, .bb-swap-go.bb-live:hover { transform: none; } }
.bb-swap-cd { display: block; text-align: center; font-size: 15.5px; font-weight: 800;
  letter-spacing: 0.14em; color: #ff9f6b; text-shadow: 0 1px 3px #000;
  font-variant-numeric: tabular-nums; white-space: nowrap; transition: color var(--bb-t-base), text-shadow var(--bb-t-base); }
/* v52: the live state is said in COLOUR, never in size. This rule used to take
   the label from 12.5px to 16px and reveal the key row, which grew the button
   by 14px at the one moment the cursor was already on it. */
.bb-swap-go.bb-live .bb-swap-cd { color: #ffd166; text-shadow: 0 1px 3px #000, 0 0 16px #ffd16680; }
/* the key only shows once pressing is actually possible */
.bb-swap-gokey { display: none; margin-top: 3px; font-size: 10px; font-weight: 800;
  letter-spacing: 0.14em; color: #c0ad76; }
.bb-swap-go.bb-live .bb-swap-gokey { display: block; text-align: center; color: #c0ad76; }
/* the wait as a bar as well as a number: a bar is read at a glance, a decimal
   has to be parsed */
.bb-swap-cdbar { display: block; height: 4px; margin: 6px auto 0; width: 100%; border-radius: 2px;
  background: #232838; overflow: hidden; }
.bb-swap-go.bb-live .bb-swap-cdbar { background: transparent; }
.bb-swap-cdfill { display: block; height: 100%; width: 100%;
  background: linear-gradient(90deg, #ff9f6b, #ffd166); transform-origin: left center; }

/* ---- v52: the respawn button as a CHARGE -------------------------------
   Three complaints, one shape. It changed size as you played (the label grew
   and a key row appeared the instant it went live); the bar DRAINED, which
   reads as something being taken away rather than as building up to the thing
   you are waiting for; and a 4px rule under a label is not much of a moment
   for the one button in the game you actually sit and wait on.
   So: a fixed 62px box that never reflows — the label holds one size, the key
   line always reserves its row — and the whole face of the button is the
   meter. A warm wash charges left to right across it behind the label, a hard
   bright front rides the edge of the charge, and faint notches in the track
   give the eye something to measure it against. At full it snaps gold and the
   existing sweep and pulse take over.
   Scoped to .bb-swap-rsp so the intermission READY button, which shares this
   markup as a plain drain bar, is left exactly as it was. */
.bb-swap-rsp { display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px; height: 62px; padding: 0 16px; }
.bb-swap-rsp .bb-swap-cd, .bb-swap-rsp .bb-swap-gokey { position: relative; z-index: 1; }
/* the key row holds its line whether or not the key does anything yet */
.bb-swap-rsp .bb-swap-gokey { display: block; visibility: hidden; margin-top: 0; }
.bb-swap-rsp.bb-live .bb-swap-gokey { visibility: visible; }
/* the trough: the whole face of the button, a shade under its own ground so
   the empty part reads as empty rather than as unlit */
.bb-swap-rsp .bb-swap-cdbar { position: absolute; inset: 0; z-index: 0; width: auto; height: auto;
  margin: 0; border-radius: inherit; background: #161a26; }
/* the charge: dim at the tail, hot at the front. The gradient rides the same
   scaleX as the fill, so the hot end is always AT the front however far it
   has got, and the tail never becomes a solid slab of colour. */
.bb-swap-rsp .bb-swap-cdfill { position: absolute; inset: 0; width: 100%; height: 100%;
  background: linear-gradient(180deg, #00000047, #00000000 34%, #00000000 66%, #0000004f),
              linear-gradient(90deg, #ff6b3a14, #ff7a3f2b 46%, #ffa85647 80%, #ffd9a666);
  transition: background var(--bb-t-panel), transform var(--bb-t-snap) linear; }
/* live, the button's own gold takes over and the meter gets out of the way */
.bb-swap-rsp.bb-live .bb-swap-cdfill {
  background: linear-gradient(180deg, #00000033, #00000000 40%, #00000000 60%, #0000003b),
              linear-gradient(90deg, #ffd1660f, #ffd16621); }
/* segment gaps, painted OVER the charge (a ::after is the last child, so it
   sits above the fill) — they turn a smooth wash into a meter you can count */
.bb-swap-rsp .bb-swap-cdbar::after { content: ""; position: absolute; inset: 0; pointer-events: none;
  background: repeating-linear-gradient(90deg, #00000000 0 24px, #0000005e 24px 26px); }
.bb-swap-rsp.bb-live .bb-swap-cdbar::after { opacity: 0; }
/* the charge front — counter-scaled in JS so it stays a hard 2px line */
.bb-swap-cdedge { position: absolute; top: 0; bottom: 0; right: 0; width: 2px;
  background: #ffe0b2; box-shadow: 0 0 10px 1px #ff9f6bcc; transform-origin: right center;
  transition: opacity var(--bb-t-base), transform var(--bb-t-snap) linear; }
.bb-swap-rsp.bb-live .bb-swap-cdedge { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .bb-swap-rsp .bb-swap-cdfill, .bb-swap-cdedge { transition: none; }
}
/* Sponsor slot: a 320x50 leaderboard box reserved under the buttons. It holds
   its space whether or not anything mounts into it, so a creative arriving
   late never reflows the grid out from under a player mid-click. */
.bb-swap-ad { width: 728px; max-width: 100%; aspect-ratio: 728 / 90; margin: 14px auto 0;
  border-radius: 8px; overflow: hidden; display: flex; align-items: center; gap: 16px;
  padding: 0 18px; box-sizing: border-box; position: relative;
  background: linear-gradient(100deg, #241a2e 0%, #3a2340 45%, #4a2a2f 100%);
  border: 1px solid #5a4358; }
/* the house creative: no network, no image, no tracking — it is CSS and text,
   so the slot demonstrates itself on a machine with nothing mounted into it */
.bb-swap-ad::after { content: 'SPONSORED'; position: absolute; top: 4px; right: 8px;
  font-size: 7.5px; font-weight: 800; letter-spacing: 0.14em; color: #9a86a8; }
.bb-ad-mark { font-size: 30px; line-height: 1; filter: drop-shadow(0 2px 4px #000a); flex: none; }
.bb-ad-copy { min-width: 0; }
.bb-ad-copy b { display: block; font-size: 19px; font-weight: 900; letter-spacing: 0.02em;
  color: #ffd166; text-shadow: 0 2px 6px #000a; line-height: 1.15;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bb-ad-copy span { display: block; margin-top: 2px; font-size: 11.5px; font-weight: 600;
  color: #d9cfe4; opacity: 0.9; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bb-ad-cta { margin-left: auto; flex: none; font-size: 11px; font-weight: 800;
  letter-spacing: 0.08em; color: #1a1a1a; background: #ffd166; border-radius: 6px;
  padding: 7px 13px; box-shadow: 0 2px 8px #0008; }
@media (max-width: 620px) {
  .bb-swap-ad { gap: 10px; padding: 0 10px; }
  .bb-ad-mark { font-size: 20px; }
  .bb-ad-copy b { font-size: 13px; }
  .bb-ad-copy span { font-size: 9px; }
  .bb-ad-cta { font-size: 9px; padding: 5px 8px; }
}
/* Stack the portrait above its name on narrower screens so every counterpart
   pair keeps its column while faces and labels still have room. */
@media (max-width: 1100px) {
  .bb-swap-face { width: 68px; height: 68px; }
  .bb-swap-now { left: calc(50% - 34px); top: 64px; bottom: auto; width: 68px; }
  .bb-swap-c { flex-direction: column; gap: 8px; padding: 10px 8px; }
  .bb-swap-txt { width: 100%; }
  .bb-swap-c b, .bb-swap-c em { text-align: center; }
  .bb-swap-grid { gap: 7px; }
  /* Keep the full class names readable; phone names wrap below. */
  .bb-swap-c b { font-size: 13px; }
  .bb-swap-c em { font-size: 10px; }
}
@media (max-width: 560px) {
  .bb-swap { padding: 14px 10px; }
  .bb-swap-grid { gap: 6px; }
  .bb-swap-c { flex-direction: column; padding: 9px 4px; gap: 7px; }
  .bb-swap-face { width: 58px; height: 58px; }
  .bb-swap-txt { width: 100%; }
  .bb-swap-now { left: calc(50% - 29px); top: 59px; bottom: auto; width: 58px; font-size: 7px; }
  .bb-swap-c b { font-size: 11px; white-space: normal; text-align: center; min-height: 26px; }
  .bb-swap-c em { display: none; }
  .bb-swap-m { gap: 3px; }
  .bb-swap-m u { font-size: 10px; }
}

/* "press E to swap" — anchored over your bro, so it reads without looking away.
   v40: was 11px with a 10px key chip, which is a caption, not a prompt you are
   meant to read mid-fight while something is shooting at you. Half again as
   big, with the pill and the chip grown to match so it stays a pill. */
/* v104: it rises 4px like every other overlay — small, because JS re-anchors
   left/top over the bro every frame and a longer travel would fight that. */
.bb-pick { position: absolute; transform: translate(-50%, -50%) translateY(4px); opacity: 0; will-change: transform;

  display: flex; align-items: center; gap: 10px; box-sizing: border-box;
  background: #101723; border: 2px solid #ffda85; border-radius: 9px;
  padding: 7px 15px 7px 7px; font-size: 20px; font-weight: 800; letter-spacing: 0.025em;
  color: #fff4d8; white-space: nowrap; transition: opacity var(--bb-t-snap), transform var(--bb-t-snap) var(--bb-out);
  text-shadow: 0 1px 0 #000; box-shadow: 0 3px 0 #0009; }
.bb-pick.bb-on { opacity: 1; transform: translate(-50%, -50%); }
.bb-pick b { display: inline-flex; align-items: center; justify-content: center;
  min-width: 34px; height: 34px; padding: 0 5px; background: #ffd166; color: #15110a;
  border-radius: 4px; font-size: 20px; font-weight: 900; text-shadow: none; }

/* tuning panel */
.bb-tune { position: absolute; top: calc(148px + var(--safe-t)); left: calc(14px + var(--safe-l)); display: none; }   /* below the FFA score card */
.bb-tune.bb-avail { display: block; }
.bb-tune-tab { pointer-events: auto; cursor: pointer; font: 700 10px/1 system-ui, sans-serif; letter-spacing: 0.14em;
  background: rgba(10, 12, 17, 0.8); color: #ffd166; border: 1px solid #3a4152; border-radius: 7px; padding: 5px 9px; }
.bb-tune-body { display: none; margin-top: 6px; width: 288px; max-height: 68vh; overflow-y: auto;
  pointer-events: auto; background: rgba(10, 12, 17, 0.94); border: 1px solid #3a4152; border-radius: 11px; padding: 10px; }
.bb-tune.bb-on .bb-tune-body { display: block; }
.bb-tune-h { font-size: 11px; letter-spacing: 0.16em; color: #ffd166; font-weight: 800; margin-bottom: 8px; }
.bb-tune-h i, .bb-tune-grp i { color: #8b93a7; font-style: normal; font-weight: 500; letter-spacing: 0.03em; }
.bb-tune-grp { font-size: 9px; letter-spacing: 0.12em; color: #7f8798; font-weight: 700;
  margin: 9px 0 5px; padding-top: 7px; border-top: 1px solid #2b3140; }
.bb-tune-row { display: grid; grid-template-columns: 96px 1fr 38px; align-items: center; gap: 6px; margin: 3px 0; font-size: 10px; }
.bb-tune-row span { color: #b9c1d1; }
.bb-tune-row b { color: #ffd166; text-align: right; font-variant-numeric: tabular-nums; }
.bb-tune-row input[type=range] { width: 100%; accent-color: #ffd166; height: 14px; }
.bb-tune-btns { display: flex; gap: 6px; margin-top: 10px; }
.bb-tune-btns button { flex: 1; cursor: pointer; font: 700 10px/1 system-ui, sans-serif; letter-spacing: 0.1em;
  background: #ffd166; color: #1a1a1a; border: 0; border-radius: 7px; padding: 7px; }
.bb-tune-btns button:last-child { background: #262b36; color: #cfd8e8; }
.bb-tune-out { width: 100%; height: 74px; margin-top: 8px; resize: vertical; font: 10px/1.35 ui-monospace, monospace;
  background: #0c0e13; color: #9fe8ff; border: 1px solid #2b3140; border-radius: 7px; padding: 6px; }

/* ---------------- the performance lab (v177, render/labtest.js) ----------------
   A status chip while the run is on, and the results box at the end. Both are
   plain DOM in the HUD layer; the box takes the pointer so its buttons work.
   Placed here, above the results panel's `.bb-res` block, because
   tools/resultspanel-check.js reads that block to the end of the file. */
.bb-lab-chip { position: absolute; left: 50%; top: calc(8px + var(--safe-t)); z-index: 12; transform: translateX(-50%);
  display: none; pointer-events: none; font: 700 11px/1 system-ui, sans-serif; letter-spacing: 0.12em; text-transform: uppercase;
  color: #1a1a1a; background: #ffd166; border-radius: 999px; padding: 6px 12px; box-shadow: 0 2px 10px #0009; white-space: nowrap; }
.bb-lab-chip.bb-on { display: block; }
.bb-lab { position: absolute; left: 50%; top: 50%; z-index: 13; transform: translate(-50%, -50%); pointer-events: auto;
  width: min(94vw, 760px); max-height: 88vh; overflow: auto; box-sizing: border-box; padding: 16px 18px;
  background: rgba(10, 12, 17, 0.96); color: #e6ebf5; border: 1px solid #3a4152; border-radius: 12px;
  font: 13px/1.45 system-ui, sans-serif; box-shadow: 0 10px 40px #000c; }
.bb-lab-h { font-size: 14px; font-weight: 800; letter-spacing: 0.14em; color: #ffd166; margin-bottom: 8px; }
.bb-lab-sub, .bb-lab-why, .bb-lab-dev { margin: 0 0 10px; color: #b9c1d1; }
.bb-lab-why { color: #ff9a7a; }
.bb-lab-dev { font-size: 11px; color: #8b93a7; word-break: break-word; }
.bb-lab-t { width: 100%; border-collapse: collapse; margin: 0 0 12px; font-variant-numeric: tabular-nums; }
.bb-lab-t th, .bb-lab-t td { text-align: right; padding: 4px 6px; border-bottom: 1px solid #2b3140; white-space: nowrap; }
.bb-lab-t th { font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: #7f8798; }
.bb-lab-t th:first-child, .bb-lab-t td:first-child { text-align: left; }
.bb-lab-btns { display: flex; gap: 8px; flex-wrap: wrap; }
.bb-lab-btns button { cursor: pointer; font: 700 12px/1 system-ui, sans-serif; letter-spacing: 0.08em;
  background: #ffd166; color: #1a1a1a; border: 0; border-radius: 8px; padding: 10px 14px; }
.bb-lab-btns button:last-child { background: #262b36; color: #cfd8e8; }
.bb-lab-btns kbd { display: inline-block; min-width: 14px; margin-right: 6px; padding: 2px 5px; border-radius: 4px; font: 700 11px/1 ui-monospace, monospace;
  background: #1a1a1a; color: #ffd166; vertical-align: 1px; }
.bb-lab-btns button:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.bb-lab-st { min-height: 18px; margin-top: 8px; font-size: 13px; font-weight: 700; color: #9fe8ff; }
.bb-lab-out { display: none; width: 100%; height: 90px; margin-top: 8px; resize: vertical; font: 10px/1.35 ui-monospace, monospace;
  background: #0c0e13; color: #9fe8ff; border: 1px solid #2b3140; border-radius: 7px; padding: 6px; box-sizing: border-box; }
.bb-lab-out:not([hidden]) { display: block; }
.bb-lab-out[hidden] { display: none; }

.bb-hitmark { position: absolute; left: 50%; top: 50%; width: 26px; height: 26px; transform: translate(-50%, -50%) rotate(45deg);
  opacity: 0; transition: opacity var(--bb-t-snap); }
.bb-hitmark::before, .bb-hitmark::after { content: ''; position: absolute; background: #fff; box-shadow: 0 0 6px #ffd166; }
.bb-hitmark::before { left: 12px; top: 0; width: 2px; height: 26px; }
.bb-hitmark::after { top: 12px; left: 0; height: 2px; width: 26px; }
.bb-hitmark.bb-on { opacity: 0.9; }
.bb-vignette { position: absolute; inset: 0; opacity: 0; transition: opacity var(--bb-t-panel);
  background: radial-gradient(ellipse at center, transparent 55%, rgba(255, 40, 40, 0.35) 100%); }
.bb-vignette.bb-on { opacity: 1; }
/* v88: QUAD DAMAGE. Its own layer, not a second class on bb-vignette — that
   one is the quarter-second stab of taking a hit, and an eight-second state
   sharing an element with it would keep clearing it. Heavier at the edges
   than in the middle so the arena stays readable while the frame is
   unmistakably red. No z-index and first in the HUD, so every readout paints
   over it. The last two seconds pulse (bb-quad-out), which is how the
   buff running out becomes something you see rather than a number you had to
   be watching; prefers-reduced-motion never gets the pulse. */
.bb-quadvig { position: absolute; inset: 0; opacity: 0; pointer-events: none;
  transition: opacity var(--bb-t-slow) ease-out;
  background: radial-gradient(ellipse at center, rgba(255, 30, 30, 0.10) 0%,
                              rgba(255, 24, 24, 0.20) 46%, rgba(206, 0, 0, 0.55) 100%); }
.bb-quadvig.bb-on { opacity: 1; }
.bb-quadvig.bb-quad-out { animation: bbQuadOut 0.55s ease-in-out infinite; }
@keyframes bbQuadOut { 0%, 100% { opacity: 1; } 50% { opacity: 0.42; } }
@media (prefers-reduced-motion: reduce) { .bb-quadvig.bb-quad-out { animation: none; } }

/* The engine's warm-up counter (.mm-warmup) is a full-screen centred overlay at
   z-index 22 that runs for warmupMs, so anything of ours in the middle of the
   screen gets a 140px "3" stamped across it. The loading column lives in the
   lower third to keep that lane clear. */
.bb-load { position: absolute; inset: 0; background: radial-gradient(circle at 50% 35%, #22242c 0%, #0b0c10 75%);
  z-index: 5; transition: opacity 0.5s; pointer-events: none; }
.bb-load.bb-load-hide { opacity: 0; }
.bb-load-inner { position: absolute; left: 50%; bottom: 18%; transform: translateX(-50%);
  text-align: center; width: min(420px, 80vw); }
.bb-load-inner h2 { font-size: 30px; letter-spacing: 0.18em; color: #ffd166; margin: 0 0 14px; text-shadow: 0 3px 12px #000; }
.bb-bar { height: 14px; border-radius: 8px; background: #1a1d24; border: 1px solid #333a48; overflow: hidden; }
.bb-bar-fill { height: 100%; width: 0; background: linear-gradient(90deg, #ffd166, #ffb340); transition: width var(--bb-t-panel); }
.bb-load-txt { margin-top: 10px; font-size: 14px; opacity: 0.8; }
.bb-load-err { margin-top: 14px; font-size: 13px; color: #ff7b7b; background: rgba(80, 10, 10, 0.5);
  border: 1px solid #a33; border-radius: 8px; padding: 8px 10px; text-align: left; display: inline-block; max-width: 100%; }
.bb-load-err:empty { display: none; }

/* Floating damage numbers (v28). A fixed DOM pool: text is set once on spawn,
   and only transform/opacity change after that, so a number never costs a
   layout. The layer itself never takes pointer events — the picker is the only
   HUD piece that does. */
/* ---- intermission panel (v36): results · arena vote · coin shop ---- */
.bb-inter { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%) scale(0.98) translateY(6px);
  width: min(860px, 94vw); max-height: 88vh; overflow-y: auto;
  background: rgba(16, 18, 24, 0.96); border: 1px solid #2c3140; border-radius: 14px;
  padding: 18px 22px 16px; box-shadow: 0 18px 60px #000a, inset 0 1px 0 #ffffff12;
  /* v104: the closed pose is also the pre-open pose, so the panel RISES into
     place and settles back down when the round takes it — the same direction
     its cards travel, and the mirror of the arrival rather than a shrink in
     place. Out is 0.16s easing IN: dismissing must never feel like waiting
     (the v43 note on the picker), and the panel is handing the screen to the
     wordmark. `.bb-on`'s transform is untouched — bbAltShake's last keyframe
     is that pose and has to stay it. */
  opacity: 0; pointer-events: none; transition: opacity var(--bb-t-base) ease-in, transform var(--bb-t-base) ease-in; z-index: 7; }
.bb-inter.bb-on { opacity: 1; pointer-events: auto; transform: translate(-50%, -50%) scale(1);
  transition: opacity var(--bb-t-panel) ease-out, transform var(--bb-t-slow) var(--bb-out); }
/* v61: the intermission lands in SEQUENCE, the way the bro picker has since
   v43. The panel used to scale in as one slab while its sibling panel dealt
   its cards out one at a time — the same game speaking in two different
   voices at the two moments a player is most likely to be reading. Header,
   board, vote, altar: four beats, 22ms apart, over in a seventh of a second.
   `backwards` holds each at its start pose during the delay so nothing
   flashes at full opacity first. Section indices come from interDom()'s
   scaffold, the offerings' from the BOOST_IDS loop. */
.bb-inter.bb-in > * { --bb-step: 22ms; }
/* the sticky READY footer is NOT part of the deal — it is the one control the
   player has twenty seconds to find, so it is there on frame one */
.bb-inter.bb-in > .bb-int-foot { animation: none; }
/* and the offerings themselves land after their heading, left to right */
.bb-inter.bb-in .bb-int-bst { --bb-deal: var(--bb-t-slow); --bb-lead: 120ms; --bb-step: 14ms; }
/* ---- v88: the intermission's tower ads ---------------------------------
   Two 160x600 skyscrapers flanking the panel. Same contract as the 728x90
   under the respawn picker: NOTHING IS FETCHED. The creative is CSS and text,
   so the slot demonstrates itself with nothing mounted into it and the game
   keeps its no-CDN, no-third-party-request promise.

   Siblings of the panel, not children — the panel scrolls (max-height 88vh,
   overflow-y auto) and a tower inside it would scroll away with the
   offerings. They also come AFTER it in the DOM so a plain sibling selector
   shows and hides them with it, rather than a second class toggle in JS that
   has to be kept in step with the first.

   The rail only exists above 1180px: 860 of panel + 2x160 of tower + gutters.
   Under that they are display:none rather than squeezed, because a squeezed
   tower pushes the sticky READY footer off a laptop screen, and READY is the
   one control the player has twenty seconds to find. */
.bb-tower { position: absolute; top: 50%; transform: translateY(-50%) scale(0.97);
  width: 160px; height: min(600px, 78vh); box-sizing: border-box;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; padding: 22px 14px; text-align: center; overflow: hidden;
  border-radius: 12px; border: 1px solid #4a3b58; box-shadow: 0 14px 40px #000a, inset 0 1px 0 #ffffff10;
  opacity: 0; pointer-events: none; transition: opacity var(--bb-t-base), transform var(--bb-t-base); z-index: 6; }
.bb-tower::after { content: 'SPONSORED'; position: absolute; top: 7px; left: 0; right: 0;
  font-size: 7.5px; font-weight: 800; letter-spacing: 0.14em; color: #9a86a8; }
.bb-tw-a { right: calc(50% + min(430px, 47vw) + 18px);
  background: linear-gradient(170deg, #241a2e 0%, #3a2340 52%, #4a2a2f 100%); }
.bb-tw-b { left: calc(50% + min(430px, 47vw) + 18px);
  background: linear-gradient(170deg, #1b2430 0%, #23364a 52%, #2c4a42 100%);
  border-color: #3a5265; }
.bb-inter.bb-on ~ .bb-tower { opacity: 1; transform: translateY(-50%) scale(1);
  transition: opacity var(--bb-t-panel) ease-out 0.09s, transform var(--bb-t-slow) var(--bb-out) 0.09s; }
.bb-tw-mark { font-size: 46px; line-height: 1; filter: drop-shadow(0 3px 6px #000a); }
.bb-tw-name { font-size: 17px; font-weight: 900; letter-spacing: 0.03em; line-height: 1.12;
  color: #ffd166; text-shadow: 0 2px 6px #000a; }
.bb-tw-b .bb-tw-name { color: #8fe6d0; }
.bb-tw-line { font-size: 12px; font-weight: 600; line-height: 1.35; color: #d9cfe4; opacity: 0.9; }
.bb-tw-cta { margin-top: 4px; font-size: 11px; font-weight: 800; letter-spacing: 0.08em;
  color: #1a1a1a; background: #ffd166; border-radius: 6px; padding: 8px 12px; box-shadow: 0 2px 8px #0008; }
.bb-tw-b .bb-tw-cta { background: #8fe6d0; }
@media (max-width: 1180px) { .bb-tower { display: none; } }
@media (max-height: 520px) { .bb-tower { display: none; } }
.bb-int-h { text-align: center; margin-bottom: 7px; }
.bb-int-h b { display: block; font-size: 19px; font-weight: 900; letter-spacing: 0.12em; color: #ffd166; }
.bb-int-h i { display: block; font-style: normal; font-size: 13px; font-weight: 700; color: #cfd8e8; margin-top: 2px; }
.bb-int-board table { width: 100%; border-collapse: collapse; font-size: 14px; }
.bb-int-board th { font-size: 10px; font-weight: 800; letter-spacing: 0.14em; color: #8b93a7;
  text-transform: uppercase; padding: 3px 8px; text-align: right; }
.bb-int-board th:first-child, .bb-int-board td:first-child { text-align: left; }
.bb-int-board td { padding: 3px 8px; text-align: right; color: #e6ebf5; border-top: 1px solid #ffffff0d;
  font-variant-numeric: tabular-nums; }
.bb-int-board td u { display: inline-block; width: 9px; height: 9px; border-radius: 50%;
  margin-right: 7px; text-decoration: none; }
.bb-int-board .bb-int-tot { font-weight: 900; color: #fff; }
.bb-int-board .bb-int-au { color: #ffc94a; font-weight: 800; }
.bb-int-board .bb-int-me td { background: #ffd16612; }
.bb-int-sec { margin: 11px 0 5px; font-size: 11px; font-weight: 800; letter-spacing: 0.22em;
  color: #8b93a7; }
.bb-int-sec i { font-style: normal; letter-spacing: 0.06em; text-transform: none; font-weight: 700; opacity: 0.8; }
.bb-int-vote, .bb-int-shop { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 8px; }
.bb-int-shop { gap: 7px; grid-template-columns: repeat(auto-fit, minmax(172px, 1fr)); }
.bb-int-bst[hidden], .bb-int-pages[hidden] { display: none; }
.bb-int-pages { display: flex; align-items: center; justify-content: center; gap: 14px; margin-top: 10px; }
.bb-int-pages button { cursor: pointer; font: 700 12px/1.2 system-ui, sans-serif; color: #ffd166;
  background: #171d29; border: 1px solid #3a4152; border-radius: 7px; padding: 8px 12px; }
.bb-int-pages button:disabled { opacity: 0.35; cursor: default; }
.bb-int-pages button:focus-visible { outline: 2px solid #ffd166; outline-offset: 2px; }
.bb-int-pages span { font-size: 11px; color: #a7b0c4; font-variant-numeric: tabular-nums; }
.bb-int-card { text-align: left; background: #191c25; border: 2px solid #2c3140; border-radius: 10px;
  padding: 9px 11px; cursor: pointer; color: #e6ebf5; font: inherit;
  transition: border-color var(--bb-t-snap), transform var(--bb-t-snap), background var(--bb-t-snap); }
.bb-int-card:hover:not(:disabled) { border-color: #4a5163; transform: translateY(-1px); }
.bb-int-card b { display: block; font-size: 14px; font-weight: 900; letter-spacing: 0.08em; }
.bb-int-card i { display: block; font-style: normal; font-size: 11.5px; color: #9aa3b5; margin-top: 2px;
  line-height: 1.35; }
.bb-int-card em { display: block; font-style: normal; font-size: 11px; font-weight: 800;
  letter-spacing: 0.1em; color: #ffd166; margin-top: 6px;
  /* the price is written in place since v104 (the card is no longer rebuilt),
     so going short reads as a change of state rather than a substitution */
  transition: color var(--bb-t-base), opacity var(--bb-t-base); }
.bb-int-card.bb-on2 { border-color: #ffd166; background: #201d12; box-shadow: 0 0 12px #ffd16633; }
.bb-int-card.bb-int-cur b::after { content: ' ·'; color: #8b93a7; }

/* ---- v71: the HORDE ballot ---------------------------------------------
   A mode, not another arena, so it spans the whole vote grid under the arena
   row instead of sitting in line with them, and it wears the swarm's bone
   green (#b9c48f, the same colour the placeholder skeleton uses) rather than
   the vote gold every other card shares. Reads as "...or, instead of any of
   these:" which is exactly what it is. */
.bb-int-card.bb-int-hordecard { grid-column: 1 / -1; background: #151a14; border-color: #3c4a35; }
.bb-int-card.bb-int-hordecard b { color: #b9c48f; }
.bb-int-card.bb-int-hordecard em { color: #b9c48f; }
.bb-int-card.bb-int-hordecard:hover:not(:disabled) { border-color: #6d8257; }
.bb-int-card.bb-int-hordecard.bb-on2 { border-color: #b9c48f; background: #1c2318;
  box-shadow: 0 0 12px #b9c48f33; }

/* ---- v53: the altar ----------------------------------------------------
   Eight offerings, each with its own hue, carried on `--bc` from the BOOSTS
   entry so the rail, the sigil, the title and the lit state are one decision
   made in one place. The sigil is a watermark rather than an icon in the text
   flow: at 34px behind the corner it colours the whole card without stealing a
   line from the blurb, and the eight sigils are what you actually recognise a
   boon by once you have played a few rounds.
   A bought card lights in its OWN colour rather than a uniform green — with
   eight of them on screen, "which ones did I take" has to be readable at a
   glance, and colour does that where a green border cannot. */
.bb-int-sec.bb-int-altar { color: #d8b4ff; letter-spacing: 0.24em; }
.bb-int-sec.bb-int-altar i { color: #8b93a7; }
.bb-int-bst { position: relative; overflow: hidden; padding: 7px 11px 7px 14px; }
.bb-int-bst i { margin-top: 1px; line-height: 1.28; }
.bb-int-bst em { margin-top: 4px; }
.bb-int-bst::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
  background: var(--bc); opacity: 0.85; }
.bb-int-bst::after { content: ""; position: absolute; inset: 0; background: var(--bc);
  opacity: 0.05; pointer-events: none; transition: opacity var(--bb-t-snap); }
.bb-int-bst b { color: var(--bc); }
.bb-bst-sig { position: absolute; right: 7px; top: 1px; font-size: 34px; line-height: 1;
  color: var(--bc); opacity: 0.2; pointer-events: none; }
.bb-int-bst:hover:not(:disabled) { border-color: var(--bc); box-shadow: 0 0 16px -4px var(--bc); }
.bb-int-bst:hover:not(:disabled)::after { opacity: 0.11; }
.bb-int-bst.bb-own { border-color: var(--bc); }
.bb-int-bst.bb-own::after { opacity: 0.14; }
/* v104: the offering SEALS on the frame the authority confirms it — about
   where altarTake's coins land. One shot, added in code with a reflow flush
   between remove and add (the file's usual restart idiom), on the card that is
   now guaranteed to still be the same element. */
.bb-int-bst.bb-took { animation: bbBstTook var(--bb-t-slow) var(--bb-out); }
.bb-int-bst.bb-took .bb-bst-sig { animation: bbSigTook 0.42s var(--bb-out); }
@keyframes bbBstTook { 0%  { box-shadow: 0 0 0 0 var(--bc); }
                       35% { box-shadow: 0 0 18px -2px var(--bc); }
                       100% { box-shadow: 0 0 0 0 transparent; } }
@keyframes bbSigTook { 0%  { opacity: 0.2; transform: scale(1); }
                       30% { opacity: 0.62; transform: scale(1.12); }
                       100% { opacity: 0.2; transform: scale(1); } }
.bb-int-bst.bb-own .bb-bst-sig { opacity: 0.5; }
.bb-int-bst.bb-own em { color: var(--bc); }
.bb-int-bst.bb-off { opacity: 0.4; cursor: not-allowed; }
.bb-int-bst.bb-off .bb-bst-sig { opacity: 0.12; }
/* v54: a card you cannot pay for is clickable now — it refuses you out loud
   rather than being a dead button — so it must not also HOVER like one you can
   buy. No lift, no colour, no glow. */
.bb-int-bst.bb-off:hover:not(:disabled) { transform: none; border-color: #2c3140; box-shadow: none; }
.bb-int-bst.bb-off:hover:not(:disabled)::after { opacity: 0.05; }
.bb-int-go { position: relative; display: block; margin: 0 auto; }
/* v53: READY sticks to the bottom of the panel. Eight offerings take the
   content past 88vh on a laptop, and the panel scrolls internally — without
   this the one control that ends the intermission sits under the fold. The
   gradient is the panel's own ground, so cards slide under it rather than
   colliding with it. */
.bb-int-foot { position: sticky; bottom: -16px; z-index: 3; margin-top: 12px;
  padding: 10px 0 16px;
  background: linear-gradient(180deg, rgba(16,18,24,0), rgba(16,18,24,0.96) 38%, rgba(16,18,24,0.96)); }

/* ---- v54: the altar answers ---------------------------------------------
   Its own layer, above the panel and outside it, because `interTick` rebuilds
   the shop's innerHTML the moment the authority confirms the purchase — around
   200ms in — and anything animating on the card itself would be thrown away
   mid-flight. Everything here is transform and opacity only, on nodes that
   remove themselves on animationend, so a burst costs no layout and leaves
   nothing behind. */
.bb-altfx { position: absolute; inset: 0; pointer-events: none; overflow: hidden; z-index: 8; }
.bb-altfx > div { position: absolute; will-change: transform, opacity; }
/* the gold physically leaves the figure you were reading on the board */
.bb-alt-coin { width: 13px; height: 13px; margin: -6.5px 0 0 -6.5px; border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #fff0c0 0%, #ffd166 45%, #b8860b 100%);
  box-shadow: 0 0 8px #ffd166aa, inset 0 -1px 2px #00000055;
  opacity: 0; animation: bbAltCoin 0.42s cubic-bezier(0.36, 0, 0.66, 1) forwards; }
@keyframes bbAltCoin {
  0%   { transform: translate(0, 0) scale(0.75); opacity: 0; }
  14%  { opacity: 1; }
  55%  { transform: translate(calc(var(--dx) * 0.55), calc(var(--dy) * 0.55 - 42px)) scale(1.15); opacity: 1; }
  100% { transform: translate(var(--dx), var(--dy)) scale(0.3); opacity: 0; }
}
/* the boon stamps itself out of the stone: a flash under a ring snapping open */
.bb-alt-flash { border-radius: 11px; background: var(--bc); opacity: 0;
  transform: translate(-50%, -50%); animation: bbAltFlash var(--bb-t-slow) ease-out forwards; }
/* 0.34, not 0.5: the card still has to be readable while it lights up */
@keyframes bbAltFlash { 0% { opacity: 0.34; } 100% { opacity: 0; } }
/* the seal: the boon's own sigil slammed onto the stone */
.bb-alt-seal { font: 400 62px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
  color: var(--bc); text-shadow: 0 0 22px var(--bc);
  transform: translate(-50%, -50%) scale(2.3); opacity: 0;
  animation: bbAltSeal 0.5s var(--bb-out) forwards; }
@keyframes bbAltSeal {
  0%   { transform: translate(-50%, -50%) scale(2.3) rotate(-14deg); opacity: 0; }
  30%  { transform: translate(-50%, -50%) scale(1) rotate(0deg); opacity: 0.95; }
  100% { transform: translate(-50%, -50%) scale(1.06) rotate(0deg); opacity: 0; }
}
.bb-alt-ring { border-radius: 12px; border: 2px solid var(--bc); opacity: 0;
  transform: translate(-50%, -50%) scale(0.55);
  animation: bbAltRing 0.52s var(--bb-out) forwards; }
@keyframes bbAltRing {
  0%   { transform: translate(-50%, -50%) scale(0.55); opacity: 0.95; }
  100% { transform: translate(-50%, -50%) scale(1.32); opacity: 0; }
}
.bb-alt-ember { width: 5px; height: 5px; margin: -2.5px 0 0 -2.5px; border-radius: 50%;
  background: var(--bc); box-shadow: 0 0 7px var(--bc);
  animation: bbAltEmber 0.6s ease-out forwards; }
@keyframes bbAltEmber {
  0%   { transform: translate(0, 0) scale(1); opacity: 0.95; }
  100% { transform: translate(var(--dx), var(--dy)) scale(0.2); opacity: 0; }
}
/* and the boon says its own name on the way out */
.bb-alt-word { font: 900 15px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
  letter-spacing: 0.16em; white-space: nowrap; color: var(--bc);
  text-shadow: 0 2px 10px #000c, 0 0 18px var(--bc);
  transform: translate(-50%, -50%); animation: bbAltWord 0.85s ease-out forwards; }
@keyframes bbAltWord {
  0%   { transform: translate(-50%, -50%) scale(0.8); opacity: 0; }
  22%  { transform: translate(-50%, calc(-50% - 8px)) scale(1.06); opacity: 1; }
  100% { transform: translate(-50%, calc(-50% - 34px)) scale(1); opacity: 0; }
}
/* the altar takes it, and the panel feels the weight. The last keyframe is the
   panel's own resting transform, so the transition it lives under has nothing
   to catch up to when the animation ends. */
.bb-inter.bb-alt-shake { animation: bbAltShake var(--bb-t-panel) ease-out; }
@keyframes bbAltShake {
  0%, 100% { transform: translate(-50%, -50%) scale(1); }
  28%      { transform: translate(calc(-50% - 3px), calc(-50% + 2px)) scale(1.004); }
  64%      { transform: translate(calc(-50% + 2px), calc(-50% - 1px)) scale(1); }
}
/* refused: the card says no and points at the number that is short */
.bb-int-bst.bb-alt-no { animation: bbAltNo var(--bb-t-slow) ease-out; }
@keyframes bbAltNo {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-5px); }
  45% { transform: translateX(4px); }
  72% { transform: translateX(-2px); }
}
.bb-int-board .bb-int-au.bb-alt-need { animation: bbAltNeed 0.7s ease-out; }
@keyframes bbAltNeed {
  0%, 100% { color: #ffc94a; transform: scale(1); }
  26%      { color: #ff6b6b; text-shadow: 0 0 12px #ff6b6b; transform: scale(1.18); }
}
/* Reduced motion is handled in JS as well as here: altarTake() plays the sound
   and spawns nothing, because a node whose animation never runs is a node whose
   animationend never fires — it would sit in the layer forever. */
@media (prefers-reduced-motion: reduce) {
  .bb-inter.bb-alt-shake, .bb-int-bst.bb-alt-no, .bb-int-board .bb-int-au.bb-alt-need { animation: none; }
  /* v61: the staggered entrance is decoration — it carries no state the panel
     does not also say in text, so it is the first thing to go. Everything
     lands at once, at full opacity, on frame one. */
  .bb-inter.bb-in > *, .bb-inter.bb-in .bb-int-bst { animation: none; }
  /* v104: the seal and the panel's rise are the same kind of decoration —
     the card still says OFFERED and the panel still arrives, instantly. */
  .bb-int-bst.bb-took, .bb-int-bst.bb-took .bb-bst-sig { animation: none; }
  .bb-inter, .bb-inter.bb-on { transition: opacity var(--bb-t-snap); transform: translate(-50%, -50%); }
  .bb-pick, .bb-pick.bb-on { transform: translate(-50%, -50%); }
}

/* KILLED BY <name> — <weapon>: rides the death closeup, and stands DOWN when
   the SWITCH BRO panel opens (v61). It sits at top 24%, which on an 800px
   viewport puts its lower half behind that panel's top edge (~178px); the
   panel also repeats "killed by X" in its own subtitle, so keeping both meant
   duplicated text, half of it clipped. Gated in deathBannerTick, not here,
   because the panel's height is not knowable from CSS. */
.bb-death { position: absolute; left: 50%; top: 24%; transform: translate(-50%, -50%) translateY(10px);
  text-align: center; opacity: 0; transition: opacity var(--bb-t-panel), transform var(--bb-t-panel); pointer-events: none; }
.bb-death.bb-on { opacity: 1; transform: translate(-50%, -50%); }
.bb-death i { display: block; font-style: normal; font-size: 13px; font-weight: 800;
  letter-spacing: 0.34em; color: #ff6b6b; text-shadow: 0 2px 8px #000c; }
.bb-death b { display: block; margin-top: 2px; font-size: 34px; font-weight: 900;
  letter-spacing: 0.06em; color: #fff; text-shadow: 0 3px 12px #000d, 0 0 24px #ff6b6b55; }
.bb-death span { display: inline-flex; align-items: center; gap: 6px; margin-top: 6px;
  font-size: 14px; font-weight: 800; letter-spacing: 0.1em; color: #ffd166;
  text-transform: uppercase; text-shadow: 0 2px 6px #000c; }
.bb-death span img { width: 22px; height: 22px; filter: drop-shadow(0 2px 3px #000a); }
.bb-swap-kb { display: block; margin-top: 3px; font-style: normal; font-size: 12px;
  font-weight: 700; letter-spacing: 0.06em; color: #ff9d9d; }
.bb-swap-kb b { color: #fff; }

/* collected coins fly to the purse: small gold discs on their own layer so the
   HUD memoizer can never rewrite them mid-flight */
.bb-coinfly { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }
.bb-coinfly u { position: absolute; left: 0; top: 0; width: 22px; height: 22px; border-radius: 50%;
  box-sizing: border-box; border: 1px solid #fff0b0;
  background: radial-gradient(circle at 35% 30%, #fff0c0 0%, #ffd166 45%, #b8860b 100%);
  box-shadow: 0 0 8px #ffd166aa, inset 0 -1px 2px #00000055;
  opacity: 0; will-change: transform, opacity; }
.bb-coinfly u::after { content: ""; position: absolute; inset: 3px; border: 1px solid #ffebaa99; border-radius: 50%; }

.bb-dmg { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }
.bb-dnum { position: absolute; left: 0; top: 0; will-change: transform, opacity;
  font: 800 19px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
  letter-spacing: 0.01em; font-variant-numeric: tabular-nums;
  text-shadow: 0 2px 4px #000c, 0 0 2px #000; white-space: nowrap; }
/* damage YOU deal — the one you want to feel */
.bb-dn-hit { color: #fff3d0; }
/* damage YOU take: red, and bigger, because it is the number that matters */
.bb-dn-me { color: #ff6b6b; font-size: 23px; text-shadow: 0 2px 5px #000e, 0 0 10px #ff000060; }
/* soaked by a shield rather than health */
.bb-dn-shield { color: #ffd166; }
@media (prefers-reduced-motion: reduce) { .bb-dnum { transition: none; } }

/* ---------------- the F1 reference card (v43) ----------------
   The nine-second quick-start card carries five controls; this carries
   everything a player asks about afterwards. Two columns on a laptop, one on a
   phone, and never taller than the viewport — a reference you have to scroll
   during a live match is a reference you close. z-index 8 puts it over the
   respawn dialog (6) and the death banner (7), because F1 is an explicit
   request and should win. */
.bb-doc { position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%) scale(0.97) translateY(10px);
  width: min(880px, 94vw); max-height: min(93vh, 760px); overflow-y: auto;
  background: rgba(9, 11, 16, 0.982); border: 1px solid #3a4152; border-radius: 16px;
  padding: 16px 20px 18px; opacity: 0; pointer-events: none; z-index: 8;
  box-shadow: 0 22px 60px #000c, 0 0 0 1px #0006;
  /* out: quick and linear, so dismissing feels instant */
  transition: opacity var(--bb-t-snap) ease-in, transform var(--bb-t-snap) ease-in; }
/* in: slower, eased hard out of the gate — the panel arrives rather than
   appears. Two different curves for the two directions is the whole trick. */
.bb-doc.bb-on { opacity: 1; transform: translate(-50%, -50%) scale(1) translateY(0);
  pointer-events: auto; transition: opacity var(--bb-t-panel) ease-out, transform var(--bb-t-slow) var(--bb-out); }
.bb-doc-hd { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding-bottom: 10px; margin-bottom: 12px; border-bottom: 1px solid #262d3b; }
.bb-doc-hd b { font-size: 13px; font-weight: 800; letter-spacing: 0.2em; color: #ffd166; }
.bb-doc-x { width: 26px; height: 26px; border-radius: 8px; cursor: pointer; font: inherit; font-size: 12px;
  background: #151922; border: 1px solid #2c3342; color: #9aa4b8;
  transition: background var(--bb-t-snap), border-color var(--bb-t-snap), color var(--bb-t-snap), transform var(--bb-t-snap); }
.bb-doc-x:hover { background: #242b3a; border-color: #4a5468; color: #e6ebf5; transform: rotate(90deg); }
.bb-doc-x:focus-visible { outline: 2px solid #ffd166; outline-offset: 2px; }
.bb-doc-cols { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 11px 26px; }
.bb-doc h4 { margin: 0 0 6px; font-size: 9px; font-weight: 800; letter-spacing: 0.18em;
  text-transform: uppercase; color: #7c869c; }
.bb-doc section { min-width: 0; }
.bb-doc p { margin: 0 0 6px; font-size: 12px; line-height: 1.48; color: #b9c2d4; }
.bb-doc p b { color: #e6ebf5; font-weight: 700; }
.bb-doc-r { display: grid; grid-template-columns: 128px 1fr; gap: 10px; align-items: baseline;
  padding: 2px 0; border-radius: 6px; transition: background var(--bb-t-snap); }
.bb-doc-r:hover { background: #141926; }
/* the colour legend is a swatch, not a key cap — it has no business reserving
   the 128px the keyboard column needs */
.bb-doc-r.bb-doc-lg { grid-template-columns: 22px 1fr; gap: 11px; align-items: start; padding: 4px 0; }
.bb-doc-r > span { display: flex; flex-wrap: wrap; gap: 3px; align-items: center; }
.bb-doc-r i { font-style: normal; font-size: 12px; line-height: 1.45; color: #b9c2d4; }
.bb-doc-r i b { color: #e6ebf5; font-weight: 700; }
.bb-doc kbd { display: inline-block; min-width: 17px; padding: 2px 5px; border-radius: 5px;
  background: #0d1119; border: 1px solid #3d4557; border-bottom-width: 2px;
  font: inherit; font-size: 9.5px; font-weight: 800; letter-spacing: 0.04em; color: #cfd8e8; text-align: center; }
/* the colour key: a swatch that IS the colour it is describing, so the legend
   cannot drift from the thing it documents without someone noticing */
.bb-doc-sw { display: inline-block; width: 15px; height: 15px; border-radius: 4px;
  box-shadow: inset 0 0 0 1px #0007, 0 1px 3px #0008; }
.bb-doc-ft { margin-top: 10px; padding-top: 8px; border-top: 1px solid #262d3b;
  font-size: 11px; color: #7c869c; text-align: center; }
.bb-doc-ft b { color: #b9c2d4; }
/* The credit line is a licence obligation, not a footnote, so it stays legible
   rather than shrinking into the border — one step down from the footer, its
   own rule above it, and it wraps instead of clipping on a narrow window.
   v90: it must also FIT. Adding two lines to the card put the credit 26px past
   the bottom edge at a 820px-tall window — still reachable, since .bb-doc
   scrolls, but a credit you have to go looking for is a worse credit. The cap
   went 86vh/720 -> 93vh/760 and the vertical rhythm tightened a few px per
   rule; measure it after any edit to this card, do not eyeball it:
     document.querySelector('.bb-doc').scrollHeight - .clientHeight   // want 0
   and measure it at 1366x768, the Chromebook this game is built for, not at
   whatever your own window happens to be. */
.bb-doc-cr { margin-top: 6px; padding-top: 6px; border-top: 1px solid #1c2230;
  font-size: 10px; line-height: 1.55; color: #6f798e; }
.bb-doc-cr b { color: #98a2b6; font-weight: 700; }
@media (max-width: 620px) { .bb-doc-cols { grid-template-columns: minmax(0, 1fr); }
                            .bb-doc-r { grid-template-columns: 104px 1fr; } }
@media (prefers-reduced-motion: reduce) {
  .bb-doc, .bb-doc.bb-on { transition: opacity var(--bb-t-snap); transform: translate(-50%, -50%); }
  .bb-doc-x:hover { transform: none; } }

/* The round-transition wipe was HERE (v45) and is gone (v59). It was a DOM veil
   at z-index 4, and the wordmark it was supposed to sit behind is a scene
   object inside the canvas — so the veil painted straight over the logo and
   dimmed it 3.4x (measured: brightest pixel luma 65.7 with the veil, 222.3
   without, against a source texture whose own brightest pixel is 221.7). A DOM
   layer can never be behind something drawn in the canvas, so the veil moved
   into the scene as a quad at renderOrder 955, one step further from the camera
   than the wordmark's 960. See logoEnsure()/logoTick() in game.js. */

/* v86: a bro who dropped out. His kills stand — the rules keep the record on
   purpose since v80 — but a row that looks exactly like everyone else's reads
   as a player still in the room, which is the one thing he is not. Dimmed, and
   labelled, rather than removed. */
.bb-srow.bb-sgone, tr.bb-int-gone { opacity: .55; }
.bb-sleft {
  font-style: normal;
  font-size: 8.5px;
  letter-spacing: .09em;
  font-weight: 800;
  margin-left: 5px;
  padding: 1px 4px;
  border-radius: 3px;
  color: #0d0f14;
  background: #8792a6;
  vertical-align: middle;
}

/* ---- the match-end panel (v109) ------------------------------------------
 * Mounted by the engine in the Round Complete card's reserved slot (SDK v167).
 * The engine owns the CONTAINER — full width, the 240px clamp, the card margin,
 * the internal scroll — so nothing here sets a height, a position or a z-index;
 * that is the boundary the contract draws, and crossing it is how a game ends
 * up fighting engine chrome.
 *
 * The palette is the ALTAR BOARD's, exactly (`.bb-int-board` above): same gold,
 * same ink, same 10px/0.14em micro-labels, same hairline. A player who has been
 * reading the between-round board for three rounds should recognise this one as
 * the same object, arriving one last time. */
.bb-res { color: #e6ebf5; }
.bb-res-h { text-align: center; margin: 0 0 8px; }
.bb-res-h b { display: block; font-size: 15px; font-weight: 900; letter-spacing: 0.14em; color: #ffd166; }
.bb-res-h i { display: block; font-style: normal; font-size: 12px; font-weight: 700; color: #8b93a7; margin-top: 2px; }
.bb-res-t { width: 100%; border-collapse: collapse; font-size: 14px; }
.bb-res-t th { font-size: 10px; font-weight: 800; letter-spacing: 0.14em; color: #8b93a7;
  text-transform: uppercase; padding: 3px 7px; text-align: right; white-space: nowrap; }
.bb-res-t th:first-child, .bb-res-t td:first-child { text-align: left; }
.bb-res-t td { padding: 3px 7px; text-align: right; color: #e6ebf5; border-top: 1px solid #ffffff0d;
  font-variant-numeric: tabular-nums; }
.bb-res-t td u { display: inline-block; width: 9px; height: 9px; border-radius: 50%;
  margin-right: 7px; text-decoration: none; }
.bb-res-t .bb-res-n { max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bb-res-t .bb-res-au { color: #ffc94a; font-weight: 800; }
.bb-res-t .bb-res-me td { background: #ffd16612; }
/* A FULL ROOM STILL FITS. Eight bros at the roomy spacing is 273px of panel in a
 * 240px slot, so the last row scrolls away — and ranked by total the last row is
 * often mine, which is the row I opened this panel to read (v92 learned the same
 * thing on the in-match scoreboard). `.bb-res-tight`, set by the panel when the
 * roster is large, buys back the 33px in leading rather than by pinning a row:
 * a sticky row needs an opaque ground, and the only ground here belongs to the
 * engine's card — a colour this game does not own and must not guess at. */
.bb-res-tight .bb-res-h { margin-bottom: 5px; }
.bb-res-tight .bb-res-h b { font-size: 14px; }
.bb-res-tight .bb-res-t td, .bb-res-tight .bb-res-t th { padding-top: 1px; padding-bottom: 1px; }
.bb-res-tight .bb-res-carry { margin-top: 5px; padding-top: 5px; }
.bb-res-t .bb-res-out td { opacity: 0.55; }
.bb-res-gone { font-style: normal; font-size: 9px; font-weight: 800; letter-spacing: 0.1em;
  margin-left: 6px; padding: 1px 4px; border-radius: 3px; color: #0d0f14; background: #8792a6; }
/* The boons: the altar's own sigils in the altar's own colours, which is the
 * one row on this screen that says what anybody was PLAYING. */
.bb-res-t .bb-res-b, .bb-res-t .bb-res-bh { text-align: center; }
.bb-res-sig { font-style: normal; font-size: 13px; line-height: 1; margin: 0 1px; }
.bb-res-none { color: #4b5468; }
/* The carry line is the only thing here a player can act on, and Rematch is
 * inches below it — so it is separated from the table rather than being a row
 * in it, and it is silent when nothing carries. */
.bb-res-carry { margin-top: 8px; padding-top: 7px; border-top: 1px solid #ffffff14;
  text-align: center; font-size: 12px; font-weight: 700; color: #cfd8e8; }
.bb-res-carry b { color: #ffc94a; font-weight: 900; }
@media (max-width: 420px) {
  .bb-res-t { font-size: 13px; }
  .bb-res-t th, .bb-res-t td { padding: 3px 4px; }
}

/* ============================================================================
 * v123 — REDUCED MOTION, for the rest of the HUD.
 *
 * v104 covered the dialogs and v61 the intermission entrance. `tools/
 * motioncheck.js` swept the whole file and found thirteen rules that MOVE — an
 * animation, or a transition carrying a transform — with no reduced-motion
 * answer at all. The bar is deliberately narrow: this preference is about
 * VESTIBULAR load, not about a static page, so the health bar keeps easing its
 * width, keys keep warming their colour, and panels keep cross-fading. Only
 * travel and repetition go.
 *
 * THE ONE THAT MATTERED MOST is `.bb-ambient.bb-lowhp`: a full-screen red
 * vignette pulsing on a 1.1s loop, INFINITELY, for as long as a player sits
 * under 25% health — which in a firefight can be a long time. It is also the
 * easiest to fix without losing anything, because the signal was never the
 * pulse: it is the red, and the red stays.
 * ==========================================================================*/
@media (prefers-reduced-motion: reduce) {
  /* the low-health warning is a COLOUR, and always was */
  .bb-ambient.bb-lowhp { animation: none; }
  /* things that announce themselves mid-screen: fade, do not scale or rise */
  .bb-toast { transition: opacity var(--bb-t-snap); transform: translate(-50%, -50%) scale(1); }
  .bb-toast.bb-on { transform: translate(-50%, -50%) scale(1); }
  .bb-death { transition: opacity var(--bb-t-panel); transform: translateX(-50%); }
  .bb-help { transition: opacity var(--bb-t-panel); transform: none; }
  /* the sponsor tower rides in beside the intermission panel; it can just be there */
  .bb-tower { transition: opacity var(--bb-t-panel); transform: translateY(-50%) scale(1); }
  .bb-inter.bb-on ~ .bb-tower { transition: opacity var(--bb-t-panel); transform: translateY(-50%) scale(1); }
  /* a card under the cursor says so in its border, not by lifting */
  .bb-int-card:hover:not(:disabled) { transform: none; }
  /* THE ALTAR'S PURCHASE FLOURISH — six one-shots (a coin flight, a flash, a
     seal, a ring, embers, a rising word) whose REAL answer is in JavaScript,
     not here: `altarTake` returns on `R.lessMotion` before building a single
     node, and a node that is never created cannot move. This block is only
     reachable in one case — the preference turns on WHILE a purchase is in
     flight, since v104 made it live rather than sampled once — and that case is
     worth covering, because a frozen coin is a coin that sits on the gold
     readout until the panel shuts. So: stop the three that exist ONLY for the
     flourish, and let the seal, flash and word simply not animate. */
  .bb-alt-coin, .bb-alt-flash, .bb-alt-seal,
  .bb-alt-ring, .bb-alt-ember, .bb-alt-word { animation: none; }
  .bb-alt-coin, .bb-alt-ring, .bb-alt-ember { display: none; }
}
