/* Shared DOM lobby styles. Source of truth: web/lobby/lobby.css - the copy inside a game's
   WebGL template is written by tools/sync-lobby.sh. Per-game colours arrive as --lobby-*
   custom properties set on <html> from LOBBY_CONFIG.palette (see lobby.js applyPalette).

   Self-hosted on purpose: no CDN, so the page still renders off Cloudflare static assets
   (or a warm cache) with no third-party request. Display faces only - body copy stays on the
   system stack, so nothing load-bearing waits on a font that may still be swapping in.
   (Press Start 2P was the wordmark face 2026-08-18 -> 2026-08-23; Unbounded replaced it for
   every game per user direction.) */
/* Unbounded, the astroball.io homepage's display face (variable 400-900, latin subset): THE
   display face for every game - wordmark, team labels, CTAs. Self-hosted like the rest; the
   file is the same one web/astroball-home ships. */
@font-face {
  font-family: "Unbounded";
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url("unbounded-latin.woff2") format("woff2");
}

/* The results screen's two faces, self-hosted for the same reason (latin subset, from Google
   Fonts' own woff2). Inter is the variable roll, so one file covers 500 -> 900; Montserrat is
   the static 900 italic and nothing else, because the display face is only ever used for the
   team names and the score numerals. Both are scoped to .lobby-results below - the rest of
   the lobby keeps the system stack, so no other phase waits on a font. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("Inter-latin.woff2") format("woff2");
}

@font-face {
  font-family: "Montserrat";
  font-style: italic;
  font-weight: 900;
  font-display: swap;
  src: url("Montserrat-Italic900-latin.woff2") format("woff2");
}

:root {
  --lobby-bg: #04060d;
  --lobby-panel: rgba(18, 19, 30, 0.72);
  --lobby-panel-border: rgba(255, 255, 255, 0.08);
  /* ONE house theme for every game (user direction 2026-08-23): the lobby always wears the
     astroball.io colours - accent, button gradient, backdrop. Games no longer set palette
     colour overrides (the mechanism still works; the 4v4 mock preset exercises it). */
  --lobby-accent: #a34bff;
  --lobby-danger: #ff6b6b;
  --lobby-text: #f4f6ff;
  --lobby-text-dim: #9aa3c7;
  --lobby-radius: 16px;
  --lobby-display: "Unbounded", "Arial Black", -apple-system, sans-serif;
  /* The results screen's own pair (S1 "Nebula Halves"): a heavy italic display face for the
     team names and the score numerals, Inter for everything else on that screen. */
  --lobby-results-display: "Montserrat", "Inter", -apple-system, Helvetica, Arial, sans-serif;
  --lobby-results-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  /* The plain terminal panel's message slot (contract addendum v1.4, kept in v1.5). It is a
     MIN height: without an `endgame` the panel has only this line and the Play again button,
     so nothing below it can move when a longer message arrives. */
  --lobby-ended-headline-h: 92px;
  /* Width of the results screen's centre column - the score tiles, the team totals and the
     award cards all live in it, and the two team halves take exactly half the rest each. */
  --lobby-results-centre-w: clamp(232px, 23vw, 420px);
  /* One source of truth for the title size: .lobby-title derives its fixed height from
     it, so the two can never drift apart at a breakpoint. */
  --lobby-title-size: clamp(32px, 6vw, 48px);
  /* ---- the two-column pre-game shell (A1) ----
     The shell is one width for every pre-game phase, the same way the old 520px panel was:
     the header and the two columns never move between boot, idle, connecting and lobby. The
     right column alone is the old panel's job, so --lobby-main-w is what a single-column
     layout (a game with nothing to say in the left column) falls back to. */
  --lobby-shell-w: 1180px;
  --lobby-main-w: 520px;
  --lobby-col-gap: clamp(24px, 3vw, 44px);
  /* A team column's own geometry, shared by the panel, its slots and the drag states. */
  --lobby-team-radius: 22px;
  --lobby-card-radius: 14px;
  --lobby-card-h: clamp(50px, 7vh, 60px);
  /* Primary action (.lobby-cta): the homepage's .play pill, and its scaled-up variant. */
  --lobby-cta-h: 52px;
  --lobby-cta-big-h: clamp(58px, 9vh, 72px);
  --lobby-cta-ink: #06070c;
  /* The astroball.io homepage button component: thick face + a gradient pair. A game that
     wants a single-colour pill just leaves the pair at its accent default. */
  --lobby-cta-font: var(--lobby-display);
  /* The astroball.io homepage's PLAY NOW gradient (red -> violet) - the one button look,
     every game, every screen. */
  --lobby-cta-grad-a: #ff2e3e;
  --lobby-cta-grad-b: #784bff;
  /* Weight for --lobby-display text (title, team labels). Unbounded is a variable face, so
     the thick homepage weight is the default; a single-weight face overrides via palette. */
  --lobby-display-weight: 900;
  /* Whole overlay backdrop as one overridable value: near-black base with the astroball.io
     red/blue glows - the house atmosphere, same on every game, light text well clear of AA.
     Static - nothing here animates, so there is nothing for prefers-reduced-motion to turn
     off. Overridable via LOBBY_CONFIG.palette ({ backdrop: "..." }) like every token, though
     no shipped game does. */
  --lobby-backdrop:
    radial-gradient(58% 42% at 15% 12%, rgba(229, 26, 26, 0.2), transparent 70%),
    radial-gradient(56% 42% at 85% 14%, rgba(0, 102, 255, 0.22), transparent 70%),
    radial-gradient(70% 50% at 50% 88%, rgba(0, 102, 255, 0.1), transparent 74%),
    var(--lobby-bg);
}

/* `align-items: flex-start` IS the composition: the shell is top-anchored in every phase
   (see .lobby-panel), and a shell taller than the viewport starts at the top and scrolls. */
#lobby-overlay {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  /* Header breathing room trimmed by a third top and bottom (user direction 2026-08-23) -
     the saved height goes to the content below. */
  padding: clamp(13px, 3vh, 27px) clamp(20px, 4vw, 56px) 40px;
  box-sizing: border-box;
  overflow-y: auto;
  background: var(--lobby-backdrop);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  color: var(--lobby-text);
}

#lobby-overlay * {
  box-sizing: border-box;
}

#lobby-overlay.lobby-hidden {
  display: none;
}

/* Minimal veil mode: host-unavailable banner during "playing", game stays visible */
#lobby-overlay.lobby-veil-mode {
  background: transparent;
  padding-top: 24px;
  pointer-events: none;
}

.lobby-veil-banner {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 10px;
  background: rgba(5, 6, 11, 0.9);
  border: 1px solid var(--lobby-panel-border);
  border-radius: 999px;
  padding: 10px 20px;
  /* No backdrop-filter here: blurring over the live WebGL canvas forces a per-frame
     compositor readback of the game's output. Opaque-ish background instead. */
}

/* One width for every phase, so the shell's edges never jump either.

   Top-anchored ALWAYS (user direction 2026-08-23, superseding the brief centred period): the
   overlay's own align-items: flex-start + padding-top clamp place the shell, so every phase
   starts from the same line and content that arrives later extends downward - no vertical
   re-composition between screens, same rule the original 520px panel shipped with. */
.lobby-panel {
  width: 100%;
  max-width: var(--lobby-shell-w);
  display: flex;
  flex-direction: column;
  gap: clamp(15px, 3vh, 32px);
}

/* Header band: the wordmark centred on the shell, the HOME pill pinned to its left. The pill
   is taken out of flow only once there is room beside the title for it; below that it sits
   above the title in normal flow, so it can never overlap the wordmark. */
.lobby-topbar {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.lobby-home {
  align-self: flex-start;
  margin-bottom: 12px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  padding: 0 16px 0 12px;
  border-radius: 999px;
  border: 1px solid var(--lobby-panel-border);
  background: rgba(255, 255, 255, 0.05);
  color: var(--lobby-text);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  transition: background 0.15s ease, border-color 0.15s ease;
}

.lobby-home:hover {
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.28);
}

.lobby-home svg {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
}

.lobby-head {
  text-align: center;
  max-width: 100%;
  min-width: 0;
}

/* The two columns. Left = what the game is (video + how to play), right = what you do next.
   `.lobby-columns-single` is the fallback for a page that supplied neither a video nor any
   HOW TO PLAY copy: the right column alone, centred at the old panel width. */
/* Centred, not top-aligned: the two columns are independent of each other's length - the left
   one is the same height in every phase, the right one is two fields on the entry screen and a
   full roster in the lobby - so anchoring both to the top stranded a quadrant of empty space
   beside whichever was shorter. Centring reads as one composition at every roster size. */
.lobby-columns {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--lobby-col-gap);
  /* Top-aligned on purpose (2026-08-23): the two columns read from the same start line in
     every phase, and the shell itself is top-anchored in the viewport (see .lobby-panel). */
  align-items: start;
}

.lobby-columns-single {
  grid-template-columns: minmax(0, var(--lobby-main-w));
  justify-content: center;
}

.lobby-aside {
  display: flex;
  flex-direction: column;
  gap: clamp(18px, 2.6vh, 26px);
  min-width: 0;
}

/* The left column's 16:9 anchor: LOBBY_CONFIG.gameplayVideo as a <video>, else
   LOBBY_CONFIG.gameplayImage as an <img>. One box either way, so the column composes the
   same whether a game has a loop yet or only a still. A page that supplies neither gets no
   box at all - there is deliberately no placeholder. */
.lobby-media {
  width: 100%;
  aspect-ratio: 16 / 9;
  display: block;
  border-radius: 18px;
  border: 1px solid var(--lobby-panel-border);
  background: #000;
  object-fit: cover;
}

@supports (color: color-mix(in srgb, red 50%, white)) {
  .lobby-media {
    border-color: color-mix(in srgb, var(--lobby-accent) 30%, transparent);
    box-shadow: 0 0 40px color-mix(in srgb, var(--lobby-accent) 14%, transparent),
      0 24px 60px rgba(0, 0, 0, 0.45);
  }
}

.lobby-body {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
}

/* Fixed HEIGHT, not merely a fixed line-height: an inline-block reports the baseline
   the FONT's own ascent/descent put it at, so a face swap could still nudge the line box
   it sits in. A flex box of an explicit height, centring one nowrap line, cannot - every
   face draws inside the same slot and nothing below the title moves. Glyphs that overshoot
   the slot (Monoton, Shrikhand) overflow it visually, which is exactly what we want.
   Width is fit-content so the click target is the wordmark, not the whole row. */
.lobby-title {
  font-family: var(--lobby-display);
  font-weight: var(--lobby-display-weight);
  /* Unbounded 900 still runs wide; 0.8x of the base clamp keeps the wordmark inside the
     shell at every breakpoint (the sizing the pixel face shipped with, kept on purpose). */
  font-size: calc(var(--lobby-title-size) * 0.8);
  display: flex;
  align-items: center;
  justify-content: center;
  width: fit-content;
  max-width: 100%;
  margin: 0 auto;
  height: calc(var(--lobby-title-size) * 1.35);
  line-height: 1;
  white-space: nowrap;
  letter-spacing: 0.005em;

  /* The one loud element on the screen. Solid white per user direction (2026-08-18) - the
     rainbow lives in the background glows, not the glyphs - lit by the same accent halo the
     homepage's h1 wears, so the lobby's wordmark and the picker's read as one family. The
     flat rgba below is the fallback for an engine without color-mix(). */
  color: #fff;
  text-shadow: 0 0 26px rgba(255, 255, 255, 0.4);
}

@supports (color: color-mix(in srgb, red 50%, white)) {
  .lobby-title {
    text-shadow: 0 0 26px color-mix(in srgb, var(--lobby-accent) 55%, transparent);
  }
}

.lobby-title-sm {
  font-size: clamp(17px, 3vw, 21px);
  font-weight: 700;
  margin: 0;
  text-wrap: pretty;
}

/* Reserved height: the subtitle text changes per phase, the box never does. */
.lobby-subtitle {
  font-size: 15px;
  line-height: 20px;
  min-height: 20px;
  margin: 8px 0 0;
  color: var(--lobby-text-dim);
  text-wrap: pretty;
}

/* Same deal for every status/error/hint line under a control. */
.lobby-status {
  font-size: 14px;
  line-height: 20px;
  min-height: 20px;
  margin: 0;
  color: var(--lobby-text-dim);
  text-align: center;
  text-wrap: pretty;
}

.lobby-status-error {
  color: var(--lobby-danger);
  font-weight: 600;
}

.lobby-field {
  display: flex;
  flex-direction: column;
  gap: 8px;
  text-align: left;
}

.lobby-label {
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--lobby-text-dim);
}

.lobby-input {
  width: 100%;
  height: var(--lobby-cta-h);
  padding: 0 22px;
  border-radius: 999px;
  border: 1px solid var(--lobby-panel-border);
  background: rgba(255, 255, 255, 0.06);
  color: var(--lobby-text);
  font-size: 17px;
  font-weight: 600;
  font-family: inherit;
}

.lobby-input::placeholder {
  color: var(--lobby-text-dim);
  font-weight: 500;
}

.lobby-input:focus {
  outline: 2px solid var(--lobby-accent);
  outline-offset: 1px;
  background: rgba(255, 255, 255, 0.1);
}

.lobby-btn {
  font-size: 17px;
  line-height: 22px;
  font-weight: 700;
  padding: 14px 24px;
  border-radius: 999px;
  border: none;
  cursor: pointer;
  font-family: inherit;
  transition: transform 0.15s ease, background 0.15s ease, opacity 0.15s ease;
}

.lobby-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.lobby-btn-secondary {
  background: rgba(255, 255, 255, 0.08);
  color: var(--lobby-text);
}

.lobby-btn-secondary:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.16);
}

/* ---- .lobby-cta: THE primary action, one component ----

   Every primary action in the lobby is this button - Create Game, Join Game, Start, Play again
   - and it is the homepage's `.play` pill (web/home/index.html): the pixel display face, a
   SOLID accent fill, near-black ink, fully rounded, hover = brightness only. Nothing here
   names a colour: --lobby-accent is the single per-game knob, so a game changes every primary
   button on every screen by setting one palette key. `.lobby-cta-big` is the same component at
   the size the lobby's final action (Start) wants. */
.lobby-cta {
  width: 100%;
  min-height: var(--lobby-cta-h);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 12px 22px;
  border: none;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--lobby-cta-grad-a), var(--lobby-cta-grad-b));
  color: #ffffff;
  font-family: var(--lobby-cta-font);
  font-weight: 900;
  font-size: 12px;
  line-height: 1.5;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-align: center;
  cursor: pointer;
  box-shadow: 0 0 24px color-mix(in srgb, var(--lobby-cta-grad-a) 50%, transparent);
  transition: transform 0.15s ease, filter 0.18s ease;
}

.lobby-cta:hover:not(:disabled) {
  transform: scale(1.02);
  filter: brightness(1.08);
}

/* Disabled is where the lobby writes WHY an action is blocked (the label itself), so it must
   stay readable - dimmed, never hidden, and with no hover affordance. */
.lobby-cta:disabled {
  opacity: 0.45;
  filter: none;
  cursor: not-allowed;
  box-shadow: none;
  animation: none;
}

.lobby-cta-big {
  min-height: var(--lobby-cta-big-h);
  font-size: clamp(15px, 1.5vw, 20px);
  letter-spacing: 0.06em;
}

/* The homepage's btnGlow pulse, generalised to the gradient pair. Enabled only on the big
   (final-action) pill, and the one animated thing in the lobby - so it is the one thing
   prefers-reduced-motion has to turn off. */
.lobby-cta-big:not(:disabled) {
  animation: lobby-btn-glow 2s ease-in-out infinite;
}

@keyframes lobby-btn-glow {
  0%,
  100% {
    box-shadow: 0 0 24px color-mix(in srgb, var(--lobby-cta-grad-a) 55%, transparent), 0 0 70px color-mix(in srgb, var(--lobby-cta-grad-b) 35%, transparent);
  }
  50% {
    box-shadow: 0 0 46px color-mix(in srgb, var(--lobby-cta-grad-a) 85%, transparent), 0 0 110px color-mix(in srgb, var(--lobby-cta-grad-b) 60%, transparent);
  }
}

@media (prefers-reduced-motion: reduce) {
  .lobby-cta-big:not(:disabled) {
    animation: none;
  }
}

.lobby-cta svg {
  width: 1.15em;
  height: 1.15em;
  flex: 0 0 auto;
}

/* Connecting phase: the body stretches to the grid row (the left column sets its height)
   so the spinner + status block can sit in the true middle of the right side instead of
   hanging at the top. */
.lobby-body-center {
  align-self: stretch;
  justify-content: center;
  align-items: center;
}

.lobby-center-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: 8px 0;
  text-align: center;
}

/* ---------- "ended": the results screen (contract addenda v1.5 -> v1.8) ----------

   Two shapes. Without an `endgame` the terminal state is the plain panel every other phase
   uses - one message line and the Play again button, nothing reserved for content that
   cannot arrive. With one, the viewport itself becomes the scoreboard: below.

   v1.8 layout. One fixed grid - half | centre column | half - with the two team colours
   painted underneath as an exact 50/50 split. Across the top runs a compact scoreboard
   strip: each half carries its team name and its score as one big numeral, and the centre
   column carries the paired team-total tiles level with the numerals. Below the strip comes
   a tall middle band, top-aligned across all three columns: the two player tables flanking
   the award stack. The chip sits at the bottom of the centre column; a gold banner is pinned
   across the winning half's top outer corner. Every slot is sized off vw/vh clamps, so the
   screen holds its proportions from a 1280x720 laptop to a 2560-wide desktop. The four
   custom properties on .lobby-results are the shared vertical rhythm: the three columns each
   stack name-h + score-h (+ strip-gap) before their band content, which is what lines the
   tables, the totals and the cards up across the divider. */
.lobby-ended-message {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--lobby-ended-headline-h);
  /* A terminal message is one short line; clipping a pathological one is preferable to
     letting it push the button down and break the shell. */
  overflow: hidden;
  text-align: center;
  font-family: var(--lobby-display);
  font-weight: var(--lobby-display-weight);
  letter-spacing: 0.01em;
}

/* The overlay stops being a centred column of panel and becomes the screen. */
#lobby-overlay.lobby-results-mode {
  padding: 0;
  overflow: hidden;
  background: var(--lobby-bg);
  align-items: stretch;
  justify-content: stretch;
}

.lobby-results {
  position: relative;
  width: 100%;
  height: 100%;
  /* Scoped here on purpose: only this screen uses Inter, so no earlier phase can be caught
     mid-swap by a face it does not need. */
  font-family: var(--lobby-results-body);
  /* Clips the WINNER banner's off-screen overhang HERE, so the overlay's own scroll size
     never grows past the viewport (the screen is fixed; nothing on it may scroll). */
  overflow: hidden;
  display: grid;
  /* The centre column is the fixed part; the two halves take whatever is left, which keeps
     them symmetrical and the column exactly on the divider. */
  grid-template-columns: 1fr var(--lobby-results-centre-w) 1fr;
  align-items: stretch;
  /* Shared vertical rhythm (see the block comment above). */
  --lobby-results-name-h: clamp(48px, 9.25vh, 96px);
  --lobby-results-score-h: clamp(80px, 17.5vh, 200px);
  --lobby-results-strip-gap: clamp(20px, 17vh, 150px);
  --lobby-results-row-h: clamp(34px, 7.5vh, 64px);
  --lobby-results-card-h: clamp(44px, 8.5vh, 72px);
  /* A half's side padding: the outer edge gets the wider margin, the divider side the
     narrower one. Named because the name/score centring below has to account for them. */
  --lobby-results-pad-outer: clamp(24px, 3.1vw, 56px);
  --lobby-results-pad-inner: clamp(12px, 1.6vw, 28px);
}

.lobby-results-backdrop {
  position: absolute;
  inset: 0;
  display: flex;
}

/* S1 "Nebula Halves". Each half is still its own team colour - never a fade to black - but
   under four layers that turn a flat slab into deep space: a vertical black wash (40% -> 52%,
   which is what buys the white text its contrast), a horizontal darkening that runs INTO the
   centre line so the seam reads as the brightest thing on the screen, a soft bloom of the
   team colour lightened toward white in the upper third, and a faint white haze at the very
   bottom. The bloom and the haze are mirrored per side, hence the two rules below.

   The plain declaration comes first every time: a browser without color-mix() drops only the
   layered one and keeps a correctly-coloured, correctly-washed half. */
.lobby-results-half {
  flex: 1 1 50%;
  --lobby-half-bloom: #ffffff;
  --lobby-half-bloom: color-mix(in srgb, var(--lobby-results-side, #232838) 45%, #ffffff);
  background:
    linear-gradient(180deg, rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.52)),
    var(--lobby-results-side, #232838);
}

.lobby-results-half-left {
  background:
    radial-gradient(55% 60% at 32% 22%, color-mix(in srgb, var(--lobby-half-bloom) 31%, transparent), transparent 70%),
    radial-gradient(70% 30% at 35% 106%, rgba(255, 255, 255, 0.1), transparent 70%),
    linear-gradient(90deg, rgba(0, 0, 0, 0) 40%, rgba(0, 0, 0, 0.36) 100%),
    linear-gradient(180deg, rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.52)),
    var(--lobby-results-side, #232838);
}

.lobby-results-half-right {
  background:
    radial-gradient(55% 60% at 68% 22%, color-mix(in srgb, var(--lobby-half-bloom) 31%, transparent), transparent 70%),
    radial-gradient(70% 30% at 65% 106%, rgba(255, 255, 255, 0.1), transparent 70%),
    linear-gradient(270deg, rgba(0, 0, 0, 0) 40%, rgba(0, 0, 0, 0.36) 100%),
    linear-gradient(180deg, rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.52)),
    var(--lobby-results-side, #232838);
}

/* The star field. One layer across BOTH halves so no seam of its own lands on the centre
   line, and hand-placed rather than tiled - a repeating pattern reads as a texture, a fixed
   scatter reads as a sky. Static: nothing here animates. */
.lobby-results-stars {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(1.6px 1.6px at 42% 20%, rgba(255, 255, 255, 0.9), transparent 70%),
    radial-gradient(0.8px 0.8px at 7% 10%, rgba(255, 255, 255, 0.7), transparent 70%),
    radial-gradient(1px 1px at 75% 8%, rgba(255, 255, 255, 0.55), transparent 70%),
    radial-gradient(1.6px 1.6px at 12% 56%, rgba(255, 255, 255, 0.55), transparent 70%),
    radial-gradient(1.6px 1.6px at 31% 12%, rgba(255, 255, 255, 0.55), transparent 70%),
    radial-gradient(1px 1px at 73% 16%, rgba(255, 255, 255, 0.9), transparent 70%),
    radial-gradient(0.8px 0.8px at 81% 75%, rgba(255, 255, 255, 0.9), transparent 70%),
    radial-gradient(0.8px 0.8px at 75% 51%, rgba(255, 255, 255, 0.55), transparent 70%),
    radial-gradient(1px 1px at 6% 72%, rgba(255, 255, 255, 0.7), transparent 70%),
    radial-gradient(0.8px 0.8px at 54% 19%, rgba(255, 255, 255, 0.9), transparent 70%),
    radial-gradient(1px 1px at 40% 72%, rgba(255, 255, 255, 0.55), transparent 70%),
    radial-gradient(1px 1px at 75% 74%, rgba(255, 255, 255, 0.7), transparent 70%),
    radial-gradient(0.8px 0.8px at 13% 71%, rgba(255, 255, 255, 0.9), transparent 70%),
    radial-gradient(1px 1px at 8% 80%, rgba(255, 255, 255, 0.7), transparent 70%),
    radial-gradient(1.6px 1.6px at 88% 69%, rgba(255, 255, 255, 0.7), transparent 70%),
    radial-gradient(1.6px 1.6px at 60% 75%, rgba(255, 255, 255, 0.7), transparent 70%),
    radial-gradient(1px 1px at 39% 32%, rgba(255, 255, 255, 0.9), transparent 70%),
    radial-gradient(1.2px 1.2px at 32% 11%, rgba(255, 255, 255, 0.9), transparent 70%),
    radial-gradient(1.6px 1.6px at 64% 44%, rgba(255, 255, 255, 0.7), transparent 70%),
    radial-gradient(0.8px 0.8px at 78% 10%, rgba(255, 255, 255, 0.9), transparent 70%),
    radial-gradient(1.2px 1.2px at 54% 22%, rgba(255, 255, 255, 0.55), transparent 70%),
    radial-gradient(0.8px 0.8px at 63% 54%, rgba(255, 255, 255, 0.9), transparent 70%),
    radial-gradient(1.2px 1.2px at 10% 98%, rgba(255, 255, 255, 0.7), transparent 70%),
    radial-gradient(1.6px 1.6px at 89% 45%, rgba(255, 255, 255, 0.9), transparent 70%),
    radial-gradient(0.8px 0.8px at 59% 9%, rgba(255, 255, 255, 0.7), transparent 70%),
    radial-gradient(0.8px 0.8px at 61% 90%, rgba(255, 255, 255, 0.55), transparent 70%),
    radial-gradient(1.2px 1.2px at 94% 90%, rgba(255, 255, 255, 0.9), transparent 70%),
    radial-gradient(1.6px 1.6px at 74% 88%, rgba(255, 255, 255, 0.7), transparent 70%),
    radial-gradient(1.2px 1.2px at 92% 50%, rgba(255, 255, 255, 0.55), transparent 70%),
    radial-gradient(1px 1px at 60% 46%, rgba(255, 255, 255, 0.9), transparent 70%),
    radial-gradient(0.8px 0.8px at 15% 64%, rgba(255, 255, 255, 0.55), transparent 70%),
    radial-gradient(1px 1px at 99% 37%, rgba(255, 255, 255, 0.9), transparent 70%),
    radial-gradient(1.6px 1.6px at 32% 51%, rgba(255, 255, 255, 0.7), transparent 70%),
    radial-gradient(1.6px 1.6px at 11% 22%, rgba(255, 255, 255, 0.7), transparent 70%),
    radial-gradient(1px 1px at 71% 36%, rgba(255, 255, 255, 0.7), transparent 70%),
    radial-gradient(1.6px 1.6px at 71% 36%, rgba(255, 255, 255, 0.7), transparent 70%),
    radial-gradient(1px 1px at 88% 49%, rgba(255, 255, 255, 0.55), transparent 70%),
    radial-gradient(1px 1px at 11% 23%, rgba(255, 255, 255, 0.55), transparent 70%),
    radial-gradient(0.8px 0.8px at 85% 30%, rgba(255, 255, 255, 0.7), transparent 70%),
    radial-gradient(1.2px 1.2px at 76% 24%, rgba(255, 255, 255, 0.7), transparent 70%),
    radial-gradient(1.6px 1.6px at 1% 19%, rgba(255, 255, 255, 0.9), transparent 70%),
    radial-gradient(1.2px 1.2px at 48% 79%, rgba(255, 255, 255, 0.55), transparent 70%),
    radial-gradient(0.8px 0.8px at 89% 66%, rgba(255, 255, 255, 0.7), transparent 70%),
    radial-gradient(1.6px 1.6px at 88% 72%, rgba(255, 255, 255, 0.7), transparent 70%),
    radial-gradient(0.8px 0.8px at 52% 51%, rgba(255, 255, 255, 0.7), transparent 70%),
    radial-gradient(0.8px 0.8px at 82% 52%, rgba(255, 255, 255, 0.55), transparent 70%);
}

/* ---- the gold WINNER frame ---- */

/* Four gold edges around the winning half. Its own layer rather than a border on the half,
   because a border there would eat into the exact 50/50 split; its inner edge sits ON the
   centre line, which is why the screen carries no divider rule of its own. Above the halves,
   below the centre column - the totals rows and the award cards straddle the seam and have
   to sit on top of it. */
.lobby-results-frame {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 50%;
  z-index: 2;
  pointer-events: none;
  /* 4px at 1280, up to 6 on a wide desktop. A border-width is floored to whole device pixels,
     so the low end is stated as 4px rather than a vw that rounds down to 3. */
  border: clamp(4px, 0.31vw, 6px) solid #f0bd3f;
  box-shadow: 0 0 26px rgba(245, 197, 66, 0.5);
}

.lobby-results-frame-left {
  left: 0;
}

.lobby-results-frame-right {
  right: 0;
}

/* ---- the WINNER banner ---- */

/* Gold ribbon pinned across the winning half's top OUTER corner, rotated 40deg and hanging
   past the edge on both ends so only the middle of the band is on screen. Sized off one
   width so the offsets keep their proportion at every viewport. The only asymmetry on the
   screen and the only colour that belongs to neither side. */
.lobby-results-winner {
  --lobby-banner-w: clamp(240px, 28vw, 420px);
  position: absolute;
  z-index: 3;
  top: calc(var(--lobby-banner-w) * 0.155);
  width: var(--lobby-banner-w);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6em;
  padding: clamp(7px, 1.2vh, 12px) 0;
  background: linear-gradient(180deg, #ffeeb6, #efbc46);
  color: #4a3406;
  font-weight: 900;
  font-size: clamp(10px, 1vw, 16px);
  letter-spacing: 0.3em;
  white-space: nowrap;
  box-shadow: 0 0 34px rgba(245, 197, 66, 0.55), 0 10px 28px rgba(0, 0, 0, 0.45);
}

.lobby-results-winner svg {
  width: 1.25em;
  height: 1.25em;
  flex: 0 0 auto;
}

.lobby-results-winner-right {
  right: calc(var(--lobby-banner-w) * -0.255);
  transform: rotate(40deg);
}

.lobby-results-winner-left {
  left: calc(var(--lobby-banner-w) * -0.255);
  transform: rotate(-40deg);
}

/* ---- a half: team name, score numeral, then the table ---- */

.lobby-results-team {
  position: relative;
  z-index: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  padding: 0 var(--lobby-results-pad-inner) clamp(16px, 3vh, 40px) var(--lobby-results-pad-outer);
}

.lobby-results-team-right {
  padding: 0 var(--lobby-results-pad-outer) clamp(16px, 3vh, 40px) var(--lobby-results-pad-inner);
}

/* The name and the numeral are centred on the HALF, not on the team column (which stops at
   the centre column's edge): a margin on the divider-facing side, sized so the slot's centre
   lands on the half's centre. A margin rather than a transform, because the entrance
   animates transform on these two elements. */
.lobby-results-team-left .lobby-results-teamname,
.lobby-results-team-left .lobby-results-score {
  margin-left: calc(var(--lobby-results-centre-w) / 2 - var(--lobby-results-pad-outer) + var(--lobby-results-pad-inner));
}

.lobby-results-team-right .lobby-results-teamname,
.lobby-results-team-right .lobby-results-score {
  margin-right: calc(var(--lobby-results-centre-w) / 2 - var(--lobby-results-pad-outer) + var(--lobby-results-pad-inner));
}

/* Display face, sat at the bottom of its fixed-height slot so the numeral below lands at the
   same y on both halves whatever the name. One ellipsised line.

   The fill is a gradient of the team's OWN colour - a near-white tint at the cap line falling
   to the colour itself at the baseline - lit by a drop-shadow in the same colour. Both stops
   are mixed from --lobby-results-side, so the rule names no game. The flat white `color`
   above the gradient is the fallback: without color-mix() the gradient declaration is dropped
   whole and the name stays plain white, never invisible. */
.lobby-results-teamname {
  height: var(--lobby-results-name-h);
  padding-top: calc(var(--lobby-results-name-h) - 1em - clamp(8px, 1.5vh, 14px));
  font-family: var(--lobby-results-display);
  font-weight: 900;
  font-style: italic;
  font-size: clamp(14px, 2.35vw, 34px);
  letter-spacing: 0.02em;
  line-height: 1;
  text-align: center;
  text-transform: uppercase;
  color: #ffffff;
  text-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

@supports ((color: color-mix(in srgb, red 50%, white)) and ((-webkit-background-clip: text) or (background-clip: text))) {
  .lobby-results-teamname {
    background: linear-gradient(
      180deg,
      color-mix(in srgb, var(--lobby-results-side, #232838) 12%, #ffffff) 0%,
      color-mix(in srgb, var(--lobby-results-side, #232838) 70%, #ffffff) 100%
    );
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    text-shadow: none;
    filter:
      drop-shadow(0 0 14px color-mix(in srgb, var(--lobby-results-side, #232838) 50%, transparent))
      drop-shadow(0 6px 18px rgba(0, 0, 0, 0.45));
  }
}

/* The side's score: one big numeral in the heavy italic display face, dead centre on its
   half, sized off the slot it lives in so it can never outgrow it. White falling to a cool
   violet-white; the winner's the same shape in gold, haloed. The flat `color` before each
   gradient is the no-background-clip fallback. */
.lobby-results-score {
  height: var(--lobby-results-score-h);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--lobby-results-display);
  font-style: italic;
  font-size: min(var(--lobby-results-score-h), 11vw);
  font-weight: 900;
  letter-spacing: -0.02em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: rgba(255, 255, 255, 0.94);
  text-shadow: 0 12px 40px rgba(0, 0, 0, 0.45);
  white-space: nowrap;
  overflow: hidden;
}

.lobby-results-score-win {
  color: #ffe9a8;
  text-shadow: 0 12px 40px rgba(0, 0, 0, 0.45), 0 0 44px rgba(255, 213, 108, 0.4);
}

/* Guarded: an engine without background-clip:text keeps the flat fills above rather than a
   transparent numeral over nothing. */
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .lobby-results-score {
    background: linear-gradient(180deg, #ffffff 0%, #cfd6ff 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    text-shadow: none;
    filter: drop-shadow(0 14px 36px rgba(0, 0, 0, 0.6));
  }

  /* background-IMAGE, not the shorthand: `background` would reset background-clip back to
     border-box and paint the numeral's whole box gold. */
  .lobby-results-score-win {
    background-image: linear-gradient(180deg, #fff6cf 0%, #f3c349 100%);
    color: transparent;
    text-shadow: none;
    filter: drop-shadow(0 0 34px rgba(245, 197, 66, 0.45)) drop-shadow(0 14px 36px rgba(0, 0, 0, 0.6));
  }
}

.lobby-results-table {
  margin-top: var(--lobby-results-strip-gap);
  display: flex;
  flex-direction: column;
  gap: clamp(4px, 0.75vh, 8px);
}

/* One row = one grid, so the two halves' columns line up with each other across the screen
   even though they are two independent tables. */
.lobby-results-row {
  display: grid;
  grid-template-columns: clamp(18px, 2vw, 34px) minmax(0, 1fr) clamp(44px, 4.6vw, 80px) clamp(44px, 4.6vw, 80px) clamp(44px, 4.6vw, 80px);
  align-items: center;
  gap: clamp(6px, 0.75vw, 14px);
  height: var(--lobby-results-row-h);
  padding: 0 clamp(10px, 1.1vw, 18px);
  border-radius: 12px;
  /* Barely-there glass. The half beneath is now a dark nebula, so a row needs only the
     faintest lift to separate - the v1.8 tint would band the whole column. */
  border: 1px solid rgba(255, 255, 255, 0.08);
  background: rgba(255, 255, 255, 0.05);
  font-size: clamp(12px, 1.2vw, 18px);
  font-weight: 700;
  color: #ffffff;
}

/* v1.7: a sender with four stat columns (endgame.columns) gets slightly narrower numeric
   tracks so the name keeps its share of the row. Widths only - the row height is fixed. */
.lobby-results-table[data-cols="4"] .lobby-results-row {
  grid-template-columns: clamp(18px, 2vw, 34px) minmax(0, 1fr) clamp(36px, 3.6vw, 62px) clamp(36px, 3.6vw, 62px) clamp(36px, 3.6vw, 62px) clamp(40px, 4vw, 70px);
}

/* The header is the same grid with no pill behind it, so the labels sit exactly over their
   columns. Labels render in caps whatever the sender wrote (the DOM text is untouched, so a
   legacy "Passes" still reads PASSES and a payload label keeps its own spelling underneath). */
.lobby-results-head {
  height: clamp(16px, 2.75vh, 26px);
  background: none;
  border-color: transparent;
  color: rgba(255, 255, 255, 0.5);
  font-size: clamp(8px, 0.72vw, 12px);
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* You. A light rule and a lit box, not a different colour: the row still belongs to its
   team - it is just the one the reader is looking for. */
.lobby-results-row-self {
  border-color: rgba(255, 255, 255, 0.9);
  background: rgba(255, 255, 255, 0.16);
  box-shadow: 0 0 18px rgba(255, 255, 255, 0.25), inset 0 0 18px rgba(255, 255, 255, 0.1);
}

/* The rank sits in its own chip. Fixed box, centred glyph - it is one character and it never
   changes the row's height. */
.lobby-results-row:not(.lobby-results-head) .lobby-results-rank {
  display: flex;
  align-items: center;
  justify-content: center;
  height: clamp(20px, 3.5vh, 30px);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.08);
  color: rgba(255, 255, 255, 0.8);
  font-size: clamp(10px, 0.95vw, 14px);
}

.lobby-results-cell {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.lobby-results-rank {
  color: rgba(255, 255, 255, 0.7);
}

.lobby-results-num,
.lobby-results-total {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.lobby-results-num {
  color: rgba(255, 255, 255, 0.9);
  font-weight: 700;
}

.lobby-results-total {
  font-size: clamp(14px, 1.45vw, 22px);
  font-weight: 900;
}

/* The header's cells are labels, not values: they inherit the header's small type, or
   "SCORE" ellipsises inside a column sized for one digit. */
.lobby-results-head .lobby-results-cell {
  font: inherit;
  letter-spacing: inherit;
}

/* ---- the centre column ---- */

.lobby-results-centre {
  position: relative;
  z-index: 2;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  /* Top padding = the halves' name slot, so the totals box below starts level with the
     numerals. */
  padding: var(--lobby-results-name-h) 0 clamp(14px, 2.4vh, 30px);
}

/* Exactly the numerals' height, with the total rows centred inside it - which is what puts
   the tiles level with the digits. Present (and empty) when the sender has no totals, so the
   award stack below still lines up with the tables. */
.lobby-results-totalsbox {
  height: var(--lobby-results-score-h);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: clamp(4px, 1vh, 8px);
}

/* One row = ONE opaque block spanning the divider, holding the two sides' figures side by
   side with no seam and no rule between them: the pair is a single comparison, not two tiles
   that happen to touch. The block carries the two teams instead in its EDGE - a 1.5px border
   graded left colour -> right colour, drawn with the padding-box/border-box trick - and in
   its glow, which spills the left colour off the left flank and the right colour off the
   right. Opaque on purpose: it sits over the centre line, where a translucent fill would show
   both halves through it.

   The first `background`/`border-color` pair is the fallback: without color-mix() the block
   keeps its dark fill and a plain light edge. */
.lobby-results-totals {
  display: grid;
  grid-template-columns: 1fr 1fr;
  width: 100%;
  border-radius: 10px;
  border: 1.5px solid rgba(255, 255, 255, 0.4);
  background: #0d0b22;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.35);
}

@supports (color: color-mix(in srgb, red 50%, white)) {
  .lobby-results-totals {
    border-color: transparent;
    background:
      linear-gradient(#0d0b22, #0d0b22) padding-box,
      linear-gradient(
          90deg,
          color-mix(in srgb, color-mix(in srgb, var(--lobby-results-side-left, #232838) 80%, #ffffff) 90%, transparent),
          color-mix(in srgb, color-mix(in srgb, var(--lobby-results-side-right, #232838) 80%, #ffffff) 90%, transparent)
        )
        border-box;
    box-shadow:
      -10px 0 24px color-mix(in srgb, var(--lobby-results-side-left, #232838) 32%, transparent),
      10px 0 24px color-mix(in srgb, var(--lobby-results-side-right, #232838) 36%, transparent),
      0 10px 26px rgba(0, 0, 0, 0.35);
  }
}

.lobby-results-total-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  height: clamp(36px, 7.25vh, 64px);
  color: #ffffff;
  min-width: 0;
}

.lobby-results-total-label {
  font-size: clamp(8px, 0.72vw, 12px);
  font-weight: 800;
  letter-spacing: 0.16em;
  opacity: 0.75;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

.lobby-results-total-value {
  font-size: clamp(16px, 2.05vw, 32px);
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

/* The award stack, level with the tables. No panel of its own: the cards float on the divider
   against both halves. */
.lobby-results-awards {
  margin-top: var(--lobby-results-strip-gap);
  display: flex;
  flex-direction: column;
  gap: clamp(4px, 0.9vh, 8px);
}

/* One award: dark glass, EDGED and LIT in the winner's team colour rather than filled with
   it. Opaque, like the totals block and for the same reason - the card floats on the centre
   line with a different half behind each end of it. The plain border-colour before the
   color-mix one is the fallback; the fill never depended on color-mix at all. */
.lobby-super-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(6px, 0.85vw, 12px);
  height: var(--lobby-results-card-h);
  padding: 0 clamp(8px, 1.1vw, 16px);
  border-radius: 14px;
  border: 1.5px solid rgba(255, 255, 255, 0.5);
  background: #0d0b22;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.45);
  overflow: hidden;
}

@supports (color: color-mix(in srgb, red 50%, white)) {
  .lobby-super-card {
    border-color: color-mix(in srgb, color-mix(in srgb, var(--lobby-super-accent, #ffffff) 80%, #ffffff) 85%, transparent);
    box-shadow:
      0 0 22px color-mix(in srgb, var(--lobby-super-accent, #ffffff) 45%, transparent),
      0 10px 26px rgba(0, 0, 0, 0.45),
      inset 0 0 24px color-mix(in srgb, var(--lobby-super-accent, #ffffff) 14%, transparent);
  }
}

.lobby-super-emoji {
  font-size: clamp(16px, 1.9vw, 28px);
  line-height: 1;
}

/* The award's own name, in a light tint of its winner's colour - the one place on the card
   the team reads as text rather than as an edge. White is the fallback. */

.lobby-super-text {
  min-width: 0;
}

/* Every line is one line: ellipsised rows are what keeps the card's height constant
   regardless of how long a display name turns out to be. */
.lobby-super-title,
.lobby-super-player,
.lobby-super-metric {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.lobby-super-title {
  font-size: clamp(8px, 0.72vw, 12px);
  font-weight: 900;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  line-height: 1.2;
  color: #ffffff;
  color: color-mix(in srgb, var(--lobby-super-accent, #ffffff) 45%, #ffffff);
}

/* The same size and weight as a table row's player name. */
.lobby-super-player {
  font-size: clamp(12px, 1.2vw, 18px);
  font-weight: 700;
  line-height: 1.35;
  color: #ffffff;
}

/* The count and its unit, centred as one block on the right. */
.lobby-super-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-width: clamp(34px, 4vw, 64px);
  max-width: clamp(56px, 6vw, 96px);
}

.lobby-super-value {
  font-size: clamp(14px, 1.6vw, 26px);
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: #ffffff;
  white-space: nowrap;
}

.lobby-super-metric {
  font-size: clamp(7px, 0.64vw, 10px);
  font-weight: 800;
  letter-spacing: 0.1em;
  opacity: 0.75;
  max-width: 100%;
}

/* Unclaimed award: same card, same height, unlit - nothing to celebrate. Declared AFTER the
   @supports block above, so it overrides the team edge and glow at equal specificity. */
.lobby-super-card-empty {
  border-color: rgba(255, 255, 255, 0.12);
  background: #0d0b22;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.45);
}

.lobby-super-card-empty .lobby-super-emoji {
  opacity: 0.35;
}

.lobby-super-card-empty .lobby-super-title,
.lobby-super-card-empty .lobby-super-player {
  color: var(--lobby-text-dim);
}

/* Pushes the chip to the bottom of the column. */
.lobby-results-spacer {
  flex: 1 1 auto;
  min-height: clamp(10px, 2.5vh, 30px);
}

/* The one control. It says where it is going and when; clicking it goes now. */
.lobby-results-return {
  align-self: center;
  margin-top: clamp(6px, 1vh, 12px);
  padding: clamp(9px, 1.6vh, 16px) clamp(18px, 2.35vw, 38px);
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.4);
  background: rgba(8, 6, 24, 0.82);
  color: #ffffff;
  font-family: inherit;
  font-size: clamp(10px, 1.02vw, 16px);
  font-weight: 800;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  /* The countdown digit changes every second; tabular figures keep the pill's box still
     (matchtest's rule-4 CTA geometry check flakes without this - seen twice). */
  font-variant-numeric: tabular-nums;
  box-shadow:
    0 0 22px rgba(255, 255, 255, 0.16),
    0 10px 30px rgba(0, 0, 0, 0.45),
    inset 0 1px 0 rgba(255, 255, 255, 0.25);
  /* The countdown digit changes every second; tabular figures keep the chip's box still. */
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease;
}

.lobby-results-return:hover:not(:disabled) {
  background: rgba(26, 22, 54, 0.9);
  border-color: rgba(255, 255, 255, 0.62);
}

/* v1.6: a peer that may not start the rematch keeps the chip's slot, dimmed and inert, so the
   screen's composition is identical on every peer. */
.lobby-results-return:disabled {
  opacity: 0.5;
  cursor: default;
}

/* One continuous ramp, in reading order. Transform + opacity only - every element already
   occupies its final box before the animation starts, so the entrance cannot shift anything.
   Delays are set per element in lobby.js. */
@keyframes lobby-results-in {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.lobby-results-enter .lobby-results-teamname,
.lobby-results-enter .lobby-results-score,
.lobby-results-enter .lobby-results-totals,
.lobby-results-enter .lobby-results-row,
.lobby-results-enter .lobby-super-card,
.lobby-results-enter .lobby-results-return {
  opacity: 0;
  animation: lobby-results-in 380ms cubic-bezier(0.22, 0.8, 0.3, 1) forwards;
}

/* The banner is placed BY a rotation, so the generic keyframe's `transform: none` would snap
   it flat when the entrance lands. It gets the same motion with its rotation folded in - one
   keyframe per side, because the two are rotated opposite ways. */
@keyframes lobby-results-in-banner-right {
  from {
    opacity: 0;
    transform: translateY(10px) rotate(40deg);
  }
  to {
    opacity: 1;
    transform: rotate(40deg);
  }
}

@keyframes lobby-results-in-banner-left {
  from {
    opacity: 0;
    transform: translateY(10px) rotate(-40deg);
  }
  to {
    opacity: 1;
    transform: rotate(-40deg);
  }
}

.lobby-results-enter .lobby-results-winner-right {
  opacity: 0;
  animation: lobby-results-in-banner-right 380ms cubic-bezier(0.22, 0.8, 0.3, 1) forwards;
}

.lobby-results-enter .lobby-results-winner-left {
  opacity: 0;
  animation: lobby-results-in-banner-left 380ms cubic-bezier(0.22, 0.8, 0.3, 1) forwards;
}

.lobby-spinner {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 3px solid rgba(255, 255, 255, 0.15);
  border-top-color: var(--lobby-accent);
  animation: lobby-spin 0.8s linear infinite;
  flex-shrink: 0;
}

.lobby-spinner-sm {
  width: 20px;
  height: 20px;
  border-width: 2px;
}

@keyframes lobby-spin {
  to {
    transform: rotate(360deg);
  }
}

.lobby-teams {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

/* A side's panel. Flat, front-on, and wearing its OWN colour: a low-alpha tint of it as the
   fill, a bright 2px edge of it, and a soft bloom of it underneath. --lobby-team is set inline
   per column from LOBBY_CONFIG.teamColors (lobby.js buildTeamColumn), so the stylesheet still
   names no game and no colour. The plain declarations are the fallback: without color-mix()
   a column keeps the old neutral panel skin rather than losing its border. */
.lobby-team-col {
  background: var(--lobby-panel);
  border: 2px solid var(--lobby-panel-border);
  border-radius: var(--lobby-team-radius);
  padding: 16px 14px 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
  transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}

@supports (color: color-mix(in srgb, red 50%, white)) {
  .lobby-team-col {
    background: color-mix(in srgb, var(--lobby-team, #232838) 14%, transparent);
    border-color: color-mix(in srgb, var(--lobby-team, #232838) 72%, #ffffff);
    box-shadow: 0 0 34px color-mix(in srgb, var(--lobby-team, #232838) 30%, transparent),
      0 24px 60px rgba(0, 0, 0, 0.4);
  }
}

/* Column header: the side's name in the display face, its count on the right. The name lives
   in its own element and holds NOTHING but the name - the roster gates read it as text. */
.lobby-team-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  min-width: 0;
  padding: 0 2px 2px;
}

.lobby-team-count {
  flex: 0 0 auto;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--lobby-text-dim);
  font-variant-numeric: tabular-nums;
}

/* Both drag states snap on instantly (transition: none) - a drop target answering 150ms late
   reads as lag under the pointer; the fade-back on leave keeps the base transition. */
.lobby-team-col.lobby-drop-target {
  transition: none;
  border-color: #ffffff;
  background: rgba(255, 255, 255, 0.12);
}

@supports (color: color-mix(in srgb, red 50%, white)) {
  .lobby-team-col.lobby-drop-target {
    border-color: color-mix(in srgb, var(--lobby-team, #ffffff) 55%, #ffffff);
    background: color-mix(in srgb, var(--lobby-team, #232838) 32%, transparent);
    box-shadow: 0 0 48px color-mix(in srgb, var(--lobby-team, #232838) 60%, transparent),
      0 24px 60px rgba(0, 0, 0, 0.4);
  }
}

/* Dragging over a full side: the drop is still allowed to happen, it just tells you
   in advance how it will end. The refusal itself is .lobby-col-error below. */
.lobby-team-col.lobby-drop-blocked {
  transition: none;
  border-color: rgba(255, 107, 107, 0.6);
  background: rgba(255, 107, 107, 0.08);
}

.lobby-team-col.lobby-col-error {
  border-color: var(--lobby-danger);
  background: rgba(255, 107, 107, 0.14);
  animation: lobby-shake 0.4s ease;
}

@keyframes lobby-shake {
  0%,
  100% {
    transform: translateX(0);
  }
  20% {
    transform: translateX(-6px);
  }
  40% {
    transform: translateX(5px);
  }
  60% {
    transform: translateX(-3px);
  }
  80% {
    transform: translateX(2px);
  }
}

/* Unbounded 900 runs WIDE. The column is half of half the shell, so the name is clamped and
   ellipsised rather than allowed to push its count off the edge. */
.lobby-team-label {
  min-width: 0;
  font-family: var(--lobby-display);
  font-size: clamp(11px, 1.1vw, 15px);
  font-weight: var(--lobby-display-weight);
  line-height: 1.3;
  letter-spacing: 0.02em;
  color: #ffffff;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

@supports (color: color-mix(in srgb, red 50%, white)) {
  .lobby-team-label {
    text-shadow: 0 0 18px color-mix(in srgb, var(--lobby-team, #232838) 70%, transparent);
  }
}

/* A query container so a card can react to the width of ITS OWN column rather than to the
   viewport: the column is half of half the shell, so the same 1000px-wide window that is
   roomy for the page is cramped for a card, and the same card is roomy on a phone in the
   stacked layout. `.lobby-card-tag` below is what that buys. */
.lobby-team-slots {
  display: flex;
  flex-direction: column;
  gap: 8px;
  container-type: inline-size;
}

/* One roster slot: grip, avatar, name, right-aligned tag. Fixed height, so a column's height
   is a function of maxPerTeam alone and a name arriving cannot move the invite row below it. */
.lobby-card {
  display: flex;
  align-items: center;
  gap: 8px;
  height: var(--lobby-card-h);
  padding: 0 10px;
  border-radius: var(--lobby-card-radius);
  border: 1.5px solid rgba(255, 255, 255, 0.1);
  background: rgba(255, 255, 255, 0.05);
  font-size: 15px;
  font-weight: 600;
  text-align: left;
  overflow-wrap: anywhere;
}

@supports (color: color-mix(in srgb, red 50%, white)) {
  .lobby-card {
    border-color: color-mix(in srgb, var(--lobby-team, #232838) 45%, transparent);
  }
}

.lobby-card-grip {
  width: 10px;
  height: 16px;
  flex: 0 0 auto;
  opacity: 0.45;
  fill: currentColor;
}

.lobby-card-avatar {
  width: 28px;
  height: 28px;
  flex: 0 0 auto;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  font-weight: 800;
  line-height: 1;
  color: #ffffff;
  background: var(--lobby-team, rgba(255, 255, 255, 0.14));
}

/* The name is the row's whole point, so it TAKES the free space rather than merely being
   allowed to keep it - the tag is `margin-left: auto` and would otherwise leave the name at
   its content width and shrink it first when the two no longer fit. */
.lobby-card-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.lobby-card-tag {
  margin-left: auto;
  flex: 0 0 auto;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--lobby-text-dim);
  white-space: nowrap;
}

/* Under ~224px of column the grip, the avatar, their gaps and a tag as long as "YOU · HOST"
   leave the name nothing - it ellipsised to a character, or to nothing at all, on a phone and
   in any 1000-1270px window. The tag is the part that goes: `.lobby-card-self` already says
   which card is yours in the team colour, and a nameless roster row is worthless. */
@container (max-width: 224px) {
  .lobby-card-tag {
    display: none;
  }
}

/* Narrower still - a 4-per-team roster on a phone - and the grip goes too. It is an
   affordance for a gesture that works without it (the whole card is the drag handle, and
   `.lobby-card-self` is what marks the one you can move); the name is information. */
@container (max-width: 160px) {
  .lobby-card-grip {
    display: none;
  }
}

/* Engines without container queries get the same call made coarsely, on the viewport: above
   1280px the columns are wide enough at every shell width, below it they may not be, so the
   tag is simply dropped. Losing a tag that would have fitted is the safe way to be wrong. */
@supports not (container-type: inline-size) {
  @media (max-width: 1279px) {
    .lobby-card-tag {
      display: none;
    }
  }
}

.lobby-card-empty {
  color: var(--lobby-text-dim);
  font-weight: 500;
  border: 1.5px dashed rgba(255, 255, 255, 0.22);
  background: transparent;
  border: 1.5px dashed rgba(255, 255, 255, 0.3);
}

.lobby-card-empty .lobby-card-avatar {
  background: transparent;
  border: 1.5px dashed rgba(255, 255, 255, 0.3);
}

/* You. Lit in your own side's colour so the card you can drag is the obvious one. */
.lobby-card-self {
  background: rgba(255, 255, 255, 0.16);
  border-color: rgba(255, 255, 255, 0.85);
}

@supports (color: color-mix(in srgb, red 50%, white)) {
  .lobby-card-self {
    background: linear-gradient(
      90deg,
      color-mix(in srgb, var(--lobby-team, #232838) 34%, transparent),
      color-mix(in srgb, var(--lobby-team, #232838) 6%, transparent)
    );
    border-color: color-mix(in srgb, var(--lobby-team, #232838) 55%, #ffffff);
    box-shadow: 0 0 24px color-mix(in srgb, var(--lobby-team, #232838) 35%, transparent);
  }
}

.lobby-card-draggable {
  cursor: grab;
  touch-action: none;
  user-select: none;
}

.lobby-card-draggable:hover {
  filter: brightness(1.15);
}

.lobby-card-dragging-source {
  opacity: 0.35;
}

/* Lives on document.body during a drag, so it inherits none of the overlay's rules - it needs
   its own box-sizing, and buildPlayerCard puts --lobby-team on the card itself (not only on
   the column) so the clone keeps its side's colour. */
.lobby-card-ghost {
  position: fixed;
  z-index: 50;
  box-sizing: border-box;
  pointer-events: none;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
  cursor: grabbing;
}

/* ---- "HOW TO PLAY" (LOBBY_CONFIG description / instructions / controls) ----

   The left column's content, under the optional gameplay video: an eyebrow, the one-line
   pitch, the rules as a tight arrowed list, then the controls drawn as the keys and the mouse
   they actually are. No card of its own - the left column IS open space, and a panel there
   would fight the two team panels opposite. */
.lobby-howto {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}

.lobby-howto-head {
  font-family: var(--lobby-display);
  font-size: 11px;
  line-height: 1.4;
  letter-spacing: 0.02em;
  color: var(--lobby-text-dim);
}

.lobby-howto-desc {
  font-size: clamp(15px, 1.4vw, 18px);
  line-height: 1.3;
  font-weight: 600;
  color: var(--lobby-text);
  text-wrap: pretty;
}

/* Tight list: the default UA margins and bullet would both fight the arrow glyphs. */
.lobby-howto-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.lobby-howto-item {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  font-size: 15px;
  line-height: 1.45;
  color: var(--lobby-text-dim);
  text-wrap: pretty;
  overflow-wrap: anywhere;
}

.lobby-howto-arrow {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
  margin-top: 5px;
  color: var(--lobby-accent);
}

.lobby-howto-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 12px 40px;
  margin-top: 4px;
}

/* ---- a control, drawn ----

   One cluster per LOBBY_CONFIG.controls entry, with the entry's `description` as the label
   BENEATH it. `key` picks the drawing: "wasd" / "arrows" -> a keycap cluster, "mouse" -> the
   mouse with its left button lit in the accent. Any other key falls back to the text chip
   below, so a game with an exotic control still renders something truthful. */
.lobby-control {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.lobby-control-label {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--lobby-text-dim);
  text-align: center;
}

.lobby-keys {
  display: grid;
  grid-template-columns: repeat(3, 36px);
  gap: 4px;
}

.lobby-key {
  width: 36px;
  height: 36px;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.04));
  box-shadow: 0 2px 0 rgba(255, 255, 255, 0.14) inset, 0 -3px 0 rgba(0, 0, 0, 0.45) inset,
    0 4px 10px rgba(0, 0, 0, 0.35);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  font-weight: 700;
  line-height: 1;
  color: #ffffff;
}

/* The empty cells above A and D in the cluster. */
.lobby-key-gap {
  width: 36px;
  height: 36px;
}

.lobby-mouse {
  width: 30px;
  height: 44px;
  display: block;
}

.lobby-mouse-btn {
  fill: var(--lobby-accent);
}

.lobby-howto-chip {
  display: flex;
  align-items: baseline;
  gap: 5px;
  min-width: 0;
  padding: 5px 10px;
  border-radius: 999px;
  border: 1px solid var(--lobby-panel-border);
  background: rgba(255, 255, 255, 0.06);
  font-size: 12px;
  line-height: 1.3;
  color: var(--lobby-text-dim);
}

.lobby-howto-key {
  font-weight: 800;
  letter-spacing: 0.06em;
  white-space: nowrap;
  color: var(--lobby-text);
}

.lobby-howto-act {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* The invite row is one pill: link glyph, INVITE, the room URL, and a white COPY LINK button
   sitting inside it. The URL stays a read-only <input> so it is still selectable by hand when
   the clipboard API is unavailable; it just stops looking like a form field. */
.lobby-share-row {
  display: flex;
  align-items: center;
  gap: 12px;
  height: 64px;
  padding: 0 8px 0 18px;
  border-radius: 999px;
  border: 1.5px solid var(--lobby-panel-border);
  background: rgba(255, 255, 255, 0.05);
  container-type: inline-size;
}

/* White chrome on the invite row (user direction 2026-08-23) - the accent stays on the CTAs. */
.lobby-share-row {
  border-color: rgba(255, 255, 255, 0.35);
}

.lobby-share-icon {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  color: rgba(255, 255, 255, 0.9);
}

/* Middle ellipsis: the head span gives way (ellipsised) while the tail - "?room=<code>",
   the entire reason the pill exists - is never cut, so the start AND the end of the link
   both show. Selecting the text and copying yields the FULL link, ellipsis or not: the
   box's copy handler rewrites the clipboard (see lobby.js). */
.lobby-share-link {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  color: var(--lobby-text);
  font-size: 13px;
  font-weight: 600;
  cursor: text;
  user-select: all;
  -webkit-user-select: all;
}

.lobby-share-head {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lobby-share-tail {
  flex: 0 0 auto;
  white-space: nowrap;
}

.lobby-copy-btn {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 48px;
  /* Wide enough for the longer of its two labels ("Invite link" / "Copied!"), so the flip
     never changes the button's width - no layout shift in the row. */
  min-width: 148px;
  padding: 0 18px;
  border-radius: 999px;
  background: #ffffff;
  color: var(--lobby-cta-ink);
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  white-space: nowrap;
}

.lobby-copy-btn:hover:not(:disabled) {
  background: #ffffff;
  filter: brightness(0.92);
}

.lobby-copy-btn svg {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
}

/* Floating toggle, not part of the panel shell: it must stay put and clickable through
   "playing" (overlay hidden) and the hostUnavailable veil (overlay pointer-events: none).
   z-index above both the overlay (20) and its veil banner/ghost card (50) so nothing in the
   lobby can ever cover it. Hidden by default; renderMuteButton() shows it for the phases
   where there is audio to mute. */
.lobby-mute-btn {
  position: fixed;
  top: 16px;
  right: 16px;
  z-index: 60;
  width: 44px;
  height: 44px;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  display: none;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  border: 1px solid var(--lobby-panel-border);
  background: rgba(5, 6, 11, 0.9);
  color: var(--lobby-text);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  /* No backdrop-filter: this button overlays the canvas for the whole match, and blur
     forces a per-frame compositor readback of the game's output. */
}

.lobby-mute-btn:hover {
  background: rgba(18, 19, 30, 0.85);
}

/* Wide enough for the wordmark to have the HOME pill beside it rather than above it.
   `top` centres the 40px pill on the title's fixed-height line (the subtitle below the title
   is not part of the alignment), so the two read as one row. */
@media (min-width: 760px) {
  .lobby-home {
    position: absolute;
    left: 0;
    top: calc((var(--lobby-title-size) * 1.35 - 40px) / 2);
    margin-bottom: 0;
  }
}

/* Two columns need roughly 1000px to stay comfortable; below that they stack.

   The ACTIONABLE half goes first when they do. Side by side, the left column is reference
   material you read past; stacked, putting it first buries the name field - and, in the
   lobby, the roster, the invite row and the host's Start button - a full screen below the
   fold, so the one thing the screen exists for is off-screen at load. `order` moves the box
   only: the DOM order (aside, then body) is what a screen reader still follows. */
@media (max-width: 999px) {
  .lobby-columns {
    grid-template-columns: minmax(0, 1fr);
    max-width: var(--lobby-main-w);
    margin: 0 auto;
  }

  .lobby-body {
    order: -1;
  }
}

@media (max-width: 380px) {
  :root {
    --lobby-title-size: 28px;
    --lobby-ended-headline-h: 72px;
  }

  .lobby-card {
    font-size: 14px;
    padding: 0 10px;
    gap: 8px;
  }

  .lobby-howto-desc {
    font-size: 14px;
  }

  .lobby-howto-item {
    font-size: 13px;
  }

  .lobby-share-row {
    padding-left: 12px;
  }

}

@media (prefers-reduced-motion: reduce) {
  .lobby-btn,
  .lobby-cta,
  .lobby-home,
  .lobby-team-col {
    transition: none;
  }

  .lobby-team-col.lobby-col-error {
    animation: none;
  }

  /* The results screen still assembles, it just arrives all at once. */
  .lobby-results-enter .lobby-results-winner,
  .lobby-results-enter .lobby-results-teamname,
  .lobby-results-enter .lobby-results-score,
  .lobby-results-enter .lobby-results-totals,
  .lobby-results-enter .lobby-results-row,
  .lobby-results-enter .lobby-super-card,
  .lobby-results-enter .lobby-results-return {
    opacity: 1;
    animation: none;
  }

  .lobby-results-return {
    transition: none;
  }
}
