:root {
  color-scheme: dark;
  --bg: #05060f;
  --line: rgba(148, 163, 255, 0.18);
  --text: #e7ecff;
  --muted: #9aa4c7;
  --player: #22d3ee;
  --player-rgb: 34, 211, 238;
  --opponent: #f472b6;
  --opponent-rgb: 244, 114, 182;
  --violet: #a78bfa;
  --violet-rgb: 167, 139, 250;
  --amber: #fbbf24;
  --focus: #f8fafc;
  --gap: 10px;
  --hud-h: 50px;
  --rail-h: 50px;
  font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background: var(--bg);
  color: var(--text);
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  overscroll-behavior: none;
  scroll-behavior: smooth;
}

/* During a match a stray swipe must not scroll the court away. */
html:has([data-arena][data-phase="running"]) {
  overflow: hidden;
}

body {
  min-width: 320px;
  margin: 0;
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

button {
  font: inherit;
  color: inherit;
  touch-action: manipulation;
  cursor: pointer;
}

button svg {
  pointer-events: none;
}

button:focus-visible,
canvas:focus-visible,
a:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 3px;
}

/* The board keeps focus during play, so its ring matches the board's glow instead of flashing white. */
canvas:focus-visible {
  outline-color: var(--player);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip-link {
  position: fixed;
  top: 0.75rem;
  left: 0.75rem;
  z-index: 10;
  transform: translateY(-200%);
  padding: 0.7rem 1rem;
  border-radius: 0.75rem;
  background: #f8fafc;
  color: #0f172a;
}

.skip-link:focus {
  transform: translateY(0);
}

/* Drifting neon light behind everything; transform-only, so it stays on the compositor. */
.aurora {
  position: fixed;
  inset: -25%;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(38% 30% at 22% 18%, rgba(var(--opponent-rgb), 0.2), transparent 70%),
    radial-gradient(42% 34% at 78% 82%, rgba(var(--player-rgb), 0.18), transparent 70%),
    radial-gradient(30% 26% at 62% 32%, rgba(var(--violet-rgb), 0.14), transparent 70%);
  animation: drift 26s ease-in-out infinite alternate;
}

@keyframes drift {
  to {
    transform: translate3d(5%, -4%, 0) rotate(10deg) scale(1.1);
  }
}

/* ---------- Game screen ---------- */

.arena {
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  gap: 6px;
  height: 100vh;
  height: 100dvh;
  padding:
    max(10px, env(safe-area-inset-top))
    max(10px, env(safe-area-inset-right))
    max(8px, env(safe-area-inset-bottom))
    max(10px, env(safe-area-inset-left));
}

/* The stage is a size container: the board takes the largest 5:8 box that fits, and the HUD
   and thumb rail share its width. */
.stage {
  container-type: size;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--gap);
  min-height: 0;
}

.stage > * {
  --board-w: min(100cqw, (100cqh - var(--hud-h) - var(--rail-h) - 2 * var(--gap)) * 0.625);
  width: var(--board-w);
  flex: none;
}

/* The HUD is as wide as the board, which on the smallest phones is under 270px. It is a size
   container, so its buttons and spacing scale with it and the scoreboard sheds its labels
   before anything could overlap or push the page sideways. */
.hud {
  container: hud / inline-size;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(6px, calc(var(--board-w) * 0.025), 10px);
  height: var(--hud-h);
}


@container hud (max-width: 330px) {
  .scoreboard {
    gap: 8px;
  }

  .scoreboard__side {
    gap: 5px;
  }

  .scoreboard__label {
    letter-spacing: 0.08em;
  }
}

/* The colors already tell the sides apart: cyan is yours at the bottom, pink theirs on top.
   A nickname needs more room than "CPU", so with one the labels go a size earlier. */
@container hud (max-width: 290px) {
  .scoreboard__label {
    display: none;
  }
}

@container hud (max-width: 330px) {
  .scoreboard--nick .scoreboard__label {
    display: none;
  }
}

.icon-button {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: rgba(15, 18, 40, 0.72);
  box-shadow: 0 0 18px rgba(var(--violet-rgb), 0.18);
  transition: transform 120ms ease, border-color 120ms ease, opacity 120ms ease;
}

.icon-button:hover:not(:disabled) {
  border-color: rgba(var(--violet-rgb), 0.6);
}

.icon-button:active:not(:disabled) {
  transform: scale(0.92);
}

.icon-button:disabled {
  opacity: 0.35;
  cursor: default;
}

.icon {
  width: 22px;
  height: 22px;
  fill: currentColor;
}

.icon-stroke {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
}

.icon-play,
[data-phase="paused"] .icon-pause,
[aria-pressed="false"] > .icon-sound-on,
[aria-pressed="true"] > .icon-sound-off {
  display: none;
}

[data-phase="paused"] .icon-play {
  display: block;
}

.scoreboard {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  font-variant-numeric: tabular-nums;
}

.scoreboard__side {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.scoreboard__label {
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The computer's nickname keeps its own case, a size smaller, and is capped at the room left
   between the score and the buttons: 67px on a 360px phone, where every nickname just fits. */
.scoreboard__label--nick {
  max-width: calc(100cqw - 273px);
  overflow: hidden;
  font-size: 0.62rem;
  letter-spacing: 0.03em;
  text-overflow: ellipsis;
  text-transform: none;
  white-space: nowrap;
}

.scoreboard__score {
  display: inline-block;
  min-width: 1.2ch;
  font-size: clamp(1.9rem, 4.2vh, 2.6rem);
  font-weight: 900;
  line-height: 1;
  text-align: center;
}

.scoreboard__side--player .scoreboard__score {
  color: var(--player);
  text-shadow: 0 0 12px rgba(var(--player-rgb), 0.8), 0 0 28px rgba(var(--player-rgb), 0.45);
}

.scoreboard__side--opponent .scoreboard__score {
  color: var(--opponent);
  text-shadow: 0 0 12px rgba(var(--opponent-rgb), 0.8), 0 0 28px rgba(var(--opponent-rgb), 0.45);
}

.scoreboard__divider {
  width: 2px;
  height: 26px;
  border-radius: 2px;
  background: linear-gradient(var(--opponent), var(--violet), var(--player));
  opacity: 0.7;
}

.scoreboard__score.is-popping {
  animation: score-pop 420ms cubic-bezier(0.2, 1.6, 0.4, 1);
}

@keyframes score-pop {
  from {
    transform: scale(1.8);
    filter: brightness(1.8);
  }
}

.board {
  /* Everything drawn over the court is sized in --u, 1% of the court's width, so the menus
     scale with it. A phone held sideways sets it from the screen instead (see Devices). */
  --u: 1cqw;
  container: board / inline-size;
  position: relative;
  aspect-ratio: 5 / 8;
  padding: 2px;
  border-radius: 22px;
  background: linear-gradient(180deg, var(--opponent), var(--violet) 50%, var(--player));
  box-shadow:
    0 -10px 40px rgba(var(--opponent-rgb), 0.18),
    0 10px 40px rgba(var(--player-rgb), 0.18),
    0 0 80px rgba(var(--violet-rgb), 0.12);
}

.board canvas {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 5 / 8;
  border-radius: 20px;
  background: var(--bg);
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}

.rail {
  position: relative;
  display: grid;
  place-items: center;
  height: var(--rail-h);
  border: 1px solid rgba(var(--player-rgb), 0.24);
  border-radius: 18px;
  background: linear-gradient(90deg, rgba(var(--player-rgb), 0.03), rgba(var(--player-rgb), 0.12), rgba(var(--player-rgb), 0.03));
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}

.rail__track {
  position: absolute;
  inset: auto 18px 11px;
  height: 3px;
  border-radius: 3px;
  background: linear-gradient(90deg, transparent, var(--player), transparent);
  box-shadow: 0 0 12px rgba(var(--player-rgb), 0.8);
  opacity: 0.8;
}

.rail__label {
  margin-bottom: 6px;
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(var(--player-rgb), 0.75);
}

.help {
  margin: 0;
  text-align: center;
  font-size: 0.78rem;
  color: var(--muted);
}

/* On touch screens the thumb rail already says how to move; the hint stays in the board's
   accessible description. */
.help__touch,
.help__keys {
  display: none;
}

kbd {
  display: inline-block;
  min-width: 1.6em;
  padding: 0.05em 0.35em;
  border: 1px solid var(--line);
  border-bottom-width: 2px;
  border-radius: 0.35em;
  font: inherit;
  font-size: 0.72rem;
  color: var(--text);
}

/* ---------- Overlays on the board ---------- */

.overlay {
  position: absolute;
  inset: 2px;
  display: grid;
  place-items: center;
  padding: calc(7 * var(--u));
  border-radius: 20px;
  background: radial-gradient(circle at 50% 42%, rgba(9, 11, 28, 0.5), rgba(5, 6, 15, 0.86));
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  animation: overlay-in 280ms ease-out both;
}

.overlay[hidden] {
  display: none;
}

@keyframes overlay-in {
  from {
    opacity: 0;
    transform: scale(1.04);
  }
}

.panel {
  display: grid;
  justify-items: center;
  gap: calc(4.2 * var(--u));
  width: 100%;
  text-align: center;
}

/* The menu comes in two groups, what the match is and the controls. On a phone held upright
   they interleave in one column, in this order; held sideways they stand side by side. */
.panel__side,
.panel__main {
  display: contents;
}

.panel--menu .logo { order: 1; }
.panel--menu .modes { order: 2; }
.panel--menu [data-solo-only],
.panel--menu [data-career-only] { order: 3; }
.panel--menu .cta { order: 4; }
.panel--menu .panel__meta { order: 5; }
.panel--menu .panel__stats { order: 6; }
.panel--menu .panel__row { order: 7; }

/* The name as a neon sign: each word is a tube over its unlit glass, with a pale core, a glow
   and a halo on the wall behind it. */
.logo {
  width: min(100%, calc(62 * var(--u)));
  margin: 0;
}

.logo__art {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

.logo__art use {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.logo__glass {
  stroke: rgba(148, 163, 255, 0.1);
  stroke-width: 6;
}

.logo__halo {
  stroke-width: 12;
  filter: url(#logo-halo);
  opacity: 0.4;
}

.logo__glow {
  stroke-width: 7;
  filter: url(#logo-glow);
}

.logo__tube {
  stroke-width: 5;
}

.logo__core {
  stroke-width: 1.6;
}

.logo__sign--paddle {
  stroke: var(--player);
}

.logo__sign--noir {
  stroke: var(--opponent);
}

.logo__rules {
  stroke: var(--violet);
}

.logo__sign--paddle .logo__core {
  stroke: #e3fcff;
}

.logo__sign--noir .logo__core {
  stroke: #ffe6f3;
}

.logo__rules .logo__core {
  stroke: #efeaff;
}

/* The sign lights up word by word as the page opens, with one stutter each: two flashes in
   all, well under three a second, done in 1.2 seconds. Then it stays on, and back from a
   match it is on already. */
.logo__sign--paddle .logo__lit {
  animation: neon-on 0.8s 0.1s both;
}

.logo__sign--noir .logo__lit {
  animation: neon-on 0.7s 0.5s both;
}

[data-played] .logo__lit {
  animation: none;
}

@keyframes neon-on {
  0% {
    opacity: 0;
  }
  16% {
    opacity: 1;
  }
  30% {
    opacity: 0.25;
  }
  52%,
  100% {
    opacity: 1;
  }
}

.panel__title {
  margin: 0;
  font-size: calc(11 * var(--u));
  font-weight: 900;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text);
  text-shadow: 0 0 calc(3 * var(--u)) rgba(var(--violet-rgb), 0.7);
}

.panel__title--result[data-winner="player"] {
  color: var(--player);
  text-shadow: 0 0 calc(3 * var(--u)) rgba(var(--player-rgb), 0.9), 0 0 calc(9 * var(--u)) rgba(var(--player-rgb), 0.45);
}

.panel__title--result[data-winner="opponent"] {
  color: var(--opponent);
  text-shadow: 0 0 calc(3 * var(--u)) rgba(var(--opponent-rgb), 0.9), 0 0 calc(9 * var(--u)) rgba(var(--opponent-rgb), 0.45);
}

.panel__eyebrow {
  margin: 0;
  font-size: max(11px, calc(3.4 * var(--u)));
  font-weight: 800;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--violet);
}

.result-score {
  margin: 0;
  font-size: calc(13 * var(--u));
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.06em;
}

.panel__meta {
  margin: 0;
  font-size: max(12px, calc(3.6 * var(--u)));
  color: var(--muted);
}

.panel__meta strong {
  color: var(--text);
}

.panel__row {
  display: flex;
  gap: calc(3 * var(--u));
}

.cta {
  width: min(78%, 22rem);
  min-height: 48px;
  padding: calc(3.6 * var(--u)) calc(5 * var(--u));
  border: 0;
  border-radius: 999px;
  font-size: max(15px, calc(5.2 * var(--u)));
  font-weight: 900;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #041018;
  background: linear-gradient(90deg, var(--player), var(--violet));
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.25), 0 calc(1.5 * var(--u)) calc(6 * var(--u)) rgba(var(--player-rgb), 0.4);
  animation: breathe 2.6s ease-in-out infinite;
  transition: transform 120ms ease;
}

.cta:active {
  transform: scale(0.96);
}

@keyframes breathe {
  50% {
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.25), 0 calc(1.5 * var(--u)) calc(9 * var(--u)) rgba(var(--violet-rgb), 0.6);
  }
}

.ghost {
  min-width: calc(30 * var(--u));
  min-height: 44px;
  padding: calc(2.6 * var(--u)) calc(5 * var(--u));
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: max(13px, calc(3.8 * var(--u)));
  font-weight: 800;
  letter-spacing: 0.08em;
  background: rgba(15, 18, 40, 0.6);
}

.ghost:hover {
  border-color: rgba(var(--violet-rgb), 0.6);
}

.segmented {
  display: flex;
  padding: calc(1 * var(--u));
  border: 1px solid var(--line);
  border-radius: 999px;
  background: rgba(10, 12, 28, 0.75);
}

.segmented button {
  min-width: max(44px, calc(20 * var(--u)));
  min-height: 44px;
  padding: calc(1.2 * var(--u)) calc(3.6 * var(--u));
  border: 0;
  border-radius: 999px;
  font-size: max(12px, calc(3.6 * var(--u)));
  font-weight: 800;
  letter-spacing: 0.06em;
  color: var(--muted);
  background: transparent;
  transition: color 150ms ease, background 150ms ease;
}

.segmented button[aria-pressed="true"] {
  color: #041018;
  background: linear-gradient(90deg, var(--player), var(--violet));
  box-shadow: 0 0 calc(4 * var(--u)) rgba(var(--player-rgb), 0.45);
}

/* The four modes as cards, two by two: an icon, the name and a line on what it is. */
.modes {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: calc(2 * var(--u));
  width: min(100%, 26rem);
}

.mode {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  column-gap: calc(2.4 * var(--u));
  min-height: 44px;
  padding: calc(1.8 * var(--u)) calc(2.6 * var(--u));
  border: 1px solid var(--line);
  border-radius: max(12px, calc(3.6 * var(--u)));
  line-height: 1.15;
  text-align: left;
  color: var(--muted);
  background: linear-gradient(180deg, rgba(22, 26, 56, 0.72), rgba(10, 12, 28, 0.72));
  transition: color 150ms ease, border-color 150ms ease, background 150ms ease, box-shadow 150ms ease, transform 120ms ease;
}

.mode:hover {
  border-color: rgba(var(--violet-rgb), 0.6);
}

.mode:active {
  transform: scale(0.97);
}

.mode__icon {
  grid-row: span 2;
  width: max(20px, calc(6.4 * var(--u)));
  height: max(20px, calc(6.4 * var(--u)));
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.mode__ball {
  fill: currentColor;
  stroke: none;
}

.mode__name {
  font-size: max(13px, calc(4 * var(--u)));
  font-weight: 800;
  letter-spacing: 0.04em;
  color: var(--text);
}

.mode__note {
  overflow: hidden;
  font-size: max(10px, calc(2.9 * var(--u)));
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.mode[aria-pressed="true"] {
  border-color: rgba(var(--player-rgb), 0.75);
  color: var(--text);
  background: linear-gradient(135deg, rgba(var(--player-rgb), 0.24), rgba(var(--violet-rgb), 0.2));
  box-shadow: 0 0 calc(4 * var(--u)) rgba(var(--player-rgb), 0.3), inset 0 0 calc(3 * var(--u)) rgba(var(--player-rgb), 0.15);
}

.mode[aria-pressed="true"] .mode__icon {
  color: var(--player);
  filter: drop-shadow(0 0 calc(1.2 * var(--u)) rgba(var(--player-rgb), 0.8));
}

.toggles {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: calc(3 * var(--u));
}

.toggle {
  display: inline-flex;
  align-items: center;
  gap: calc(2.4 * var(--u));
  min-height: 44px;
  padding: 0 calc(3.2 * var(--u));
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: max(12px, calc(3.4 * var(--u)));
  font-weight: 700;
  color: var(--muted);
  background: rgba(15, 18, 40, 0.6);
}

.toggle[hidden] {
  display: none;
}

/* The switch knob. */
.toggle::before {
  content: "";
  width: calc(7 * var(--u));
  height: calc(4 * var(--u));
  border-radius: 999px;
  background: radial-gradient(circle at 30% 50%, #cbd5e1 0 calc(1.4 * var(--u)), transparent calc(1.5 * var(--u))), rgba(148, 163, 184, 0.3);
  transition: background 150ms ease;
}

.toggle[aria-pressed="true"] {
  color: var(--text);
}

.toggle[aria-pressed="true"]::before {
  background: radial-gradient(circle at 70% 50%, #041018 0 calc(1.4 * var(--u)), transparent calc(1.5 * var(--u))), var(--player);
  box-shadow: 0 0 calc(3 * var(--u)) rgba(var(--player-rgb), 0.6);
}

/* The music track picker: each tap moves to the next track and plays a sample of it. */
.track {
  display: inline-flex;
  align-items: center;
  gap: calc(1.6 * var(--u));
  min-height: 44px;
  padding: 0 calc(3.2 * var(--u));
  border: 1px solid rgba(var(--player-rgb), 0.45);
  border-radius: 999px;
  font-size: max(12px, calc(3.4 * var(--u)));
  font-weight: 700;
  color: var(--text);
  background: rgba(15, 18, 40, 0.6);
}

.track::before {
  content: "♪" / "";
  color: var(--player);
  text-shadow: 0 0 calc(2 * var(--u)) rgba(var(--player-rgb), 0.8);
}

.badge {
  margin: 0;
  padding: calc(1.4 * var(--u)) calc(3.4 * var(--u));
  border-radius: 999px;
  font-size: max(11px, calc(3.4 * var(--u)));
  font-weight: 900;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #1c1300;
  background: var(--amber);
  box-shadow: 0 0 calc(5 * var(--u)) rgba(251, 191, 36, 0.55);
}

.badge[hidden] {
  display: none;
}

/* Stars: earned ones in amber; the glyphs are decoration, the words beside them are read. */
.stars {
  font-size: max(12px, calc(3.6 * var(--u)));
  letter-spacing: 0.12em;
  white-space: nowrap;
  color: var(--amber);
  text-shadow: 0 0 calc(1.6 * var(--u)) rgba(251, 191, 36, 0.55);
}

.stars[hidden] {
  display: none;
}

.stars--result {
  margin: 0;
  font-size: max(20px, calc(7 * var(--u)));
  line-height: 1;
}

/* The career rival picker: steps either side of a card with the rival's name, its place on
   the ladder, a boss tag and the best stars earned against it. */
.rival {
  display: flex;
  align-items: center;
  gap: calc(2 * var(--u));
  width: min(100%, 24rem);
}

.rival[hidden] {
  display: none;
}

.rival__step {
  flex: none;
  width: 44px;
  height: 44px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--text);
  background: rgba(15, 18, 40, 0.6);
}

.rival__step:disabled {
  cursor: default;
  opacity: 0.3;
}

.rival__card {
  display: flex;
  flex: 1;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  column-gap: calc(2 * var(--u));
  min-width: 0;
  margin: 0;
  padding: calc(1.2 * var(--u)) calc(2 * var(--u));
  border: 1px solid var(--line);
  border-radius: 16px;
  text-align: center;
  background: rgba(10, 12, 28, 0.75);
}

.rival__name {
  flex-basis: 100%;
  overflow: hidden;
  font-size: max(14px, calc(4.2 * var(--u)));
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rival__place {
  font-size: max(11px, calc(3 * var(--u)));
  font-weight: 700;
  color: var(--muted);
}

.rival__boss {
  padding: 0 calc(1.4 * var(--u));
  border-radius: 999px;
  font-size: max(10px, calc(2.6 * var(--u)));
  font-weight: 900;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #1a0410;
  background: var(--opponent);
}

.rival__tired {
  padding: 0 calc(1.4 * var(--u));
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: max(10px, calc(2.6 * var(--u)));
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

.rival__boss[hidden],
.rival__tired[hidden] {
  display: none;
}

/* A career rival's name over the court as a match against it starts, gone before the serve. */
.rival-banner {
  position: absolute;
  top: 28%;
  left: 50%;
  z-index: 1;
  display: grid;
  justify-items: center;
  gap: calc(1 * var(--u));
  margin: 0;
  transform: translateX(-50%);
  font-size: calc(7.5 * var(--u));
  font-weight: 900;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--text);
  text-shadow: 0 0 calc(3 * var(--u)) rgba(var(--opponent-rgb), 0.9), 0 0 calc(8 * var(--u)) rgba(var(--violet-rgb), 0.5);
  pointer-events: none;
  animation: banner 2.2s ease-out both;
}

.rival-banner[hidden] {
  display: none;
}

.rival-banner small {
  padding: 0 calc(2 * var(--u));
  border-radius: 999px;
  font-size: 0.42em;
  letter-spacing: 0.4em;
  color: #1a0410;
  text-shadow: none;
  background: var(--opponent);
}

@keyframes banner {
  0% {
    opacity: 0;
    transform: translateX(-50%) scale(1.25);
  }
  14%,
  78% {
    opacity: 1;
    transform: translateX(-50%) scale(1);
  }
  100% {
    opacity: 0;
    transform: translateX(-50%) scale(1);
  }
}

/* The record badges and the "Continue?" line: rows of their own on the result screen, or
   one row where the screen is short. With all of them hidden they take no room at all. */
.result-notes {
  display: contents;
}

.result-notes:not(:has(> :not([hidden]))) {
  display: none;
}

/* After a loss, the arcade's "Continue?" counts down, then gives up. Decoration only. */
.continue {
  margin: 0;
  font-size: max(13px, calc(4.2 * var(--u)));
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--amber);
  text-shadow: 0 0 calc(2.4 * var(--u)) rgba(251, 191, 36, 0.6);
}

.continue[hidden] {
  display: none;
}

.continue[data-state="over"] {
  color: var(--opponent);
  text-shadow: 0 0 calc(2.4 * var(--u)) rgba(var(--opponent-rgb), 0.6);
}

.continue.is-ticking {
  animation: continue-tick 360ms cubic-bezier(0.2, 1.6, 0.4, 1);
}

@keyframes continue-tick {
  from {
    transform: scale(1.18);
  }
}

/* Shorter courts fit the menu with 44px controls by tightening the spacing and the logo. */
@container board (max-width: 350px) {
  .overlay {
    padding: calc(5 * var(--u));
  }

  .panel {
    gap: calc(3 * var(--u));
  }

  .logo {
    width: min(100%, calc(58 * var(--u)));
  }

  .toggles {
    gap: calc(2 * var(--u));
  }
}

@container board (max-width: 300px) {
  .logo {
    width: min(100%, calc(54 * var(--u)));
  }
}

/* ---------- Modes, lives, match point ---------- */

.hud__group {
  display: flex;
  gap: clamp(4px, 2cqw, 8px);
}

.segmented[hidden],
.toggle[hidden],
.panel__stats[hidden],
.match-point[hidden] {
  display: none;
}

.lives {
  position: absolute;
  top: 100%;
  left: 50%;
  display: flex;
  gap: 4px;
  transform: translate(-50%, -2px);
  font-size: 0.9rem;
  line-height: 1;
}

.lives[hidden] {
  display: none;
}

.lives__heart {
  color: var(--opponent);
  text-shadow: 0 0 8px rgba(var(--opponent-rgb), 0.9);
  transition: color 300ms ease, text-shadow 300ms ease, transform 300ms ease;
}

.lives__heart--lost {
  color: rgba(148, 163, 184, 0.35);
  text-shadow: none;
  transform: scale(0.85);
}

.scoreboard {
  position: relative;
}

.match-point {
  position: absolute;
  top: calc(3 * var(--u));
  left: 50%;
  z-index: 1;
  margin: 0;
  padding: calc(1.2 * var(--u)) calc(3.4 * var(--u));
  border-radius: 999px;
  transform: translateX(-50%);
  font-size: calc(3.2 * var(--u));
  font-weight: 900;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: #1c1300;
  background: var(--amber);
  box-shadow: 0 0 calc(5 * var(--u)) rgba(251, 191, 36, 0.6);
  pointer-events: none;
  animation: match-point-pulse 1.1s ease-in-out infinite;
}

.match-point[data-side="opponent"] {
  top: auto;
  bottom: calc(3 * var(--u));
}

@keyframes match-point-pulse {
  50% {
    transform: translateX(-50%) scale(1.06);
    box-shadow: 0 0 calc(9 * var(--u)) rgba(251, 191, 36, 0.85);
  }
}

.panel__stats,
.panel__note {
  margin: 0;
  font-size: max(12px, calc(3.2 * var(--u)));
  color: var(--muted);
}

.panel__note {
  min-height: 1.4em;
}

.ghost--small {
  display: inline-flex;
  align-items: center;
  gap: calc(1.6 * var(--u));
  min-width: 0;
  padding: 0 calc(3.6 * var(--u));
  font-size: max(12px, calc(3.3 * var(--u)));
  letter-spacing: 0.04em;
  white-space: nowrap;
}

.ghost__icon {
  flex: none;
  width: max(16px, calc(4.4 * var(--u)));
  height: max(16px, calc(4.4 * var(--u)));
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.85;
}

/* ---------- Tutorial ---------- */

/* A modal dialog in the browser's top layer: the page behind it is inert and dimmed, focus
   starts on its button and Escape closes it. It is its own size container, so it reads the
   same over a phone's court and beside a desktop cabinet. */
.dialog {
  container-type: inline-size;
  width: min(92vw, 30rem);
  max-height: min(88dvh, 46rem);
  padding: 6cqw;
  border: 1px solid rgba(var(--violet-rgb), 0.45);
  border-radius: 22px;
  background: radial-gradient(circle at 50% 0%, rgba(var(--violet-rgb), 0.16), transparent 60%), rgba(9, 11, 28, 0.96);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.04), 0 18px 70px rgba(0, 0, 0, 0.6), 0 0 60px rgba(var(--violet-rgb), 0.25);
  color: var(--text);
  overflow: hidden;
}

.dialog[open] {
  display: grid;
  animation: overlay-in 280ms ease-out both;
}

.dialog::backdrop {
  background: radial-gradient(circle at 50% 42%, rgba(9, 11, 28, 0.55), rgba(5, 6, 15, 0.88));
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

/* The steps scroll when the dialog is taller than a small screen; the title and button stay put. */
.panel--tutorial {
  gap: 4cqw;
  min-height: 0;
  max-height: calc(min(88dvh, 46rem) - 12cqw - 2px);
  grid-template-rows: auto minmax(0, 1fr) auto;
}

.panel--tutorial .panel__title {
  font-size: 9cqw;
}

.panel--tutorial .tutorial {
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: 1cqw;
}

.tutorial {
  display: grid;
  gap: 2.6cqw;
  width: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
  text-align: left;
}

.tutorial li {
  display: grid;
  grid-template-columns: 9cqw minmax(0, 1fr);
  align-items: center;
  gap: 2.6cqw;
  width: 100%;
  padding: 2.6cqw 3cqw;
  border: 1px solid var(--line);
  border-radius: 3cqw;
  background: rgba(15, 18, 40, 0.7);
  font-size: max(13px, 3.8cqw);
  line-height: 1.4;
  color: var(--muted);
}

.tutorial strong {
  color: var(--text);
}

.tutorial__icon {
  display: grid;
  place-items: center;
  width: 9cqw;
  height: 9cqw;
  border-radius: 50%;
  background: rgba(var(--player-rgb), 0.16);
  font-size: 4.6cqw;
  color: var(--player);
}

/* On a short screen the tips sit closer, so all five fit a small phone without scrolling. */
@media (max-height: 600px) {
  .tutorial {
    gap: 2cqw;
  }

  .tutorial li {
    padding: 1.8cqw 2.6cqw;
    line-height: 1.3;
  }
}

/* ---------- Settings ---------- */

/* Every switch in one modal, the same kind as the tutorial: a row each, with a line on where
   it applies, and the credits last. The rows scroll on a short screen; the title and Done
   stay put. */
.panel--settings {
  gap: 4cqw;
  min-height: 0;
  max-height: calc(min(88dvh, 46rem) - 12cqw - 2px);
  grid-template-rows: auto minmax(0, 1fr) auto;
}

.panel--settings .panel__title {
  font-size: 9cqw;
}

.settings {
  display: grid;
  align-content: start;
  gap: 2cqw;
  width: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.setting {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 3cqw;
  min-height: 48px;
  padding: 1.6cqw 3.6cqw;
  border: 1px solid var(--line);
  border-radius: 3cqw;
  font-size: max(14px, 4cqw);
  font-weight: 700;
  line-height: 1.25;
  text-align: left;
  color: var(--text);
  background: rgba(15, 18, 40, 0.7);
}

.setting[hidden] {
  display: none;
}

.setting:hover {
  border-color: rgba(var(--violet-rgb), 0.6);
}

.setting__text {
  display: grid;
  min-width: 0;
}

.setting__note {
  font-size: max(12px, 3.3cqw);
  font-weight: 500;
  color: var(--muted);
}

/* A switch on the right: the knob slides over when it is on. */
.switch {
  position: relative;
  flex: none;
  width: 44px;
  height: 26px;
  border-radius: 999px;
  background: rgba(148, 163, 184, 0.3);
  transition: background 150ms ease, box-shadow 150ms ease;
}

.switch::before {
  content: "";
  position: absolute;
  top: 4px;
  left: 4px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #cbd5e1;
  transition: transform 150ms ease, background 150ms ease;
}

[aria-pressed="true"] > .switch {
  background: var(--player);
  box-shadow: 0 0 12px rgba(var(--player-rgb), 0.6);
}

[aria-pressed="true"] > .switch::before {
  transform: translateX(18px);
  background: #041018;
}

/* The track picker names the track playing; a tap moves to the next one. */
.setting__value {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: 2cqw;
  color: var(--player);
}

.setting__value::before {
  content: "♪" / "";
  text-shadow: 0 0 8px rgba(var(--player-rgb), 0.8);
}

.setting__value::after {
  content: "›" / "";
  font-size: 1.3em;
  line-height: 1;
  color: var(--muted);
}

.settings__about {
  margin: 0;
  font-size: max(12px, 3.3cqw);
  text-align: center;
  color: var(--muted);
}

.settings__about a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--violet);
}

/* On a short screen the rows sit closer, so all of them fit without scrolling, down to a
   small phone. */
@media (max-height: 760px) {
  .panel--settings {
    gap: 3cqw;
  }

  .panel--settings .panel__title {
    font-size: 8cqw;
  }

  .settings {
    gap: 1.2cqw;
  }

  .setting {
    min-height: 44px;
    padding-block: 1cqw;
  }
}

/* ---------- Title screen ---------- */

/* The title screen shows the name, not a match: the score, the pause button, the thumb rail
   and the key hints appear as one starts, in the room kept for them, so nothing moves. */
.scoreboard,
.rail,
.help {
  transition: opacity 250ms ease, visibility 250ms;
}

[data-phase="ready"] .scoreboard,
[data-phase="ready"] [data-hud-pause],
[data-phase="ready"] .rail,
[data-phase="ready"] .help {
  visibility: hidden;
  opacity: 0;
}

/* ---------- Two players ---------- */

/* Both people steer on the board itself; the thumb rail would only get in the second player's way. */
[data-mode="duo"] .rail {
  display: none;
}

[data-mode="duo"] .help__duo {
  display: inline;
}

.help__duo {
  display: none;
}

/* Player 2 reads the top of the scoreboard from the other side of the phone. */
[data-mode="duo"][data-phase="running"] .scoreboard__side--opponent,
[data-mode="duo"][data-phase="paused"] .scoreboard__side--opponent {
  transform: rotate(180deg);
}

/* ---------- Devices ---------- */

/* Mouse and trackpad: no thumb rail, keyboard hints instead of touch hints. */
@media (hover: hover) and (pointer: fine) {
  :root {
    --rail-h: 0px;
  }

  .rail {
    display: none;
  }

  .help__keys {
    display: inline;
  }
}

/* A phone held sideways. The HUD turns into a column at the left edge, so the court gets the
   full height, and the menu, pause and result screens open as sheets across the rest of the
   screen, sized from its height rather than from the narrow court. The menu's two groups
   stand side by side. */
@media (orientation: landscape) and (max-height: 520px) and (pointer: coarse) {
  :root {
    --rail-h: 0px;
    --hud-w: 52px;
  }

  .rail {
    display: none;
  }

  .stage {
    display: grid;
    grid-template-columns: var(--hud-w) minmax(0, 1fr);
    align-items: center;
    justify-items: center;
    column-gap: var(--gap);
  }

  .stage > * {
    --board-w: min(100cqw - var(--hud-w) - var(--gap), 100cqh * 0.625);
  }

  .hud {
    grid-template-columns: none;
    grid-template-rows: auto minmax(0, 1fr) auto;
    justify-items: center;
    width: var(--hud-w);
    height: min(100cqh, var(--board-w) * 1.6);
  }

  .hud__group {
    flex-direction: column;
  }

  .scoreboard {
    flex-direction: column;
    gap: 8px;
  }

  .scoreboard__divider {
    width: 26px;
    height: 2px;
  }

  .overlay {
    --u: min(0.85vh, 0.5vw);
    position: fixed;
    inset: 0 0 0 calc(max(10px, env(safe-area-inset-left)) + var(--hud-w) + var(--gap));
    z-index: 3;
    overflow-y: auto;
    padding: max(10px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right)) max(10px, env(safe-area-inset-bottom)) 16px;
    border-radius: 0;
  }

  .panel {
    gap: calc(3 * var(--u));
    max-width: 44rem;
  }

  .panel--menu {
    grid-template-columns: minmax(0, 0.7fr) minmax(0, 1.3fr);
    align-items: center;
    column-gap: calc(5 * var(--u));
  }

  .panel__side,
  .panel__main {
    display: grid;
    justify-items: center;
    gap: calc(3 * var(--u));
  }

  /* The mode cards take two rows here; tighter spacing keeps the whole menu on a 320px-tall screen. */
  .panel__main {
    gap: calc(1.6 * var(--u));
  }

  .segmented {
    padding: calc(0.5 * var(--u));
  }

  .logo {
    width: min(100%, calc(52 * var(--u)));
  }

  .toggles {
    gap: calc(2 * var(--u));
  }

  .result-notes {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    gap: calc(2 * var(--u)) calc(4 * var(--u));
  }

  /* The tutorial and the settings use the width instead: their rows stand two by two, sized
     for the screen's short side. The credits fill the settings' last cell, and Done moves up
     beside their title. */
  .dialog {
    width: min(94vw, 46rem);
    max-height: 94dvh;
    padding: 12px 16px;
  }

  .panel--tutorial,
  .panel--settings {
    gap: 10px;
    max-height: calc(94dvh - 26px);
  }

  .panel--tutorial .panel__title,
  .panel--settings .panel__title {
    font-size: 26px;
  }

  .tutorial,
  .settings {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
  }

  .tutorial li {
    grid-template-columns: 30px minmax(0, 1fr);
    gap: 10px;
    padding: 8px 12px;
    border-radius: 12px;
    font-size: 13px;
    line-height: 1.35;
  }

  .tutorial__icon {
    width: 30px;
    height: 30px;
    font-size: 15px;
  }

  .setting {
    min-height: 44px;
    padding: 6px 12px;
    border-radius: 12px;
    font-size: 15px;
  }

  .setting__note,
  .settings__about {
    font-size: 12px;
  }

  .panel--settings {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-rows: auto minmax(0, 1fr);
  }

  .panel--settings .panel__title {
    grid-area: 1 / 1;
    justify-self: start;
  }

  .panel--settings [data-dismiss-settings] {
    grid-area: 1 / 2;
  }

  .settings {
    grid-area: 2 / 1 / auto / -1;
  }

  .settings__about {
    align-self: center;
  }

  .dialog .cta {
    width: auto;
    min-width: 12rem;
    min-height: 44px;
    padding-block: 8px;
    font-size: 15px;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  /* The sign is simply on, without the stutter. */
  .logo__lit {
    animation: none !important;
  }
}
