:root {
  --bg: #05070c;
  --panel: rgba(5, 7, 12, 0.84);
  --panel-strong: rgba(9, 14, 23, 0.92);
  --panel-border: rgba(255, 255, 255, 0.14);
  --text: #eef3ff;
  --text-dim: #94a0b8;
  --accent: #3bf5c8;
  --blue: #4da6ff;
  --gold: #ffd166;
  --panel-soft: rgba(255, 255, 255, 0.055);
  --control-surface: rgba(10, 15, 24, 0.74);
  --control-hover: rgba(255, 255, 255, 0.07);
  --accent-ink: #03100d;
  --secondary-ink: #03101b;
  --danger: #ff4d6d;
  --radius: 8px;
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left: env(safe-area-inset-left, 0px);
}

/* The one place the canvas HUD can find out where the notch is. Script cannot read the four
   properties above — getPropertyValue hands back the unresolved env() token, because a custom
   property is substituted where it is used and env() only resolves in a real declaration. A
   padding is a real declaration, so getComputedStyle on this element reports the insets as
   pixels. app.js/safeArea() creates it and measures it; overriding --safe-* on :root moves the
   HUD, which is how it is tested without a notched device. */
.safe-area-probe {
  position: fixed;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  visibility: hidden;
  pointer-events: none;
  padding: var(--safe-top) var(--safe-right) var(--safe-bottom) var(--safe-left);
}

* {
  box-sizing: border-box;
}

html,
body {
  width: 100%;
  height: 100%;
  margin: 0;
  overflow: hidden;
  background: #05070c;
  color: var(--text);
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  letter-spacing: 0;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}

button,
input {
  font: inherit;
}

button {
  cursor: pointer;
  transition:
    transform 120ms ease,
    border-color 120ms ease,
    background 120ms ease,
    filter 120ms ease,
    box-shadow 120ms ease;
}

button:hover {
  filter: brightness(1.04);
}

button:active {
  transform: translateY(1px);
}

button:focus-visible,
input:focus-visible,
summary:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--accent) 70%, white 0%);
  outline-offset: 2px;
}

#game {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  display: block;
  touch-action: none;
}

/* A high-DPI canvas fill touches every physical pixel for every frame of the flash. This layer's
   colour is painted once and only its opacity is animated by the compositor. It remains above the
   game canvas, where the wash has always appeared, without intercepting steering. */
#prize-wash {
  position: fixed;
  z-index: 1;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  will-change: opacity;
}

/* Live Team Deathmatch kills, in the only clear strip between the map and team board. The names
   carry team colour while the verb stays neutral, so "Alpha has killed Beta" can be read at a
   glance without turning the whole sentence into one side's announcement. */
.team-kill-feed {
  position: fixed;
  z-index: 5;
  top: calc(var(--safe-top) + 12px);
  left: 50%;
  display: grid;
  gap: 6px;
  width: min(420px, calc(100vw - var(--safe-left) - var(--safe-right) - 32px));
  transform: translateX(-50%);
  pointer-events: none;
}

.team-kill-notice {
  justify-self: center;
  max-width: 100%;
  padding: 7px 12px;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 999px;
  background: rgba(5, 8, 13, 0.86);
  box-shadow: 0 8px 26px rgba(0, 0, 0, 0.34);
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 800;
  line-height: 1.2;
  text-align: center;
  text-overflow: ellipsis;
  white-space: nowrap;
  animation: team-kill-in 180ms ease-out both;
}

.team-kill-notice strong { font-weight: 900; }
.team-kill-notice.is-leaving { animation: team-kill-out 260ms ease-in both; }

@keyframes team-kill-in {
  from { opacity: 0; transform: translateY(-8px) scale(0.96); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes team-kill-out {
  from { opacity: 1; transform: translateY(0); }
  to { opacity: 0; transform: translateY(-7px); }
}

@media (prefers-reduced-motion: reduce) {
  .team-kill-notice,
  .team-kill-notice.is-leaving { animation: none; }
}

.menu-shell {
  position: fixed;
  inset: 0;
  display: grid;
  /* "safe" is load-bearing, not decoration. A centred grid item taller than its container
     overflows BOTH edges, and the half that goes off the top cannot be scrolled back to —
     which is exactly what happened on a phone once the rounds drawer listed eight matches:
     the list ran off the screen and nothing would scroll. safe centre falls back to start
     the moment the content stops fitting, so the overflow all goes one way, downwards,
     where the scrollbar can reach it. */
  align-items: safe center;
  justify-items: center;
  gap: 14px;

  /* The shell's own vertical padding, named so the panel inside it can be capped at the space
     that is actually left. It was a literal in five places and the cap was a sixth number
     guessing at them — see .menu-panel's max-height. */
  --shell-top: 58px;
  --shell-bottom: 18px;
  padding:
    calc(var(--shell-top) + var(--safe-top))
    calc(18px + var(--safe-right))
    calc(var(--shell-bottom) + var(--safe-bottom))
    calc(18px + var(--safe-left));
  pointer-events: none;
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.menu-shell.is-hidden,
.is-hidden {
  display: none !important;
}

.menu-panel,
.end-panel,
.pause-panel {
  width: min(520px, calc(100vw - 36px - var(--safe-left) - var(--safe-right)));
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.03), transparent 32%),
    var(--panel);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: var(--radius);
  box-shadow:
    0 22px 70px rgba(0, 0, 0, 0.48),
    inset 0 1px 0 rgba(255, 255, 255, 0.08);
  backdrop-filter: blur(18px) saturate(1.22);
  -webkit-backdrop-filter: blur(18px) saturate(1.22);
  pointer-events: auto;
}

/* The cap is the shell's own padding, not a number that hopes to match it.

   It was `100vh - 36px`, and the shell's vertical padding is 58+18 at this size and four other
   pairs across the breakpoints — 86+28 on a desktop, 72+16 on a phone. So every panel was allowed
   to be taller than the space it sits in, by up to 78px. Nothing showed it until a party lobby had
   a full party in it — eight people, when a side was eight: measured at 320x568 the panel's box
   ended 10px BELOW the bottom of the
   screen with `body { overflow: hidden }` above it, so the last of its own scrollable content
   could not be reached at all, and at 844x390 it overflowed by 78 and "Start the round" was off
   the screen with nothing that would scroll to it.

   dvh rather than vh for the same class of reason: on a phone browser `100vh` is the viewport with
   the address bar hidden, which is not the viewport a player is looking at while it is showing. */
.menu-panel {
  display: grid;
  gap: 14px;
  padding: 18px;
  max-height: calc(100vh - var(--shell-top, 58px) - var(--shell-bottom, 18px)
    - var(--safe-top) - var(--safe-bottom));
  max-height: calc(100dvh - var(--shell-top, 58px) - var(--shell-bottom, 18px)
    - var(--safe-top) - var(--safe-bottom));
  overflow-y: auto;
  overscroll-behavior: contain;
}

.builder-panel {
  width: min(780px, calc(100vw - 36px - var(--safe-left) - var(--safe-right)));
  background: var(--panel-strong);
}

/* The pinned band: the header and the preview, held at the top of the panel's own scroller.

   Full-bleed — negative margins cancelling the panel's 18px padding — so the grids slide
   underneath it rather than past it in a gutter, and painted in `--panel-strong` rather than
   `--panel` because the panel's own ground is translucent and a translucent band over it composites
   to a different colour. Near-opaque is what a header needs anyway. */
.builder-top {
  position: sticky;
  top: 0;
  z-index: 4;
  display: grid;
  gap: 14px;
  margin: -18px -18px 0;
  padding: 18px 18px 12px;
  background: var(--panel-strong);
}

.home-head {
  min-height: 42px;
  justify-content: center;
  text-align: center;
}

.home-head h1 {
  width: 100%;
}

.site-brand {
  position: fixed;
  z-index: 3;
  top: calc(16px + var(--safe-top));
  left: 50%;
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  gap: 9px;
  min-height: 34px;
  padding: 7px 12px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 999px;
  background: rgba(5, 7, 12, 0.72);
  color: var(--text);
  font-size: 14px;
  font-weight: 900;
  white-space: nowrap;
  pointer-events: none;
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.34);
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
}

#client-build {
  position: fixed;
  z-index: 2;
  left: calc(8px + var(--safe-left));
  bottom: calc(6px + var(--safe-bottom));
  color: rgba(255, 255, 255, 0.42);
  font: 500 10px/1.2 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  letter-spacing: 0.02em;
  white-space: nowrap;
  /* This is diagnostic chrome, never a control. A fixed element with pointer events enabled wins
     elementsFromPoint and can make a real button underneath look inexplicably dead. */
  pointer-events: none;
  user-select: none;
}

.inner-window {
  position: fixed;
  z-index: 8;
  inset: 50% auto auto 50%;
  display: grid;
  gap: 22px;
  width: min(560px, calc(100vw - 32px - var(--safe-left) - var(--safe-right)));
  max-height: calc(100vh - 48px - var(--safe-top) - var(--safe-bottom));
  padding: 24px;
  border: 1px solid var(--panel-border);
  border-radius: 16px;
  background: linear-gradient(180deg, rgba(20, 28, 36, 0.98), rgba(14, 20, 26, 0.98));
  box-shadow: 0 28px 90px rgba(0, 0, 0, 0.52);
  transform: translate(-50%, -50%);
  overflow: auto;
  pointer-events: auto;
  animation: innerWindowIn 180ms ease-out;
}

.inner-window.is-hidden {
  display: none !important;
}

.inner-window-subtitle {
  margin-top: 5px;
  color: var(--text-dim);
  font-size: 13px;
}

.help-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.help-grid section {
  min-height: 112px;
  padding: 16px;
  border: 1px solid var(--panel-border);
  border-radius: 12px;
  background: rgba(30, 38, 51, 0.5);
}

.help-grid h3 {
  margin: 0 0 7px;
  color: var(--text);
  font-size: 15px;
}

.help-grid p {
  margin: 0;
  color: var(--text-dim);
  font-size: 13px;
  line-height: 1.45;
}

.join-form {
  display: grid;
  gap: 14px;
}

.join-form .play-button {
  width: 100%;
}

@keyframes innerWindowIn {
  from { opacity: 0; transform: translate(-50%, calc(-50% + 8px)); }
  to { opacity: 1; transform: translate(-50%, -50%); }
}

@media (max-width: 560px) {
  .inner-window {
    width: min(calc(100vw - 24px), 560px);
    max-height: calc(100vh - 32px - var(--safe-top) - var(--safe-bottom));
    padding: 18px;
  }

  .help-grid { grid-template-columns: 1fr; }
}

.brand-light {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 18px var(--accent);
}

.app-menu-toggle {
  position: fixed;
  z-index: 7;
  top: calc(14px + var(--safe-top));
  right: calc(14px + var(--safe-right));
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: var(--radius);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.1), transparent),
    rgba(5, 7, 12, 0.82);
  box-shadow: 0 14px 36px rgba(0, 0, 0, 0.36);
  backdrop-filter: blur(16px) saturate(1.2);
  -webkit-backdrop-filter: blur(16px) saturate(1.2);
}

.app-menu-toggle span,
.app-menu-toggle::before,
.app-menu-toggle::after {
  content: "";
  width: 19px;
  height: 2px;
  display: block;
  border-radius: 999px;
  background: var(--text);
}

.app-menu-toggle {
  gap: 5px;
}

.app-menu-toggle::before,
.app-menu-toggle::after {
  position: static;
}

.app-menu {
  position: fixed;
  z-index: 9;
  inset: 0;
  display: grid;
  justify-items: end;
  background: rgba(0, 0, 0, 0.42);
  pointer-events: auto;
}

.app-menu-panel {
  width: min(410px, calc(100vw - var(--safe-left)));
  height: 100%;
  display: grid;
  align-content: start;
  gap: 12px;
  padding:
    calc(18px + var(--safe-top))
    calc(16px + var(--safe-right))
    calc(18px + var(--safe-bottom))
    16px;
  border-left: 1px solid rgba(255, 255, 255, 0.16);
  background:
    linear-gradient(180deg, rgba(59, 245, 200, 0.08), transparent 32%),
    rgba(5, 7, 12, 0.94);
  box-shadow: -22px 0 70px rgba(0, 0, 0, 0.5);
  overflow-y: auto;
  overscroll-behavior: contain;
  backdrop-filter: blur(20px) saturate(1.2);
  -webkit-backdrop-filter: blur(20px) saturate(1.2);
}

.app-menu-head h2 {
  font-size: 22px;
}

h1,
h2,
h3,
p {
  margin: 0;
}

h1 {
  font-size: 38px;
  line-height: 1;
  font-weight: 850;
  letter-spacing: 0;
}

h2 {
  font-size: 28px;
  line-height: 1;
  font-weight: 850;
  letter-spacing: 0;
}

h3 {
  color: var(--text-dim);
  font-size: 12px;
  line-height: 1;
  font-weight: 800;
  letter-spacing: 0;
  text-transform: uppercase;
}

.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.panel-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.icon-button {
  position: relative;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.08);
  color: var(--text);
}

.icon-button::before {
  content: "";
  width: 18px;
  height: 18px;
  display: block;
  background: var(--text);
  mask: var(--icon) center / contain no-repeat;
  -webkit-mask: var(--icon) center / contain no-repeat;
}

#sound-toggle,
#builder-sound-toggle {
  --icon: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 9h4l5-4v14l-5-4H4zM17 8c1 1 1.5 2.3 1.5 4S18 15 17 16M19.5 5.5C21 7.3 22 9.5 22 12s-1 4.7-2.5 6.5' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

#sound-toggle.is-off,
#builder-sound-toggle.is-off {
  opacity: 0.52;
}

#sound-toggle.is-off::after,
#builder-sound-toggle.is-off::after {
  content: "";
  position: absolute;
  width: 24px;
  height: 2px;
  background: var(--danger);
  transform: rotate(-38deg);
}

.close-button {
  --icon: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6 6l12 12M18 6L6 18' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round'/%3E%3C/svg%3E");
}

.random-button {
  --icon: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16 3h5v5M4 20 21 3M21 16v5h-5M15 15l6 6M4 4l5 5' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

.refresh-button {
  width: 34px;
  height: 34px;
  --icon: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20 11a8 8 0 0 0-14.6-4.5L4 8M4 4v4h4M4 13a8 8 0 0 0 14.6 4.5L20 16M16 16h4v4' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

.refresh-button::before {
  width: 16px;
  height: 16px;
}

.snake-preview {
  width: 100%;
  height: 132px;
  display: block;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.045);
}

.launch-grid {
  display: grid;
  grid-template-columns: minmax(430px, 1.15fr) minmax(300px, 0.85fr);
  gap: 16px;
  align-items: start;
}

.preview-column,
.play-column {
  display: grid;
  gap: 12px;
  min-width: 0;
  align-content: start;
}

.play-column {
  align-content: start;
  min-height: 188px;
  padding: 14px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: var(--radius);
  background:
    linear-gradient(135deg, rgba(59, 245, 200, 0.08), rgba(77, 166, 255, 0.06)),
    rgba(255, 255, 255, 0.045);
}

.action-grid,
.builder-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

.builder-actions > :only-child {
  grid-column: 1 / -1;
}

.action-grid .plain-button {
  grid-column: 1 / -1;
}

.play-button,
.plain-button,
.end-actions button {
  min-height: 48px;
  border-radius: var(--radius);
  font-weight: 850;
  letter-spacing: 0;
}

.play-button {
  border: 0;
  background: var(--accent);
  color: #03100d;
}

.play-button.secondary {
  background: #4da6ff;
  color: #03101b;
}

.plain-button,
.end-actions button:last-child {
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.1);
  color: var(--text);
}

.play-button:disabled,
.plain-button:disabled {
  cursor: progress;
  filter: saturate(0.62) brightness(0.78);
}

.account-card,
.scoreboard-card,
.audio-card {
  display: grid;
  gap: 10px;
  padding: 12px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.065);
}

.app-menu .account-card {
  gap: 12px;
  padding: 12px;
  border-color: rgba(77, 166, 255, 0.18);
  background:
    linear-gradient(135deg, rgba(59, 245, 200, 0.08), rgba(77, 166, 255, 0.07)),
    rgba(255, 255, 255, 0.045);
}

.account-main {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
}

.account-copy {
  display: grid;
  gap: 3px;
  min-width: 0;
}

.profile-crown {
  justify-self: start;
  padding: 3px 7px;
  border: 1px solid rgba(245, 190, 65, 0.3);
  border-radius: 999px;
  background: rgba(245, 190, 65, 0.1);
  color: var(--gold);
  font-size: 10px;
  font-weight: 850;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.drawer-profile-pane.is-view-hidden {
  display: none !important;
}

.profile-stat-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}

.profile-stat {
  display: grid;
  gap: 4px;
  min-width: 0;
  padding: 13px 8px;
  border: 1px solid var(--panel-border);
  border-radius: 13px;
  background:
    radial-gradient(circle at 50% 0, rgba(20, 184, 166, 0.12), transparent 72%),
    rgba(255, 255, 255, 0.035);
  text-align: center;
}

.profile-stat strong {
  overflow: hidden;
  color: var(--text);
  font-size: 23px;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  text-overflow: ellipsis;
}

.profile-stat span {
  color: var(--muted);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.profile-section-heading,
.profile-win-head,
.profile-win-detail {
  display: flex;
  align-items: center;
}

.profile-section-heading,
.profile-win-head {
  justify-content: space-between;
  gap: 10px;
}

.profile-refresh {
  padding: 3px 0;
  border: 0;
  background: transparent;
  color: var(--accent-hover);
  font-size: 11px;
  font-weight: 800;
  cursor: pointer;
}

.profile-refresh:disabled {
  cursor: wait;
  opacity: 0.55;
}

.profile-win-list {
  display: grid;
  gap: 8px;
}

.profile-win {
  display: grid;
  gap: 8px;
  padding: 12px;
  border: 1px solid var(--panel-border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.03);
}

.profile-win-head strong {
  color: var(--text);
  font-size: 13px;
}

.profile-win-head time {
  color: var(--muted);
  font-size: 11px;
}

.profile-win-detail {
  flex-wrap: wrap;
  gap: 6px;
}

.profile-win-detail span {
  padding: 3px 7px;
  border-radius: 999px;
  background: rgba(20, 184, 166, 0.09);
  color: var(--text-dim);
  font-size: 10px;
  font-weight: 750;
}

#account-name {
  font-size: 15px;
  font-weight: 900;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#account-sub {
  color: var(--text-dim);
  font-size: 12px;
  font-weight: 700;
}

.coin-badge {
  min-width: 86px;
  min-height: 48px;
  display: grid;
  align-content: center;
  justify-items: end;
  padding: 6px 10px;
  border: 1px solid rgba(59, 245, 200, 0.2);
  border-radius: var(--radius);
  background: rgba(59, 245, 200, 0.11);
}

.coin-badge span {
  color: var(--text-dim);
  font-size: 10px;
  font-weight: 850;
  text-transform: uppercase;
}

.coin-badge strong {
  color: var(--accent);
  font-size: 22px;
  line-height: 1;
  font-weight: 950;
}

.auth-actions {
  display: flex;
  justify-content: start;
  align-items: center;
  gap: 8px;
  max-width: none;
  flex-wrap: wrap;
}

.google-slot {
  min-height: 40px;
}

.auth-button {
  min-height: 40px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.1);
  color: var(--text);
  padding: 0 12px;
  font-size: 13px;
  font-weight: 850;
  white-space: nowrap;
}

.auth-button.primary {
  min-width: 96px;
  border: 0;
  background: var(--accent);
  color: #03100d;
}

/* Follows the theme — see --apple-fill in app.js. Hard-coded white put a white button on a
   near-white panel under every light theme. */
.auth-button.apple {
  background: var(--apple-fill, #000000);
  color: var(--apple-ink, #ffffff);
}

.local-auth {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 8px;
}

.local-auth input {
  min-width: 0;
  min-height: 40px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.08);
  color: var(--text);
  padding: 0 11px;
  outline: none;
}

.local-auth input::placeholder {
  color: rgba(148, 160, 184, 0.82);
}

#auth-email,
#auth-password,
.auth-submit-row {
  grid-column: 1 / -1;
}

.auth-submit-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 8px;
  align-items: center;
}

.auth-mode-switch {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px;
  padding: 4px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.06);
}

.scoreboard-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.scoreboard-head h2 {
  font-size: 18px;
}

.scoreboard-list {
  display: grid;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.scoreboard-row {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr) auto;
  align-items: center;
  gap: 9px;
  min-height: 34px;
  color: var(--text);
}

.scoreboard-rank {
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(59, 245, 200, 0.16);
  color: var(--accent);
  font-size: 12px;
  font-weight: 900;
}

.scoreboard-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
  font-weight: 850;
}

.scoreboard-meta {
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 760;
  text-align: right;
  white-space: nowrap;
}

.scoreboard-empty {
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 700;
}

.home-dock {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 14px;
  align-items: start;
}

.app-menu .audio-card {
  min-height: 72px;
  background: rgba(255, 255, 255, 0.045);
}

.audio-card {
  align-content: start;
}

.audio-summary {
  min-height: 34px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  color: var(--text);
  font-size: 18px;
  font-weight: 850;
  cursor: pointer;
  list-style: none;
}

.audio-summary::-webkit-details-marker {
  display: none;
}

.audio-summary::after {
  content: "";
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
  background: var(--text-dim);
  mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m6 9 6 6 6-6' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
  -webkit-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m6 9 6 6 6-6' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}

.audio-card[open] .audio-summary::after {
  transform: rotate(180deg);
}

.audio-card .audio-grid {
  padding-top: 10px;
}

.audio-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 9px;
}

.audio-grid.compact {
  grid-template-columns: 1fr;
}

.audio-row,
.range-row {
  min-height: 42px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  color: var(--text);
  font-size: 13px;
  font-weight: 850;
}

.audio-row input {
  width: 20px;
  height: 20px;
  accent-color: var(--accent);
}

.range-row {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: 104px minmax(0, 1fr);
}

.range-row input {
  width: 100%;
  accent-color: var(--accent);
}

.source-row {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px;
  padding: 4px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.06);
}

/* Three positions rather than two. A modifier rather than a change to .source-row itself,
   because the prize flash picker below it has three buttons in a two-column grid and has shipped
   that way — retuning it is a look decision, not part of this. */
.source-row-three {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.source-button {
  min-height: 34px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 850;
}

.source-button.is-active {
  background: rgba(59, 245, 200, 0.18);
  color: var(--accent);
}

.compact-row {
  display: grid;
  grid-template-columns: 1fr 126px;
  gap: 10px;
  align-items: end;
}

.field {
  display: grid;
  gap: 7px;
}

.field span {
  color: var(--text-dim);
  font-size: 12px;
  font-weight: 800;
  line-height: 1;
  text-transform: uppercase;
}

.field input {
  width: 100%;
  height: 46px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.08);
  color: var(--text);
  padding: 0 13px;
  outline: none;
}

.field input:focus {
  border-color: color-mix(in srgb, var(--accent) 65%, white 0%);
  box-shadow: 0 0 0 3px rgba(59, 245, 200, 0.18);
}

.segmented {
  min-height: 46px;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px;
  padding: 4px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.06);
}

.segment {
  min-height: 36px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text-dim);
  font-weight: 800;
}

.segment.is-active {
  background: rgba(59, 245, 200, 0.18);
  color: var(--accent);
}

.builder-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}

/* ---- roster ---- */

.roster-row {
  display: flex;
  align-items: stretch;
  gap: 8px;
  min-width: 0;
}

/* Scrolls rather than wraps. Eight chips is the cap, and a strip that grows a second row on a
   narrow window would push the tabs and the whole builder down as somebody adds snakes. */
.snake-roster {
  display: flex;
  flex: 1 1 auto;
  gap: 8px;
  min-width: 0;
  overflow-x: auto;
  padding-bottom: 2px;
  scrollbar-width: none;
}

.snake-roster::-webkit-scrollbar {
  display: none;
}

.snake-chip {
  display: flex;
  flex: 0 0 auto;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  width: 78px;
  padding: 7px 6px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.08);
  color: var(--text);
}

/* Tinted with the accent rather than lightened: the theme-aware block below repaints every
   control surface for a pastel background, and a chip that said "selected" by being whiter would
   say nothing at all on Blush. */
.snake-chip.is-active {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}

/* The bands the snake is actually wearing, not a solid swatch: a flag or a painting is what makes
   one chip tellable from another at a glance. */
.chip-body {
  width: 100%;
  height: 11px;
  border-radius: 999px;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.3);
}

.chip-name {
  max-width: 100%;
  overflow: hidden;
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 760;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.snake-chip.is-active .chip-name {
  color: var(--text);
}

/* Dashed, so the one chip that is not a snake does not read as one. */
.snake-chip.add {
  justify-content: center;
  width: 48px;
  border-style: dashed;
  color: var(--text-dim);
  font-size: 22px;
  font-weight: 300;
}

.roster-delete {
  flex: 0 0 auto;
  align-self: center;
  padding: 8px 12px;
  border: 1px solid color-mix(in srgb, var(--danger, #ff4d6d) 45%, transparent);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--danger, #ff4d6d) 10%, transparent);
  color: var(--danger, #ff4d6d);
  font-size: 12px;
  font-weight: 760;
}

.builder-tabs {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 4px;
  padding: 4px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
}

.builder-tab {
  min-width: 0;
  min-height: 38px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 850;
  overflow: hidden;
  text-overflow: ellipsis;
}

.builder-tab.is-active {
  background: var(--accent);
  color: #03100d;
}

.builder-pages {
  min-height: 260px;
}

.builder-page {
  display: grid;
  gap: 14px;
}

.control-block {
  display: grid;
  gap: 9px;
}

.control-block.wide {
  grid-column: 1 / -1;
}

.swatches {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(32px, 32px));
  gap: 9px;
  min-height: 34px;
}

.swatch {
  width: 32px;
  height: 32px;
  border: 2px solid transparent;
  border-radius: 50%;
  background: var(--swatch);
  box-shadow: inset 0 0 0 2px rgba(0, 0, 0, 0.3);
}

.swatch.is-active {
  border-color: #ffffff;
  box-shadow:
    inset 0 0 0 2px rgba(0, 0, 0, 0.3),
    0 0 0 3px rgba(59, 245, 200, 0.24);
}

.option-grid {
  display: grid;
  gap: 8px;
}

.pattern-grid {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

.skin-grid,
.theme-grid {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.option {
  min-height: 40px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.08);
  color: var(--text);
  font-weight: 760;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.option.skin-option {
  display: grid;
  gap: 6px;
  min-height: 82px;
  align-content: center;
  justify-items: center;
  padding: 8px 6px;
}

.option.chip-option {
  min-height: 38px;
  padding-inline: 14px;
  border-radius: 999px;
}

.skin-art {
  position: relative;
  width: 100%;
  aspect-ratio: 1.55;
  display: grid;
  place-items: center;
  border-radius: 6px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.08);
  font-size: 42px;
  line-height: 1;
}

.skin-art.plain {
  background: var(--skin-plain, #3bf5c8);
}

.skin-art.flag {
  background: transparent;
}

.skin-bands {
  width: 100%;
  height: 6px;
  border-radius: 999px;
  background: var(--skin-bands, transparent);
}

.option-label,
.face-name {
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.option.is-active {
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  background: rgba(59, 245, 200, 0.16);
  color: var(--accent);
}

/* The cover note and the button that answers it, read as one thing rather than as prose with a
   button near it. First on the Colour tab, because it is what is stopping the rest of the tab from
   doing anything. */
.skin-cover {
  display: grid;
  gap: 10px;
  padding: 12px 14px;
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  background: var(--panel-soft);
}

.skin-cover .hint {
  margin: 0;
}

/* Quiet, not a call to action. It is the way out of a state, and a snake in a flag is a perfectly
   good snake — a blue slab here would read as "you have made a mistake". */
.skin-take-off {
  padding: 10px 14px;
  border: 1px solid var(--panel-border);
  border-radius: 999px;
  background: var(--control-surface);
  color: var(--text);
  font-size: 13.5px;
  font-weight: 760;
}

.skin-take-off:hover { background: var(--control-hover); }

/* ---- padlocks ----------------------------------------------------------------------------
   A locked chip is dimmed, not disabled. It is still a button — it opens the confirmation — and a
   greyed-out control that nonetheless responds is a worse lie than a dim one that does.

   The art dims and the label does not: the point is that you can see exactly what you would be
   getting, and the level under it is the one thing that has to stay legible. Builder.razor.css
   dims the same halves for the same reason; a padlock that looked different in the two clients
   would read as two different rules. */

/* 0.85, not 0.4. Dimmed to 0.4 the shop was the least legible thing on the screen: the thirty-odd
   hats and eyes a player might spend coins on were the ones they could not see, while the three
   they already own were bright. A padlock is meant to be an invitation, and nobody is tempted by a
   shape they cannot make out. Builder.razor.css lifted the same halves by the same amount. */
.option.is-locked .skin-art,
.option.is-locked .skin-bands,
.option.is-locked .face-canvas {
  opacity: 0.85;
}

.option.is-locked .option-label,
.option.is-locked .face-name {
  color: var(--text-dim);
}

.option-lock {
  max-width: 100%;
  color: var(--text-dim);
  font-size: 10px;
  font-weight: 760;
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---- where the player stands ----
   Under the preview and above the roster, because it is about the account rather than about the
   snake in front of you — and because the very next thing a player does after seeing a padlock is
   look for how far away it is. The app's builder stacks the same three lines in the same order. */

.standing {
  display: grid;
  gap: 6px;
  padding: 10px 12px;
  border: 1px solid var(--panel-border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.04);
}

.standing-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.standing-level {
  min-width: 0;
  font-size: 13px;
  font-weight: 760;
  color: var(--text);
  /* One line. "Level 12 · 1250 food to level 13" would otherwise wrap on a narrow window and push
     the bar down a row, so the panel changes height as the number grows. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.standing-coins {
  flex: 0 0 auto;
  font-size: 12px;
  font-weight: 760;
  color: var(--accent);
}

/* Fixed height and a rounded track: a bar visible only when part-full reads as an error at 0% and
   at 100%. */
.standing-bar {
  height: 6px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.09);
  overflow: hidden;
}

.standing-bar > span {
  display: block;
  width: 0;
  height: 100%;
  border-radius: 999px;
  background: var(--accent);
  /* Grows rather than jumps when a purchase or a refresh moves it. */
  transition: width 240ms ease;
}

/* The account's copy of the panel sits inside a card that already has a border, so it drops its
   own and keeps the spacing. Same three lines, same order, one less box. */
.account-standing {
  margin-top: 12px;
  padding: 0;
  border: 0;
  background: none;
}

.standing-next {
  font-size: 11.5px;
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.face-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 9px;
}

.face-option {
  width: 78px;
  min-height: 94px;
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 4px;
  padding: 6px 5px;
}

.face-canvas {
  width: 58px;
  height: 58px;
  display: block;
}

.face-name {
  color: var(--text-dim);
  font-size: 10.5px;
  font-weight: 760;
  line-height: 1.1;
}

.face-option.is-active .face-name {
  color: var(--accent);
}

.accessory-list {
  display: grid;
  gap: 14px;
}

.hint {
  color: var(--text-dim);
  font-size: 12.5px;
  line-height: 1.45;
}

.status {
  min-height: 18px;
  color: var(--text-dim);
  font-size: 13px;
  line-height: 18px;
}

.status.is-bad {
  color: var(--danger);
}

.end-panel {
  position: fixed;
  left: calc(18px + var(--safe-left));
  bottom: calc(18px + var(--safe-bottom));
  display: grid;
  gap: 12px;
  padding: 18px;
  pointer-events: auto;
}

.end-panel.is-death {
  inset: 0;
  z-index: 6;
  place-content: center;
  justify-items: center;
  padding: 28px;
  text-align: center;

  /* This shares a rule with the menu and pause cards, which pins width to 520px and gives them a
     bordered, rounded, shadowed panel. `inset: 0` cannot stretch a box that has an explicit width,
     so without these the death wash rendered as a 520px column with a hard rounded edge down the
     middle of the screen instead of covering it.

     `max-width` has to be reset for the same reason and was the half that got missed: the narrow
     breakpoint also caps these panels at `100vw - 24px - the safe areas`, which is a card inset
     and nonsense for a full-screen wash. It left the red exactly 24px short of the edge on a
     phone — full height, visibly not full width. */
  width: auto;
  max-width: none;
  max-height: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;

  /* Thinner than it was, and barely blurred. The world keeps running behind this now, and the
     point of the wash is to colour the aftermath rather than hide it — at 0.58 with a 3px blur
     you could tell something was moving but not what. */
  background: rgba(92, 8, 18, 0.42);
  backdrop-filter: blur(1px);
  -webkit-backdrop-filter: blur(1px);

  /* Fades in once, then breathes. Slow and shallow on purpose: the wash sits under a screen you
     are reading, so it should register as the game still being alive underneath rather than as a
     flash competing with the title. */
  animation:
    death-wash 520ms ease-out both,
    death-breathe 3.6s ease-in-out 520ms infinite;
}

/* The stun: the same full-screen wash as the death, in the game's own colours rather than the
   danger red.
 
   That is the point of it. The two screens used to differ in palette for no reason a player could
   name — reported as "when the screen shows the revive, it's a different theme" — because one was
   the ordinary panel and the other the red one, and both said you were dead. Now the red IS the
   death: as long as you are still in the round the colours are the round's, and they turn when
   you actually go. It does not breathe either; a pulsing background under a decision on a clock
   is a distraction, where under a score you are reading it is atmosphere. */
.end-panel.is-stunned {
  inset: 0;
  z-index: 6;
  place-content: center;
  justify-items: center;
  padding: 28px;
  text-align: center;

  /* Same overrides as .is-death, and for the same reason: these panels share a rule that pins
     them to a 520px card, and `inset: 0` cannot stretch a box with an explicit width. */
  width: auto;
  max-width: none;
  max-height: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;

  background: rgba(4, 8, 16, 0.52);
  backdrop-filter: blur(1px);
  -webkit-backdrop-filter: blur(1px);
  animation: death-wash 320ms ease-out both;
}

/* The explanation under the clock now, not the thing being announced, so it sits well below the
   size the same words take on the death screen. */
.end-panel.is-stunned h2 {
  color: var(--text);
  font-size: clamp(24px, 5vw, 40px);
  letter-spacing: 0.06em;
  text-shadow: 0 0 28px rgba(0, 0, 0, 0.6);
  animation: death-title 460ms cubic-bezier(.2, 1.4, .4, 1) both;
}

.end-panel.is-stunned p { color: var(--text-dim); font-size: 16px; }

/* Quiet on purpose. It is the alternative to the thing the player came here for, and it has to be
   findable without competing with it — the mistake in the other direction is a give-up button that
   reads as the primary action on a screen selling a way to carry on. */
.give-up-button {
  grid-column: 1 / -1;
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.24);
  color: var(--text-dim);
}

/* The clock, which is the largest thing on this screen and the first thing read.
 
   Asked for by name: above the "you are stunned" and bigger than it. It sat under the buttons in
   small print, which is where the eye arrives last, for the number the whole decision is being
   made against.
 
   tabular-nums and a fixed min-width are what stop it rocking sideways as it counts. The digit is
   the element the eye is fixed on and a proportional one moves its own centre every second, which
   at this size reads as a fault rather than as a clock. */
/* Scoped to the panel, and deliberately at three classes' worth of specificity: `.end-panel
   .is-stunned p` below sets the sub-line to 16px, and a bare `.stun-clock` loses to it — which is
   how the clock shipped, briefly, at the size of a caption. */
.end-panel.is-stunned .stun-clock {
  margin: 0 0 -6px;
  min-width: 2ch;
  text-align: center;
  font-size: clamp(56px, 13vw, 88px);
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  text-shadow: 0 0 30px color-mix(in srgb, var(--accent) 40%, transparent);
}



.end-panel.is-death h2 {
  color: #fff1f2;
  font-size: clamp(38px, 7vw, 76px);
  letter-spacing: 0.06em;
  text-shadow: 0 0 28px rgba(255, 64, 82, 0.72);
  animation: death-title 700ms cubic-bezier(.2, 1.4, .4, 1) both;
}

.end-panel.is-death p { color: rgba(255, 225, 228, 0.86); font-size: 16px; }

/* The win: the third state of this panel, and the one that was missing.
 
   A Royale winner used to get `.is-death` — the danger wash, at the one moment in the game worth
   celebrating. It is the same full-screen wash as the other two, because the round is still being
   drawn behind all three and a card would hide the thing the player has just won; what changes is
   the colour, which is the game's own accent rather than the danger red. The change of palette is
   the outcome, exactly as it is between the stun and the death.
 
   It breathes like the death rather than sitting still like the stun. There is no clock here and
   nothing to decide, so movement under the score is atmosphere rather than a distraction — the
   same argument that keeps it off the stunned screen, pointed the other way. */
.end-panel.is-win {
  inset: 0;
  z-index: 6;
  place-content: center;
  justify-items: center;
  padding: 28px;
  text-align: center;

  /* Same overrides as the other two, and for the same reason: these panels share a rule that pins
     them to a 520px card, and `inset: 0` cannot stretch a box with an explicit width. */
  width: auto;
  max-width: none;
  max-height: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;

  background: color-mix(in srgb, var(--accent) 16%, rgba(4, 8, 16, 0.62));
  backdrop-filter: blur(1px);
  -webkit-backdrop-filter: blur(1px);
  animation:
    death-wash 520ms ease-out both,
    win-breathe 3.6s ease-in-out 520ms infinite;
}

/* The placement, at the size the death screen gives its verdict. On a win the placement IS the
   news — `#1` is the whole headline — where on a death it is a consolation in the line
   underneath. That asymmetry is the app's (RoundVerdict.WonHeadline) and it is deliberate. */
.end-panel.is-win h2 {
  color: var(--accent);
  font-size: clamp(38px, 7vw, 76px);
  letter-spacing: 0.06em;
  text-shadow: 0 0 30px color-mix(in srgb, var(--accent) 55%, transparent);
  animation: death-title 700ms cubic-bezier(.2, 1.4, .4, 1) both;
}

.end-panel.is-win p { color: var(--text); font-size: 16px; }

/* Scoped to the panel, and deliberately at two classes' worth of specificity: `.end-panel.is-win
   p` below sets every paragraph on this screen to 16px, and a bare `.end-trophy` loses to it —
   which shipped the trophy at the size of a full stop, exactly as the stun clock once shipped at
   the size of a caption. Caught in a screenshot, which is the only thing that catches it. */
.end-panel.is-win .end-trophy {
  margin: 0;
  font-size: clamp(44px, 9vw, 64px);
  line-height: 1;
  filter: drop-shadow(0 0 22px color-mix(in srgb, #F5C542 60%, transparent));
  animation: trophy-pop 700ms cubic-bezier(0.2, 1.5, 0.4, 1) both;
}

/* What coming first actually was, rather than a second way of saying "#1". */
.end-panel.is-win .end-crown {
  margin: -4px 0 0;
  font-size: 18px;
  font-weight: 700;
  color: var(--text);
}

/* ---- the middle tier -------------------------------------------------------------------------

   A side that came second got `.is-death`: the red wash, the death sting and the music cut, under
   the headline "YOUR TEAM CAME 2ND". That is the same fault the win branch above was written to
   fix, one tier down, with the sound saying the opposite of the words.

   The red is not about a placement. It is about YOUR SNAKE dying, and in Team Deathmatch those are
   different events that need not happen in the same minute: your own death is terminal but not
   final, the round carries on and you watch your side, and a RESULT can arrive while your snake is
   still perfectly alive — once the ring has closed the round is decided on the field, so second,
   third and last can all still have snakes standing when the verdict lands.

   So there are four states on this panel now and `.is-death` is reached only by an individual
   verdict. Both new ones are the same full-screen wash as the other three, because the round is
   still being drawn behind all of them; what changes is the colour, which is how this panel has
   always said what happened. Which class a placement wears is Core's answer, carried in
   js/teams.js as `teamTone` — see RoundVerdict.TeamTone for why the cut is at second. */
.end-panel.is-runner-up,
.end-panel.is-placed {
  inset: 0;
  z-index: 6;
  place-content: center;
  justify-items: center;
  padding: 28px;
  text-align: center;

  /* Same overrides as the other three, and for the same reason: these panels share a rule that
     pins them to a 520px card, and `inset: 0` cannot stretch a box with an explicit width. */
  width: auto;
  max-width: none;
  max-height: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;

  backdrop-filter: blur(1px);
  -webkit-backdrop-filter: blur(1px);

  /* Fades in and then holds. It does not breathe: the death and the win both do, because both are
     announcements, and a placement is a result being read. The stunned screen makes the same call
     from the other direction. */
  animation: death-wash 520ms ease-out both;
}

/* Second of more than two: the near miss, with a silver cast over the dark rather than a colour of
   its own. It is the only losing outcome a player replays in their head, and in a four-sided round
   it is the natural cut — but it is still a loss, so it is marked rather than celebrated. */
.end-panel.is-runner-up {
  background: color-mix(in srgb, #C7CEDB 9%, rgba(4, 8, 16, 0.60));
}

/* Third down to last: the dark on its own, and nothing else. The words carry it — "YOUR TEAM CAME
   3RD" and "YOUR TEAM CAME LAST" already differ in the one way that matters, and three shades of
   losing would only make the worst one feel designed for. */
.end-panel.is-placed {
  background: rgba(4, 8, 16, 0.64);
}

.end-panel.is-runner-up h2,
.end-panel.is-placed h2 {
  color: var(--text);
  font-size: clamp(38px, 7vw, 76px);
  letter-spacing: 0.06em;
  animation: death-title 700ms cubic-bezier(.2, 1.4, .4, 1) both;
}

/* The glow is the whole difference between the two, at half the trophy's strength for the one that
   nearly had it and absent for the ones that did not. */
.end-panel.is-runner-up h2 { text-shadow: 0 0 24px rgba(199, 206, 219, 0.45); }
.end-panel.is-placed h2 { text-shadow: 0 0 24px rgba(0, 0, 0, 0.55); }

.end-panel.is-runner-up p,
.end-panel.is-placed p { color: var(--text-dim); font-size: 16px; }

/* The medal shares the trophy's box and its pop, and only its colour changes: a second-place mark
   that arrived differently from a first-place one would read as a different kind of thing rather
   than as the next one down. Scoped at two classes' worth of specificity for the reason the
   trophy's rule records — `.is-runner-up p` above sets every paragraph on this panel to 16px, and
   a bare `.end-trophy` loses to it and ships at the size of a full stop. */
.end-panel.is-runner-up .end-trophy {
  margin: 0;
  font-size: clamp(44px, 9vw, 64px);
  line-height: 1;
  filter: drop-shadow(0 0 18px rgba(199, 206, 219, 0.55));
  animation: trophy-pop 700ms cubic-bezier(0.2, 1.5, 0.4, 1) both;
}

.end-panel.is-runner-up .end-crown,
.end-panel.is-placed .end-crown {
  margin: -4px 0 0;
  font-size: 18px;
  font-weight: 700;
  color: var(--text);
}

/* ---- where all four sides came --------------------------------------------------------------

   The team round's result, under the verdict on the same shared panel. A losing side used to be
   told "YOU ARE DEAD" and nothing about where it finished, which in a mode whose entire result is
   a team's standing is the one fact the screen existed to carry.

   A fixed-width column rather than a full-width table: the wash covers the screen, and four short
   rows stretched across a 1440px window would read as a list of unrelated words. The rank column
   is an em-based track so "still in" and "1st" cannot shove the names out of line. */
.end-teams {
  list-style: none;
  width: min(100%, 300px);
  margin: 18px 0 0;
  padding: 16px 0 0;
  border-top: 1px solid rgba(255, 255, 255, 0.16);
  display: grid;
  gap: 8px;
  text-align: left;
}

.end-team-row {
  display: grid;
  grid-template-columns: 4.2em 12px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  font-size: 15px;
}

.end-team-place { font-weight: 700; opacity: 0.72; }

/* A side that has not finished yet reads as a note rather than as a rank, because it is not one:
   your own placement is final the moment your side is out, and the sides above you are still being
   sorted by a round that has not ended. */
.end-team-place.is-unplaced { font-size: 11.5px; font-weight: 600; opacity: 0.6; }

.end-team-swatch { width: 12px; height: 12px; border-radius: 3px; }

.end-team-name {
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.end-team-yours {
  font-size: 10.5px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  white-space: nowrap;
  opacity: 0.85;
}

/* The two washes set every paragraph on the panel; the list is not a paragraph, so it needs its
   own colour or it inherits the page's and disappears into the red. */
.end-panel.is-death .end-teams { color: rgba(255, 225, 228, 0.92); }
.end-panel.is-win .end-teams { color: var(--text); }
.end-panel.is-runner-up .end-teams,
.end-panel.is-placed .end-teams { color: var(--text); }

.end-panel.is-death .end-team-row.is-yours .end-team-name,
.end-panel.is-death .end-team-row.is-yours .end-team-yours { color: #fff1f2; }

.end-panel.is-win .end-team-row.is-yours .end-team-name,
.end-panel.is-win .end-team-row.is-yours .end-team-yours { color: var(--accent); }

/* Your own row, on the two tiers that are neither. Silver for the side that nearly had it and
   plain text for the rest — the same one-step difference the headline makes, so a reader finding
   their row and a reader reading the verdict are told the same thing. */
.end-panel.is-runner-up .end-team-row.is-yours .end-team-name,
.end-panel.is-runner-up .end-team-row.is-yours .end-team-yours { color: #edf1f7; }

.end-panel.is-placed .end-team-row.is-yours .end-team-name,
.end-panel.is-placed .end-team-row.is-yours .end-team-yours { color: var(--text); }

/* A team verdict is a sentence where an individual one is a number. "YOUR TEAM CAME LAST" at the
   76px ceiling above took three lines on a 390px phone and pushed the standings — the thing the
   headline exists to introduce — off the bottom of the screen. */
.end-panel.is-death:has(.end-teams:not(.is-hidden)) h2,
.end-panel.is-win:has(.end-teams:not(.is-hidden)) h2,
.end-panel.is-runner-up:has(.end-teams:not(.is-hidden)) h2,
.end-panel.is-placed:has(.end-teams:not(.is-hidden)) h2 {
  font-size: clamp(26px, 6vw, 44px);
}

/* Fixed rather than contained, so the burst covers the screen instead of the panel's content box,
   and inert to the pointer so it can never swallow a tap on Play again. */
.win-confetti {
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 7;
}

.win-confetti span {
  position: absolute;
  top: -6vh;
  left: var(--x);
  width: 9px;
  height: 14px;
  border-radius: 2px;
  background: hsl(var(--hue) 90% 62%);
  animation: confetti-fall 2.8s linear var(--delay) forwards;
}

@keyframes win-breathe {
  0%, 100% { background-color: color-mix(in srgb, var(--accent) 14%, rgba(4, 8, 16, 0.62)); }
  50%      { background-color: color-mix(in srgb, var(--accent) 26%, rgba(4, 8, 16, 0.52)); }
}

@keyframes trophy-pop {
  0%   { transform: scale(0.2) rotate(-25deg); opacity: 0; }
  60%  { transform: scale(1.15) rotate(6deg); opacity: 1; }
  100% { transform: scale(1) rotate(0deg); opacity: 1; }
}

@keyframes confetti-fall {
  0%   { transform: translate3d(0, 0, 0) rotate(0deg); opacity: 1; }
  85%  { opacity: 1; }
  100% { transform: translate3d(var(--drift), 112vh, 0) rotate(var(--spin)); opacity: 0; }
}

@keyframes death-wash { from { opacity: 0; } to { opacity: 1; } }
@keyframes death-breathe {
  0%, 100% { background-color: rgba(92, 8, 18, 0.5); }
  50%      { background-color: rgba(126, 14, 30, 0.68); }
}
@keyframes death-title { from { transform: scale(.82); opacity: 0; } to { transform: scale(1); opacity: 1; } }

.pause-panel {
  position: fixed;
  z-index: 5;
  top: 50%;
  left: 50%;
  display: grid;
  gap: 14px;
  padding: 18px;
  transform: translate(-50%, -50%);
  pointer-events: auto;
}

/* One column, because there is one action. It was two — "Home" and "Build Snake" — and the second
   left the round for a screen that cannot affect it, which is the bug this row was cut down for. A
   1fr 1fr grid would leave the survivor sitting in half the panel with a hole beside it. */
.pause-actions {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
}

.end-panel h2 {
  font-size: 28px;
  line-height: 1.05;
}

.end-panel p {
  color: var(--text-dim);
}

.end-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

.revive-button {
  grid-column: 1 / -1;
}

@media (max-width: 560px) {
  .menu-shell {
    align-items: safe center;
    justify-items: center;
    /* Named so .menu-panel's max-height follows the padding rather than guessing at it. */
    --shell-top: 54px;
    --shell-bottom: 12px;
    padding:
      calc(var(--shell-top) + var(--safe-top))
      calc(12px + var(--safe-right))
      calc(var(--shell-bottom) + var(--safe-bottom))
      calc(12px + var(--safe-left));
  }

  .menu-panel,
  .builder-panel,
  .end-panel,
  .pause-panel {
    width: 100%;
    max-width: calc(100vw - 24px - var(--safe-left) - var(--safe-right));
  }

  h1 {
    font-size: 30px;
  }

  .compact-row,
  .builder-grid,
  .action-grid,
  .builder-actions,
  .account-card,
  .account-main,
  .local-auth,
  .audio-grid,
  .launch-grid,
  .home-dock {
    grid-template-columns: 1fr;
  }

  #auth-email,
  #auth-password,
  .auth-submit-row {
    grid-column: 1 / -1;
  }

  .app-menu-panel {
    width: min(390px, 100vw);
  }

  .coin-badge {
    justify-items: start;
  }

  .auth-actions {
    justify-content: start;
    max-width: none;
    flex-wrap: wrap;
  }

  .range-row {
    grid-template-columns: 1fr;
    gap: 4px;
  }

  .match-field {
    max-width: none;
  }

  .pattern-grid,
  .skin-grid,
  .theme-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Larger lobby identity; still removed completely for every active match state. */
.site-brand {
  /* The top padding is clearance for the crown, not decoration. The crown is absolutely placed at
     top: -17px against this header, so it hangs above the box and the wordmark has to start below
     where it lands. Measured on the live desktop layout: the crown's lower edge sat at y=42 while
     the wordmark's box began at y=28, overlapping the first line by 14px. Wider viewports were the
     only ones affected because this breakpoint sets the wordmark at 30px against the phone's 24px
     while lifting the crown only 4px further, so the larger type grew up into it.

     Keyed to the crown's own numbers rather than a round figure: 17px of lift, plus the 4px the
     crown's tilt carries it past its box, plus 5px of air. Change one and this has to move. */
  min-height: 90px !important;
  padding: 26px 10px 6px 4px !important;
}
.brand-type strong { font-size: 30px !important; }
.brand-type strong + strong { font-size: 28px !important; }
.brand-crown {
  top: -17px !important;
  left: 27px !important;
  width: 47px !important;
  height: 38px !important;
}
@media (max-width: 560px) {
  .site-brand {
    min-height: 58px !important;
    padding: 6px 8px 4px 3px !important;
  }
  .brand-type strong { font-size: 24px !important; }
  .brand-type strong + strong { font-size: 22px !important; }
  .brand-crown {
    top: -13px !important;
    left: 21px !important;
    width: 38px !important;
    height: 31px !important;
  }
}

/* The lobby brand is useful before launch, but competes with the arena HUD during play. */
body.is-playing .site-brand {
  opacity: 0 !important;
  visibility: hidden !important;
  pointer-events: none !important;
  transform: translateY(-12px) !important;
}

/* Keep the wordmark readable without placing a panel over the arena. */
.site-brand {
  min-height: 48px !important;
  padding: 4px 6px 4px 2px !important;
  border: 0 !important;
  border-radius: 0 !important;
  background: transparent !important;
  box-shadow: none !important;
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
}

/* The account/guest pill belongs to the lobby, never over the in-game HUD. */
body.is-playing .account-menu-toggle {
  opacity: 0 !important;
  visibility: hidden !important;
  pointer-events: none !important;
  transform: translateY(-12px) !important;
}

/* Final lobby composition: the menu is an open game scene, with glass reserved for actions. */
.site-brand {
  display: inline-flex;
  align-items: center;
  position: fixed;
  gap: 0;
  min-height: 48px;
  padding: 4px 6px 4px 2px;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.brand-crown {
  position: absolute;
  top: -11px;
  left: 18px;
  width: 31px;
  height: 25px;
  object-fit: contain;
  filter: drop-shadow(0 0 5px rgba(248, 211, 106, 0.25));
  transform: rotate(-16deg);
}

.brand-type {
  display: grid;
  line-height: 0.86;
  text-align: left;
  margin-left: 0;
}

.brand-type strong {
  color: var(--text);
  font-family: Poppins, Inter, sans-serif;
  font-size: 20px;
  font-style: italic;
  font-weight: 950;
  letter-spacing: 0.035em;
}

.brand-type strong + strong {
  color: var(--accent);
  font-size: 18px;
}

.winners-open-button {
  grid-column: 1 / -1;
}

.scoreboard-panel {
  width: min(760px, calc(100vw - 56px - var(--safe-left) - var(--safe-right)));
  max-height: calc(100vh - 122px - var(--safe-top) - var(--safe-bottom));
  gap: 22px;
  padding: 28px;
  border-radius: 18px;
  overflow: auto;
}

.page-heading {
  display: flex;
  align-items: center;
  gap: 14px;
}

.page-heading h1 {
  color: var(--text);
  font-size: clamp(30px, 5vw, 46px);
  font-weight: 900;
  line-height: 1;
}

.scoreboard-search {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: 10px;
}

.scoreboard-search .play-button {
  min-width: 112px;
  min-height: 48px;
}

.scoreboard-page-list {
  min-height: 300px;
  align-content: start;
  gap: 8px;
}

.scoreboard-page-list .scoreboard-row {
  min-height: 52px;
  padding: 0 12px;
  border: 1px solid var(--panel-border);
  border-radius: 10px;
  background: rgba(30, 38, 51, 0.54);
}

.scoreboard-pagination,
.pagination-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.scoreboard-page-status {
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 750;
}

.pagination-actions .plain-button {
  min-height: 42px;
  padding-inline: 14px;
}

.sr-live {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

@media (max-width: 560px) {
  .site-brand {
    min-height: 48px;
    padding: 5px 11px 5px 9px;
  }

  .brand-crown { top: -9px; left: 13px; width: 25px; height: 21px; }
  .brand-type strong { font-size: 16px; }
  .brand-type strong + strong { font-size: 15px; }

  .scoreboard-panel {
    width: min(calc(100vw - 24px - var(--safe-left) - var(--safe-right)), 620px);
    max-height: calc(100vh - 88px - var(--safe-top) - var(--safe-bottom));
    padding: 18px 14px;
  }

  .scoreboard-search {
    grid-template-columns: 1fr;
  }

  .scoreboard-search .play-button { width: 100%; }
  .scoreboard-pagination { align-items: stretch; flex-direction: column; }
  .pagination-actions { width: 100%; }
  .pagination-actions .plain-button { flex: 1; }
}

@media (max-height: 560px) and (orientation: landscape) {
  .menu-shell {
    align-items: safe center;
  }

  .menu-panel {
    max-width: 680px;
  }

  #home-panel {
    align-items: start;
  }

  #home-panel .panel-head,
  #home-panel .status {
    grid-column: 1 / -1;
  }

  .snake-preview {
    height: 104px;
  }
}

/* Premium lobby/settings refresh */
:root {
  --bg: #0e141a;

  /* Translucent, not the opaque #141c24 this used to be. The panels carry backdrop-filter, which
     does nothing behind a solid colour — so the glass was only ever glass in the stylesheet, and
     the live arena now running behind the lobby was completely hidden by it. Kept dark enough that
     white text on it still clears 4.5:1 against the brightest thing the arena can put behind it. */
  --panel: rgba(20, 28, 36, 0.68);
  --panel-strong: rgba(30, 38, 51, 0.82);
  --panel-soft: rgba(30, 38, 51, 0.72);
  --panel-border: rgba(255, 255, 255, 0.08);
  --text: #ffffff;
  --text-dim: #a7b0bb;
  --muted: rgba(167, 176, 187, 0.7);
  --accent: #14b8a6;
  --accent-hover: #25d2bf;
  --blue: #3882f6;
  --radius: 14px;
  --radius-lg: 16px;
  --shadow-soft: 0 22px 70px rgba(0, 0, 0, 0.34);
  --shadow-card: 0 18px 42px rgba(0, 0, 0, 0.22);
}

html {
  background: var(--bg);
}

body {
  background:
    radial-gradient(circle at 18% 18%, rgba(20, 184, 166, 0.08), transparent 28%),
    radial-gradient(circle at 78% 22%, rgba(56, 130, 246, 0.08), transparent 30%),
    var(--bg);
  color: var(--text);
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

h1,
h2,
.site-brand,
.play-button,
.plain-button,
.segment,
.source-button,
.auth-toggle-button {
  font-family: Poppins, Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

button,
input {
  transition:
    background-color 180ms ease-out,
    border-color 180ms ease-out,
    box-shadow 180ms ease-out,
    color 180ms ease-out,
    opacity 180ms ease-out,
    transform 180ms ease-out;
}

button:focus-visible,
input:focus-visible {
  outline: 2px solid rgba(20, 184, 166, 0.95);
  outline-offset: 3px;
}

.site-brand {
  top: calc(24px + var(--safe-top));
  left: calc(28px + var(--safe-left));
  right: auto;
  width: auto;
  min-height: 42px;
  padding: 0 16px;
  transform: none;
  border: 1px solid var(--panel-border);
  border-radius: 999px;
  background: rgba(20, 28, 36, 0.78);
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.22);
  color: var(--text);
  letter-spacing: 0;
  pointer-events: none;
  backdrop-filter: blur(10px);
}

.brand-light {
  width: 10px;
  height: 10px;
  border-radius: 999px;
  background: var(--accent);
  box-shadow: 0 0 18px rgba(20, 184, 166, 0.68);
}

.account-menu-toggle {
  position: fixed;
  z-index: 7;
  top: calc(22px + var(--safe-top));
  right: calc(28px + var(--safe-right));
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 46px;
  max-width: calc(100vw - 56px - var(--safe-left) - var(--safe-right));
  padding: 6px 12px 6px 8px;
  border: 1px solid var(--panel-border);
  border-radius: 999px;
  background: rgba(20, 28, 36, 0.82);
  color: var(--text);
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.24);
  backdrop-filter: blur(10px);
  cursor: pointer;
}

.account-menu-toggle:hover {
  border-color: rgba(20, 184, 166, 0.32);
  background: rgba(30, 38, 51, 0.92);
  transform: translateY(-1px);
}

.hamburger-icon,
.hamburger-icon::before,
.hamburger-icon::after,
.hamburger-icon > span {
  display: block;
  width: 20px;
  height: 2px;
  border-radius: 999px;
  background: currentColor;
}

.hamburger-icon {
  position: relative;
  flex: 0 0 auto;
  background: transparent;
}

.hamburger-icon::before,
.hamburger-icon::after {
  content: "";
  position: absolute;
  left: 0;
}

.hamburger-icon::before {
  top: -6px;
}

.hamburger-icon::after {
  top: 6px;
}

.top-coin-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 32px;
  padding: 0 10px;
  border: 1px solid rgba(20, 184, 166, 0.28);
  border-radius: 999px;
  background: rgba(20, 184, 166, 0.1);
  color: var(--text);
  font-size: 13px;
  font-weight: 800;
  line-height: 1;
}

.coin-dot {
  width: 13px;
  height: 13px;
  border: 2px solid rgba(255, 255, 255, 0.7);
  border-radius: 999px;
  background:
    radial-gradient(circle at 38% 30%, #fff7b0 0 22%, transparent 23%),
    linear-gradient(135deg, #fef08a, #f59e0b);
  box-shadow: 0 0 14px rgba(245, 158, 11, 0.34);
}

.player-avatar {
  position: relative;
  display: inline-block;
  flex: 0 0 auto;
  border-radius: 999px;
  background:
    radial-gradient(circle at 42% 34%, rgba(255, 255, 255, 0.88) 0 8%, transparent 9%),
    radial-gradient(circle at 60% 34%, rgba(255, 255, 255, 0.88) 0 8%, transparent 9%),
    linear-gradient(135deg, var(--accent), var(--blue));
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.2),
    0 0 20px rgba(20, 184, 166, 0.18);
}

.player-avatar::before,
.player-avatar::after {
  content: "";
  position: absolute;
  top: 36%;
  width: 4px;
  height: 4px;
  border-radius: 999px;
  background: #071017;
}

.player-avatar::before {
  left: 39%;
}

.player-avatar::after {
  right: 28%;
}

.mini-avatar {
  width: 34px;
  height: 34px;
}

.account-avatar {
  width: 52px;
  height: 52px;
}

.top-account-name {
  color: var(--text);
  font-size: 14px;
  font-weight: 750;
  white-space: nowrap;
}

.chevron-icon {
  width: 9px;
  height: 9px;
  border-right: 2px solid var(--text-dim);
  border-bottom: 2px solid var(--text-dim);
  transform: translateY(-2px) rotate(45deg);
}

.menu-shell {
  align-items: center;
  justify-items: center;
  min-height: 100vh;
  /* Named so .menu-panel's max-height follows the padding rather than guessing at it. This is the
     pair a desktop and a phone in landscape both get, and the one the cap was furthest out on:
     114px of padding against a cap that reserved 36. */
  --shell-top: 86px;
  --shell-bottom: 28px;
  padding:
    calc(var(--shell-top) + var(--safe-top))
    calc(28px + var(--safe-right))
    calc(var(--shell-bottom) + var(--safe-bottom))
    calc(28px + var(--safe-left));
}

.menu-panel,
.builder-panel,
.end-panel,
.pause-panel {
  border: 1px solid var(--panel-border);
  background: rgba(20, 28, 36, 0.82);
  box-shadow: var(--shadow-card);
  backdrop-filter: blur(12px);
}

.home-head {
  justify-content: center;
  text-align: center;
}

.home-head h1 {
  font-size: clamp(42px, 5vw, 56px);
  font-weight: 900;
  line-height: 1;
  letter-spacing: 0;
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.52);
}

.launch-grid {
  display: grid;
  grid-template-columns: minmax(420px, 1.2fr) minmax(330px, 0.8fr);
  align-items: stretch;
  gap: 18px;
}

.preview-column {
  min-width: 0;
}

.play-column {
  display: grid;
  align-content: start;
  gap: 14px;
  min-height: 276px;
  padding: 18px;
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-lg);
  background: var(--panel-strong);
  box-shadow: var(--shadow-card);
}

.compact-row {
  display: grid;
  grid-template-columns: 1fr minmax(120px, 150px);
  gap: 12px;
}

.segmented,
.auth-mode-switch,
.source-row {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 4px;
  padding: 4px;
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  background: rgba(12, 18, 24, 0.55);
}

.segment,
.source-button {
  min-height: 44px;
  border: 1px solid transparent;
  border-radius: 10px;
  background: transparent;
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0;
  cursor: pointer;
}

.segment:hover,
.source-button:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.04);
}

.segment.is-active,
.source-button.is-active {
  border-color: rgba(20, 184, 166, 0.28);
  background: linear-gradient(180deg, var(--accent-hover), var(--accent));
  color: var(--text);
  box-shadow: 0 10px 24px rgba(20, 184, 166, 0.18);
}

.field {
  gap: 6px;
  min-width: 0;
}

.field span {
  color: var(--muted);
  font-size: 11px;
  font-weight: 850;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.field input,
.local-auth input {
  height: 48px;
  border: 1px solid var(--panel-border);
  border-radius: 12px;
  background: rgba(12, 18, 24, 0.74);
  color: var(--text);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.02);
}

.field input::placeholder,
.local-auth input::placeholder {
  color: rgba(167, 176, 187, 0.55);
}

.action-grid {
  display: grid;
  gap: 12px;
}

.play-button,
.plain-button,
.auth-toggle-button,
.auth-button {
  min-height: 60px;
  border-radius: 12px;
  font-size: 16px;
  font-weight: 850;
  letter-spacing: 0;
}

.play-button {
  border: 1px solid rgba(255, 255, 255, 0.06);
  background: linear-gradient(180deg, var(--accent-hover), var(--accent));
  color: var(--text);
  box-shadow: 0 16px 34px rgba(20, 184, 166, 0.22);
}

.play-button:hover {
  background: linear-gradient(180deg, #35e0cd, #16c6b3);
  transform: translateY(-1px);
}

.play-button.secondary {
  background: linear-gradient(180deg, #4a92ff, var(--blue));
  color: var(--text);
  box-shadow: 0 16px 34px rgba(56, 130, 246, 0.22);
}

.play-button.secondary:hover {
  background: linear-gradient(180deg, #5ca0ff, #428cff);
}

.plain-button {
  border: 1px solid var(--panel-border);
  background: rgba(12, 18, 24, 0.7);
  color: rgba(255, 255, 255, 0.84);
  box-shadow: none;
}

.plain-button:hover {
  border-color: rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.055);
  color: var(--text);
  transform: translateY(-1px);
}

#builder-open {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
}

#builder-open::before {
  content: "";
  width: 18px;
  height: 18px;
  background: currentColor;
  opacity: 0.8;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m14.6 4.6 4.8 4.8'/%3E%3Cpath d='M18.5 2.5a2.1 2.1 0 0 1 3 3L7 20l-4 1 1-4Z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m14.6 4.6 4.8 4.8'/%3E%3Cpath d='M18.5 2.5a2.1 2.1 0 0 1 3 3L7 20l-4 1 1-4Z'/%3E%3C/svg%3E") center / contain no-repeat;
}

.home-dock {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
}

.scoreboard-card {
  padding: 18px;
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-lg);
  background: var(--panel-strong);
  box-shadow: var(--shadow-card);
}

.scoreboard-head {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 14px;
}

.trophy-icon {
  position: relative;
  width: 44px;
  height: 44px;
  border: 1px solid rgba(20, 184, 166, 0.14);
  border-radius: 13px;
  background: rgba(20, 184, 166, 0.1);
}

.trophy-icon::before {
  content: "";
  position: absolute;
  inset: 11px;
  background: var(--accent);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9H4.5a2.5 2.5 0 0 1 0-5H6'/%3E%3Cpath d='M18 9h1.5a2.5 2.5 0 0 0 0-5H18'/%3E%3Cpath d='M4 22h16'/%3E%3Cpath d='M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22'/%3E%3Cpath d='M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22'/%3E%3Cpath d='M18 2H6v7a6 6 0 0 0 12 0Z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9H4.5a2.5 2.5 0 0 1 0-5H6'/%3E%3Cpath d='M18 9h1.5a2.5 2.5 0 0 0 0-5H18'/%3E%3Cpath d='M4 22h16'/%3E%3Cpath d='M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22'/%3E%3Cpath d='M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22'/%3E%3Cpath d='M18 2H6v7a6 6 0 0 0 12 0Z'/%3E%3C/svg%3E") center / contain no-repeat;
}

.scoreboard-head h2 {
  margin: 0;
  color: var(--text);
  font-size: 18px;
  font-weight: 850;
  letter-spacing: 0;
}

.scoreboard-subtitle {
  margin-top: 3px;
  color: var(--text-dim);
  font-size: 14px;
  line-height: 1.35;
}

.scoreboard-list {
  margin-top: 14px;
}

.scoreboard-row,
.scoreboard-empty {
  border-color: var(--panel-border);
  background: rgba(12, 18, 24, 0.42);
  color: var(--text-dim);
}

.refresh-button {
  background: rgba(255, 255, 255, 0.04);
}

.app-menu {
  display: grid;
  justify-items: end;
  align-items: stretch;
  padding: 0;
  background: rgba(3, 7, 11, 0.58);
  opacity: 1;
  visibility: visible;
  backdrop-filter: blur(3px);
  transition:
    opacity 200ms ease-out,
    backdrop-filter 200ms ease-out;
}

.app-menu.is-hidden {
  display: grid !important;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
}

.app-menu-panel {
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: min(416px, calc(100vw - 24px));
  height: calc(100vh - var(--safe-top) - var(--safe-bottom));
  min-height: 0;
  padding: calc(24px + var(--safe-top)) calc(24px + var(--safe-right)) calc(20px + var(--safe-bottom)) 24px;
  border: 1px solid var(--panel-border);
  border-right: 0;
  border-radius: var(--radius-lg) 0 0 var(--radius-lg);
  background:
    linear-gradient(180deg, rgba(20, 28, 36, 0.98), rgba(15, 22, 30, 0.98)),
    var(--panel);
  box-shadow: -26px 0 70px rgba(0, 0, 0, 0.42);
  overflow-y: auto;
  transform: translateX(0);
  transition: transform 210ms ease-out;
}

.app-menu.is-hidden .app-menu-panel {
  transform: translateX(24px);
}

.app-menu-head {
  flex: 0 0 auto;
}

.app-menu-head h2 {
  color: var(--text);
  font-size: 28px;
  font-weight: 850;
  letter-spacing: 0;
}

.panel-actions {
  gap: 8px;
}

.icon-button {
  border: 1px solid var(--panel-border);
  background-color: rgba(255, 255, 255, 0.045);
}

.icon-button:hover {
  border-color: rgba(255, 255, 255, 0.14);
  background-color: rgba(255, 255, 255, 0.075);
}

.drawer-section {
  display: grid;
  gap: 12px;
}

.control-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  margin-top: 10px;
  cursor: pointer;
}

.control-row span {
  display: grid;
  gap: 3px;
  font-size: 14px;
  font-weight: 650;
}

.control-row small {
  color: var(--muted);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.35;
}

.control-row input[type="checkbox"] {
  flex: none;
  width: 20px;
  height: 20px;
  margin-top: 2px;
  accent-color: var(--accent);
}

/* A control-row whose control is too wide to sit beside its label, so it goes underneath it.
   The range reuses .volume-slider: one slider look in the drawer, not two. */
.slider-row {
  display: grid;
  gap: 8px;
  margin-top: 10px;
  cursor: pointer;
}

.slider-row span {
  display: grid;
  gap: 3px;
  font-size: 14px;
  font-weight: 650;
}

.slider-row small {
  color: var(--muted);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.35;
}

/* A subordinate row whose parent is off. Dimmed and inert rather than removed, so the
   relationship between the two is visible instead of the row simply vanishing. */
.control-row.is-disabled {
  opacity: 0.42;
  cursor: not-allowed;
}

.drawer-label {
  margin: 0;
  color: var(--muted);
  font-size: 12px;
  font-weight: 850;
  letter-spacing: 0.12em;
  line-height: 1;
  text-transform: uppercase;
}

.account-section {
  padding-bottom: 20px;
  border-bottom: 1px solid var(--panel-border);
}

.drawer-card,
.account-card {
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-lg);
  background: var(--panel-strong);
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.18);
}

.account-card {
  display: grid;
  gap: 16px;
  padding: 18px;
}

.account-main {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 13px;
}

.account-copy {
  display: grid;
  min-width: 0;
  gap: 4px;
}

#account-name {
  min-width: 0;
  overflow: hidden;
  color: var(--text);
  font-size: 16px;
  font-weight: 820;
  line-height: 1.2;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#account-sub {
  color: var(--text-dim);
  font-size: 13px;
  line-height: 1.35;
}

.coin-badge {
  min-width: 58px;
  padding: 8px 10px;
  border: 1px solid rgba(20, 184, 166, 0.18);
  border-radius: 12px;
  background: rgba(20, 184, 166, 0.08);
  color: var(--text);
  text-align: right;
}

.coin-badge span {
  color: var(--muted);
  font-size: 10px;
  font-weight: 850;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.coin-badge strong {
  display: block;
  margin-top: 2px;
  font-size: 18px;
  line-height: 1;
}

/* The pending-verification notice, which the browser did not have at all.
 
   The app's is the reference (SettingsSheet.razor.css) and this is the same shape in this page's
   tokens: an accent-tinted card inside the account card, because it is asking for something rather
   than reporting something. It sits above the sign-out toggle so it is read before the controls
   underneath it, not after them. */
.verify-notice {
  margin: 2px 0 4px;
  padding: 12px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  border-radius: 12px;
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}

.verify-notice strong { display: block; color: var(--text); font-size: 13px; }

.verify-notice p {
  margin: 6px 0 10px;
  color: var(--text-dim);
  font-size: 12px;
  line-height: 1.45;

  /* An email address has no spaces to break at, and a long one pushed the app's panel wider than
     the sheet before this. The drawer here is narrower still. */
  overflow-wrap: anywhere;
}

.verify-notice .auth-status { margin: 8px 0 0; }

.verify-actions {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 8px;
}

.verify-actions button {
  appearance: none;
  min-height: 42px;
  padding: 10px;
  border-radius: 10px;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}

.verify-actions button:disabled { opacity: .5; cursor: default; }

.verify-refresh {
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--accent-ink);
}

.verify-resend {
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: var(--control-surface);
  color: var(--text);
}

.auth-toggle-button {
  width: 100%;
  min-height: 48px;
  border: 1px solid rgba(20, 184, 166, 0.46);
  background: rgba(20, 184, 166, 0.06);
  color: var(--accent-hover);
  cursor: pointer;
}

.auth-toggle-button:hover {
  border-color: rgba(20, 184, 166, 0.7);
  background: rgba(20, 184, 166, 0.11);
  color: var(--text);
}

.account-auth-panel {
  display: grid;
  gap: 14px;
  padding: 15px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 14px;
  background: rgba(6, 11, 16, 0.46);
}

.account-auth-panel.is-hidden,
.local-auth.is-hidden,
.auth-providers.is-hidden,
.auth-actions.is-hidden,
.google-slot.is-hidden,
.auth-button.is-hidden {
  display: none !important;
}

.auth-panel-intro {
  display: grid;
  gap: 5px;
}

.auth-panel-intro h4,
.auth-panel-intro p {
  margin: 0;
}

.auth-panel-intro h4 {
  color: var(--text);
  font-size: 18px;
}

.auth-panel-intro p {
  color: var(--text-dim);
  font-size: 13px;
  line-height: 1.45;
}

.auth-mode-tabs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 4px;
  padding: 4px;
  border: 1px solid var(--panel-border);
  border-radius: 11px;
  background: rgba(0, 0, 0, 0.2);
}

.auth-mode-tabs button {
  min-height: 39px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 850;
  cursor: pointer;
}

.auth-mode-tabs button.is-active {
  background: rgba(20, 184, 166, 0.17);
  color: var(--text);
  box-shadow: inset 0 0 0 1px rgba(20, 184, 166, 0.26);
}

.auth-providers {
  display: grid;
  gap: 13px;
}

.local-auth {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}

.auth-field {
  display: grid;
  gap: 6px;
  color: var(--text);
  font-size: 12px;
  font-weight: 800;
}

.auth-field input {
  width: 100%;
  padding: 0 13px;
}

.auth-field small {
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 600;
  line-height: 1.35;
}

.auth-submit-row {
  display: grid;
  gap: 10px;
}

.auth-mode-switch {
  min-width: 0;
}

.auth-button {
  width: 100%;
  min-height: 46px;
  border: 1px solid var(--panel-border);
  background: rgba(12, 18, 24, 0.72);
  color: var(--text);
  cursor: pointer;
}

.auth-button.primary {
  border-color: rgba(20, 184, 166, 0.3);
  background: linear-gradient(180deg, var(--accent-hover), var(--accent));
  color: var(--text);
}

.auth-actions {
  display: grid;
  gap: 10px;
}

/* Google's published skins, straight from their branding page — see --google-fill in app.js.
   The G is never recoloured and never replaced with a letter, which is what this used to be. */
.auth-button.google {
  border-color: var(--google-border, #8E918F);
  background: var(--google-fill, #131314);
  color: var(--google-ink, #E3E3E3);
  font-family: Roboto, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.25px;
}

/* Both marks lead the label as one centred group, which is what Apple and Google each ask for
   and the only arrangement a long localised label cannot pull apart. */
.auth-button.apple,
.auth-button.google {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}

.auth-button.apple {
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", sans-serif;
  font-weight: 500;
  letter-spacing: -0.2px;
}

.provider-mark {
  width: 18px;
  height: 18px;
  flex: none;
}

/* Apple draw their mark a shade taller than the cap height, and its visual centre sits below its
   bounding box. */
.auth-button.apple .provider-mark {
  width: 19px;
  height: 19px;
  margin-top: -2px;
}

/* Deleting an account is destructive and irreversible: quiet until it is asked for, and
   unmistakable once it is. */
.auth-button.danger {
  border-color: color-mix(in srgb, var(--danger, #F0475F) 45%, transparent);
  background: color-mix(in srgb, var(--danger, #F0475F) 10%, transparent);
  color: var(--danger, #F0475F);
}

.auth-button.danger-solid {
  border-color: var(--danger, #F0475F);
  background: var(--danger, #F0475F);
  color: #FFFFFF;
}

.danger-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

.danger-actions.is-hidden {
  display: none;
}

.auth-button:disabled {
  cursor: not-allowed;
  opacity: 0.62;
}

.provider-status {
  margin: -4px 2px 1px;
  color: var(--text-dim);
  font-size: 11px;
  line-height: 1.4;
  text-align: center;
}

.provider-status.is-hidden {
  display: none;
}

.auth-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--text-dim);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.auth-divider::before,
.auth-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--panel-border);
}

.auth-link-button {
  justify-self: start;
  padding: 2px 0;
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font-size: 12px;
  font-weight: 750;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.auth-link-button:hover {
  color: var(--accent-hover);
}

.auth-link-button.is-hidden {
  display: none !important;
}

.auth-field.is-hidden {
  display: none !important;
}

.password-reset-panel {
  display: grid;
  gap: 11px;
  padding: 12px;
  border: 1px solid var(--panel-border);
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.16);
}

.password-reset-panel.is-hidden {
  display: none !important;
}

.password-reset-panel > p {
  margin: 0;
  color: var(--text-dim);
  font-size: 12px;
  line-height: 1.4;
}

.password-reset-panel input {
  width: 100%;
  min-height: 42px;
  padding: 0 12px;
  border: 1px solid var(--panel-border);
  border-radius: 9px;
  background: rgba(7, 12, 17, 0.68);
  color: var(--text);
}

.auth-status {
  margin: 0;
  color: var(--text-dim);
  font-size: 12px;
  line-height: 1.35;
}

.audio-settings {
  gap: 16px;
}

.audio-control {
  display: grid;
  gap: 10px;
}

.audio-control-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  color: var(--text);
  font-size: 15px;
  font-weight: 760;
}

.audio-mute-button {
  width: 36px;
  height: 36px;
  position: relative;
}

.audio-mute-button::before {
  content: "";
  width: 18px;
  height: 18px;
  background: var(--text);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M11 5 6 9H2v6h4l5 4Z'/%3E%3Cpath d='M15.54 8.46a5 5 0 0 1 0 7.07'/%3E%3Cpath d='M19.07 4.93a10 10 0 0 1 0 14.14'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M11 5 6 9H2v6h4l5 4Z'/%3E%3Cpath d='M15.54 8.46a5 5 0 0 1 0 7.07'/%3E%3Cpath d='M19.07 4.93a10 10 0 0 1 0 14.14'/%3E%3C/svg%3E") center / contain no-repeat;
}

.audio-mute-button.is-off::after {
  content: "";
  position: absolute;
  top: 10px;
  left: 17px;
  width: 2px;
  height: 18px;
  border-radius: 999px;
  background: #ff6b6b;
  transform: rotate(-40deg);
  box-shadow: 0 0 0 2px rgba(20, 28, 36, 0.82);
}

.visually-hidden {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden !important;
  clip: rect(0, 0, 0, 0) !important;
  white-space: nowrap !important;
  border: 0 !important;
}

.volume-slider {
  --range-progress: 55%;
  width: 100%;
  height: 24px;
  margin: 0;
  padding: 0;
  accent-color: var(--accent);
  background: transparent;
  cursor: pointer;
  appearance: none;
}

.volume-slider::-webkit-slider-runnable-track {
  height: 8px;
  border: 1px solid var(--panel-border);
  border-radius: 999px;
  background:
    linear-gradient(90deg, var(--accent) 0 var(--range-progress), rgba(7, 12, 17, 0.9) var(--range-progress) 100%);
}

.volume-slider::-webkit-slider-thumb {
  width: 18px;
  height: 18px;
  margin-top: -6px;
  border: 2px solid var(--text);
  border-radius: 999px;
  background: var(--text);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.32);
  appearance: none;
}

.volume-slider::-moz-range-track {
  height: 8px;
  border: 1px solid var(--panel-border);
  border-radius: 999px;
  background: rgba(7, 12, 17, 0.9);
}

.volume-slider::-moz-range-progress {
  height: 8px;
  border-radius: 999px;
  background: var(--accent);
}

.volume-slider::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border: 2px solid var(--text);
  border-radius: 999px;
  background: var(--text);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.32);
}

.volume-slider:hover::-webkit-slider-thumb,
.volume-slider:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 6px rgba(20, 184, 166, 0.12), 0 4px 14px rgba(0, 0, 0, 0.32);
}

.music-source-card {
  gap: 12px;
  padding: 18px;
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-lg);
  background: var(--panel-strong);
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.16);
}

.source-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.info-icon {
  position: relative;
  width: 22px;
  height: 22px;
  border: 1px solid var(--panel-border);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.035);
}

.info-icon::before {
  content: "i";
  position: absolute;
  inset: 0;
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 850;
  line-height: 20px;
  text-align: center;
}

.settings-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: auto;
  padding-top: 2px;
  color: var(--muted);
}

.privacy-link {
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--muted);
  font-size: 13px;
  cursor: pointer;
}

.privacy-link:hover {
  color: var(--text);
}

@media (max-width: 860px) {

  .launch-grid {
    grid-template-columns: 1fr;
  }

  .play-column {
    min-height: 0;
  }
}

@media (max-width: 560px) {
  .site-brand {
    top: calc(14px + var(--safe-top));
    left: calc(14px + var(--safe-left));
    min-height: 38px;
    padding: 0 12px;
    font-size: 14px;
  }

  .account-menu-toggle {
    top: calc(12px + var(--safe-top));
    right: calc(12px + var(--safe-right));
    gap: 7px;
    min-height: 40px;
    padding: 5px 9px 5px 6px;
  }

  .top-account-name {
    display: none;
  }

  .top-coin-pill {
    min-height: 29px;
    padding: 0 8px;
  }

  .mini-avatar {
    width: 30px;
    height: 30px;
  }

  .menu-shell {
    /* Named so .menu-panel's max-height follows the padding rather than guessing at it. */
    --shell-top: 70px;
    --shell-bottom: 12px;
    padding:
      calc(var(--shell-top) + var(--safe-top))
      calc(12px + var(--safe-right))
      calc(var(--shell-bottom) + var(--safe-bottom))
      calc(12px + var(--safe-left));
  }

  .home-head h1 {
    font-size: clamp(34px, 10vw, 42px);
  }

  .compact-row {
    grid-template-columns: 1fr;
  }

  .play-button,
  .plain-button {
    min-height: 56px;
  }

  .scoreboard-card,
  .play-column {
    padding: 15px;
  }

  .app-menu-panel {
    width: 100vw;
    height: 100vh;
    padding: calc(20px + var(--safe-top)) calc(18px + var(--safe-right)) calc(18px + var(--safe-bottom)) calc(18px + var(--safe-left));
    border-radius: 0;
    border-right: 0;
    border-left: 0;
  }

  .account-main {
    grid-template-columns: auto minmax(0, 1fr) auto;
  }

  .coin-badge {
    grid-column: auto;
    justify-self: end;
    text-align: right;
  }
}

@media (max-width: 360px) {
  .account-main {
    grid-template-columns: auto 1fr;
  }

  .coin-badge {
    grid-column: 1 / -1;
    justify-self: stretch;
    text-align: left;
  }
}

.lobby-preview-buffer {
  display: none !important;
}

.launch-grid {
  grid-template-columns: minmax(0, 1fr) minmax(310px, 330px);
  align-items: start;
}

.home-dock {
  width: min(660px, calc(100% - 360px));
  align-self: end;
}

.settings-footer-actions {
  display: flex;
  align-items: center;
  gap: 18px;
}

@media (max-width: 860px) {

  .launch-grid {
    grid-template-columns: 1fr;
  }

  .home-dock {
    width: 100%;
  }

  
}

/* Live lobby background */
.lobby-preview-buffer {
  display: none !important;
}

.launch-grid {
  grid-template-columns: minmax(300px, 360px);
  align-self: center;
  justify-self: end;
  width: min(360px, 100%);
}

.play-column {
  background: rgba(30, 38, 51, 0.82);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

.home-dock {
  width: min(620px, 62%);
  align-self: end;
  justify-self: start;
}

.scoreboard-card {
  background: rgba(30, 38, 51, 0.66);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

@media (max-width: 860px) {

  .launch-grid {
    width: 100%;
    justify-self: stretch;
  }

  .home-dock {
    width: 100%;
  }

  
}

.home-head h1 {
  font-size: 56px;
}

@media (max-width: 860px) {

  .home-head h1 {
    font-size: 48px;
  }
}

@media (max-width: 560px) {
  

  .home-head h1 {
    font-size: 40px;
  }
}

@media (max-width: 360px) {
  .home-head h1 {
    font-size: 36px;
  }
}

/* Final web publish polish */
#game {
  touch-action: auto;
}

body.is-playing {
  overscroll-behavior: none;
}

body.is-playing #game {
  touch-action: none;
}

.cog-button {
  position: relative;
}

.cog-button::before {
  content: "";
  width: 19px;
  height: 19px;
  background: var(--text);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.38a2 2 0 0 0-.73-2.73l-.15-.09a2 2 0 0 1-1-1.74v-.51a2 2 0 0 1 1-1.72l.15-.1a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2Z'/%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.38a2 2 0 0 0-.73-2.73l-.15-.09a2 2 0 0 1-1-1.74v-.51a2 2 0 0 1 1-1.72l.15-.1a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2Z'/%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3C/svg%3E") center / contain no-repeat;
}

.cog-button.is-active {
  border-color: rgba(20, 184, 166, 0.42);
  background: rgba(20, 184, 166, 0.12);
  box-shadow: 0 0 0 4px rgba(20, 184, 166, 0.08);
}

.cog-button.is-active::before {
  background: var(--accent-hover);
}

.quick-settings-toggle {
  position: fixed;
  z-index: 7;
  right: calc(18px + var(--safe-right));
  bottom: calc(18px + var(--safe-bottom));
  width: 48px;
  height: 48px;
  border: 1px solid rgba(20, 184, 166, 0.34);
  border-radius: 50%;
  background: rgba(13, 21, 28, 0.88);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.32);
  backdrop-filter: blur(12px);
  cursor: pointer;
}

body.is-playing .quick-settings-toggle {
  display: none;
}

.drawer-setting-hint {
  margin: -2px 0 2px;
  color: var(--text-dim);
  font-size: 12px;
  line-height: 1.4;
}

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

.drawer-theme-option {
  min-width: 0;
  min-height: 54px;
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr);
  align-items: center;
  gap: 9px;
  padding: 7px 9px;
  border: 1px solid var(--panel-border);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.05);
  color: var(--text);
  font-size: 12px;
  font-weight: 800;
  text-align: left;
  cursor: pointer;
}

.drawer-theme-option.is-active {
  border-color: rgba(20, 184, 166, 0.72);
  background: rgba(20, 184, 166, 0.12);
  box-shadow: inset 0 0 0 1px rgba(20, 184, 166, 0.18);
}

.drawer-theme-swatch {
  width: 34px;
  height: 34px;
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 9px;
}

.drawer-settings-pane {
  animation: drawerPaneIn 180ms ease-out;
}

.drawer-settings-pane.is-hidden {
  display: none !important;
}

@keyframes drawerPaneIn {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

a.plain-button,
a.privacy-link {
  text-decoration: none;
}

.how-to-play-button::before {
  content: "?";
  display: inline-grid;
  place-items: center;
  width: 22px;
  height: 22px;
  margin-right: 8px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 999px;
  color: var(--accent-hover);
  font-size: 13px;
  font-weight: 900;
}

.home-dock {
  width: min(620px, 56%);
  margin-top: auto;
}

.settings-footer {
  gap: 14px;
}

.settings-footer-actions {
  display: flex;
  align-items: center;
  gap: 16px;
}

@media (max-width: 860px) {

  .home-dock {
    width: 100%;
  }
}

@media (max-width: 560px) {

  .action-grid {
    gap: 10px;
  }
}

@media (max-width: 560px) {
  .menu-shell {
    width: 100vw;
    justify-items: start;
    /* Named so .menu-panel's max-height follows the padding rather than guessing at it. This is
       the pair a phone actually gets, and the one measured overflowing at 320x568. */
    --shell-top: 72px;
    --shell-bottom: 16px;
    padding:
      calc(var(--shell-top) + var(--safe-top))
      calc(12px + var(--safe-right))
      calc(var(--shell-bottom) + var(--safe-bottom))
      calc(12px + var(--safe-left));
    overflow-x: hidden;
  }

  .home-head {
    min-width: 0;
    height: auto;
    justify-content: center;
  }

  .home-head h1 {
    max-width: 100%;
    overflow: hidden;
    font-size: clamp(30px, 9vw, 36px);
    line-height: 1.04;
    text-overflow: clip;
    white-space: nowrap;
  }

  .home-dock,
  .scoreboard-card,
  .compact-row,
  .action-grid,
  .match-field,
  .field input {
    width: min(100%, 330px);
    min-width: 0;
    max-width: 330px;
    justify-self: center;
  }

  .compact-row {
    grid-template-columns: minmax(0, 1fr);
  }

  .account-menu-toggle {
    left: min(calc(100vw - 64px - var(--safe-right)), 326px);
    right: auto;
    width: 44px;
    height: 44px;
    min-height: 44px;
    justify-content: center;
    padding: 0;
    overflow: hidden;
  }

  .account-menu-toggle .chevron-icon,
  .top-account-name {
    display: none;
  }

  .account-menu-toggle .top-coin-pill {
    display: none !important;
  }
}

/* Off at the owner's request. The markup stays in index.html so this is one line to undo, and
   because the poke interaction and its animations are worth keeping around rather than deleting.
   `display: none` also stops the crown float and the blink from running, which is a small win for
   the idle scene's cost. */
.lobby-mascot {
  display: none;

  width: min(330px, 86%);
  margin: -104px auto -6px -22px;
  pointer-events: auto;
  cursor: pointer;
  touch-action: manipulation;
}

.lobby-mascot svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
  filter:
    drop-shadow(0 0 22px rgba(173, 69, 242, 0.4))
    drop-shadow(0 18px 20px rgba(0, 0, 0, 0.45));
}

.mascot-crown {
  transform-origin: 243px 60px;
  animation: mascotCrownFloat 2.8s ease-in-out infinite;
}

.mascot-eyes {
  transform-box: fill-box;
  transform-origin: center;
  animation: mascotBlink 6.2s ease-in-out infinite;
  animation-delay: -1.4s;
}

.mascot-eye { cursor: pointer; }

.mascot-pupil {
  /* SVG translate transforms use a different coordinate space across browsers;
     keep pupils in their authored eye coordinates so they cannot float away. */
  transform: none !important;
  transition: none;
}

.lobby-mascot.is-poked .mascot-eyes {
  animation: mascotPokeBlink 440ms ease-in-out both;
}

.lobby-mascot.is-poked[data-poke-side="left"] .mascot-pupil {
  transform: none !important;
}

.lobby-mascot.is-poked[data-poke-side="right"] .mascot-pupil {
  transform: none !important;
}

@keyframes mascotBlink {
  0%, 42%, 47%, 100% { transform: scaleY(1); }
  44%, 45% { transform: scaleY(0.08); }
}

@keyframes mascotPokeBlink {
  0%, 100% { transform: scaleY(1); }
  28%, 48% { transform: scaleY(0.08); }
}

@keyframes mascotCrownFloat {
  0%, 100% { transform: translateY(0) rotate(-5deg); }
  50% { transform: translateY(-5px) rotate(2deg); }
}

/* The crown on the wordmark gets the same lift, so the two read as one idea. */
.brand-crown {
  animation: brandCrownFloat 3s ease-in-out infinite;
  transform-origin: 50% 80%;
  /* Float above the first letter instead of sitting on top of the wordmark. */
  top: -8px;
  left: 12px;
  transform: rotate(-10deg);
}

@keyframes brandCrownFloat {
  0%, 100% { transform: translateY(0) rotate(-8deg); }
  50% { transform: translateY(-4px) rotate(-2deg); }
}

/* --- rounds --- */

.rounds-strip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  margin-top: 14px;
  padding: 12px 14px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 13px;
  background: rgba(5, 9, 14, 0.34);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 750;
  text-align: left;
}

.rounds-strip:hover { border-color: rgba(59, 245, 200, 0.34); color: var(--text); }
.rounds-strip-label { display: inline-flex; align-items: center; gap: 9px; }

.rounds-pip {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 10px var(--accent);
}

.rounds-strip[data-quiet="true"] .rounds-pip {
  background: rgba(148, 160, 184, 0.5);
  box-shadow: none;
}

.rounds-strip-chevron { font-size: 12px; color: rgba(148, 160, 184, 0.8); }
.rounds-strip-chevron::after { content: " \25BE"; }
.rounds-strip[aria-expanded="true"] .rounds-strip-chevron::after { content: " \25B4"; }

.rounds-drawer {
  margin-top: 12px;
  padding-top: 4px;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
}

.rounds-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 2px 9px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.rounds-title,
.rounds-server {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  color: rgba(148, 160, 184, 0.8);
}

.rounds-title { letter-spacing: 0.15em; text-transform: uppercase; }
.rounds-server { font-variant-numeric: tabular-nums; }
.rounds-server b { color: var(--accent); font-weight: 700; }

.rounds-list {
  display: block;
  max-height: min(52dvh, 360px);
  margin: 0;
  padding: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  list-style: none;
}

.round-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 11px;
  padding: 12px 2px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.055);
}

.round-row:last-child { border-bottom: 0; }

.round-id {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 13px;
  font-weight: 600;
}

.round-meta { min-width: 0; }

.round-line {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 7px;
}

.round-tag {
  padding: 2.5px 6px;
  border-radius: 5px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 9.5px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.round-tag.classic { color: var(--accent); background: rgba(59, 245, 200, 0.12); }
.round-tag.royale { color: #c069ff; background: rgba(173, 69, 242, 0.15); }

.round-state {
  font-size: 11.5px;
  color: rgba(148, 160, 184, 0.85);
  font-variant-numeric: tabular-nums;
}

.round-state.is-starting { color: var(--gold); }

.round-bar {
  position: relative;
  height: 5px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.1);
  overflow: hidden;
}

.round-bar i {
  position: absolute;
  inset: 0 auto 0 0;
  display: block;
  border-radius: 3px;
  background: linear-gradient(90deg, #12a596, var(--accent));
}

.round-row.royale .round-bar i { background: linear-gradient(90deg, #7a2de2, #c069ff); }
.round-row.is-shut .round-bar i { background: rgba(148, 160, 184, 0.4); }
.round-row.is-shut .round-id { color: var(--text-dim); }

.round-right { display: flex; align-items: center; gap: 11px; }

.round-count {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.round-count b { color: var(--text); font-weight: 700; }

.round-join {
  padding: 8px 15px;
  border: 1px solid rgba(59, 245, 200, 0.34);
  border-radius: 10px;
  background: rgba(59, 245, 200, 0.1);
  color: var(--accent);
  font-size: 12px;
  font-weight: 800;
}

.round-join:hover { background: rgba(59, 245, 200, 0.2); }
.round-row.royale .round-join {
  border-color: rgba(173, 69, 242, 0.45);
  background: rgba(173, 69, 242, 0.12);
  color: #c069ff;
}

.round-shut {
  padding: 8px 2px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(148, 160, 184, 0.75);
}

.rounds-empty { padding: 18px 2px; color: var(--text-dim); font-size: 13px; }
.rounds-empty b { display: block; margin-bottom: 4px; color: var(--text); font-size: 14px; }

.rounds-code { display: flex; gap: 8px; margin-top: 13px; }

.rounds-code input {
  flex: 1;
  min-width: 0;
  padding: 11px 13px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 11px;
  background: rgba(5, 9, 14, 0.7);
  color: var(--text);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 13px;
  letter-spacing: 0.08em;
}

.rounds-code input::placeholder { color: rgba(148, 160, 184, 0.6); letter-spacing: 0.04em; }
.rounds-code input:focus { outline: 0; border-color: rgba(59, 245, 200, 0.34); }

.rounds-code button {
  padding: 0 17px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 11px;
  background: rgba(255, 255, 255, 0.07);
  color: var(--text-dim);
  font-size: 12.5px;
  font-weight: 800;
}

.rounds-code button:hover { color: var(--text); border-color: rgba(59, 245, 200, 0.34); }

/* --- chips --- */

.lobby-chips {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-top: 16px;
}

.lobby-chips .chip {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 12px 6px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 13px;
  background: rgba(5, 9, 14, 0.34);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: var(--text-dim);
  font-size: 11.5px;
  font-weight: 750;
}

.lobby-chips .chip:hover { border-color: rgba(59, 245, 200, 0.34); color: var(--text); }

.lobby-chips .chip svg {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
}

@media (max-width: 560px) {
  .lobby-mascot { width: min(280px, 82%); margin: -84px auto -4px -16px; }
}

@media (prefers-reduced-motion: reduce) {
  .end-panel.is-death,
  .end-panel.is-death h2,
  .end-panel.is-win,
  .end-panel.is-win h2,
  .end-panel.is-win .end-trophy,
  .end-panel.is-runner-up,
  .end-panel.is-runner-up h2,
  .end-panel.is-runner-up .end-trophy,
  .end-panel.is-placed,
  .end-panel.is-placed h2,
  .mascot-crown,
  .brand-crown,
  .mascot-eyes,
  .mascot-pupil { animation: none; transition: none; }

  /* Not merely stilled. A screenful of confetti held motionless mid-fall is a screenful of
     coloured rectangles over the score, which is worse than either the animation or nothing. The
     app's results page makes the same call for the same reason. */
  .win-confetti { display: none; }
}

/* The chip carries its own icon, so the old button's masked pencil would be a
   second one stacked above the label. */
#builder-open::before{
  content: none !important;
}

#builder-open {
  display: flex !important;
  flex-direction: column !important;
  gap: 6px !important;
}

/* ==========================================================================
   THE LOBBY
   One panel, one primary action, the live rounds behind a strip, and the
   crowned snake behind the glass. It owns every class it uses — the old
   .home-panel rules were removed rather than overridden, because eleven
   generations of them were fighting each other over the same four properties
   and the result was a near-miss of the design in every browser width.
   ========================================================================== */

.lobby {
  position: relative;
  z-index: 1;
  display: block;
  width: min(400px, calc(100vw - 28px - var(--safe-left) - var(--safe-right)));
  padding: 0;
  border: 0;
  background: none;
  pointer-events: auto;
}

/* The glass. A separate layer so the snake can sit between it and the arena. */
.lobby::before {
  content: "";
  position: absolute;
  z-index: 1;
  inset: 0;
  border: 1px solid rgba(59, 245, 200, 0.16);
  border-radius: 22px;
  background:
    linear-gradient(180deg, rgba(17, 29, 40, 0.94), rgba(9, 15, 22, 0.96)),
    radial-gradient(circle at 50% 0%, rgba(20, 184, 166, 0.1), transparent 62%);
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  transition: border-color 240ms ease-out, background 240ms ease-out, box-shadow 240ms ease-out;
}

.lobby.map-royale::before {
  border-color: rgba(173, 69, 242, 0.3);
  background:
    linear-gradient(180deg, rgba(26, 20, 34, 0.94), rgba(15, 12, 22, 0.96)),
    radial-gradient(circle at 50% 0%, rgba(173, 69, 242, 0.16), transparent 62%);
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.5), 0 0 46px rgba(173, 69, 242, 0.08);
}

.lobby-preview-buffer { display: none; }

/* --- the snake, behind the glass ---------------------------------------- */

.lobby-mascot {
  position: absolute;
  z-index: 0;
  left: -212px;
  bottom: calc(100% - 206px);
  width: 430px;
  pointer-events: none;
}

.lobby-mascot svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
  filter:
    drop-shadow(0 0 26px rgba(173, 69, 242, 0.45))
    drop-shadow(0 18px 22px rgba(0, 0, 0, 0.5));
}

.mascot-crown {
  transform-origin: 243px 60px;
  animation: mascotCrownFloat 2.8s ease-in-out infinite;
}

@keyframes mascotCrownFloat {
  0%, 100% { transform: translateY(0) rotate(-5deg); }
  50% { transform: translateY(-5px) rotate(2deg); }
}

.brand-crown {
  animation: brandCrownFloat 3s ease-in-out infinite;
  transform-origin: 50% 80%;
  top: -8px;
  left: 12px;
  transform: rotate(-10deg);
}

@keyframes brandCrownFloat {
  0%, 100% { transform: translateY(0) rotate(-8deg); }
  50% { transform: translateY(-4px) rotate(-2deg); }
}

/* --- controls, above the glass ------------------------------------------ */

.lobby-body {
  position: relative;
  z-index: 2;
  padding: 20px;
}

.lobby-controls { display: block; }

.lobby-modes {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
  padding: 4px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 15px;
  background: rgba(5, 9, 14, 0.30);
  backdrop-filter: blur(26px) saturate(1.4);
  -webkit-backdrop-filter: blur(26px) saturate(1.4);
}

.lobby-mode {
  padding: 12px 8px;
  border: 0;
  border-radius: 11px;
  background: transparent;
  color: var(--text-dim);
  font-size: 14.5px;
  font-weight: 800;
  line-height: 1.25;
  transition: background-color 200ms ease-out, box-shadow 200ms ease-out, color 200ms ease-out;
}

.lobby-mode span {
  display: block;
  margin-top: 3px;
  font-size: 10.5px;
  font-weight: 600;
  color: rgba(148, 160, 184, 0.72);
}

.lobby-mode.is-active {
  background: rgba(255, 255, 255, 0.07);
  color: var(--text);
  box-shadow: inset 0 0 0 1.5px rgba(59, 245, 200, 0.34);
}

.lobby-mode.is-active span { color: var(--accent); }

.lobby.map-royale .lobby-mode.is-active {
  box-shadow: inset 0 0 0 1.5px rgba(173, 69, 242, 0.55);
}

.lobby.map-royale .lobby-mode.is-active span { color: #c069ff; }

.lobby-play {
  width: 100%;
  min-height: 62px;
  margin-top: 14px;
  border: 0;
  border-radius: 15px;
  background: linear-gradient(180deg, #2fe3d0, #12a596);
  color: #04222a;
  font-size: 19px;
  font-weight: 900;
  letter-spacing: -0.01em;
  box-shadow: 0 12px 28px rgba(23, 208, 191, 0.22), inset 0 1px rgba(255, 255, 255, 0.3);
  transition: transform 140ms ease, filter 140ms ease, background 240ms ease-out;
}

.lobby.map-royale .lobby-play {
  background: linear-gradient(180deg, #c069ff, #7a2de2);
  color: #f8f0ff;
  box-shadow: 0 12px 28px rgba(140, 45, 226, 0.28), inset 0 1px rgba(255, 255, 255, 0.22);
}

.lobby-play:hover { transform: translateY(-2px); filter: brightness(1.06); }

/* Solo is the secondary action, not a link. It is a <button> and always has been, but nothing in
   this stylesheet ever claimed the class, so it fell through to the bare button reset and rendered
   as plain text under a large green primary — which reads as a caption rather than something you
   can press, and gives a text-sized tap target where the button above it has a 62px one.

   Deliberately quieter than .lobby-play: an outline and a translucent ground rather than a second
   filled slab, so the hierarchy still says "online first". Matches .lobby-card .play-solo in the
   app, which had the same problem stated the other way round — there it was underlined on purpose. */
.lobby-solo {
  width: 100%;
  min-height: 46px;
  margin-top: 10px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 13px;
  background: rgba(5, 9, 14, 0.34);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: var(--text);
  font-size: 15px;
  font-weight: 750;
}

.lobby-solo:hover {
  border-color: rgba(59, 245, 200, 0.34);
  background: rgba(5, 9, 14, 0.5);
}

.lobby-solo:active { transform: scale(0.99); }

.lobby-chips {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-top: 16px;
}

.lobby-chip {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 12px 6px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 13px;
  background: rgba(5, 9, 14, 0.34);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: var(--text-dim);
  font-size: 11.5px;
  font-weight: 750;
}

.lobby-chip::before { content: none; }
.lobby-chip:hover { border-color: rgba(59, 245, 200, 0.34); color: var(--text); }

.lobby-chip svg {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
}

.lobby .status { margin: 10px 0 0; text-align: center; }
.lobby .status:empty { display: none; }

@media (max-width: 560px) {
  .lobby-mascot { left: -34px; bottom: calc(100% - 126px); width: 264px; }
  .lobby-body { padding: 17px; }
}

@media (prefers-reduced-motion: reduce) {
  .mascot-crown,
  .brand-crown,
  .mascot-eyes,
  .mascot-pupil { animation: none; transition: none; }
  .lobby-play { transition: none; }
}

/* Keep the crown clear of the compact two-line wordmark on phones too. */
@media (max-width: 560px) {
  .brand-crown { top: -7px; left: 9px; }
}

/* --- phone lobby: mascot, wordmark ---------------------------------------
   These three selectors have picked up override layers over several passes, several of them
   !important, so this block deliberately sits last and owns the phone layout outright rather than
   adding a fourth set of exceptions somewhere in the middle of the cascade. Untangling the earlier
   layers would mean re-testing the desktop lobby too, which is not what is broken. */
@media (max-width: 560px) {
  /* Rising from behind the top edge of the panel, the way the desktop lobby does it: the mascot
     is part of the card rather than something floating in the arena behind it.
     What was wrong before was the PROPORTION, not the anchor — at 264px wide with its bottom edge
     126px below the panel top, only about 50px of a 177px mascot cleared the glass, so all you saw
     was a crown and two eyes. Smaller, and tucked in by far less, so the head and most of the body
     are above the edge and only the tail disappears behind it. */
  .lobby-mascot {
    left: -26px;
    right: auto;
    bottom: calc(100% - 92px);
    width: min(300px, 80%);
    margin: 0;
    transform: none;
  }

  /* Clear of the status bar and the notch, and far enough from the mascot that the crown on the
     wordmark and the crown on the mascot are not competing for the same corner. */
  .site-brand {
    top: calc(38px + var(--safe-top)) !important;
    left: calc(18px + var(--safe-left)) !important;
  }

  /* Crown in front and fully clear of the caps: it sits ABOVE the wordmark rather than across it.
     Tucking it behind the letters was tried and reads as damage — the type eats the middle of the
     crown and what is left looks like two broken shapes. The type is pushed down by the crown's
     own height so there is room to place it in, rather than having it overlap for want of space. */
  .brand-type {
    position: relative;
    z-index: 1;
    padding-top: 26px;
  }

  .brand-crown {
    z-index: 2;
    top: -2px !important;
    left: 14px !important;
    width: 34px !important;
    height: 27px !important;
  }
}

/* Thumbs, not cursors. The footer links were 15px against the 44px that a finger actually needs.
   The padding grows the hit area without changing how any of them look.

   The short axis is in the query as well as the long one, because a phone held sideways is still
   a phone and every one of these rules used to be keyed to max-width alone. Measured at 844x390 —
   an iPhone 14 in landscape — the drawer's footer links were back to 15.0px tall while the same
   finger got 44px in portrait. Nothing here changes how anything looks at any size, so a short
   desktop window picking the rules up costs nothing. */
@media (max-width: 560px), (max-height: 560px) {
  .privacy-link {
    padding: 14px 6px;
    margin: -14px -6px;
  }

  .icon-button {
    min-width: 40px;
    min-height: 40px;
  }

  /* Ten 32.0x32.0 dots, in a grid that had 9px of the row spare — the smallest controls in the
     builder and the ones you are meant to poke repeatedly. Six 44px columns fit the same 328px
     row, so the swatches grow into space that was already there. */
  .swatches {
    grid-template-columns: repeat(auto-fill, minmax(44px, 44px));
  }

  .swatch {
    width: 44px;
    height: 44px;
  }

  /* 52.1x32.0, in a list scrolled by the same thumb that has to hit it. */
  .round-join {
    min-height: 40px;
  }

  /* The pause panel's sliders are the browser's own, and measured 328.0x16.0 — full width, but a
     16px band to land a thumb on while a round is paused behind it. The drawer's styled sliders
     are 24px; match them rather than invent a third height. */
  .range-row input[type="range"] {
    height: 24px;
  }
}

/* --- coin store and bundle editor --------------------------------------- */
.coin-store { display: grid; gap: 10px; }
.coin-store-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(120px, 100%), 1fr));
  gap: 10px;
}

.coin-bundle {
  display: grid;
  gap: 3px;
  padding: 12px;
  border: 1px solid var(--panel-border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.03);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.coin-bundle:hover:not(:disabled) { border-color: var(--accent); background: rgba(59, 245, 200, 0.08); }
.coin-bundle:disabled { opacity: 0.55; cursor: not-allowed; }
.coin-bundle strong { font-size: 15px; font-weight: 850; }
.coin-bundle span { color: var(--accent); font-size: 13px; font-weight: 800; }
.coin-bundle small { color: var(--muted); font-size: 11px; }

/* Sits under the buy buttons, not beside them: it has to be read before a purchase, but it is not
   competing with the prices for attention. */
.coin-store-terms {
  margin: 10px 0 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--muted);
}

.coin-store-terms a { color: var(--muted); text-decoration: underline; }
.coin-store-terms a:hover { color: var(--text); }

/* The revive button carries its own way out when the coins are not there, rather than standing
   next to a second button competing for the same decision. */
.revive-button { display: inline-flex; align-items: center; justify-content: center; gap: 10px; }

.revive-cta {
  padding: 3px 10px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.16);
  border: 1px solid rgba(255, 255, 255, 0.28);
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.02em;
}

/* Short of coins: the button is still live, but it reads as the way to fix that rather than as
   the revive it cannot yet do. */
.revive-button.is-short { filter: saturate(0.75) brightness(0.94); }

.revive-note {
  margin: 0;
  font-size: 13px;
  color: rgba(255, 225, 228, 0.8);
  text-align: center;
}

.revive-button:disabled { opacity: 0.55; cursor: default; }

/* --- invoices, in the player's own drawer -------------------------------- */

.invoice-list { display: grid; gap: 8px; }

.invoice {
  display: grid;
  gap: 4px;
  padding: 11px 12px;
  border: 1px solid var(--panel-border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.03);
}

.invoice-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.invoice-ref { font-size: 13px; font-weight: 800; letter-spacing: 0.04em; font-variant-numeric: tabular-nums; }
.invoice-date { color: var(--muted); font-size: 12px; }

.invoice-body { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px; }
.invoice-coins { color: var(--accent); font-size: 13px; font-weight: 700; }
.invoice-price { margin-left: auto; font-size: 13px; font-variant-numeric: tabular-nums; }

/* A refund is the one thing on here somebody might be looking for specifically, so it says so
   in words rather than leaving them to compare two numbers. */
.invoice-refund {
  flex-basis: 100%;
  color: var(--gold);
  font-size: 11.5px;
  font-weight: 700;
}

.invoice-receipt {
  justify-self: start;
  margin-top: 2px;
  color: var(--accent);
  font-size: 12px;
  font-weight: 700;
  text-decoration: none;
}

.invoice-receipt:hover { text-decoration: underline; }

/* The arena theme also sits behind the lobby wordmark. Light themes need dark lettering and dark
   themes need light lettering; these variables are updated with the selected game theme. */
.brand-type strong {
  color: var(--brand-primary, var(--text));
  text-shadow: 0 1px 2px var(--brand-shadow, rgba(0, 0, 0, 0.75));
}

.brand-type strong + strong {
  color: var(--brand-secondary, var(--accent));
}

/* The wordmark needs one coherent contrast contract. Its surface now follows the selected arena:
   dark glass/light type on Midnight, pale glass/dark type on every pastel theme. */
.site-brand {
  padding: 7px 11px 7px 8px;
  border: 1px solid var(--brand-border, rgba(255, 255, 255, 0.1));
  border-radius: 13px;
  background: var(--brand-surface, rgba(20, 28, 36, 0.82));
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.16);
  backdrop-filter: blur(9px);
  -webkit-backdrop-filter: blur(9px);
}

.brand-crown { left: 20px; }

/* Background effects are a second setting, not extra colour themes. The miniature treatment is
   intentionally monochrome so the player can understand the shape before combining a palette. */
.background-effect-title { margin-top: 20px; }

.background-effect-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
}

.background-effect-option {
  display: grid;
  gap: 7px;
  min-width: 0;
  padding: 7px;
  border: 1px solid var(--panel-border);
  border-radius: 11px;
  background: rgba(255, 255, 255, 0.035);
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 750;
  text-align: left;
}

.background-effect-option.is-active {
  border-color: var(--accent);
  background: rgba(20, 184, 166, 0.11);
  color: var(--text);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 16%, transparent);
}

.background-effect-art {
  position: relative;
  display: block;
  height: 38px;
  overflow: hidden;
  border-radius: 7px;
  background-color: #111a22;
  opacity: 0.9;
}

.background-effect-art.is-clean {
  background: linear-gradient(145deg, #111a22, #1b2832);
}

.background-effect-art.is-orbits {
  background:
    radial-gradient(circle at 18% 70%, transparent 0 8px, rgba(45, 224, 207, 0.35) 9px 10px, transparent 11px),
    radial-gradient(circle at 74% 30%, transparent 0 13px, rgba(45, 224, 207, 0.28) 14px 15px, transparent 16px),
    #111a22;
}

.background-effect-art.is-waves {
  background:
    repeating-radial-gradient(ellipse at 0 50%, transparent 0 10px, rgba(45, 224, 207, 0.24) 11px 12px, transparent 13px 22px),
    #111a22;
}

.background-effect-art.is-hexes {
  background:
    linear-gradient(30deg, transparent 24%, rgba(45, 224, 207, 0.22) 25% 27%, transparent 28% 74%, rgba(45, 224, 207, 0.22) 75% 77%, transparent 78%),
    linear-gradient(150deg, transparent 24%, rgba(45, 224, 207, 0.22) 25% 27%, transparent 28% 74%, rgba(45, 224, 207, 0.22) 75% 77%, transparent 78%),
    #111a22;
  background-size: 26px 45px;
}

/* Right angles and a junction dot — the swatch says "board", which is what the floor draws. */
.background-effect-art.is-circuit {
  background:
    linear-gradient(to right, transparent 0 18%, rgba(45, 224, 207, 0.26) 18% 20%, transparent 20%),
    linear-gradient(to bottom, transparent 0 55%, rgba(45, 224, 207, 0.26) 55% 58%, transparent 58%),
    radial-gradient(circle at 19% 56%, rgba(45, 224, 207, 0.4) 0 2.5px, transparent 3px),
    #111a22;
  background-size: 26px 34px, 26px 34px, 26px 34px, auto;
}

.background-effect-art.is-drift {
  background:
    repeating-linear-gradient(24deg, transparent 0 9px,
      rgba(45, 224, 207, 0.22) 9px 10px, transparent 10px 19px),
    #111a22;
}

/* --- coin statement ------------------------------------------------------ */
.coin-statement-list { display: grid; gap: 8px; }

.coin-statement-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 5px 12px;
  padding: 11px 12px;
  border: 1px solid var(--panel-border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.03);
}

.coin-statement-head { display: grid; gap: 2px; min-width: 0; }
.coin-statement-head strong { font-size: 13px; }
.coin-statement-head time { color: var(--muted); font-size: 11.5px; }
.coin-statement-delta { align-self: center; font-size: 13px; font-variant-numeric: tabular-nums; }
.coin-statement-delta.is-credit { color: var(--accent); }
.coin-statement-delta.is-debit { color: #ff9aa9; }

.coin-statement-details {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 3px;
}

.coin-statement-details span {
  padding: 3px 7px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.055);
  color: var(--text-dim);
  font-size: 10.5px;
}

.coin-statement-details .is-note {
  flex-basis: 100%;
  padding-inline: 0;
  background: none;
  border-radius: 0;
  line-height: 1.4;
}

/* ---------- final lobby theme and scrolling ownership ----------
   Keep these rules last: this stylesheet contains older lobby iterations above, and these are
   the current dimensions/scroll contract for both desktop and touch layouts. */
.lobby {
  width: min(520px, calc(100vw - 28px - var(--safe-left) - var(--safe-right)));
}

.rounds-list {
  max-height: min(44dvh, 380px);
  overscroll-behavior-y: auto;
  touch-action: pan-y;
}

.menu-shell {
  overscroll-behavior-y: auto;
  scroll-padding-block: calc(76px + var(--safe-top)) calc(18px + var(--safe-bottom));
}

.rounds-code-block {
  margin-top: 14px;
  padding: 13px;
  border: 1px solid rgba(59, 245, 200, 0.2);
  border-radius: 13px;
  background: rgba(5, 9, 14, 0.5);
}

.rounds-code-block label {
  display: block;
  margin-bottom: 8px;
  color: var(--text);
  font-size: 12px;
  font-weight: 850;
  letter-spacing: 0.02em;
}

.rounds-code-block .rounds-code { margin-top: 0; }
.rounds-code-block small {
  display: block;
  margin-top: 7px;
  color: var(--text-dim);
  font-size: 10.5px;
  line-height: 1.35;
}

/* The canvas now uses the selected arena theme. Let the glass inherit enough of that colour to
   look intentional instead of putting the same near-black card over every light/pastel lobby. */
.lobby::before {
  border-color: color-mix(in srgb, var(--brand-secondary, var(--accent)) 28%, transparent);
}

@media (max-width: 560px) {
  .lobby { width: calc(100vw - 24px - var(--safe-left) - var(--safe-right)); }
  .rounds-list { max-height: min(42dvh, 330px); }
  .round-row { grid-template-columns: minmax(62px, auto) minmax(0, 1fr) auto; gap: 8px; }
  .round-right { gap: 7px; }
  .round-join { padding-inline: 12px; }
  .background-effect-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---------- the coins dialogue ---------- */

.coins-dialog {
  width: min(460px, calc(100vw - 32px));
  /* A dialogue is not a page: it clips rather than scrolls unless told otherwise, and this one
     is opened from the death screen, which on a phone is landscape — about 390px of height for
     a heading, a balance, two explainers and a list of bundles. Without a ceiling and somewhere
     to scroll, the bundles simply were not reachable. */
  max-height: min(88dvh, 640px);
  padding: 22px;
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--panel-border);
  border-radius: 16px;
  background: var(--panel);
  color: var(--text);
}

.coins-dialog::backdrop { background: rgba(4, 8, 12, 0.66); }

/* The confirmation in front of fifty coins. Centred and small: this is a question with two
   answers, not a place to be. UnlockOffer.razor.css is the same sheet on the phone. */
.unlock-dialog {
  width: min(400px, calc(100vw - 32px));
  padding: 22px 20px 18px;
  border: 1px solid var(--panel-border);
  border-radius: 16px;
  /* --panel-strong and a blur, not --panel. The panel token is 84% opaque and the grid under it
     showed straight through the price: the tab pill sat behind the title and the chips behind the
     buttons. Legible in a screenshot and wrong for the one dialogue in the game that is about
     money — a question this specific should not have the answer to a different one showing
     through it. */
  background: var(--panel-strong);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
  color: var(--text);
  text-align: center;
}

.unlock-dialog::backdrop { background: rgba(4, 8, 12, 0.72); }

/* The same padlock the chip wears, so the sheet is visibly about the thing that was tapped. */
.unlock-mark { display: block; font-size: 30px; line-height: 1; margin-bottom: 10px; }

.unlock-title { margin: 0 0 10px; font-size: 20px; }

/* The three numbers. Loudest line after the title, because it is the one that has to be read
   before the button is pressed. */
.unlock-body { margin: 0 0 8px; font-size: 15px; line-height: 1.5; color: var(--text); }

.unlock-earn { margin: 0 0 4px; font-size: 13px; line-height: 1.5; color: var(--text-dim); }

.unlock-said { margin: 12px 0 0; font-size: 13.5px; font-weight: 760; color: var(--danger); }
.unlock-said:empty { margin: 0; }

.unlock-acts { display: flex; gap: 10px; margin-top: 18px; }

/* Both buttons share the row evenly rather than the primary one taking it. Declining is the more
   common answer and must not be the smaller target. */
.unlock-acts > button { flex: 1; min-width: 0; }
.coins-dialog-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.coins-dialog-head h2 { margin: 0; font-size: 20px; }
.coins-dialog-balance { margin: 6px 0 16px; color: var(--text-dim); font-size: 14px; }

.coins-ways { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 16px; }

.coins-way {
  padding: 12px;
  border: 1px solid var(--panel-border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.03);
}

.coins-way h3 { margin: 0 0 4px; font-size: 13px; letter-spacing: 0.02em; }
.coins-way p { margin: 0; font-size: 12.5px; color: var(--muted); line-height: 1.5; }

.coins-dialog-bundles { display: grid; gap: 8px; }

.coins-bundle {
  display: flex; align-items: center; justify-content: space-between; gap: 6px 12px;
  flex-wrap: wrap;
  padding: 12px 14px;
  border: 1px solid var(--panel-border);
  border-radius: 12px;
  background: var(--panel-strong);
  color: var(--text);
  font: inherit;
  cursor: pointer;
}

.coins-bundle:hover { border-color: var(--accent); background: rgba(20, 184, 166, 0.12); }
.coins-bundle-amount { font-weight: 800; font-size: 15px; }
.coins-bundle-price { font-variant-numeric: tabular-nums; color: var(--text-dim); }

@media (max-width: 460px) {
  .coins-ways { grid-template-columns: 1fr; }
}

/* Landscape phone — how the game is actually held, and the shape the death screen appears in.
   Everything tightens so the bundles are on screen rather than one scroll away. */
@media (max-height: 560px) {
  .coins-dialog { max-height: 92dvh; padding: 14px 16px; }
  .coins-dialog-head h2 { font-size: 17px; }
  .coins-dialog-balance { margin: 4px 0 10px; font-size: 13px; }
  .coins-ways { grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 10px; }
  .coins-way { padding: 9px 10px; }
  .coins-way h3 { font-size: 12px; }
  .coins-way p { font-size: 11.5px; line-height: 1.4; }
  .coins-bundle { padding: 9px 12px; }
}

/* Groups the browse list, so eight rounds read as two choices. */
.rounds-heading {
  margin: 10px 0 2px;
  padding: 0 2px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  list-style: none;
}

.rounds-heading:first-child { margin-top: 0; }

/* Theme-aware interface surfaces. The arena palette is a player setting, so every label and
   card above it must use the same contrast contract instead of keeping the old dark UI. */
.menu-panel,
.builder-panel,
.end-panel,
.pause-panel,
.play-column,
.scoreboard-card,
.app-menu-panel,
.inner-window,
.coins-dialog {
  border-color: var(--panel-border);
  background: var(--panel);
  color: var(--text);
}

html,
body {
  background: var(--bg);
}

.lobby::before {
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--panel-strong) 94%, transparent), var(--panel)),
    radial-gradient(circle at 50% 0%, color-mix(in srgb, var(--accent) 10%, transparent), transparent 62%);
}

.lobby.map-royale::before {
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--panel-strong) 94%, transparent), var(--panel)),
    radial-gradient(circle at 50% 0%, rgba(173, 69, 242, 0.13), transparent 62%);
}

.lobby-modes,
.lobby-chip,
.round-row,
.rounds-code-block,
.rounds-code input,
.rounds-code button,
.field input,
.local-auth input,
.segmented,
.auth-mode-switch,
.source-row,
.builder-tabs,
.snake-chip,
.drawer-card,
.account-card,
.icon-button,
.option,
.background-effect-option,
.drawer-theme-option,
.profile-stat,
.profile-win,
.coin-statement-row,
.invoice,
.coins-bundle,
.coins-way {
  border-color: var(--panel-border);
  background: var(--control-surface);
  color: var(--text);
}

.lobby-mode.is-active,
.segment:hover,
.source-button:hover,
.option:hover,
.lobby-chip:hover,
.round-row:hover {
  background: var(--control-hover);
}

.lobby-mode span,
.lobby-chip,
.field span,
.option-label,
.face-name,
.round-meta,
.round-state,
.rounds-title,
.rounds-server,
.rounds-code-block small,
.drawer-setting-hint {
  color: var(--text-dim);
}

.option.is-active,
.background-effect-option.is-active,
.drawer-theme-option.is-active {
  border-color: color-mix(in srgb, var(--accent) 68%, transparent);
  background: color-mix(in srgb, var(--accent) 13%, var(--control-surface));
  color: var(--accent);
}

.swatch.is-active {
  border-color: var(--text);
  box-shadow:
    inset 0 0 0 2px rgba(0, 0, 0, 0.24),
    0 0 0 3px color-mix(in srgb, var(--accent) 28%, transparent);
}

/* Complete the theme contract for interactive chrome. Icons are masks filled with currentColor,
   so changing these foreground tokens updates the hamburger, close, sound and refresh artwork as
   well as their labels. Provider-branded buttons deliberately keep their provider colours. */
body {
  background:
    radial-gradient(circle at 18% 18%, color-mix(in srgb, var(--accent) 8%, transparent), transparent 28%),
    radial-gradient(circle at 78% 22%, color-mix(in srgb, var(--blue) 8%, transparent), transparent 30%),
    linear-gradient(180deg, var(--bg), var(--bg-end, var(--bg)));
}

button:focus-visible,
input:focus-visible,
summary:focus-visible {
  outline-color: var(--accent);
}

.account-menu-toggle,
.quick-settings-toggle,
.rounds-strip,
.scoreboard-row,
.scoreboard-empty,
.auth-mode-tabs,
.account-auth-panel,
.password-reset-panel,
.password-reset-panel input,
.music-source-card,
.plain-button,
.profile-refresh {
  border-color: var(--panel-border);
  background: var(--control-surface);
  color: var(--text);
}

.account-menu-toggle:hover,
.quick-settings-toggle:hover,
.rounds-strip:hover,
.plain-button:hover,
.profile-refresh:hover {
  border-color: color-mix(in srgb, var(--accent) 44%, var(--panel-border));
  background: var(--control-hover);
  color: var(--text);
}

.icon-button,
.refresh-button,
.quick-settings-toggle,
.rounds-strip,
.profile-refresh {
  color: var(--text-dim);
}

.play-button,
.auth-button.primary,
.segment.is-active,
.source-button.is-active,
.builder-tab.is-active {
  border-color: color-mix(in srgb, var(--accent) 58%, transparent);
  background: linear-gradient(180deg, var(--accent-hover), var(--accent));
  color: var(--accent-ink);
  box-shadow: 0 12px 28px color-mix(in srgb, var(--accent) 24%, transparent);
}

.play-button.secondary {
  background: linear-gradient(180deg, color-mix(in srgb, var(--blue) 82%, white), var(--blue));
  color: var(--secondary-ink);
  box-shadow: 0 12px 28px color-mix(in srgb, var(--blue) 22%, transparent);
}

.lobby:not(.map-royale) .lobby-play {
  background: linear-gradient(180deg, var(--accent-hover), var(--accent));
  color: var(--accent-ink);
  box-shadow: 0 12px 28px color-mix(in srgb, var(--accent) 24%, transparent);
}

.auth-toggle-button,
.auth-link-button,
.invoice-receipt,
.privacy-link:hover {
  color: var(--accent);
}

.auth-toggle-button {
  border-color: color-mix(in srgb, var(--accent) 50%, transparent);
  background: color-mix(in srgb, var(--accent) 8%, var(--control-surface));
}

.auth-mode-tabs button.is-active {
  background: color-mix(in srgb, var(--accent) 16%, var(--control-surface));
  color: var(--text);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 28%, transparent);
}

.field input::placeholder,
.local-auth input::placeholder,
.rounds-code input::placeholder,
.password-reset-panel input::placeholder {
  color: var(--text-dim);
  opacity: 0.7;
}

.coin-badge,
.top-coin-pill,
.trophy-icon {
  border-color: color-mix(in srgb, var(--accent) 28%, transparent);
  background: color-mix(in srgb, var(--accent) 10%, var(--control-surface));
}

.rounds-drawer,
.rounds-head,
.round-row {
  border-color: var(--panel-border);
}

:root[data-theme-tone="light"] .site-brand,
:root[data-theme-tone="light"] .lobby-mascot {
  filter: drop-shadow(0 8px 20px rgba(42, 42, 53, 0.16));
}

.help-grid section,
.background-effect-art,
.scoreboard-row,
.scoreboard-empty {
  border-color: var(--panel-border);
  background-color: var(--control-surface);
}

.background-effect-art.is-clean {
  background: linear-gradient(145deg, var(--panel-strong), var(--control-surface));
}

.background-effect-art.is-orbits {
  background:
    radial-gradient(circle at 18% 70%, transparent 0 8px, color-mix(in srgb, var(--accent) 35%, transparent) 9px 10px, transparent 11px),
    radial-gradient(circle at 74% 30%, transparent 0 13px, color-mix(in srgb, var(--accent) 28%, transparent) 14px 15px, transparent 16px),
    var(--panel-strong);
}

.background-effect-art.is-waves {
  background:
    repeating-radial-gradient(ellipse at 0 50%, transparent 0 10px, color-mix(in srgb, var(--accent) 26%, transparent) 11px 12px, transparent 13px 22px),
    var(--panel-strong);
}

.background-effect-art.is-hexes {
  background:
    linear-gradient(30deg, transparent 24%, color-mix(in srgb, var(--accent) 24%, transparent) 25% 27%, transparent 28% 74%, color-mix(in srgb, var(--accent) 24%, transparent) 75% 77%, transparent 78%),
    linear-gradient(150deg, transparent 24%, color-mix(in srgb, var(--accent) 24%, transparent) 25% 27%, transparent 28% 74%, color-mix(in srgb, var(--accent) 24%, transparent) 75% 77%, transparent 78%),
    var(--panel-strong);
}

.rounds-strip-chevron,
.rounds-title,
.rounds-server,
.round-state,
.round-shut {
  color: var(--text-dim);
}

.round-bar,
.volume-slider::-moz-range-track {
  background: var(--control-hover);
}

.volume-slider::-webkit-slider-runnable-track {
  background: linear-gradient(
    90deg,
    var(--accent) 0 var(--range-progress),
    var(--control-surface) var(--range-progress) 100%);
}

/* The danger colour, on the two screens that mean danger. A placement is deliberately not one of
   them and is left to the neutral rule above — this block sits far below it in the file and would
   otherwise repaint "of 4 teams · Final length 240" in the red the whole tier exists to drop. */
.end-panel.is-death p,
.revive-note {
  color: var(--danger);
}

.coins-bundle:hover,
.cog-button.is-active {
  border-color: color-mix(in srgb, var(--accent) 48%, transparent);
  background: color-mix(in srgb, var(--accent) 13%, var(--control-surface));
}

.play-button:hover,
.auth-button.primary:hover {
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--accent-hover) 88%, white),
    var(--accent));
  color: var(--accent-ink);
}

.play-button.secondary:hover {
  background: linear-gradient(180deg, color-mix(in srgb, var(--blue) 72%, white), var(--blue));
  color: var(--secondary-ink);
}

.auth-toggle-button:hover {
  border-color: color-mix(in srgb, var(--accent) 70%, transparent);
  background: color-mix(in srgb, var(--accent) 13%, var(--control-surface));
  color: var(--text);
}

.round-tag.classic,
.round-join:not(.royale) {
  border-color: color-mix(in srgb, var(--accent) 36%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent);
}

/* The winners board with nothing on it. Without this the panel is a tall blank gap with
   pagination stranded at the bottom of it. */
.scoreboard-empty {
  margin: 28px 0;
  padding: 0 8px;
  text-align: center;
  color: var(--text-dim);
  font-size: 14px;
  line-height: 1.5;
}


/* A name the server would refuse. Marked as it is typed rather than corrected under the cursor. */
#name.is-bad {
  border-color: var(--danger, #ff4d6d);
}

.field-hint {
  display: block;
  margin-top: 5px;
  color: var(--text-dim);
  font-size: 11.5px;
  line-height: 1.4;
}

.field-hint.is-bad {
  color: var(--danger, #ff4d6d);
}

/* ---------- phone layout ----------------------------------------------------------------------
   Everything below came out of walking the DOM at 360, 390 and 430 wide (and the same three
   sideways), reporting every box whose right edge passed the viewport, every element whose
   scrollWidth beat its clientWidth, and every control the fixed chrome hit-tests in front of.
   Measured, not eyeballed: two pixels is invisible to the eye and unmistakable to
   getBoundingClientRect, and two pixels is exactly what the first of these was. */

/* The UA stylesheet puts `margin: 2px` on input[type=range], and margin sits OUTSIDE border-box —
   so a `width: 100%` slider overhangs its track by 2px on each side. That is the whole of the bug
   that let the iOS settings sheet be dragged sideways. `.volume-slider` already reset it, but the
   pause panel's plain sliders never did: they measured L=33..R=361 inside a `.range-row` running
   L=31..R=359, and the row reported scrollWidth 330 against clientWidth 328. Reset it once for
   every range in the client, so the next slider that gets added cannot bring it back. */
input[type="range"] {
  margin: 0;
}

/* When one overflow axis is anything but `visible`, the other computes from `visible` to `auto`.
   Every panel listed here sets only `overflow-y` (or `overflow: auto`), so each was already a
   horizontal scroll container that nobody asked for — `.app-menu-panel` measured overflow-x: auto
   without a single declaration saying so. Nothing overflows them today (scrollWidth == clientWidth
   at 360, 390 and 430), so this is a deliberate guard rather than a fix: sideways panning is never
   wanted in any of them, and one stray pixel is all it takes. `hidden` rather than `clip` because
   `auto` already clips — the only thing being taken away is the dragging. */
.menu-panel,
.inner-window,
.scoreboard-panel,
.app-menu-panel,
.coins-dialog,
.rounds-list {
  overflow-x: hidden;
}

/* And the reason a guard was worth having: a grid item's `min-width` defaults to `auto`, so it
   refuses to shrink below its own min-content and pushes the track out past the panel instead.
   The winners header measured a 280.9px min-content — a trophy, a heading and two 40px icon
   buttons — inside a 268px content box, so at 320px wide every child of the panel ran from L=27
   to R=307.9 against a content edge of 293.0, and the whole panel could be dragged 15px sideways.
   `minmax(0, 1fr)` lets the track be the width it was told to be; the headings inside already
   ellipsis rather than spill. */
.menu-panel {
  grid-template-columns: minmax(0, 1fr);
}

/* "Round Glasses" is the one accessory name that will not fit a 78px tile: 80px of text in a 66px
   content box, rendering as "Round Glass...". Every other name measured under budget, but the
   names are data in Core, so widening the tile to today's longest one only moves the problem.
   Let the label take a second line instead; the tile's 94px min-height and the row's stretch
   absorb it. */
.face-name {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  white-space: normal;
  text-align: center;
}

/* At 360px wide the five builder tabs get 54px of track each, and "Colour" needs 56px — the last
   letter was being shaved off by the ellipsis. The two pixels come out of the button's own UA side
   padding rather than out of the type or the 38px hit area, and the label is centred, so nothing
   moves at any wider size. */
.builder-tab {
  padding-inline: 4px;
}

/* On a phone the lobby panels run nearly edge to edge, so the two pieces of fixed chrome land ON
   them instead of beside them. Measured at 390x844: the wordmark painted over the whole of the
   builder's <h2>Snake</h2> — 84x28px of it — and over 101x36px of the Classic tab once the rounds
   drawer made the lobby tall enough to reach y=112. The cog is worse, because it takes the tap as
   well as the pixels: 35x48px of "Play Online" at 430x932, the whole of the winners board's
   "Next" at 360x740, and 35x32px of a round's "Join" at 390x844.

   The app already does exactly this while a game is running (`body.is-playing`); a panel open over
   the lobby is the same situation, and the drawer behind the hamburger is still the way to the
   settings the cog opens. Phones only — on a desktop the panel is centred and the chrome sits
   beside it, where it is wanted. */
@media (max-width: 560px) {
  body:has(.menu-panel:not(.is-hidden)) .site-brand,
  body:has(#rounds-drawer:not([hidden])) .site-brand {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
  }

  body:has(.menu-panel:not(.is-hidden)) .quick-settings-toggle,
  body:has(.inner-window:not(.is-hidden)) .quick-settings-toggle,
  body:has(#rounds-drawer:not([hidden])) .quick-settings-toggle {
    display: none;
  }
}

/* ---------- the colour painter ----------

   Sits under the single-colour swatches in the Colour tab. The iOS builder has the same control
   with the same gestures (ColourPainter.razor); a snake a player can paint in one client and not
   the other is the "the snake I designed is not the snake I got" failure this game keeps guarding
   against, so the two are built to look and behave alike rather than merely both existing. */

.paint-start {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  background: var(--control-surface);
  color: var(--text);
  text-align: left;
}

.paint-start:hover { background: var(--control-hover); }

/* Three stripes rather than a word: the button has to look like the thing it makes. */
.paint-start-art {
  flex: 0 0 auto;
  width: 46px;
  height: 22px;
  border-radius: 999px;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.25);
}

.paint-start-words { display: grid; gap: 2px; min-width: 0; }
.paint-start-title { font-weight: 800; font-size: 14px; }
.paint-start-sub { font-size: 11.5px; font-weight: 600; color: var(--text-dim); }

.paint-editor { display: grid; gap: 9px; }

.paint-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.paint-head h3 { margin: 0; }

/* Always on screen while a painting is: the way out of a tool has to be as easy to find as the
   way in, or single colour stops being the default in practice. */
.paint-plain {
  padding: 5px 11px;
  border: 1px solid var(--panel-border);
  border-radius: 999px;
  background: var(--control-surface);
  color: var(--text-dim);
  font-size: 11.5px;
  font-weight: 760;
}

.paint-plain:hover { color: var(--text); background: var(--control-hover); }

.paint-row { display: flex; gap: 8px; align-items: stretch; }

/* Butted together, so the cycle reads as one stretch of painted snake rather than as a row of
   separate answers. */
.paint-strip {
  flex: 1;
  display: flex;
  gap: 2px;
  min-width: 0;
  border-radius: 999px;
  overflow: hidden;
}

.paint-slot {
  flex: 1;
  min-width: 0;
  height: 44px;
  border: none;
  background: var(--swatch, #3bf5c8);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.25);
}

/* Drawn inside the slot rather than around it: the slots have no gaps, so an outer ring would
   sit on top of its neighbours. */
.paint-slot.is-active {
  box-shadow:
    inset 0 0 0 3px var(--text),
    inset 0 0 0 4px rgba(0, 0, 0, 0.35);
}

.paint-slot.is-add {
  flex: 0 0 44px;
  border-radius: 999px;
  background: var(--control-surface);
  box-shadow: inset 0 0 0 1px var(--panel-border);
  color: var(--text-dim);
  font-size: 21px;
  font-weight: 800;
  line-height: 1;
}

.paint-slot.is-add.is-active {
  box-shadow: inset 0 0 0 2px var(--accent);
  color: var(--accent);
}

/* A fixed number of BANDS rather than a fixed number of cycles, so every band is the same width
   whatever the cycle length and the last cycle may run out part-way — which is what a real snake
   does. The fade says "and on down the body" instead of ending at a hard edge. */
.paint-repeat {
  height: 14px;
  border-radius: 999px;
  background-repeat: repeat-x;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.25);
  -webkit-mask-image: linear-gradient(90deg, #000 0%, #000 72%, transparent 100%);
  mask-image: linear-gradient(90deg, #000 0%, #000 72%, transparent 100%);
}

/* Says out loud what the bar above it is. Without it the bar is only a second, longer version of
   the strip, and the thing it exists to show — that the cycle comes round again — is left to be
   inferred. */
.paint-repeat-note {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--text-dim);
}

.paint-acts { display: flex; gap: 8px; }

.paint-act {
  min-width: 44px;
  padding: 7px 12px;
  border: 1px solid var(--panel-border);
  border-radius: 999px;
  background: var(--control-surface);
  color: var(--text);
  font-size: 12.5px;
  font-weight: 760;
}

.paint-act:hover:not(:disabled) { background: var(--control-hover); }
.paint-act:disabled { opacity: 0.32; cursor: default; }

.paint-lift {
  margin-left: auto;
  border-color: color-mix(in srgb, var(--danger, #ff4d6d) 45%, transparent);
  background: color-mix(in srgb, var(--danger, #ff4d6d) 12%, transparent);
  color: var(--danger, #ff4d6d);
}

/* ---------- the lobby's legal footer -----------------------------------------------------------
   Terms, Privacy and Support existed only inside the settings drawer, where they inherit
   `visibility: hidden` while it is shut — so they had a layout box and were never painted, and
   nobody who did not open the drawer could find them. Apple and the payment terms both expect
   these reachable from the front page, and a player looking for support should not have to guess
   which panel hides it.

   Part of the lobby column rather than fixed to the viewport, deliberately: a recent audit found
   this client's fixed chrome winning elementsFromPoint over the buttons beneath it, and a footer
   pinned to the bottom of a phone screen is exactly how that happens again. */
.lobby-legal {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 4px 18px;
    margin-top: 18px;
    padding-top: 2px;
}

.lobby-independence {
    flex-basis: 100%;
    margin: 0 0 2px;
    color: var(--text-dim, #94a0b8);
    font-size: 11.5px;
    line-height: 1.45;
    text-align: center;
}

.lobby-legal a {
    /* 44px of hit area from padding rather than height, so the row stays visually light while a
       thumb still has something to land on. Vertical only — a negative margin does not undo a
       horizontal overhang, which is what let the legal pages be dragged sideways. */
    padding: 12px 0;
    font-size: 12.5px;
    font-weight: 600;
    letter-spacing: 0.01em;
    color: var(--text-dim, #94a0b8);
    text-decoration: none;
}

.lobby-legal a:hover,
.lobby-legal a:focus-visible { color: var(--text, #e8edf6); text-decoration: underline; }

/* --- Year in Review -------------------------------------------------------------------------
   The one screen in this game whose whole job is to be a pleasure, and the only one a player is
   likely to screenshot on purpose. Everything here is built by wwwroot/js/year-in-review.js, which
   is imported on demand — the round-up is an event an admin switches on, not a permanent fixture,
   so none of this markup exists in the page until somebody asks for it.

   Two rules shaped the whole block. The reveal is ONE timeline — the brand settles, the year
   lands, then the numbers arrive in order — because a handful of independent effects reads as a
   page that has not finished loading, while a sequence reads as something being presented to you.
   And nothing here is fixed chrome: the overlay is created on open and removed on close. The audit
   that preceded this work found fixed chrome over the lobby swallowing taps meant for the buttons
   underneath it, and a full-screen layer parked under a class is that bug waiting to come back. */

.yir-overlay {
  position: fixed;
  z-index: 12;                      /* above the drawer at 9, which is the highest thing here */
  inset: 0;
  overflow: hidden;
  background: color-mix(in srgb, var(--bg, #05070c) 88%, transparent);
  backdrop-filter: blur(14px) saturate(1.1);
  -webkit-backdrop-filter: blur(14px) saturate(1.1);
}

.yir-overlay[data-motion="playing"] {
  animation: yirOverlayIn 260ms ease-out both;
}

/* The colour behind everything, on its own layer so the compositor moves a transform and an
   opacity instead of repainting the panel sitting on top of it every frame. */
.yir-aurora {
  position: absolute;
  inset: -25%;
  pointer-events: none;
  background:
    radial-gradient(38% 34% at 22% 18%, color-mix(in srgb, var(--accent, #3bf5c8) 34%, transparent), transparent 70%),
    radial-gradient(42% 38% at 82% 74%, color-mix(in srgb, var(--gold, #ffd166) 26%, transparent), transparent 72%),
    radial-gradient(46% 40% at 62% 8%, color-mix(in srgb, var(--blue, #4da6ff) 20%, transparent), transparent 74%);
  opacity: 0.78;
  will-change: transform;
}

.yir-overlay[data-motion="playing"] .yir-aurora {
  animation: yirAurora 26s ease-in-out infinite alternate;
}

.yir-scroll {
  position: relative;
  display: grid;
  /* "safe" for the same reason .menu-shell uses it: a centred item taller than its container
     overflows both edges, and the half above the top cannot be scrolled back to. */
  align-items: safe center;
  justify-items: center;
  height: 100%;
  padding:
    calc(20px + var(--safe-top))
    calc(16px + var(--safe-right))
    calc(24px + var(--safe-bottom))
    calc(16px + var(--safe-left));
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.yir-card {
  position: relative;
  display: grid;
  gap: 16px;
  width: min(560px, 100%);
  padding: 24px;
  border: 1px solid var(--panel-border, rgba(255, 255, 255, 0.14));
  border-radius: 20px;
  background:
    radial-gradient(76% 44% at 8% 0, color-mix(in srgb, var(--accent, #3bf5c8) 13%, transparent), transparent 68%),
    radial-gradient(66% 40% at 100% 6%, color-mix(in srgb, var(--gold, #ffd166) 10%, transparent), transparent 70%),
    linear-gradient(180deg, rgba(255, 255, 255, 0.06), transparent 34%),
    var(--panel-strong, rgba(9, 14, 23, 0.92));
  box-shadow: 0 30px 90px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.1);
  overflow: hidden;
}

/* One diagonal sweep across the panel as it arrives, and then never again. A repeating sheen on a
   screen a player is reading is a distraction; a single one is the sense that something has just
   been handed to them. */
.yir-overlay[data-motion="playing"] .yir-card::after {
  content: "";
  position: absolute;
  inset: -40% -60%;
  pointer-events: none;
  background: linear-gradient(104deg, transparent 42%, rgba(255, 255, 255, 0.13) 50%, transparent 58%);
  animation: yirSheen 1500ms 380ms ease-out both;
}

.yir-brand {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

/* The padding is the crown's headroom, and it is load-bearing. In the page header the crown hangs
   off the top of a fixed bar that has the safe-area inset above it; here the wordmark is the first
   thing in the card, so a negative offset put the crown straight through the middle of SLITHER —
   which reads as a typo, not a brand. */
.yir-mark {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding-top: 22px;
  padding-left: 2px;
}

/* Sized and angled the way .brand-crown is in the page header. */
.yir-crown {
  position: absolute;
  top: 0;
  left: 14px;
  width: 26px;
  height: 21px;
  object-fit: contain;
  filter: drop-shadow(0 0 5px rgba(248, 211, 106, 0.28));
  transform: rotate(-16deg);
}

.yir-wordmark {
  display: grid;
  line-height: 0.86;
  text-align: left;
}

/* The site wordmark, not a second one. Same family, same italic, same weight, same tracking as
   .brand-type strong — a retrospective that invented its own logotype would be the one screen in
   the game that looked like it came from somewhere else. */
.yir-wordmark strong {
  color: var(--text, #eef3ff);
  font-family: Poppins, Inter, sans-serif;
  font-size: 19px;
  font-style: italic;
  font-weight: 950;
  letter-spacing: 0.035em;
}

.yir-wordmark strong + strong {
  color: var(--accent, #3bf5c8);
  font-size: 17px;
}

.yir-year {
  margin: 0;
  font-family: Poppins, Inter, sans-serif;
  font-size: clamp(34px, 11vw, 46px);
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  line-height: 0.9;
  letter-spacing: -0.02em;
  background: linear-gradient(140deg, var(--accent, #3bf5c8), var(--gold, #ffd166));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* The player's other years. Quiet by design: this is a way back to 2025, not a navigation bar,
   and it only exists at all when the caller has offered to fetch another one. */
.yir-years {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: -4px;
}

.yir-year-chip {
  min-height: 34px;
  padding: 0 12px;
  border: 1px solid var(--panel-border, rgba(255, 255, 255, 0.14));
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.05);
  color: var(--muted, var(--text-dim, #94a0b8));
  font-size: 12.5px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}

.yir-year-chip.is-current {
  border-color: color-mix(in srgb, var(--accent, #3bf5c8) 40%, transparent);
  background: color-mix(in srgb, var(--accent, #3bf5c8) 14%, transparent);
  color: var(--text, #eef3ff);
  cursor: default;
}

.yir-headline {
  margin: 0;
  color: var(--text, #eef3ff);
  font-size: clamp(26px, 7.4vw, 38px);
  font-weight: 900;
  line-height: 1.04;
  letter-spacing: -0.015em;
  text-wrap: balance;
}

.yir-subhead {
  margin: -6px 0 0;
  color: var(--muted, var(--text-dim, #94a0b8));
  font-size: 14.5px;
  line-height: 1.45;
}

.yir-stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  margin: 2px 0 0;
  padding: 0;
  list-style: none;
}

/* The same card the profile drawer uses, so a number here and a number there are recognisably the
   same kind of thing. Left-aligned rather than centred: these carry a unit and often a caption. */
.yir-stat {
  display: grid;
  align-content: start;
  gap: 5px;
  min-width: 0;
  padding: 14px 14px 15px;
  border: 1px solid var(--panel-border, rgba(255, 255, 255, 0.14));
  border-radius: 14px;
  background:
    radial-gradient(circle at 50% 0, color-mix(in srgb, var(--accent, #3bf5c8) 11%, transparent), transparent 72%),
    rgba(255, 255, 255, 0.035);
}

.yir-stat.is-hero {
  grid-column: 1 / -1;
  padding: 18px 18px 20px;
  border-color: color-mix(in srgb, var(--accent, #3bf5c8) 30%, transparent);
  background:
    radial-gradient(84% 130% at 12% 0, color-mix(in srgb, var(--accent, #3bf5c8) 18%, transparent), transparent 70%),
    rgba(255, 255, 255, 0.045);
}

.yir-stat-label {
  color: var(--muted, var(--text-dim, #94a0b8));
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.yir-stat-figure {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin: 0;
  min-width: 0;
}

.yir-stat-value {
  color: var(--text, #eef3ff);
  font-family: Poppins, Inter, sans-serif;
  font-size: clamp(25px, 7.6vw, 32px);
  font-weight: 900;
  /* Tabular figures, or a number counting up jitters sideways on every frame. */
  font-variant-numeric: tabular-nums;
  line-height: 1;
  letter-spacing: -0.01em;
}

.yir-stat.is-hero .yir-stat-value {
  font-size: clamp(44px, 15vw, 62px);
  background: linear-gradient(140deg, var(--accent, #3bf5c8), var(--gold, #ffd166));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.yir-stat-unit {
  color: var(--muted, var(--text-dim, #94a0b8));
  font-size: 12px;
  font-weight: 750;
}

.yir-stat.is-hero .yir-stat-unit {
  font-size: 15px;
}

.yir-stat-caption {
  color: var(--muted, var(--text-dim, #94a0b8));
  font-size: 11.5px;
  line-height: 1.35;
}

.yir-empty {
  margin: 0;
  padding: 22px 16px;
  border: 1px dashed var(--panel-border, rgba(255, 255, 255, 0.14));
  border-radius: 14px;
  color: var(--muted, var(--text-dim, #94a0b8));
  font-size: 14px;
  line-height: 1.5;
  text-align: center;
}

.yir-footnote {
  margin: 0;
  color: var(--muted, var(--text-dim, #94a0b8));
  font-size: 11.5px;
  line-height: 1.45;
}

.yir-actions {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 10px;
}

.yir-export {
  display: grid;
  gap: 10px;
  margin: 0;
  padding-top: 16px;
  border-top: 1px solid var(--panel-border, rgba(255, 255, 255, 0.14));
}

.yir-export.is-hidden {
  display: none !important;
}

/* The picture has to be saveable by hand, and `body` switches both of these off for the whole
   client so a drag on the arena never selects text or raises iOS's callout menu. Without the
   override, long-pressing the card does nothing at all — which would leave a download the page
   starts itself as the only way out, and that is exactly the one iOS Safari refuses. */
.yir-export-image {
  width: 100%;
  height: auto;
  border: 1px solid var(--panel-border, rgba(255, 255, 255, 0.14));
  border-radius: 14px;
  background: var(--bg, #05070c);
  -webkit-touch-callout: default;
  -webkit-user-select: auto;
  user-select: auto;
}

.yir-export-caption {
  color: var(--muted, var(--text-dim, #94a0b8));
  font-size: 12px;
  line-height: 1.45;
  text-align: center;
}

.yir-download {
  display: grid;
  place-items: center;
  min-height: 44px;
  padding: 0 14px;
  text-decoration: none;
}

/* The door in the profile drawer. Ships hidden; app.js reveals it when /review/year answers. */
#year-review-section .play-button {
  width: 100%;
}

/* The reveal. One timeline: brand, year, headline, then the cards in order, then the buttons. The
   stat delay is derived from --yir-index, which the module sets per card, so the pacing lives here
   rather than in script — and it is the same 90ms step the count-up uses. */
.yir-overlay[data-motion="playing"] .yir-brand,
.yir-overlay[data-motion="playing"] .yir-years,
.yir-overlay[data-motion="playing"] .yir-headline,
.yir-overlay[data-motion="playing"] .yir-subhead,
.yir-overlay[data-motion="playing"] .yir-empty,
.yir-overlay[data-motion="playing"] .yir-footnote,
.yir-overlay[data-motion="playing"] .yir-actions {
  animation: yirRise 560ms cubic-bezier(0.2, 0.9, 0.25, 1) both;
}

.yir-overlay[data-motion="playing"] .yir-brand { animation-delay: 60ms; }
.yir-overlay[data-motion="playing"] .yir-years { animation-delay: 150ms; }
.yir-overlay[data-motion="playing"] .yir-headline { animation-delay: 200ms; }
.yir-overlay[data-motion="playing"] .yir-subhead { animation-delay: 280ms; }
.yir-overlay[data-motion="playing"] .yir-empty { animation-delay: 420ms; }
.yir-overlay[data-motion="playing"] .yir-footnote { animation-delay: 900ms; }
.yir-overlay[data-motion="playing"] .yir-actions { animation-delay: 980ms; }

.yir-overlay[data-motion="playing"] .yir-year {
  animation: yirYear 700ms 120ms cubic-bezier(0.2, 1.2, 0.3, 1) both;
}

.yir-overlay[data-motion="playing"] .yir-stat {
  animation: yirRise 620ms cubic-bezier(0.2, 0.9, 0.25, 1) both;
  animation-delay: calc(var(--yir-index, 0) * 90ms + 420ms);
}

@keyframes yirOverlayIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes yirRise {
  from { opacity: 0; transform: translateY(16px) scale(0.985); }
  to { opacity: 1; transform: none; }
}

@keyframes yirYear {
  from { opacity: 0; transform: translateY(10px) scale(0.82); }
  to { opacity: 1; transform: none; }
}

@keyframes yirSheen {
  from { opacity: 0; transform: translateX(-46%); }
  30% { opacity: 1; }
  to { opacity: 0; transform: translateX(46%); }
}

@keyframes yirAurora {
  from { transform: translate3d(-3%, -2%, 0) scale(1); }
  to { transform: translate3d(3%, 4%, 0) scale(1.12); }
}

@media (max-width: 560px) {
  .yir-scroll {
    padding:
      calc(14px + var(--safe-top))
      calc(12px + var(--safe-right))
      calc(18px + var(--safe-bottom))
      calc(12px + var(--safe-left));
  }

  .yir-card { padding: 18px; gap: 14px; }
  .yir-mark { padding-top: 20px; }
  .yir-crown { top: 0; left: 11px; width: 24px; height: 20px; }
  .yir-wordmark strong { font-size: 16px; }
  .yir-wordmark strong + strong { font-size: 15px; }
}

/* Not merely stilled. A player who has turned on Reduce Motion has already told every site they do
   not want a screenful of movement, and the honest answer to that on a celebration screen is the
   numbers without the ceremony — the same call the results panel makes when it hides the confetti
   rather than freezing it mid-fall. The count-up is switched off in script for the same reason:
   this stylesheet cannot stop a number ticking. */
@media (prefers-reduced-motion: reduce) {
  .yir-overlay,
  .yir-overlay .yir-aurora,
  .yir-overlay .yir-card::after,
  .yir-overlay .yir-brand,
  .yir-overlay .yir-years,
  .yir-overlay .yir-year,
  .yir-overlay .yir-headline,
  .yir-overlay .yir-subhead,
  .yir-overlay .yir-empty,
  .yir-overlay .yir-stat,
  .yir-overlay .yir-footnote,
  .yir-overlay .yir-actions {
    animation: none !important;
    transition: none !important;
  }

  .yir-overlay .yir-card::after { content: none; }
}

/* ---- Team Deathmatch ---------------------------------------------------------------------
   The lobby entry and the party panel. Kept to the same words, the same order and the same
   shape as the app's — TeamDeathmatchParityTests holds the strings, and this holds the layout
   they sit in. */

/* Shaped like the year-in-review card the app puts above it, and for the same reason: both are
   "another thing you could do from here" rather than a mode in the chooser, and two entry points
   of the same kind that looked different would read as two kinds of thing.

   The margin is the whole difference between that being true and being merely intended. It was
   zero, and this row is the only block in the lobby that had none — .lobby-play has 14, .lobby-solo
   10, .rounds-strip 14 — so it sat with its top edge exactly on the mode chooser's bottom edge, at
   the same radius and against the same border colour. Measured at 390: the chooser ends at 324 and
   this began at 324. What that draws is a THIRD ROW OF THE CHOOSER, directly above the two verbs
   that act on the chooser — and pressing Play online after reading it does not start a deathmatch.
   Twelve, to match the 12 the app already carries below this same row. */
.team-deathmatch {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  width: 100%;
  margin-top: 12px;
  padding: 11px 12px;
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  background: var(--panel-soft);
  color: var(--text);
  text-align: left;
  cursor: pointer;
  transition: transform 90ms ease, filter 90ms ease;
}

.team-deathmatch:hover { background: var(--control-hover); }
.team-deathmatch:active { transform: scale(0.985); filter: brightness(1.2); }

/* The four team colours as the mark, in Core's order — painted from SlitherTeams rather than
   written in here, so they are the same four the arena will draw.

   Quartered rather than a row of four bars, and both halves of that are the point. A 2x2 tile is
   the shape of the thing it stands for — four sides — and it is a MARK, sitting in the row where
   the chips below put an icon, which is what makes the row read as a door rather than a banner.
   A 45x22 stripe of four saturated hues, flush against the left edge of a lobby that is otherwise
   black and one green, was the loudest element on the panel and the first thing the eye landed
   on, for something that is not the primary action. Quartering it drops the colour from 792 square
   pixels to 400 without losing a single one of the four, and hands the 23px it gives back to the
   line of text beside it, which is the one thing on the row that runs out of room first.

   Markup untouched in both clients — four <i> elements painted from Teams.All — so this is a
   layout change and neither client has learned a colour of its own. */
.tdm-flags {
  display: grid;
  grid-template-columns: repeat(2, 10px);
  gap: 2px;
}

.tdm-flags i {
  display: block;
  width: 10px;
  height: 10px;
  border-radius: 2.5px;
}

.tdm-main { display: grid; gap: 2px; min-width: 0; }
.tdm-main strong { font-size: 13.5px; font-weight: 750; }

.tdm-main small {
  overflow: hidden;
  color: var(--text-dim);
  font-size: 11px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tdm-arrow { color: var(--text-dim); font-size: 18px; }

/* ---- the party lobby --------------------------------------------------------------------- */

.party-panel { gap: 12px; }

.party-head {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: 10px;
}

.party-head h1 { margin: 0; font-size: 20px; font-weight: 850; }
.party-tagline { margin: 3px 0 0; font-size: 12.5px; color: var(--text-dim); }

.party-state { margin: 0; font-size: 14.5px; line-height: 1.35; text-align: center; }
.party-queue { margin: 0; font-size: 12.5px; color: var(--accent); text-align: center; }

.party-tip,
.party-builder,
.party-game-choice {
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  background: var(--panel-soft);
}

.party-tip { display: grid; gap: 3px; padding: 9px 11px; color: var(--text-dim); font-size: 11.5px; }
.party-tip strong { color: var(--accent); font-size: 10px; letter-spacing: .05em; text-transform: uppercase; }

.party-builder {
  display: grid;
  gap: 2px;
  padding: 10px 11px;
  color: var(--text);
  text-align: left;
  cursor: pointer;
}
.party-builder strong { font-size: 13px; }
.party-builder small { color: var(--text-dim); }

.party-game-choice { display: grid; gap: 9px; padding: 11px; }
.party-choice-head { display: flex; justify-content: space-between; gap: 8px; font-size: 12.5px; }
.party-choice-head span { color: var(--accent); }
.party-game-choice label { display: grid; grid-template-columns: 1fr auto; gap: 5px 8px; color: var(--text-dim); font-size: 11.5px; }
.party-game-choice select {
  min-width: 150px;
  border: 1px solid var(--panel-border);
  border-radius: 8px;
  background: var(--control-surface);
  color: var(--text);
  padding: 6px;
}
.party-game-choice > small,
.party-private-options > small { color: var(--text-dim); font-size: 11px; line-height: 1.35; }
.party-private-options { display: grid; gap: 8px; }
.party-private-options input[type="range"] { grid-column: 1 / -1; width: 100%; accent-color: var(--accent); }
.party-private-options output { color: var(--text); }

.party-seats {
  list-style: none;
  display: grid;
  gap: 6px;
  margin: 0;
  padding: 0;
}

/* Two columns when the screen is wide and short, which is a phone held sideways.
 *
 * Measured when a side was eight: eight rows in a single column is 378px of list, and a 844x390
 * landscape phone leaves the panel 276px for everything it has to say — the list showed one seat
 * and a scrollbar. There is no shortage of width there: the panel is 520px in an 844px window.
 *
 * A side is four now, so the list is half that and the overflow it was fixing is gone. The rule
 * stays because the shortage it was measured against has not: seats are only part of what the
 * panel holds — a state line, a queue line, a ready line, a share box, a code and two buttons —
 * and two columns of two still buys back 83px of the 276 for nothing.
 *
 * Not on a desktop, where the height is not the constraint: four seats in one column read as a
 * list of four, and two columns of two read as two pairs. */
@media (min-width: 620px) and (max-height: 620px) {
  .party-seats { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* And the two buttons side by side, for the same reason and against the same shortage: stacked
     they are 120px of a 276px panel, which is most of what a sideways phone has to give. */
  .party-acts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.party-seat {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  background: var(--panel-soft);
  font-size: 13.5px;
}

/* An empty seat is drawn quietly rather than left out. It is the room the party still has, which
   is exactly what somebody deciding whether to send another link is looking for. */
.party-seat.is-empty { opacity: 0.42; border-style: dashed; }
.party-seat.is-ready { border-color: color-mix(in srgb, var(--accent) 45%, var(--panel-border)); }

.party-seat-number { color: var(--text-dim); font-size: 11.5px; text-align: right; }
.party-seat-name { font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.party-seat-status { font-size: 11.5px; color: var(--text-dim); white-space: nowrap; }
.party-seat.is-ready .party-seat-status { color: var(--accent); }

/* Air above and below. Butted straight against the last seat and the share box it read as one more
   row of the list rather than as a summary of it. */
.party-ready-line { margin: 2px 0; text-align: center; font-size: 12.5px; color: var(--text-dim); }

.party-share {
  display: grid;
  justify-items: center;
  gap: 8px;
  padding: 14px 12px;
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  background: var(--panel-soft);
}

.party-share-line { font-size: 12.5px; color: var(--text-dim); }

/* Bordered rather than filled, and deliberately so: Start is the decision on this screen and
   Share supports it. Both drawn in the accent put two equal-weight greens one above the other and
   left nothing saying which one the party is waiting for — the same mistake the connect screen
   records making with two primaries side by side. */
.party-share-button {
  width: 100%;
  min-height: 44px;
  border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent);
  font-weight: 850;
  cursor: pointer;
}

.party-share-button:disabled { cursor: not-allowed; filter: saturate(0.62) brightness(0.78); }

/* The code in full, because somebody will read it out rather than send it — and because it is the
   one string on this screen anybody can repeat back. Monospace is how a person knows to read it
   character by character. */
.party-code-shown {
  font-family: ui-monospace, Menlo, monospace;
  font-size: 14.5px;
  letter-spacing: 0.06em;
  user-select: all;
}

/* The one decision on this screen, held where it can be seen.
 *
 * A party lobby grows: a row per seat, a share box and a code, and on a 320x568 phone a full party
 * of eight — a side's worth, before a side became four — put that 279px past what the panel can
 * show. "Start the round" was below the fold with nothing on screen to suggest it existed, and the
 * owner scrolls past their own party to find the button their party is waiting for. Four rows have
 * far more room, and the rule stays because being below the fold at all is the fault, not being a
 * long way below it.
 *
 * Sticky rather than a nested scroller on the seat list. A list that scrolls inside a panel that
 * scrolls is two gestures a thumb has to tell apart, and the rounds drawer is the one place that
 * earns it. Here nothing needs to be hidden; the action just has to stop leaving.
 *
 * Full-bleed, on negative margins that cancel the panel's own 18px padding, so the rows slide
 * underneath it rather than past it in a gutter. Painted in `--panel-strong` rather than `--panel`
 * for the reason the builder's pinned band records: the panel's ground is translucent, and a
 * translucent band over it composites to a different colour. */
.party-acts {
  display: grid;
  gap: 6px;
  position: sticky;
  bottom: -18px;
  z-index: 1;
  margin: 0 -18px -18px;
  padding: 8px 18px 18px;
  background: var(--panel-strong);
}

.party-blocked { margin: 0; text-align: center; font-size: 12px; color: var(--text-dim); }

.party-start { display: grid; gap: 20px; }
.party-code-block { display: grid; gap: 8px; }
.party-code-block label { font-size: 12.5px; color: var(--text-dim); }
.party-code-block small { font-size: 11.5px; color: var(--text-dim); }

.party-code-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; }

.party-code-row input {
  min-width: 0;
  min-height: 44px;
  padding: 0 12px;
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  background: var(--control-surface);
  color: var(--text);
  /* 16px or larger, or mobile Safari zooms the whole page in on focus and leaves the lobby
     scrolled sideways with no way back that does not look like a bug. */
  font: 700 16px ui-monospace, Menlo, monospace;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.party-code-row button {
  min-height: 44px;
  padding: 0 18px;
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  background: var(--control-hover);
  color: var(--text);
  font-weight: 800;
  cursor: pointer;
}

.party-code-row button:disabled { cursor: not-allowed; filter: saturate(0.62) brightness(0.78); }
