* { margin: 0; padding: 0; box-sizing: border-box; }

html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #0b1220;
  color: #e6edf7;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior: none;
}

/* Inline icon from assets/icons/ (iconHtml in ui.js), sized to the text it
   sits in. Containers that need a different size override width/height. */
.ic {
  display: inline-block;
  width: 1.2em;
  height: 1.2em;
  vertical-align: -0.28em;
  pointer-events: none;
}

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

#topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  padding: calc(env(safe-area-inset-top) + 6px) 8px 6px;
  pointer-events: none;
}

#leaderboard {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

.lbRow {
  display: flex;
  align-items: center;
  gap: 6px;
  background: rgba(8, 14, 26, 0.65);
  border-radius: 4px;
  padding: 2px 6px;
  width: fit-content;
  max-width: 72vw;
}

.lbRow.me { outline: 1px solid rgba(255, 255, 255, 0.35); }

.lbToggle { cursor: pointer; pointer-events: auto; font-weight: 600; padding: 4px 8px; }
.lbTeamRow { cursor: pointer; font-weight: 600; }
.lbTeamRow:hover { background: rgba(8, 14, 26, 0.85); }

/* Fog of war only (see UI.fogLeaderboardHtml). A row's true rank, since the
   rows shown are not consecutive; a leader the viewer has not met, which is
   named but takes no clicks; and the "+N unknown" line under the list. */
.lbRank { flex: 0 0 auto; min-width: 14px; text-align: right; opacity: 0.6; }
.lbRow.unmet { cursor: default; }
.lbRow.unmet:hover { background: rgba(8, 14, 26, 0.65); }
.lbRow.unmet .lbName { font-style: italic; opacity: 0.8; }
.lbUnknown { font-style: italic; opacity: 0.7; }

.lbCaret {
  flex: 0 0 auto;
  width: 9px;
  font-size: 9px;
  text-align: center;
  opacity: 0.6;
  transition: transform 0.15s;
}
.lbCaret.open { transform: rotate(90deg); }

/* Indented to the caret's width so a team's roster reads as nested under it,
   whether it's the full list (expanded) or just your own row (collapsed). */
.lbChildren {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 2px 0 2px 15px;
}

.lbSwatch {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  flex: 0 0 auto;
}

.lbName { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lbMark { flex: 0 0 auto; font-size: 10px; display: flex; gap: 1px; }
/* Right-aligned in a fixed column so the treasuries stack into something
   comparable at a glance rather than jittering as the digits change. */
.lbGold { flex: 0 0 auto; width: 46px; text-align: right; color: #f0c869; opacity: 0.9; }
.lbPct { flex: 0 0 auto; width: 40px; text-align: right; opacity: 0.75; }

/* Half-width and centered rather than edge-to-edge: on anything wider than a
   phone, a full-span HUD left the troop/gold readouts and the build bar
   stretched uncomfortably far apart from each other. Phones get an exception
   below — there isn't a spare half-screen to give up there, and forcing the
   same width wrapped the troop row onto a second line. */
#hud {
  position: fixed;
  left: 50%;
  bottom: 0;
  width: 50%;
  min-width: 320px;
  transform: translateX(-50%);
  padding: 4px 12px calc(env(safe-area-inset-bottom) + 6px);
  background: linear-gradient(to top, rgba(8, 14, 26, 0.98), rgba(8, 14, 26, 0.85));
  display: flex;
  flex-direction: column;
  gap: 3px;
}

@media (max-width: 700px) {
  #hud {
    left: 0;
    right: 0;
    width: auto;
    min-width: 0;
    transform: none;
  }
}

/* --- Status row: growth chip · population bar · gold chip ----------------- */
/* Three fixed-height pieces side by side rather than the old label/value/rate
   text stacked across two separate rows — the population bar itself carries
   its own current/max reading instead of being a bare strip underneath it,
   so this one row replaces what used to take three. */
#statusRow {
  display: flex;
  align-items: stretch;
  gap: 6px;
  height: 30px;
}

#growthChip, #goldChip {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 0 10px;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

#growthChip {
  background: rgba(111, 224, 138, 0.14);
  border: 1px solid rgba(111, 224, 138, 0.35);
  color: #6fe08a;
}

#troopGrowth.stalled { color: #e0a86f; }
#growthChip:has(#troopGrowth.stalled) { background: rgba(224, 168, 111, 0.14); border-color: rgba(224, 168, 111, 0.35); }

#goldChip {
  background: rgba(240, 200, 105, 0.14);
  border: 1px solid rgba(240, 200, 105, 0.35);
  color: #f0c869;
}

#popBar {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.1);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

#popBarLabel {
  position: relative;
  z-index: 1;
  font-size: 16px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}

#popBarSep { opacity: 0.6; margin: 0 3px; font-weight: 400; }

/* --- Fronts & defenses: your own attacks/boats, plus incoming ones -------- */
/* One chip per active attack or boat touching the player, in either
   direction. Pushes and boats the player launched carry an X to retreat
   them; incoming attacks and boats read as defenses and have none — there is
   nothing to cancel about someone else's army.
   Floated above #hud's own gradient panel instead of laid out inside it: an
   in-flow row here grew that panel taller every time a fight was active, and
   its solid background sat behind chips that already carry their own. As an
   overlay, chips float directly over the map and the panel underneath never
   changes size, whether there are zero fronts or a screenful of them. The
   row itself ignores clicks so gaps between chips still reach the map;
   individual chips opt back in below. */
#frontsRow {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  padding: 0 12px 6px;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  pointer-events: none;
}

#frontsRow .frontChip, #frontsRow .frontClose {
  pointer-events: auto;
}

.frontChip {
  display: flex;
  align-items: center;
  gap: 5px;
  background: rgba(16, 26, 46, 0.9);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 6px;
  padding: 3px 6px;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}

/* Blue for a push or boat the player sent, red for one aimed at them —
   matching the same colour split render.js already uses for front labels. */
.frontChip.mine { border-color: rgba(109, 180, 255, 0.4); }
.frontChip.theirs { border-color: rgba(255, 107, 107, 0.4); }

.frontIcon { font-size: 13px; line-height: 1; }
.frontIcon .ic { width: 1.25em; height: 1.25em; vertical-align: middle; }
.frontTroops { font-weight: 600; }
.frontName { opacity: 0.7; max-width: 96px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Already leaving — dimmed and inert rather than removed, so the chip doesn't
   just vanish out from under a thumb mid-tap. */
.frontChip.retreating { opacity: 0.55; }

/* Visually a small dot, but padded out to a real touch target underneath —
   16px was reliably missable on a phone, where this sits right next to a
   chip full of tap-adjacent text. background-clip keeps the red circle
   itself small while the invisible padding around it still catches the tap. */
.frontClose {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  padding: 3px;
  border-radius: 50%;
  border: 0;
  background: rgba(220, 60, 70, 0.85) content-box;
  background-clip: content-box;
  color: #fff;
  font-size: 11px;
  line-height: 16px;
  text-align: center;
  font-weight: 700;
  cursor: pointer;
}

.frontClose:active { filter: brightness(0.85); }

/* Overlaid on the pop bar's top-right corner rather than its own row — empty
   (and thus zero-height/width via :not(:empty) below) every match except the
   rare one with a traitor in it, so it costs no layout space the rest of the
   time. #popBar clips to its rounded corners, so this stays inside it rather
   than poking above; the countdown is worth a little crowding on the one
   line while it runs. */
#traitorChip {
  position: absolute;
  top: 50%;
  right: 5px;
  transform: translateY(-50%);
  z-index: 2;
}

#traitorChip:not(:empty) {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: #ff8a8a;
  background: rgba(180, 40, 40, 0.55);
  border: 1px solid rgba(255, 120, 120, 0.5);
  border-radius: 4px;
  padding: 1px 5px;
}

/* --- Debug panel: dev-only gold cheats, top-right ------------------------- */

#debugToggle {
  position: fixed;
  /* Sits below #pauseBtn, which owns the top-right corner. */
  top: calc(env(safe-area-inset-top) + 42px);
  right: 8px;
  z-index: 8;
  padding: 4px 10px;
  font-size: 11px;
  font-weight: 700;
  border-radius: 8px;
  border: 1px solid rgba(255, 120, 120, 0.35);
  background: rgba(120, 30, 30, 0.55);
  color: #fff;
  cursor: pointer;
  white-space: nowrap;
}

#debugToggle:active { filter: brightness(0.85); }

#debugPanel {
  position: fixed;
  /* Sits below #debugToggle. */
  top: calc(env(safe-area-inset-top) + 74px);
  right: 8px;
  /* Without a width cap, this row of six buttons is wider than most phones
     and a narrow desktop window — it used to just run off the left edge of
     the screen with no way to reach the buttons that fell off. Wrapping
     inside a capped width keeps every button on-screen instead. */
  max-width: calc(100vw - 16px);
  z-index: 8;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: 6px;
  padding: 6px 8px;
  border-radius: 8px;
  background: rgba(120, 30, 30, 0.55);
  border: 1px solid rgba(255, 120, 120, 0.35);
  font-size: 11px;
}

#debugLabel {
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  opacity: 0.75;
  padding-right: 2px;
}

#debugPanel button {
  padding: 5px 8px;
  font-size: 11px;
  font-weight: 600;
  border-radius: 6px;
  border: 0;
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
  cursor: pointer;
  white-space: nowrap;
}

#debugPanel button:active { filter: brightness(0.85); }

#debugPanel button.armed {
  background: rgba(255, 160, 90, 0.9);
  color: #2a1200;
}

/* --- Pause button (singleplayer) ------------------------------------------ */

#pauseBtn {
  position: fixed;
  top: calc(env(safe-area-inset-top) + 6px);
  right: 8px;
  z-index: 8;
  padding: 6px 12px;
  font-size: 12px;
  font-weight: 700;
  border-radius: 8px;
  border: 0;
  background: rgba(11, 18, 32, 0.75);
  color: #fff;
  cursor: pointer;
  white-space: nowrap;
}

#pauseBtn:active { filter: brightness(0.85); }

#pauseBtn.paused {
  background: rgba(255, 190, 70, 0.95);
  color: #2a1a00;
}

#pauseBtn.hidden { display: none; }

/* --- Incoming-nuke warning ------------------------------------------------ */
/* One row per hostile nuke that will hit the player's land, naming who fired
   it and counting down to impact. Tap a row to jump to the target. Red and
   pulsing on purpose: it's the most urgent thing that can be on screen. */
#nukeAlert {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.nukeAlertRow {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  border-radius: 8px;
  background: rgba(90, 14, 20, 0.95);
  border: 1px solid rgba(255, 90, 90, 0.8);
  font-size: 13px;
  font-weight: 700;
  color: #ffd9d9;
  cursor: pointer;
  animation: nukeAlertPulse 0.9s ease-in-out infinite alternate;
}

.nukeAlertText { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nukeAlertTime { flex: 0 0 auto; font-variant-numeric: tabular-nums; color: #fff; }

@keyframes nukeAlertPulse {
  from { border-color: rgba(255, 90, 90, 0.5); }
  to { border-color: rgba(255, 150, 150, 1); box-shadow: 0 0 10px rgba(255, 60, 60, 0.6); }
}

/* --- Donation-received toast (ticket #36) --------------------------------- */
/* One row per gift from an ally, gone on its own once Fx.DONATION_TOAST_LIFETIME
   elapses — friendly green, not urgent, and not clickable like the nuke rows. */
#donationAlert {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.donationAlertRow {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  border-radius: 8px;
  background: rgba(14, 70, 30, 0.95);
  border: 1px solid rgba(110, 255, 140, 0.7);
  font-size: 13px;
  font-weight: 700;
  color: #d9ffe0;
  /* Fade-out spans the row's whole life, matching Fx.DONATION_TOAST_LIFETIME
     (3s) — the row is removed from the DOM the instant it ends, so the two
     durations must stay in sync or it'll either vanish early or blink back
     opaque for a frame at the end. */
  animation: donationAlertIn 0.2s ease-out, donationAlertFade 3s ease-in forwards;
}

.donationAlertText { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

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

@keyframes donationAlertFade {
  0%, 60% { opacity: 1; }
  100% { opacity: 0; }
}

/* --- Diplomacy banner ----------------------------------------------------- */

#diploBanner {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px 10px;
  border-radius: 8px;
  background: rgba(16, 26, 46, 0.95);
  border: 1px solid rgba(255, 255, 255, 0.16);
  font-size: 13px;
}

#diploText { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

#diploBanner button {
  flex: 0 0 auto;
  padding: 6px 10px;
  font-size: 13px;
  font-weight: 600;
  border-radius: 6px;
  border: 0;
  background: #3d7dff;
  color: #fff;
  cursor: pointer;
}

#diploNo { background: rgba(255, 255, 255, 0.14); }
#diploBanner button:active { filter: brightness(0.85); }

/* Time left to answer, draining left to right. */
#diploBar {
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: 4px;
  height: 2px;
  border-radius: 1px;
  background: rgba(255, 255, 255, 0.12);
}

#diploBarFill { height: 100%; width: 100%; background: #6db4ff; border-radius: 1px; }

/* --- Radial menu ---------------------------------------------------------- */

/* --- Hover panel: quick stats for the nation under the cursor -------------- */

/* 2x throughout — the original 220px/13px reading was too small to make out
   at a glance, especially with a build ring now sharing the top of the screen. */
#hoverPanel {
  position: fixed;
  top: calc(env(safe-area-inset-top) + 6px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 8;
  width: 300px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 12px;
  border-radius: 12px;
  background: rgba(8, 14, 26, 0.88);
  border: 1px solid rgba(255, 255, 255, 0.16);
  font-size: 20px;
  pointer-events: none;
  white-space: nowrap;
}

#hpTop { display: flex; align-items: center; gap: 10px; }

#hpSwatch {
  width: 18px;
  height: 18px;
  border-radius: 5px;
  flex: 0 0 auto;
}

#hpMain { display: flex; flex-direction: column; line-height: 1.15; flex: 1 1 auto; overflow: hidden; }
#hpName { font-weight: 600; font-size: 20px; overflow: hidden; text-overflow: ellipsis; }
#hpSub { font-size: 15px; opacity: 0.65; }
#hpSub:empty { display: none; }

.hpIcon { font-size: 15px; line-height: 1; }
.hpIcon .ic { width: 1em; height: 1em; vertical-align: middle; }

/* Fixed-size bar, same fill logic as the player's own #popBar: width tracks
   home reserve / cap. A second, differently-coloured segment stacked after it
   shows the slice currently out on campaign, so a nation that looks full at a
   glance but has half its army marching still reads as exposed. Gold sits in
   the same row since the panel dropped its dedicated gold line. */
#hpStatRow {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 16px;
  font-variant-numeric: tabular-nums;
}

#hpPopValue { flex: 0 0 auto; opacity: 0.9; }
#hpPopMax { flex: 0 0 auto; opacity: 0.55; }

#hpPopBar {
  position: relative;
  flex: 1 1 auto;
  height: 10px;
  border-radius: 5px;
  background: rgba(255, 255, 255, 0.12);
  overflow: hidden;
}

#hpPopHome, #hpPopMarch {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  height: 100%;
}

#hpPopHome { background: #3d7dff; }
#hpPopMarch { background: #ff9f43; }

#hpStatDivider {
  width: 1px;
  height: 16px;
  background: rgba(255, 255, 255, 0.14);
  flex: 0 0 auto;
}

#hpGoldValue { color: #f0c869; flex: 0 0 auto; }

/* Bot's opinion of the player, as a face (UI.updateBotFace). */
#hpFace { flex: 0 0 auto; font-size: 22px; line-height: 1; }
#hpFace .ic { width: 1em; height: 1em; vertical-align: middle; }
#hpFace:empty { display: none; }

/* Spawn selection: shown between Start and the human's first tap, prompting
   where to place their capital while every Nation and Tribe's own starting
   territory is already visible on the map underneath. */
#spawnBanner {
  position: fixed;
  top: calc(env(safe-area-inset-top) + 6px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 8;
  padding: 10px 18px;
  border-radius: 12px;
  background: rgba(8, 14, 26, 0.88);
  border: 1px solid rgba(255, 255, 255, 0.16);
  font-size: 15px;
  font-weight: 600;
  text-align: center;
  pointer-events: none;
  white-space: nowrap;
}

#spawnBanner.warn { color: #ffb46b; }

/* MP-4.2: diagnostic-only desync notice (UI.showDesyncWarning). Same fixed
   top-center placement family as #spawnBanner, offset lower so the two never
   overlap if a desync is somehow flagged during spawn select, styled as a
   warning (red-ish border/text) instead of the neutral spawn banner. Clicking
   it dismisses it locally — see index.html's comment on #desyncBanner. */
#desyncBanner {
  position: fixed;
  top: calc(env(safe-area-inset-top) + 44px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 20;
  padding: 10px 18px;
  border-radius: 12px;
  background: rgba(38, 10, 10, 0.92);
  border: 1px solid rgba(255, 100, 100, 0.45);
  color: #ff8f8f;
  font-size: 14px;
  font-weight: 600;
  text-align: center;
  max-width: min(90vw, 480px);
  cursor: pointer;
}

/* MP-4.1: reconnect catch-up readout (UI.updateCatchup). Same fixed
   top-center placement family as #spawnBanner/#desyncBanner, offset below
   both so all three can in principle be visible without overlapping; neutral
   coloring (not a warning) since being behind and draining is expected,
   working behavior, not a problem to flag like #desyncBanner's. Not
   `cursor: pointer` — unlike the two banners above it isn't click-dismissible,
   it just tracks Runner.pendingTurns() and hides itself once the backlog is
   gone. */
#catchupBanner {
  position: fixed;
  top: calc(env(safe-area-inset-top) + 82px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 20;
  padding: 10px 18px;
  border-radius: 12px;
  background: rgba(8, 14, 26, 0.88);
  border: 1px solid rgba(255, 255, 255, 0.16);
  color: #cfe0ff;
  font-size: 14px;
  font-weight: 600;
  text-align: center;
  max-width: min(90vw, 480px);
  pointer-events: none;
}

#radial {
  position: fixed;
  inset: 0;
  z-index: 9;
  touch-action: none;
}

#radialMenu { position: absolute; }
#radialMenu svg { display: block; overflow: visible; }

.rSlot {
  stroke: rgba(8, 14, 26, 0.9);
  stroke-width: 2;
  fill: rgba(22, 34, 58, 0.94);
  cursor: pointer;
}

.rSlot.good:active, .rSlot.good:hover { fill: rgba(38, 78, 140, 0.96); }
.rSlot.danger { fill: rgba(96, 30, 36, 0.94); }
.rSlot.danger:active, .rSlot.danger:hover { fill: rgba(140, 40, 46, 0.96); }

/* The three unbuilt quadrants. Visible, inert, and obviously reserved. */
.rSlot.empty { fill: rgba(18, 26, 44, 0.55); cursor: default; }
.rSlot.disabled { fill: rgba(22, 34, 58, 0.6); cursor: not-allowed; }

.rCentre { stroke: rgba(255, 255, 255, 0.55); stroke-width: 2; cursor: pointer; }

#radialMenu text {
  pointer-events: none;
  text-anchor: middle;
  dominant-baseline: middle;
  fill: #e6edf7;
}

.rIcon { font-size: 20px; }
.rLabel { font-size: 12px; font-weight: 600; }
.rNote { font-size: 9px; opacity: 0.65; }
.rCentreIcon { font-size: 16px; font-weight: 700; }

#radialMenu .dim { opacity: 0.45; }
/* Icons sit over the wedges and centre disc; clicks must reach those. */
#radialMenu image { pointer-events: none; }

.rName {
  position: absolute;
  left: 50%;
  bottom: -6px;
  transform: translateX(-50%);
  white-space: nowrap;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  background: rgba(8, 14, 26, 0.9);
  border-radius: 4px;
  padding: 2px 8px;
  pointer-events: none;
}

/* Marks peak growth (~42%), so the level worth holding at is visible. */
#popBarPeak {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  margin-left: -1px;
  background: rgba(255, 255, 255, 0.75);
}

#popBarFill {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  height: 100%;
  width: 0%;
  background-color: #3d7dff;
  /* No transition on width: the bar must read as the live value. Animating
     it makes it glide toward the truth after an attack instead of showing
     it. */
}

/* Stacked right after the fill, same split as the hover panel's bar: the
   home reserve you can still commit, then whatever's already out on
   campaign. A nation that's thrown half its army into an attack should read
   as exposed here, not just "full" because the total still fills the bar. */
#popBarMarch {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  height: 100%;
  width: 0%;
  background: #ff9f43;
}

/* --- Ratio row: attack-ratio readout + slider ------------------------------ */
#ratioRow { display: flex; align-items: center; gap: 10px; }

#ratioLabel {
  flex: 0 0 auto;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

#ratioValue { font-weight: 700; }
#ratioTroops { opacity: 0.6; }

#ratio {
  flex: 1;
  -webkit-appearance: none;
  appearance: none;
  height: 28px;
  background: transparent;
}

#ratio::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.18);
}

#ratio::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 26px;
  height: 26px;
  margin-top: -10px;
  border-radius: 50%;
  background: #e6edf7;
  border: 2px solid #0b1220;
}

#ratio::-moz-range-track { height: 6px; border-radius: 3px; background: rgba(255, 255, 255, 0.18); }
#ratio::-moz-range-thumb { width: 24px; height: 24px; border-radius: 50%; background: #e6edf7; border: 2px solid #0b1220; }

/* --- Build bar ------------------------------------------------------------ */
/* One compact icon button per entry in Game.UNITS, so it grows with that
   table. Scrolls sideways rather than wrapping: the rest of OpenFront's list
   is another eight structures, and a HUD that grows a second row on a phone
   eats the map. See ui.js's setupBuildBar for the wheel-scroll and edge-fade
   that keep the overflow reachable and visible on a narrow window. */
#buildBar {
  display: flex;
  gap: 5px;
  overflow-x: auto;
  scrollbar-width: none;
  padding-bottom: 1px;
  /* Centered while the row fits; "safe" falls back to start alignment once
     more buttons than fit are added, so growth never clips the first icon
     behind an unreachable scroll offset. */
  justify-content: safe center;
}

#buildBar::-webkit-scrollbar { display: none; }

.buildBtn {
  position: relative;
  flex: 0 0 auto;
  width: 52px;
  height: 52px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(16, 26, 46, 0.9);
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.bbKey {
  position: absolute;
  top: 2px;
  left: 3px;
  font-size: 9px;
  opacity: 0.5;
  line-height: 1;
}

.bbIcon { font-size: 19px; line-height: 1; }
.bbIcon .ic { width: 1.1em; height: 1.1em; vertical-align: middle; }
.bbName { display: none; }
.bbBody { display: flex; line-height: 1; }
.bbCost { font-size: 10px; color: #f0c869; font-variant-numeric: tabular-nums; }

/* Owned count as a corner badge rather than a second stacked line — the
   button is too small at 52px to fit a name, a cost AND a count as separate
   lines, and the cost (what the NEXT one costs) stays worth showing even
   once you own several. */
.bbCount {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 15px;
  padding: 0 3px;
  border-radius: 7px;
  background: #2d5a9e;
  border: 1px solid rgba(255, 255, 255, 0.3);
  font-size: 9px;
  font-weight: 700;
  line-height: 14px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.bbCount:empty { display: none; }

/* Unaffordable is dimmed, never disabled — a button you cannot press is also a
   button that cannot tell you what it would have cost. */
.buildBtn.poor { opacity: 0.6; }
.buildBtn.poor .bbCost { color: #ff8a8a; }

/* Nuke buttons before a Missile Silo is owned: dimmed like .poor (same
   press-but-explain philosophy — the hint line spells out "Build a Missile
   Silo first"), but without .poor's red cost tint, since the blocker here is
   the missing Silo, not the gold. */
.buildBtn.locked { opacity: 0.45; }

.buildBtn.armed {
  border-color: #6db4ff;
  background: rgba(38, 78, 140, 0.95);
}

.buildBtn:active { filter: brightness(0.85); }

/* Doubles as the placement instruction and, briefly, as the refusal reason when
   a build lands somewhere it cannot go. */
#hint { font-size: 11px; opacity: 0.45; text-align: center; }
#hint.warn { color: #ffb46b; opacity: 0.95; }

#overlay {
  position: fixed;
  inset: 0;
  background: rgba(56, 80, 104, 1);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  z-index: 10;
  overflow-y: auto;
}

/* Menu backdrop (js/menubg.js): a canvas just under the overlay, which turns
   translucent and darkens the edges so the menu stays the focus. */
#menuBg {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 9;
}
#overlay.menuBgOn {
  background: radial-gradient(ellipse at center, rgba(12, 20, 34, 0.55), rgba(8, 12, 22, 0.85));
}
#menuBg:has(~ #overlay.hidden) { display: none; }
/* The overlay is translucent now, so keep the game's own layers from showing
   through it (the HUD on first load, the finished match after Play Again). */
body:has(#overlay:not(.hidden)) :is(#game, #topbar, #hud, #pauseBtn, #hoverPanel, #debugToggle, #debugPanel) {
  visibility: hidden;
}

.hidden { display: none !important; }

.panel {
  max-width: 340px;
  width: 100%;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: auto;
}

.panel h1 { font-size: 28px; letter-spacing: 0.02em; }
.panel p { font-size: 14px; opacity: 0.7; line-height: 1.5; }
.panel label { font-size: 14px; opacity: 0.8; }

.panel select {
  padding: 6px 8px;
  font-size: 15px;
  border-radius: 6px;
  border: 1px solid rgba(255, 255, 255, 0.2);
  background: #131c2e;
  color: inherit;
}

.panel input[type="number"] {
  width: 64px;
  padding: 6px;
  font-size: 15px;
  border-radius: 6px;
  border: 1px solid rgba(255, 255, 255, 0.2);
  background: #131c2e;
  color: inherit;
  text-align: center;
}

.panel button {
  padding: 14px;
  font-size: 16px;
  font-weight: 600;
  border-radius: 10px;
  border: 0;
  background: #3d7dff;
  color: #fff;
  cursor: pointer;
}

.panel button:active { background: #2f66d8; }

/* End-of-match popup (#22): a compact card floating over the live map rather
   than a full-screen cover, so the final map stays visible and the player can
   keep panning/zooming. The full-viewport wrapper passes pointer events
   through; only the card catches them. `.minimized` collapses the card to
   just its title plus the restore button. */
#endOverlay {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: calc(env(safe-area-inset-top) + 120px) 16px 16px;
  z-index: 10;
  pointer-events: none;
}

#endOverlay .panel {
  position: relative;
  pointer-events: auto;
  width: 100%;
  max-width: 300px;
  padding: 20px 22px 18px;
  gap: 10px;
  background: rgba(10, 16, 30, 0.92);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 14px;
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.5);
}

#endOverlay .panel h1 { font-size: 24px; }
#endOverlay .panel button#restartBtn { padding: 11px; font-size: 15px; }

#endOverlay .panel button#endMinBtn {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 28px;
  height: 28px;
  padding: 0;
  font-size: 18px;
  line-height: 28px;
  border-radius: 7px;
  background: rgba(255, 255, 255, 0.1);
}

#endOverlay.minimized .panel { padding: 8px 44px 8px 16px; max-width: max-content; }
#endOverlay.minimized .panel h1 { font-size: 16px; }
#endOverlay.minimized #endText,
#endOverlay.minimized #restartBtn { display: none; }

/* --- Main menu redesign: the open rotating lobby (#12) is the primary call
   to action — a hero card above everything else — with Singleplayer/Host/
   join-by-code demoted to a small secondary tab row underneath. ---------- */

#quickJoin {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 16px;
  border-radius: 12px;
  background: linear-gradient(180deg, rgba(61, 125, 255, 0.18), rgba(61, 125, 255, 0.05));
  border: 1px solid rgba(61, 125, 255, 0.4);
}

#quickJoin .lobbyLabel { text-align: center; opacity: 0.75; }

#quickJoinMap.hidden { display: none; }

#quickJoinInfo {
  font-size: 13px;
  opacity: 0.85;
  min-height: 1.2em;
}

.panel button.primaryBtn {
  padding: 16px;
  font-size: 17px;
}

.panel button.primaryBtn:disabled {
  opacity: 0.4;
  cursor: default;
}

.orDivider {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  opacity: 0.4;
  text-align: center;
  margin: -4px 0;
}

/* --- MP-2.3: lobby UI — mode tabs, host/join panels, roster ---------------- */

#modeTabs {
  display: flex;
  gap: 6px;
}

.modeTab {
  flex: 1;
  padding: 6px 6px;
  font-size: 12px;
  font-weight: 600;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: transparent;
  color: #fff;
  opacity: 0.75;
  cursor: pointer;
}

.modeTab.active {
  opacity: 1;
  background: #1c2740;
  border-color: rgba(61, 125, 255, 0.6);
}

.modeBody {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Two related fields share a row instead of each claiming its own —
   the biggest single cut to the panel's vertical footprint. Falls back
   to stacked on narrow screens so labels/selects never get squeezed. */
.optRow2 {
  display: flex;
  gap: 10px;
}

.optRow2 > label {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}

.optRow2 > label select,
.optRow2 > label input[type="number"] {
  width: 100%;
  box-sizing: border-box;
}

.panel input[type="text"] {
  padding: 6px 8px;
  font-size: 15px;
  border-radius: 6px;
  border: 1px solid rgba(255, 255, 255, 0.2);
  background: #131c2e;
  color: inherit;
  text-align: center;
}

#nameRow input { margin-left: 6px; width: 180px; }

.lobbyLabel {
  font-size: 12px;
  opacity: 0.6;
  text-align: left;
}

#lobbyCode {
  font-size: 26px;
  font-weight: 700;
  letter-spacing: 0.14em;
  padding: 10px;
  border-radius: 8px;
  background: #131c2e;
  border: 1px dashed rgba(255, 255, 255, 0.3);
}

.lobbyRoster {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 140px;
  overflow-y: auto;
  text-align: left;
}

.lobbyRoster li {
  padding: 6px 8px;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.05);
  margin-bottom: 4px;
  font-size: 14px;
}

.lobbyRoster li.isHost::after {
  content: " (host)";
  opacity: 0.5;
  font-size: 12px;
}

#joinWaiting {
  font-size: 13px;
  opacity: 0.6;
}

#lobbyCode { cursor: pointer; }

.lobbyStatus {
  font-size: 13px;
  padding: 6px 8px;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.05);
  text-align: left;
}

.lobbyStatus.ok::before {
  content: "";
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 8px;
  border-radius: 50%;
  background: #4cd07d;
}

.lobbyRoster li.isYou::after {
  content: " (you)";
  opacity: 0.5;
  font-size: 12px;
}

.lobbyRoster li.isHost.isYou::after {
  content: " (host, you)";
}

.lobbyRoster li.justJoined {
  animation: lobbyJoinFlash 1.6s ease-out;
}

@keyframes lobbyJoinFlash {
  from { background: rgba(76, 208, 125, 0.55); }
  to { background: rgba(255, 255, 255, 0.05); }
}

.panel button.lobbyLeave {
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.2);
  opacity: 0.75;
}

#lobbyError {
  color: #ff6b6b;
  font-size: 13px;
}

#licenseFooter {
  margin-top: 10px;
  font-size: 11px;
  opacity: 0.55;
  text-align: center;
}

#licenseFooter a {
  color: inherit;
  text-decoration: underline;
}

.checkboxLabel {
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-size: 14px;
}

.linkBtn {
  background: transparent;
  border: none;
  padding: 0;
  margin-left: 8px;
  font-size: 12px;
  color: #3d7dff;
  opacity: 0.85;
  cursor: pointer;
  text-decoration: underline;
}

#publicLobbyList li {
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  gap: 8px;
}

#publicLobbyList li:hover {
  background: rgba(255, 255, 255, 0.12);
}

#publicLobbyList li .lobbyListCount {
  opacity: 0.6;
  font-size: 12px;
}

#publicLobbyList .lobbyListEmpty {
  cursor: default;
  opacity: 0.5;
  font-size: 13px;
}

#publicLobbyList .lobbyListEmpty:hover {
  background: rgba(255, 255, 255, 0.05);
}

/* Procedural map options (UI.setupMapGen): knob selects, then a preview of
   the map those knobs and the seed will generate. */
.mapGen {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
}

.mapGen .optRow2 > label { font-size: 13px; }

.mapPreview canvas {
  display: block;
  width: 100%;
  aspect-ratio: 2 / 1;
  border-radius: 6px;
  background: #12223c;
  image-rendering: pixelated;
}

.mapPreview { position: relative; }

.mapPreviewBar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 6px;
  font-size: 12px;
}

/* Which landform the preview shows, over its top-left corner. */
.mapPreviewNote {
  position: absolute;
  top: 6px;
  left: 6px;
  padding: 2px 6px;
  border-radius: 4px;
  background: rgba(6, 10, 20, 0.7);
  font-size: 11px;
  pointer-events: none;
}
.mapPreviewNote:empty { display: none; }

.mapPreviewBar input[type="number"] {
  width: 120px;
  margin-left: 4px;
}

.mapPreviewBar button {
  padding: 4px 10px;
  font-size: 12px;
}

/* --- Main menu layout: mobile (vertical, one column) by default; on wide
   screens the title/name/open-game card sit left and the mode tabs + forms
   sit right, so the whole menu fits without scrolling. ------------------- */

.menuCol {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
}

@media (max-width: 599px) {
  #overlay { padding: 16px; align-items: flex-start; }
  #overlay .menuPanel { max-width: none; }
  #nameRow input { width: 60%; }
}

/* One centered column at every width: title and name, the open-game card, then
   the mode row and its form. The "or" divider only made sense between columns. */
#overlay .menuPanel { max-width: 460px; gap: 16px; }
#overlay .menuMain h1 { font-size: 40px; }
.brand { display: flex; align-items: center; justify-content: center; gap: 0.3em; }
.brand .brandMark { height: 1.5em; width: auto; flex: 0 0 auto; }
#overlay .orDivider { display: none; }
#overlay .modeTab { min-width: 0; padding: 12px 6px; font-size: 14px; }

/* A mode is open: its form replaces the open-game card. Back sits above the title,
   left-aligned with the column, and goes when the tabs do (in a lobby). */
#overlay.modeOpen #quickJoin { display: none !important; }
.menuPanel:has(#modeTabs.hidden) #modeBack { display: none; }
#modeBack {
  align-self: flex-start;
  width: auto;
  margin: 0;
  padding: 6px 14px;
  font-size: 13px;
  color: #fff;
}

/* --- Options (js/options.js) ------------------------------------------------ */
/* Cog in the top-right corner, above the menu overlay; hidden once a match
   is running (the overlay is what it opens from). */
#optionsBtn {
  position: fixed;
  top: calc(env(safe-area-inset-top) + 10px);
  right: 12px;
  z-index: 11;
  padding: 6px;
  border-radius: 8px;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  opacity: 0.35;
}
#optionsBtn:hover { opacity: 0.8; }
body:has(#overlay.hidden) #optionsBtn { display: none; }

#optionsOverlay {
  position: fixed;
  inset: 0;
  z-index: 12;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(4, 8, 16, 0.6);
}
.optionsPanel {
  background: rgba(14, 24, 42, 0.98);
  border-radius: 14px;
  padding: 20px;
  text-align: left;
}
.optionsPanel h2 { margin: 0 0 4px; text-align: center; }
.optRow { display: flex; align-items: center; gap: 10px; font-size: 14px; cursor: pointer; }
.optRow input[type="checkbox"] { flex: none; margin: 0; }
.optRow select { margin-left: auto; }
.optRow small { display: block; font-size: 11px; opacity: 0.6; }

#perfStats {
  position: fixed;
  left: 8px;
  bottom: 6px;
  z-index: 8;
  font: 11px/1 ui-monospace, Menlo, monospace;
  opacity: 0.7;
  pointer-events: none;
}
body:has(#overlay:not(.hidden)) #perfStats { visibility: hidden; }

/* UI scale: only the edge-anchored HUD pieces scale; the radial menu and the
   canvas are positioned from the cursor and stay as they are. */
:is(#topbar, #hud, #pauseBtn, #hoverPanel, #debugToggle, #debugPanel) { zoom: var(--ui-scale, 1); }

/* Hide hint bar: a placement refusal (.warn) still shows. */
body.noHint #hint:not(.warn) { display: none; }

#communityLinks { margin-top: 14px; font-size: 13px; text-align: center; }
#communityLinks a { display: inline-block; opacity: 0.8; line-height: 0; }
#communityLinks img { width: 28px; height: 28px; }
#communityLinks a:hover { opacity: 1; }
#buildInfo { margin-top: 10px; font-size: 11px; opacity: 0.5; font-variant-numeric: tabular-nums; }
