/* BATTLE ROYALE — 2 to 6 pads on one screen, and everything that flies between
   them. Nothing in here applies unless #stage is in battle layout, so every
   other mode renders exactly the CSS it always did.

   The look is the rest of the game taken to its conclusion: a phosphor tube.
   Ordnance is stroke-drawn and lit from inside, damage is drawn as the tube
   failing rather than as a badge saying it failed, and nothing on this layer is
   a colour the palette does not already own. */

/* ---------- the table ---------- */

/* Landscape / wide: rows stay shallow so six pads still read at a glance. */
#stage[data-layout='battle'] { gap: 8px; }
#stage[data-layout='battle'][data-seats='2'] { grid-template-columns: 1fr 1fr; }
#stage[data-layout='battle'][data-seats='3'] { grid-template-columns: repeat(3, 1fr); }
#stage[data-layout='battle'][data-seats='4'] { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
#stage[data-layout='battle'][data-seats='6'] { grid-template-columns: repeat(3, 1fr); grid-template-rows: 1fr 1fr; }

/* Five is the awkward one: three across, then two centred underneath. A sixth
   empty cell would read as a missing player, which is worse than an odd row. */
#stage[data-layout='battle'][data-seats='5'] {
  grid-template-columns: repeat(6, 1fr);
  grid-template-rows: 1fr 1fr;
}
#stage[data-layout='battle'][data-seats='5'] .panel { grid-column: span 2; }
#stage[data-layout='battle'][data-seats='5'] .panel:nth-child(4) { grid-column: 2 / span 2; }
#stage[data-layout='battle'][data-seats='5'] .panel:nth-child(5) { grid-column: 4 / span 2; }

@media (orientation: portrait) {
  #stage[data-layout='battle'][data-seats='2'] { grid-template-columns: 1fr; grid-template-rows: 1fr 1fr; }
  #stage[data-layout='battle'][data-seats='3'] { grid-template-columns: 1fr; grid-template-rows: repeat(3, 1fr); }
  #stage[data-layout='battle'][data-seats='4'] { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
  #stage[data-layout='battle'][data-seats='5'],
  #stage[data-layout='battle'][data-seats='6'] {
    grid-template-columns: 1fr 1fr;
    grid-template-rows: repeat(3, 1fr);
  }
  #stage[data-layout='battle'][data-seats='5'] .panel,
  #stage[data-layout='battle'][data-seats='5'] .panel:nth-child(4),
  #stage[data-layout='battle'][data-seats='5'] .panel:nth-child(5) { grid-column: auto; }
  #stage[data-layout='battle'][data-seats='5'] .panel:nth-child(5) { grid-column: span 2; }
}

/* Sitting round a table means half the room is reading upside down. The rows are
   measured, not assumed — see markRows() — so this works at every count and both
   orientations. The :not() guard is load-bearing: when every pad is in a single
   row there is no far side of the table, and rotating "row 0" turned BOTH pads
   of a landscape duel upside down at once. */
#stage[data-layout='battle'].faceoff:not([data-rows='1']) .panel[data-row='0'] { transform: rotate(180deg); }

/* Four or more pads: shrink the furniture so the score still dominates. */
#stage[data-layout='battle'][data-seats='4'] .panel,
#stage[data-layout='battle'][data-seats='5'] .panel,
#stage[data-layout='battle'][data-seats='6'] .panel { gap: clamp(1px, 0.5vh, 6px); }
#stage[data-layout='battle'][data-seats='4'] .score,
#stage[data-layout='battle'][data-seats='5'] .score,
#stage[data-layout='battle'][data-seats='6'] .score { font-size: clamp(1.2rem, 4.4vh, 2.1rem); }
#stage[data-layout='battle'][data-seats='4'] .number,
#stage[data-layout='battle'][data-seats='5'] .number,
#stage[data-layout='battle'][data-seats='6'] .number { font-size: clamp(0.8rem, 2.6vh, 1.3rem); }
#stage[data-layout='battle'][data-seats='5'] .tapsbar,
#stage[data-layout='battle'][data-seats='6'] .tapsbar { display: none; }
#stage[data-layout='battle'] .taphint { display: none; }

/* Battle pads have a rail bolted to the bottom edge — keep the furniture off it. */
#stage[data-layout='battle'] .panel { padding-bottom: 12px; }

/* ---------- the weapon mark ---------- */

/* Every sigil is one drawing in currentColor, so a single --c lights the glyph,
   its bloom and its casing together. Set once, here, rather than eight times. */
.sg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
  color: inherit;
}
.sg .sg-spin { transform-origin: 12px 12px; animation: sgSpin 3.4s linear infinite; }
.sg .sg-pulse { animation: sgPulse 1.5s ease-in-out infinite; }
.sg .sg-flick { animation: sgFlick 1.1s steps(1, end) infinite; }
@keyframes sgSpin { to { transform: rotate(360deg); } }
@keyframes sgPulse { 50% { opacity: 0.35; } }
@keyframes sgFlick { 0%, 62%, 74% { opacity: 1; } 66%, 92% { opacity: 0.15; } }

/* ---------- the pad's own hardware ---------- */

/* THE WEAPON RAIL. The paytable on the glass: one notch per unit of charge, and
   each notch wears the tier of the special that loaded it — so a rail of four
   greys and a rail of two golds are visibly different weapons waiting to happen,
   which is the whole economy of this mode rendered as furniture. */
.chargebar {
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: 5px;
  height: 7px;
  display: flex;
  gap: 2px;
  z-index: 7;
  pointer-events: none;
}
.chargebar::before,
.chargebar::after {
  content: '';
  position: absolute;
  top: -2px;
  bottom: -2px;
  width: 3px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line));
  opacity: 0.8;
}
.chargebar::before { left: -6px; border-right: 0; }
.chargebar::after { right: -6px; border-left: 0; }
.chargebar i {
  flex: 1;
  position: relative;
  background: color-mix(in srgb, var(--line) 80%, transparent);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04);
  transition: background 180ms ease-out, box-shadow 180ms ease-out;
}
.chargebar i.on {
  background: var(--sc, var(--tier-common));
  box-shadow: 0 0 5px color-mix(in srgb, var(--sc, var(--tier-common)) 60%, transparent),
              inset 0 -2px 0 rgba(255, 255, 255, 0.18);
}
/* The notch that just filled gets the hot cap, so you can see the rail load. */
.chargebar i.head { box-shadow: 0 0 16px var(--sc, var(--tier-common)), inset 0 0 0 1px #fff; filter: brightness(1.4); }
/* One from full. The empty notch that is about to be taken strobes — this is
   the most tense the mode ever gets and it deserves to be visible from a metre.
   (`.next` is placed by syncBattlePad; there is no CSS-only way to say "the
   first notch that is not lit", and the selector that tried died silently.) */
.chargebar i.next { animation: railNext 620ms ease-in-out infinite; }
@keyframes railNext {
  50% { background: var(--cc, var(--gold)); box-shadow: 0 0 14px var(--cc, var(--gold)); }
}
/* The whole rail strobing white is the discharge — it happens for 260ms as the
   shot leaves and is the pad's own confirmation that the thing has gone. */
.chargebar.fired i { animation: railFire 260ms ease-out both; }
.chargebar.fired i:nth-child(2) { animation-delay: 22ms; }
.chargebar.fired i:nth-child(3) { animation-delay: 44ms; }
.chargebar.fired i:nth-child(4) { animation-delay: 66ms; }
.chargebar.fired i:nth-child(5) { animation-delay: 88ms; }
.chargebar.fired i:nth-child(6) { animation-delay: 110ms; }
.chargebar.fired i:nth-child(7) { animation-delay: 132ms; }
.chargebar.fired i:nth-child(8) { animation-delay: 154ms; }
.chargebar.fired i:nth-child(9) { animation-delay: 176ms; }
.chargebar.fired i:nth-child(10) { animation-delay: 198ms; }
@keyframes railFire {
  0% { background: #fff; box-shadow: 0 0 22px #fff; }
  100% { background: color-mix(in srgb, var(--line) 80%, transparent); box-shadow: none; }
}

.chip.chargechip {
  color: var(--cc, var(--dim));
  border-color: color-mix(in srgb, var(--cc, var(--line)) 55%, transparent);
  font-variant-numeric: tabular-nums;
}
.chip.chargechip .cwc { display: inline-block; width: 0.78em; height: 0.78em; vertical-align: -0.08em; margin-right: 4px; }

/* THE CROWN, drawn. It marks the seat every unaimed weapon in the game is
   already pointed at, which makes it the single most consequential piece of
   state on the table — it does not get to be a 0.8rem emoji. */
.crown {
  width: 1.05rem;
  height: 1.05rem;
  color: var(--gold);
  filter: drop-shadow(0 0 7px color-mix(in srgb, var(--gold) 75%, transparent));
  animation: crownBreathe 2.6s ease-in-out infinite;
}
@keyframes crownBreathe { 50% { filter: drop-shadow(0 0 14px var(--gold)); } }
.panel.leader { border-color: color-mix(in srgb, var(--gold) 55%, var(--line)); }
.panel.leader .cnr { border-color: color-mix(in srgb, var(--gold) 85%, transparent); }

/* THE TARGET RETICLE. One per table, living in the war layer, and it physically
   slides from the old leader's pad to the new one the instant the lead changes.
   Everybody watches the crosshair move — which is the point, because that is
   the moment the whole table's aim changed with it. */
.bt-reticle {
  position: absolute;
  pointer-events: none;
  border: 0;
  transition: left 420ms cubic-bezier(0.34, 1.2, 0.4, 1),
              top 420ms cubic-bezier(0.34, 1.2, 0.4, 1),
              width 420ms cubic-bezier(0.34, 1.2, 0.4, 1),
              height 420ms cubic-bezier(0.34, 1.2, 0.4, 1),
              opacity 200ms linear;
  opacity: 0;
}
.bt-reticle.on { opacity: 1; }
.bt-reticle i {
  position: absolute;
  width: 20px;
  height: 20px;
  border: 2px solid var(--gold);
  box-shadow: 0 0 12px color-mix(in srgb, var(--gold) 60%, transparent);
}
/* Framed just INSIDE the pad. Outside, it reached across an 8px grid gap into
   whoever is sitting next door — so at four pads the crosshair on the leader
   appeared to be drawn on their neighbour. Inside at 2px it still nests clearly
   outside the pad's own brackets at 7px, and it belongs to exactly one seat. */
.bt-reticle i:nth-child(1) { top: 2px; left: 2px; border-right: 0; border-bottom: 0; }
.bt-reticle i:nth-child(2) { top: 2px; right: 2px; border-left: 0; border-bottom: 0; }
.bt-reticle i:nth-child(3) { bottom: 2px; left: 2px; border-right: 0; border-top: 0; }
.bt-reticle i:nth-child(4) { bottom: 2px; right: 2px; border-left: 0; border-top: 0; }
/* No caption. At six pads there is no room for one, and there does not need to
   be: a gold lock-on frame plus the crown in that pad's header already says who
   the whole table is shooting at. */
.bt-reticle b { display: none; }
.bt-reticle.jump i { animation: retSnap 420ms cubic-bezier(0.34, 1.4, 0.4, 1); }
@keyframes retSnap { 40% { scale: 1.5; opacity: 0.6; } }

/* The pad while a spread weapon is leaving it: the brackets flare in the
   weapon's colour and the tile stays lit while the rest of the room goes dark. */
.panel.armed { border-color: var(--wc, var(--gold)); box-shadow: 0 0 30px -6px var(--wc, var(--gold)); }
.panel.armed .cnr { border-color: var(--wc, var(--gold)); animation: cnrflick 0.9s ease-in-out infinite; }
@keyframes cnrflick { 50% { opacity: 0.35; } }

.panel.bt-recoil { animation: btRecoil 300ms cubic-bezier(0.2, 0.9, 0.3, 1); }
@keyframes btRecoil {
  0% { translate: 0 0; }
  22% { translate: var(--rkx, 0) var(--rky, 0); }
  100% { translate: 0 0; }
}

/* INCOMING. Not a red pulse on a box — an alert frame that converges, with the
   glow weighted toward the edge the thing is actually arriving from. */
.panel.incoming {
  box-shadow: inset 0 0 40px -8px color-mix(in srgb, var(--miss) 65%, transparent);
}
.panel.incoming::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 8;
  border-radius: inherit;
  background: radial-gradient(
    62% 62% at calc(50% + var(--ivx, 0%)) calc(50% + var(--ivy, 0%)),
    color-mix(in srgb, var(--miss) 26%, transparent),
    transparent 70%
  );
  animation: incoming 420ms ease-in-out infinite;
}
.panel.incoming .cnr {
  border-color: var(--miss);
  animation: incomingCnr 420ms ease-in-out infinite;
}
@keyframes incoming { 50% { opacity: 0.35; } }
@keyframes incomingCnr {
  50% { scale: 0.78; box-shadow: 0 0 12px color-mix(in srgb, var(--miss) 70%, transparent); }
}

/* BROWNOUT — the pad is still on, it is just not worth anything. The panel
   itself browns out (the accent drains toward the dead-amber the weapon flies
   in) and a filament flickers over it at an ugly, irregular mains rhythm. */
.panel.bt-weak { border-color: color-mix(in srgb, #c9973f 45%, var(--line)); }
.panel.bt-weak .score,
.panel.bt-weak .number { filter: saturate(0.4) brightness(0.78); }
/* The watermark lives in the corner, not over the score. Centred, it sat on top
   of the two numbers the player is actually trying to read — which is a strange
   way to tell somebody their next three hits are worth half. The fact goes in a
   chip in the header where every other piece of pad state already lives; the
   corner just carries the mood. */
.weakbadge {
  position: absolute;
  right: 8px;
  bottom: 16px;
  color: #c9973f;
  pointer-events: none;
  z-index: 3;
  opacity: 0.3;
  animation: weakflick 2.4s steps(1, end) infinite;
}
.weakbadge svg { width: clamp(1.8rem, 6.5vh, 3.2rem); height: clamp(1.8rem, 6.5vh, 3.2rem); }
.weakbadge b { display: none; }

/* ...and the fact itself, stated where facts are stated. */
.chip.weakchip {
  font-family: var(--display);
  font-size: 0.6rem;
  letter-spacing: 0.1em;
  color: #c9973f;
  border-color: color-mix(in srgb, #c9973f 55%, transparent);
  animation: weakflick 2.4s steps(1, end) infinite;
}
@keyframes weakflick {
  0%, 41%, 47%, 62%, 100% { opacity: 1; }
  44%, 64%, 66% { opacity: 0.22; }
}

/* STATIC — the tube gives up: snow, a rolling hsync bar, and a colour fringe on
   the digits. Three things a real screen does when it loses signal, instead of
   a grey rectangle with the word JAMMED on it. */
.bt-snow {
  position: absolute;
  inset: 0;
  z-index: 6;
  pointer-events: none;
  opacity: 0.55;
  mix-blend-mode: screen;
  overflow: hidden;
  border-radius: inherit;
  background:
    repeating-linear-gradient(0deg, rgba(255, 255, 255, 0.16) 0 1px, transparent 1px 2px),
    repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.1) 0 1px, transparent 1px 3px);
  animation: snowroll 0.18s steps(3, end) infinite;
}
@keyframes snowroll {
  33% { transform: translate(1px, -2px); }
  66% { transform: translate(-2px, 1px); }
}
.bt-hsync {
  position: absolute;
  left: -10%;
  right: -10%;
  height: 16%;
  background: linear-gradient(
    180deg,
    transparent,
    rgba(255, 255, 255, 0.22) 35%,
    rgba(79, 216, 235, 0.3) 55%,
    transparent
  );
  animation: hsync 1.35s linear infinite;
}
@keyframes hsync { from { top: -20%; } to { top: 108%; } }
.panel.bt-scrambled .number {
  color: var(--tier-uncommon);
  text-shadow: 1.5px 0 0 color-mix(in srgb, var(--miss) 70%, transparent),
               -1.5px 0 0 color-mix(in srgb, var(--tier-uncommon) 70%, transparent);
  animation: scrambleJitter 0.14s steps(2, end) infinite;
}
@keyframes scrambleJitter { 50% { translate: -1.5px 0; } }
.panel.bt-scrambled .score { opacity: 0.3; filter: blur(0.6px); }

/* JAM — the taps do not quietly vanish off the counter. The segments they were
   burn out, in the place the player was already looking. */
.tapsbar i.bt-burn { animation: tapBurn 640ms ease-out both; }
@keyframes tapBurn {
  0% { background: #fff; box-shadow: 0 0 18px #fff; }
  30% { background: #7f8cff; box-shadow: 0 0 16px #7f8cff; }
  100% { background: var(--line); box-shadow: none; opacity: 0.55; }
}

/* ---------- the air between the pads ---------- */

.bt-layer {
  position: fixed;
  inset: 0;
  z-index: 50; /* over the pads, under the backdrop and every sheet */
  pointer-events: none;
  overflow: hidden;
}

/* The phosphor canvas. Never cleared, only faded — see warfx.js. */
.bt-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.bt-shot {
  position: absolute;
  width: 62px;
  height: 62px;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--c);
  display: grid;
  place-items: center;
  pointer-events: none;
  will-change: transform;
}
.bt-shot > * { grid-area: 1 / 1; }
.bt-shot.swattable {
  pointer-events: auto;
  cursor: crosshair;
  touch-action: none;
}

/* The casing: the frame the mark travels inside. Weight tracks the tier. */
.bt-case {
  width: 100%;
  height: 100%;
  overflow: visible;
  filter: drop-shadow(0 0 10px color-mix(in srgb, var(--c) 70%, transparent));
}
/* The armoured ring turns; the target ring does not. A crosshair that spins is
   not a crosshair. */
.bt-case .bt-ring2 { animation: caseSpin 2.6s linear infinite; transform-origin: 50% 50%; }
.bt-shot.swattable .bt-case .bt-ring2 { animation: caseDash 1.1s linear infinite; }
@keyframes caseDash { to { stroke-dashoffset: -23; } }
.bt-case .bt-ticks { animation: caseSpin 4.2s linear infinite reverse; transform-origin: 50% 50%; opacity: 0.75; }
.bt-case.heavy .bt-ring2 { animation-duration: 1.6s; }
.bt-case.heavy .bt-armour { animation: caseSpin 3.1s linear infinite reverse; transform-origin: 50% 50%; }
@keyframes caseSpin { to { rotate: 360deg; } }

/* The four chevrons that mean "this one you can hit". They breathe inward, so
   the swattable ordnance is the ordnance that looks like a target. */
.bt-case .bt-chev {
  stroke: var(--c);
  fill: none;
  transform-origin: 50% 50%;
  animation: chevPulse 0.72s ease-in-out infinite;
}
@keyframes chevPulse {
  0%, 100% { scale: 1; opacity: 0.55; }
  50% { scale: 0.86; opacity: 1; }
}

.bt-shot .bt-glyph {
  width: 52%;
  height: 52%;
  color: var(--c);
  filter: drop-shadow(0 0 8px var(--c)) drop-shadow(0 0 20px color-mix(in srgb, var(--c) 55%, transparent));
}
/* Epics and Mythics fly with a hot white core inside the colour. Rarity is
   spectacle — the same rule the rest of the game runs on. */
.bt-shot.t-epic .bt-glyph,
.bt-shot.t-mythic .bt-glyph { filter: drop-shadow(0 0 6px #fff) drop-shadow(0 0 26px var(--c)); }

.bt-shot.bt-spread { width: 96px; height: 96px; }
.bt-shot.landing { opacity: 0; transition: opacity 160ms ease-out; }

/* The tag left where a shot was knocked out of the air. */
.bt-swattag {
  position: absolute;
  translate: -50% -50%;
  color: var(--c);
  pointer-events: none;
  animation: swatTag 700ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
.bt-swattag b {
  font-family: var(--display);
  font-size: 1.05rem;
  letter-spacing: 0.12em;
  color: var(--hit);
  text-shadow: 0 0 12px var(--hit), 0 0 30px color-mix(in srgb, var(--hit) 60%, transparent);
}
@keyframes swatTag {
  0% { scale: 0.4; opacity: 0; }
  18% { scale: 1.25; opacity: 1; }
  32% { scale: 1; }
  100% { scale: 1.05; opacity: 0; translate: -50% -140%; }
}

/* The score physically changing seats during a SWAP. */
.bt-ghostscore {
  position: absolute;
  font-family: var(--mono);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: var(--c);
  text-shadow: 0 0 18px var(--c), 0 0 44px color-mix(in srgb, var(--c) 60%, transparent);
  pointer-events: none;
  white-space: nowrap;
}

/* The mark a landed weapon leaves on the pad it hit. */
.bt-stamp {
  position: absolute;
  translate: -50% -50%;
  width: clamp(3.4rem, 15vh, 7.6rem);
  height: clamp(3.4rem, 15vh, 7.6rem);
  color: var(--c);
  filter: drop-shadow(0 0 18px var(--c)) drop-shadow(0 0 44px color-mix(in srgb, var(--c) 60%, transparent));
  pointer-events: none;
}
.bt-stamp.t-epic,
.bt-stamp.t-mythic { width: clamp(4.6rem, 21vh, 11rem); height: clamp(4.6rem, 21vh, 11rem); }
/* Arming rises out of the rail, so it is smaller than a hit by design — you
   should never mistake your own reward for somebody else's attack. */
.bt-stamp.bt-arm { width: clamp(2.4rem, 9vh, 4.6rem); height: clamp(2.4rem, 9vh, 4.6rem); }
.bt-stamp.bt-arm.t-epic { width: clamp(3rem, 12vh, 6rem); height: clamp(3rem, 12vh, 6rem); }

/* HIT-STOP. The fighting-game beat: the picture stops and goes white for a few
   dozen milliseconds and the brain reads the gap as weight.

   The flash is its own element and the freeze is a transform on #stage — never
   a `filter` on an ancestor of .bt-layer, which would make that ancestor a
   containing block and drop every shot in the air onto the floor. */
.bt-flashframe {
  position: fixed;
  inset: 0;
  z-index: 96;
  pointer-events: none;
  background: var(--c, #fff);
  mix-blend-mode: screen;
  opacity: 0.42;
  animation: flashFrame 240ms ease-out forwards;
}
@keyframes flashFrame { to { opacity: 0; } }
html.bt-hitstop #stage { scale: 1.012; }
html.bt-hitstop .panel { animation-play-state: paused; }

/* The room holding its breath while an Epic climbs to the middle of it. */
html.bt-dread #stage { filter: brightness(0.52) saturate(0.7); transition: filter 460ms ease-out; }
html.bt-dread #stage .panel.armed { filter: none; }
#stage { transition: filter 320ms ease-out, scale 90ms ease-out; }

/* ---------- the lobby sheet ---------- */

/* A FRAMED sheet: fixed head, scrolling body, pinned foot.
   The lobby used to be one long scroll — seat count, keyboard map, all eight
   weapons, then START. On a phone that put the only button anybody needs below
   the fold, so starting a game began with a scroll past documentation. Now the
   count is the first thing and START is always the last thing, whatever the
   viewport does. */
.sheet-framed {
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden; /* the body scrolls, not the sheet */
}
.sheet-framed .sheet-head {
  flex: 0 0 auto;
  padding: 18px 18px 10px;
}
.sheet-framed .sheet-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 0 18px 4px;
  overscroll-behavior: contain;
}
.sheet-framed .sheet-foot {
  flex: 0 0 auto;
  padding: 12px 18px calc(12px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  background: var(--ground-2);
}
.sheet-framed .sheet-foot .sheet-actions { margin-top: 0; }
/* the body has more to show — say so instead of hoping they guess */
.sheet-framed .sheet-body::after {
  content: '';
  display: block;
  height: 2px;
}

/* Collapsibles. The arsenal and the keyboard map are reference material: worth
   having, not worth making everyone scroll past to press START. */
.fold {
  border: 1px solid var(--line);
  border-radius: 12px;
  margin: 0 0 8px;
  background: color-mix(in srgb, var(--ground) 55%, transparent);
  overflow: hidden;
}
.fold > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 11px 13px;
  cursor: pointer;
  list-style: none;
  font-family: var(--display);
  font-size: 0.62rem;
  letter-spacing: 0.12em;
  color: var(--ink);
}
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary::after {
  content: '▾';
  font-family: var(--mono);
  font-size: 0.7rem;
  color: var(--dim);
  transition: rotate 180ms ease;
}
.fold[open] > summary::after { rotate: 180deg; }
.fold > summary:hover { color: var(--gold); }
.fold .fold-tag {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 0.5rem;
  letter-spacing: 0.08em;
  color: var(--dim);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 2px 8px;
  white-space: nowrap;
}
.fold .fold-tag.ready { color: var(--hit); border-color: color-mix(in srgb, var(--hit) 55%, transparent); }
.fold-in { padding: 2px 13px 12px; }

/* ---------- per-seat sign-in ---------- */

/* One browser, one cookie — but up to six players. Seats 2-6 therefore hold a
   short-lived bearer token instead of a session cookie, which is also why only
   seat 1 can use Google: OAuth is a redirect and it lands in exactly one
   cookie. That constraint is stated on the row rather than left to be
   discovered. */
.seatrow {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 7px;
  padding: 7px 0;
  border-bottom: 1px solid var(--line);
}
.seatrow:last-child { border-bottom: 0; }
.seatrow .sdot {
  width: 22px;
  height: 22px;
  border-radius: 6px;
  display: grid;
  place-items: center;
  font-family: var(--display);
  font-size: 0.5rem;
  color: var(--sc);
  border: 1px solid color-mix(in srgb, var(--sc) 55%, transparent);
  background: color-mix(in srgb, var(--sc) 14%, transparent);
}
.seatrow .sfields {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
  min-width: 0;
}
.seatrow input {
  min-width: 0;
  width: 100%;
  font-family: var(--mono);
  font-size: 0.68rem;
  padding: 9px 10px;
  border-radius: 9px;
  border: 1px solid var(--line);
  background: var(--ground);
  color: var(--ink);
}
.seatrow input:focus { outline: none; border-color: var(--gold); }
.seatrow .sgo {
  font-family: var(--display);
  font-size: 0.54rem;
  letter-spacing: 0.08em;
  padding: 9px 11px;
  border-radius: 9px;
  border: 1px solid var(--line);
  background: var(--ground-3);
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
}
.seatrow .sgo:hover { border-color: var(--gold); color: var(--gold); }
.seatrow.in .sname {
  font-family: var(--display);
  font-size: 0.6rem;
  letter-spacing: 0.06em;
  color: var(--hit);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.seatrow .sout {
  font-family: var(--mono);
  font-size: 0.5rem;
  letter-spacing: 0.08em;
  padding: 7px 9px;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--dim);
  cursor: pointer;
}
.seatrow .sout:hover { color: var(--miss); border-color: var(--miss); }
.seatrow .serr {
  grid-column: 2 / -1;
  font-family: var(--mono);
  font-size: 0.52rem;
  color: var(--miss);
  letter-spacing: 0.04em;
}
.seatrow .sgoogle {
  grid-column: 2 / -1;
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--mono);
  font-size: 0.5rem;
  color: var(--dim);
  letter-spacing: 0.04em;
}
.seatrow .sgoogle button {
  font-family: var(--mono);
  font-size: 0.5rem;
  letter-spacing: 0.06em;
  padding: 6px 9px;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--ground-3);
  color: var(--ink);
  cursor: pointer;
}
.seatrow .sgoogle button:hover { border-color: var(--gold); color: var(--gold); }

/* ---------- which board ---------- */

.boardpick {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
  margin: 2px 0 8px;
}
.boardpick button {
  font-family: var(--display);
  font-size: 0.62rem;
  letter-spacing: 0.12em;
  padding: 10px 0;
  border-radius: 10px;
  border: 1px solid var(--line);
  background: var(--ground-3);
  color: var(--dim);
  cursor: pointer;
}
.boardpick button.on {
  color: var(--gold);
  border-color: var(--gold);
  background: color-mix(in srgb, var(--gold) 12%, var(--ground-3));
}
.lb-row .lb-wins {
  font-family: var(--mono);
  font-size: 0.54rem;
  color: var(--gold);
  letter-spacing: 0.06em;
  margin-left: 8px;
  white-space: nowrap;
}

/* Narrow phones: the seat row stacks so the fields stay tappable. */
@media (max-width: 430px) {
  .seatrow { grid-template-columns: auto 1fr; }
  .seatrow .sfields { grid-column: 2; grid-template-columns: 1fr; }
  .seatrow .sgo { grid-column: 2; justify-self: stretch; }
  .howmany button { font-size: 1.2rem; padding: 12px 0; }
}


.howmany {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 8px;
  margin: 4px 0 12px;
}
.howmany button {
  font-family: var(--display);
  font-size: 1.5rem;
  padding: 14px 0;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: var(--ground-2);
  color: var(--ink);
  cursor: pointer;
  transition: border-color 140ms, color 140ms, box-shadow 180ms;
}
.howmany button:hover { border-color: var(--gold); }
.howmany button.on {
  border-color: var(--gold);
  color: var(--gold);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--gold) 40%, transparent), 0 0 24px color-mix(in srgb, var(--gold) 35%, transparent);
}

.arsenal { display: flex; flex-direction: column; gap: 2px; margin: 2px 0 6px; }
.arsenal-row {
  display: grid;
  grid-template-columns: 30px 1fr auto;
  align-items: center;
  gap: 8px;
  padding: 5px 6px;
  border-bottom: 1px solid var(--line);
}
.arsenal-row .ag {
  width: 23px;
  height: 23px;
  margin: 0 auto;
  color: var(--wc, var(--ink));
  filter: drop-shadow(0 0 6px color-mix(in srgb, var(--wc, var(--ink)) 60%, transparent));
}
.arsenal-row .an { font-family: var(--display); font-size: 0.66rem; color: var(--wc, var(--ink)); }
.arsenal-row .ab { font-size: 0.58rem; color: var(--dim); letter-spacing: 0.04em; }
.arsenal-row .at {
  font-family: var(--mono);
  font-size: 0.52rem;
  color: var(--wc, var(--dim));
  border: 1px solid color-mix(in srgb, var(--wc, var(--line)) 50%, transparent);
  border-radius: 999px;
  padding: 2px 7px;
  white-space: nowrap;
}

/* The recap wraps once there are more than a duel's worth of columns. */
#recap-scores.many { flex-wrap: wrap; gap: 10px 16px; }
#recap-scores.many .recap-player { flex: 1 1 30%; min-width: 92px; }
#recap-scores.many .recap-player .rscore { font-size: 1.35rem; }
#recap-scores.many .recap-player .rsub { margin-top: 4px; }
#recap-scores.many .runstrip { height: 7px; }

/* The same crown that marked them all game, on the seat that survived it. */
.rcrown {
  display: inline-block;
  width: 0.9em;
  height: 0.9em;
  vertical-align: -0.1em;
  margin-left: 5px;
  color: var(--gold);
  filter: drop-shadow(0 0 6px color-mix(in srgb, var(--gold) 70%, transparent));
}

.warstat { color: var(--gold); }

/* ---------- reduced motion ---------- */

/* Everything above still READS with the motion switched off: the rail is still
   loaded, the crown is still on the leader, the pad is still browned out. Only
   the movement goes. */
@media (prefers-reduced-motion: reduce) {
  .bt-snow, .bt-hsync, .panel.incoming::after, .panel.incoming .cnr,
  .panel.armed .cnr, .weakbadge, .crown, .chargebar i, .bt-case .bt-ring2,
  .bt-case .bt-ticks, .bt-case .bt-chev, .bt-case.heavy .bt-armour,
  .sg .sg-spin, .sg .sg-pulse, .sg .sg-flick, .panel.bt-scrambled .number,
  .bt-reticle i { animation: none !important; }
  .bt-reticle { transition: none; }
  html.bt-dread #stage { filter: none; }
}
html[data-reduced] .bt-snow,
html[data-reduced] .bt-hsync,
html[data-reduced] .panel.incoming::after,
html[data-reduced] .panel.incoming .cnr,
html[data-reduced] .panel.armed .cnr,
html[data-reduced] .weakbadge,
html[data-reduced] .crown,
html[data-reduced] .chargebar i,
html[data-reduced] .bt-case .bt-ring2,
html[data-reduced] .bt-case .bt-ticks,
html[data-reduced] .bt-case .bt-chev,
html[data-reduced] .bt-case.heavy .bt-armour,
html[data-reduced] .sg .sg-spin,
html[data-reduced] .sg .sg-pulse,
html[data-reduced] .sg .sg-flick,
html[data-reduced] .panel.bt-scrambled .number,
html[data-reduced] .bt-reticle i { animation: none !important; }
html[data-reduced] .bt-reticle { transition: none; }
html[data-reduced].bt-dread #stage { filter: none; }

/* The keyboard, drawn as a keyboard, each key wearing the colour of the pad it
   drives. Explains the key banks by being a picture of them. */
.keymap {
  display: flex;
  flex-direction: column;
  gap: 3px;
  align-items: center;
  margin: 2px 0 10px;
}
.kmrow { display: flex; gap: 3px; }
.kmrow:nth-child(3) { padding-left: 10px; }
.kmrow:nth-child(4) { padding-left: 26px; }
.kmrow i {
  font-family: var(--mono);
  font-style: normal;
  font-size: 0.5rem;
  line-height: 1;
  text-transform: uppercase;
  width: 17px;
  height: 17px;
  display: grid;
  place-items: center;
  border-radius: 3px;
  color: var(--kc, var(--dim));
  border: 1px solid color-mix(in srgb, var(--kc, var(--line)) 55%, transparent);
  background: color-mix(in srgb, var(--kc, var(--line)) 15%, var(--ground-2));
}

/* ---------- LOW GRAPHICS ----------
   Battle is where this matters most: six pads means six of every ambient loop
   at once, plus projectiles in the air. See styles.css for the governing rule —
   remove cost, keep every signal. */

/* Ambient pulses. The charge bar still fills and the brackets are still the
   weapon's colour; they simply stop animating between events. */
html[data-gfx='low'] .panel.charged .chargebar i,
html[data-gfx='low'] .panel.armed .cnr,
html[data-gfx='low'] .weakbadge,
html[data-gfx='low'] .bt-shot .bt-halo {
  animation: none !important;
}

/* INCOMING is a warning, so it may not simply be switched off. The pulsing
   inset shadow becomes a solid outline: same message, one paint, no loop. */
html[data-gfx='low'] .panel.incoming {
  animation: none !important;
  outline: 3px solid var(--miss);
  outline-offset: -3px;
}

/* STATIC still blinds the pad — the scramble is the weapon — but the animated
   snow texture and the screen blend go. A flat wash reads the same and costs a
   single paint instead of one per frame. */
html[data-gfx='low'] .bt-snow {
  animation: none !important;
  background: rgba(255, 255, 255, 0.12);
  opacity: 1;
}
html[data-gfx='low'] .panel.bt-scrambled .number { color: var(--tier-uncommon); }

/* The projectile keeps its glyph and its ring — it has to stay a tappable
   target — but loses the spinning blurred halo behind it. */
html[data-gfx='low'] .bt-shot .bt-halo {
  border-style: solid;
  background: none;
}

/* DREAD is a telegraph, not decoration — the room going dark is how you know
   something big is on its way — so LOW may not simply drop it. But a
   brightness/saturate filter over the entire stage is one of the most expensive
   things this game can ask a weak GPU for. Opacity says the same thing, is
   composited rather than rasterized, and costs almost nothing. */
html[data-gfx='low'].bt-dread #stage {
  filter: none !important;
  opacity: 0.55;
  transition: opacity 460ms ease-out;
}
html[data-gfx='low'].bt-dread #stage .panel.armed { opacity: 1; }
