/*
 * Copyright (C) 2025-2026 Tim Riker <timriker@gmail.com>
 * Licensed under the GNU Affero General Public License v3.0 (AGPLv3).
 * Source: https://github.com/timriker/bzo
 * See LICENSE or https://www.gnu.org/licenses/agpl-3.0.html
 */

html, body {
  margin: 0;
  padding: 0;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  background: #000;
  font-family: 'Arial', sans-serif;
  color: #fff;
  touch-action: none;
  overscroll-behavior: none;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -webkit-app-region: no-drag;
}

:root {
  --color-bg: #000;
  --color-fg: #fff;
  --color-accent: #4CAF50;
  --color-accent-light: #66bb6a;
  --color-state-on: #66bb6a;
  --color-state-off: #e57373;
  --color-btn-bg: rgba(76,175,80,0.15);
  --color-btn-bg-active: #4CAF50;
  --color-btn-bg-hover: rgba(76,175,80,0.35);
  --color-btn-bg-focus: #81c784;
  --color-btn-bg-shadow: #4CAF5088;
  --color-panel-bg: rgba(0,0,0,0.5);
  --color-panel-bg-solid: rgba(0,0,0,0.8);

  /* BZFlag's targeting box, in its own units: MaxMotionSize 37 and NoMotionSize
     10 half-extents (global.h:88-91) scaled by min(width / 256, height / 192),
     which is what HUDRenderer::resize computes at the default mouseboxsize of
     5, where its effScale equals that scale. The mouse mapping reads its
     geometry back off these boxes, so the drawn edge is where the input
     saturates. */
  /* The radar's own size, shared so the debug HUD can stop short of it rather
     than guess. Matches what #radar asks for: square, and clamped at both ends. */
  --radar-size: clamp(90px, min(52.5vw, 52.5vh), 360px);
  --hud-edge-gap: 10px;

  --motion-unit: min(calc(100vw / 256), calc(100vh / 192));
  --motion-box: calc(74 * var(--motion-unit));
  --motion-dead-box: calc(10 * var(--motion-unit));

  /* Upstream sizes its whole HUD off that one scale rather than off breakpoints,
     and bzo used it only for the targeting box. The panels use it too now, so a
     screen that is small in either direction gets a HUD in proportion instead of
     one chosen by a width breakpoint it may fall between: a phone in landscape
     is 857x411, which no width rule reads as small.

     Clamped, because upstream is scaling geometry and this is scaling text. The
     unit is 5.63px on a 1920x1080 desktop and 2.14px on that phone, so a linear
     20px becomes 7.6px -- the right proportion and unreadable. The ceilings are
     what the desktop already had, so nothing there changes; the floors are
     legibility. Between them the size follows the screen continuously. */
  --hud-unit: var(--motion-unit);
  --hud-font-title: clamp(13px, calc(3.6 * var(--hud-unit)), 20px);
  --hud-font-body: clamp(11px, calc(2.5 * var(--hud-unit)), 14px);
  --hud-font-small: clamp(10px, calc(2.1 * var(--hud-unit)), 12px);
  --hud-gap: clamp(4px, calc(2 * var(--hud-unit)), 12px);
  --hud-pad: clamp(4px, calc(1.8 * var(--hud-unit)), 10px);
  /* Was 4vmin, which is 43px on a desktop and 16px on a phone. In the box's own
     unit it keeps its desktop width and stays legible on a small screen. */
  --altimeter-width: clamp(14px, calc(8 * var(--hud-unit)), 48px);

  /* The gap from a HUD panel to the edge of the screen. One value so the radar,
     the scoreboard, the debug HUD and the chat cannot drift apart.

     Deliberately not env(safe-area-inset-*), which every one of these used to
     read. On a phone in landscape the cutout inset costs an edge of a 411px-tall
     screen, and that is real estate bzo cannot spare: the camera hole covering a
     corner of the scoreboard is a better trade than the whole panel moving in to
     avoid it, and the covered part is still touchable. `viewport-fit=cover`
     stays, because drawing under the cutout is now the point. */
  --hud-edge: 10px;

  /* The chat's palette. Named here rather than written at each rule because the
     XR chat panel paints the same chat onto a canvas and reads these back with
     getComputedStyle -- two lists of colours would drift the moment one of them
     changed, and a message that is yellow on the desktop and white in the
     headset is the same message telling you two different things. A kind with no
     colour of its own falls back to --chat-text, which is what `chat` does. */
  --chat-bg: rgba(0, 0, 0, 0.5);
  --chat-text: #fff;
  --chat-kind-server: #ffe082;
  --chat-kind-misc: #b3e5fc;
  --chat-kind-debug: #b0bec5;
  --chat-kind-action: #ffccbc;
  --chat-kind-direct-in: #80deea;
  --chat-kind-direct-out: #b39ddb;
  /* Team chat, for the part of the line that is neither the team nor the sender:
     the message itself. The `[Team]` label takes the team's own colour and the
     callsign takes the sender's own, both set per run in `formatNetworkMessage`,
     so this is only the fallback the rest of the line inherits. Upstream paints
     the whole line the team's ANSI colour, which it can because its team mates
     all share one; bzo shades them apart, so it has a second colour to spend and
     spends it on saying who is talking. */
  --chat-kind-team: #a5d6a7;
  /* The admin channel. Upstream marks it in yellow, which is the colour it also
     gives a ServerPlayer callsign (playing.cxx:1332). */
  --chat-kind-admin: #ffd54f;
  --chat-tab-idle: #ddd;
  --chat-tab-active: #fff;
  --chat-tab-unread: #ff8a80;
}

/* Scrollbars follow the theme rather than the platform's grey. `scrollbar-color`
   and `scrollbar-width` are the standard properties and are inherited, so
   setting them on the root reaches every panel that scrolls; the WebKit
   pseudo-elements below cover the Chrome and Safari versions that predate
   them. */
html {
  scrollbar-width: thin;
  scrollbar-color: var(--color-accent) var(--color-panel-bg);
}

::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

::-webkit-scrollbar-track {
  background: var(--color-panel-bg);
  border-radius: 5px;
}

::-webkit-scrollbar-thumb {
  background: var(--color-btn-bg-hover);
  border: 1px solid var(--color-accent);
  border-radius: 5px;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--color-accent);
}

::-webkit-scrollbar-corner {
  background: transparent;
}

#operatorOverlay, #operatorOverlay *, #audioOverlay, #audioOverlay *, #helpPanel, #helpPanel * {
  pointer-events: auto !important;
}

canvas {
  display: block;
  touch-action: none;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
}

/* Chat window responsive wrapping and scrolling */
#chatTarget,
#chatInput {
  height: clamp(24px, calc(12 * var(--hud-unit)), 32px);
  font-size: 1em;
  border-radius: 8px;
  border: none;
  background: var(--color-panel-bg-solid);
  color: var(--color-fg);
  padding: 6px 8px;
  outline: none;
  z-index: 101;
  display: inline-block;
  box-sizing: border-box;
}
#chatTarget {
  margin-right: 6px;
  min-width: 80px;
  max-width: 160px;
}
#chatInput {
  width: 100%;
  margin-top: 2px;
  display: block;
  flex: 1 1 auto;
  min-width: 0;
}
#chatWindow {
  position: fixed;
  left: 50%;
  bottom: var(--hud-edge);
  transform: translateX(-50%);
  width: 95vw;
  max-height: 33vh;
  background: var(--chat-bg);
  color: var(--chat-text);
  /* Six lines of messages are sized off this, so a fixed 16px asked for 120px of
     a 135px budget on a short screen and squeezed the tab row out entirely. */
  font: var(--hud-font-body) monospace;
  padding: var(--hud-pad) var(--hud-pad) calc(var(--hud-pad) * 0.75) var(--hud-pad);
  border-radius: 12px 12px 0 0;
  z-index: 100;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 4px;
  box-sizing: border-box;
  overflow: hidden;
  align-items: stretch;
}

/* The messages are the part that gives. The window packs to its bottom edge and
   hides its overflow, so anything that does not fit is cut off the *top* -- and
   the tabs are the first child. A min-height of six lines meant the messages
   refused to shrink and pushed the tabs off a short screen instead. Six lines is
   a ceiling now, not a floor: on a tall screen it fills to six, on a short one it
   shows fewer and the tabs and the input row keep their place. */
#chatMessages {
  flex: 1 1 auto;
  min-height: 0;
  max-height: calc(6 * 1.25em + 0.4em);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding-bottom: 0.2em;
}

#chatMessages .chat-line {
  line-height: 1.25;
  overflow-wrap: break-word;
  word-break: break-word;
  white-space: pre-wrap;
  max-width: 100%;
  box-sizing: border-box;
  user-select: text;
  -webkit-user-select: text;
  -webkit-touch-callout: default;
}

#chatMessages .chat-line.chat-kind-team {
  color: var(--chat-kind-team);
}

#chatMessages .chat-line.chat-kind-admin {
  color: var(--chat-kind-admin);
}

#chatMessages .chat-line.chat-kind-server {
  color: var(--chat-kind-server);
}

#chatMessages .chat-line.chat-kind-misc {
  color: var(--chat-kind-misc);
}

#chatMessages .chat-line.chat-kind-debug {
  color: var(--chat-kind-debug);
}

#chatMessages .chat-line.chat-kind-action {
  color: var(--chat-kind-action);
}

#chatMessages .chat-line.chat-kind-direct-in {
  color: var(--chat-kind-direct-in);
}

#chatMessages .chat-line.chat-kind-direct-out {
  color: var(--chat-kind-direct-out);
}

.chat-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  /* Neither of these gives up room; the messages above do. */
  flex: 0 0 auto;
}

.chat-tab {
  border: 1px solid #777;
  background: rgba(24, 24, 24, 0.9);
  color: var(--chat-tab-idle);
  border-radius: 5px;
  font: var(--hud-font-small) monospace;
  padding: calc(var(--hud-pad) / 2) var(--hud-pad);
  cursor: pointer;
}

.chat-tab.active {
  border-color: var(--chat-tab-active);
  color: var(--chat-tab-active);
  background: rgba(52, 52, 52, 0.95);
}

.chat-tab.unread {
  border-color: #c62828;
  color: var(--chat-tab-unread);
}

#chatWindow .chat-input-row {
  display: flex;
  align-items: center;
  flex-grow: 0;
  flex-shrink: 0;
  min-height: clamp(30px, calc(15 * var(--hud-unit)), 40px);
  width: 100%;
  background: transparent;
}

/* The panel covers the bottom of the screen, which is exactly where mouse
   control puts the cursor to drive backwards. While chat is idle only the tabs
   and the Send button take the pointer, so a click anywhere else over the panel
   is a click on the battlefield -- including on the input, which the Send
   button alone opens. Chat entry hands the panel's own controls back. */
#chatWindow,
#chatWindow .chat-tabs,
#chatWindow .chat-input-row {
  pointer-events: none;
}

#chatWindow .chat-tab,
#sendBtn {
  pointer-events: auto;
}

body.chat-active #chatMessages,
body.chat-active #chatTarget,
body.chat-active #chatInput {
  pointer-events: auto;
}

/* Shaped like a chat tab, in the UI's green rather than the tab strip's grey,
   because it acts on chat rather than selecting one of its views. */
#sendBtn {
  flex: 0 0 auto;
  /* Send comes first in the row -- `[Send] [target] [input]` -- so the gap it
     owns is the one on its right. A left margin here would only inset the row
     from the edge of the panel. */
  margin-right: 6px;
  border: 1px solid var(--color-accent);
  border-radius: 5px;
  background: var(--color-btn-bg);
  color: var(--color-accent);
  font: 12px monospace;
  padding: 3px 7px;
  cursor: pointer;
  box-sizing: border-box;
}

#sendBtn.active {
  border-color: var(--color-accent-light);
  background: var(--color-btn-bg-active);
  color: var(--color-fg);
}

#chatTarget {
  height: 32px;
  font-size: 1em;
  margin-right: 6px;
  border-radius: 8px;
  border: none;
  background: rgba(0,0,0,0.8);
  color: #fff;
  padding: 6px 8px;
  outline: none;
  z-index: 101;
  display: inline-block;
  box-sizing: border-box;
  min-width: 80px;
  max-width: 160px;
}
/* Responsive chat area for small screens */
@media (orientation: landscape) and (min-width: 801px) {
  body.virtual-controls-active #chatWindow {
    left: calc(2vw + 120px + 10px);
    right: calc(2vw + 120px + 10px);
    width: auto;
    transform: none;
  }
}

/* The debug panel sits in the bottom right corner, so chat gives up that much
   of its right edge while the panel is open. Its left edge does not move: only
   the side the panel is on has anything to avoid. 2.5vw is where the centered
   95vw panel already starts, so the edge stays exactly where it was. */
@media (orientation: landscape) and (min-width: 901px) {
  body.chat-avoid-debug #chatWindow {
    left: 2.5vw;
    right: calc(10px + 340px);
    width: auto;
    max-width: none;
    transform: none;
  }

  body.virtual-controls-active.chat-avoid-debug #chatWindow {
    left: calc(2vw + 120px + 10px);
  }
}

@media (max-width: 800px) {
  #chatWindow {
    width: 98vw;
    max-width: 98vw;
    min-width: 0;
    max-height: 33vh;
    font-size: 12px;
    left: 50%;
    right: auto !important;
    top: auto !important;
    bottom: 0 !important;
    transform: translateX(-50%) !important;
    margin: 0 !important;
    padding: 4px 4px 2px 4px;
    border-radius: 10px 10px 0 0;
    position: fixed !important;
    box-sizing: border-box;
    z-index: 100;
  }
  #chatWindow input,
  #chatWindow textarea {
    font-size: 12px;
    padding: 2px 4px;
  }
  #chatWindow div {
    font-size: 12px;
    margin-bottom: 2px;
    max-width: 100%;
  }

  #chatMessages {
    min-height: calc(6 * 1.2em + 0.35em);
    max-height: calc(6 * 1.2em + 0.35em);
  }
}
/* Mobile Controls Overlay Styles */
#controlsOverlay {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  pointer-events: none;
  z-index: 9999;
  display: none;

  /* The button stack has to fit a phone held in landscape, where the visible
     viewport is only a few hundred pixels tall. A fixed 80px button on a 90px
     pitch needs 368px for four, which pushed the top of the ladder off a Pixel
     and left the one below it stranded near the top corner. Sizes scale with
     the viewport instead, and every button's offset is derived from one pitch,
     so they keep an equal margin whatever they scale to. On a desktop the
     minimums win and the layout is unchanged. */
  --mobile-btn-size: min(80px, 15vh);
  --mobile-btn-gap: min(10px, 2vh);
  /* svh, not vh: on mobile Chrome `vh` is the *large* viewport, the one with the
     URL bar retracted, so a stack sized in vh is taller than what is actually on
     screen. svh is the viewport with the browser UI showing, which is the height
     that has to fit. Declared second so a browser without svh keeps the vh pair
     above rather than dropping the size entirely. */
  --mobile-btn-size: min(80px, 15svh);
  --mobile-btn-gap: min(10px, 2svh);
  --mobile-btn-pitch: calc(var(--mobile-btn-size) + var(--mobile-btn-gap));
  --mobile-btn-base: 2vw;
}

.mobile-joystick {
  position: absolute;
  left: 2vw;
  bottom: 5vh;
  width: 120px;
  height: 120px;
  background: rgba(60,60,60,0.18);
  border-radius: 50%;
  pointer-events: auto;
  display: flex;
  align-items: center;
  justify-content: center;
}

@media (orientation: landscape) {
  .mobile-joystick {
    left: 2vw;
  }
}

.joystick-knob {
  width: 60px;
  height: 60px;
  background: rgba(200,200,200,0.7);
  border-radius: 50%;
  box-shadow: 0 2px 8px rgba(0,0,0,0.2);
  pointer-events: auto;
  transition: background 0.1s;
}

.mobile-btn {
  position: absolute;
  right: 2vw;
  width: var(--mobile-btn-size);
  height: var(--mobile-btn-size);
  background: rgba(76,175,80,0.85);
  color: #fff;
  font-size: calc(var(--mobile-btn-size) * 0.45);
  border: none;
  border-radius: 50%;
  pointer-events: auto;
  touch-action: manipulation;
  box-shadow: 0 2px 8px rgba(0,0,0,0.18);
  display: flex;
  align-items: center;
  justify-content: center;
  user-select: none;
}

.mobile-btn.pressed {
  filter: brightness(1.2) drop-shadow(0 0 12px #fff8);
  transform: scale(0.93);
  opacity: 0.85;
  transition: filter 0.1s, transform 0.1s, opacity 0.1s;
}

/* Repeated addition rather than `pitch * 2`. Chrome on Android drops a `bottom`
   built from a number multiplied by a parenthesised length -- the rule is simply
   discarded, and an absolutely positioned button with no offset falls to its
   static position at the top of the overlay, which is the bug this ladder had.
   Addition is the one form nothing argues with. */
#identifyBtn {
  bottom: calc(var(--mobile-btn-base) + var(--mobile-btn-pitch) + var(--mobile-btn-pitch) + var(--mobile-btn-pitch));
}
#dropBtn {
  bottom: calc(var(--mobile-btn-base) + var(--mobile-btn-pitch) + var(--mobile-btn-pitch));
}
#jumpBtn { bottom: calc(var(--mobile-btn-base) + var(--mobile-btn-pitch)); }
#fireBtn { bottom: var(--mobile-btn-base); background: rgba(244,67,54,0.85); }

@media (min-width: 900px) {
  #controlsOverlay { display: none; }
}

.hudButtons button {
  background: var(--color-btn-bg);
  color: var(--color-accent);
  border: 2px solid var(--color-accent);
  border-radius: 5px;
  font-weight: bold;
  padding: 6px 6px;
  transition: background 0.2s, color 0.2s, border-color 0.2s;
  outline: none;
  cursor: pointer;
}
.hudButtons button.active {
  background: var(--color-btn-bg-active);
  color: var(--color-fg);
  border-color: var(--color-accent-light);
  box-shadow: 0 0 6px var(--color-btn-bg-shadow);
}
.hudButtons button:focus {
  border-color: var(--color-btn-bg-focus);
}
.hudButtons button:hover {
  background: var(--color-btn-bg-hover);
  color: var(--color-fg);
}

body {
  font-family: 'Arial', sans-serif;
  overflow: hidden;
  background: var(--color-bg);
  color: var(--color-fg);
}

#mainhud {
  position: fixed;
  top: var(--hud-edge);
  left: var(--hud-edge);
  padding: 1vw;
  border-radius: 10px;
  box-sizing: border-box;
  background: var(--color-panel-bg);
}

@media (max-width: 900px), (max-width: 600px) {
  #mainhud {
    width: 35vw;
    height: auto;
    padding: 5px;
    font-size: 5px;
  }
}

#info h1 {
  font-size: 24px;
  margin-bottom: 15px;
  color: #4CAF50;
}

#stats {
  margin-bottom: var(--hud-gap);
  font-size: var(--hud-font-body);
  line-height: 1.6;
  display: flex;
  align-items: center;
  gap: var(--hud-gap);
  justify-content: space-between;
}

/* The whole label opens Settings, so the whole label says so: the tooltip and
   the pointer belong to it rather than to the name alone. The flag takes the
   name's size, because upstream draws the pair as one run of text and a smaller
   flag beside a larger name reads as two things. */
#playerLabel {
  cursor: pointer;
}

#playerName,
#playerFlag {
  color: #4CAF50;
  font-weight: bold;
  font-size: var(--hud-font-title);
}

.settingsIcon {
  font-size: 1.5em;
}

.xrTextField {
  position: fixed;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  padding: 0;
  border: 0;
  opacity: 0;
  pointer-events: none;
}

#xrQuickBtn {
  display: none;
}

#xrQuickBtn.xrAvailable {
  display: inline-block;
}

#xrQuickBtn .settingsIcon {
  font-size: 1.3em;
}

.hudButtons {
  display: flex !important;
  gap: 6px;
  margin-left: 8px;
  justify-content: center;
  flex-wrap: wrap;
}
#stats span {
  font-weight: bold;
  color: #4CAF50;
}

#scoreboard {
  border-top: 1px solid #444;
  padding-top: 1vw;
}

#scoreboard h3 {
  font-size: 16px;
  margin-bottom: 10px;
  color: #4CAF50;
}

#scoreboardHeader {
  display: flex;
  justify-content: space-between;
  font-size: var(--hud-font-small);
  color: #888;
  margin-bottom: 5px;
  padding-bottom: 5px;
  border-bottom: 1px solid #333;
}

#scoreboardList {
  font-size: var(--hud-font-body);
  max-height: none;
}

#teamScoreboard {
  font-size: var(--hud-font-body);
  margin-bottom: var(--hud-gap);
}

.teamScoreboardEmpty {
  display: none;
}

.teamScoreHeader {
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  color: #888;
  margin-bottom: 5px;
  padding-bottom: 5px;
  border-bottom: 1px solid #333;
}

.teamScoreEntry {
  margin: 3px 0;
  padding: 3px 8px;
}

.scoreboardEntry {
  display: flex;
  justify-content: space-between;
  margin: calc(var(--hud-gap) / 2) 0;
  padding: calc(var(--hud-pad) / 2) var(--hud-pad);
  border-radius: 3px;
}

.scoreboardEntry.current {
  color: #4CAF50;
  font-weight: bold;
  background: rgba(76, 175, 80, 0.25);
}

.scoreboardName {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Name and flag are one item wherever they appear, so a row's spacing pushes
   the score away from the pair instead of the flag away from the name. Upstream
   writes them as one string with no space at all -- callsign, "/", abbreviation
   -- and only the colour changes at the slash. */
.playerLabel {
  display: inline-flex;
  align-items: baseline;
  min-width: 0;
}

.scoreboardFlag {
  white-space: nowrap;
  font-weight: bold;
}

/* The authentication indicator sits tight against the name it belongs to, as
   upstream's does -- there is no space between them, only a colour change. */
.scoreboardStatus {
  white-space: nowrap;
  font-weight: bold;
}

/* ScoreboardRenderer.cxx:562 drops a blank line in front of the first observer.
   Sorting them last says where they are; the gap says they are a separate group
   rather than the worst players on the board. */
.scoreboardEntry.startsObservers {
  margin-top: 0.55em;
}

/* The rabbit mark stands off the name it follows, unlike the flag and the
   authentication indicator, because it is a word rather than a suffix. */
.scoreboardRabbit:not(:empty) {
  white-space: nowrap;
  margin-left: 6px;
}

.scoreboardStats {
  margin-left: 10px;
  white-space: nowrap;
}

#helpHint {
  margin-top: 15px;
  padding-top: 15px;
  border-top: 1px solid #444;
  font-size: 14px;
  color: #4CAF50;
}

#helpHint strong {
  color: #fff;
  font-size: 16px;
}

#helpPanel {
  z-index: 200;
  display: none;
}

#helpPanel .dialogTitle {
  margin: 0 0 20px 0;
  color: #4CAF50;
  font-size: 2rem;
  font-weight: bold;
}

#helpPanel .dialogContent {
  display: flex;
  flex-direction: column;
}

#helpPanel h1 {
  font-size: 2rem;
  margin-bottom: 20px;
  color: #4CAF50;
  text-align: center;
}

#helpPanel h2 {
  font-size: 1.3em;
  margin-top: 20px;
  margin-bottom: 10px;
  color: #4CAF50;
  border-bottom: 1px solid #444;
  padding-bottom: 5px;
}

#helpPanel h2:first-child {
  margin-top: 0;
}

#helpPanel p {
  margin: 8px 0;
  font-size: 1em;
  line-height: 1.6;
}

#helpPanel strong {
  color: #fff;
}

.closeBtn {
  border: 2px solid #4CAF50;
  border-radius: 5px;
  background: rgba(76, 175, 80, 0.2);
  color: #4CAF50;
  font-size: 1.15em;
  font-weight: bold;
  line-height: 1;
  cursor: pointer;
  padding: 2px 10px;
  transition: all 0.2s;
}

.closeBtn:hover {
  background: rgba(76, 175, 80, 0.4);
  border-color: #66bb6a;
}

.closeBtn:active {
  background: rgba(76, 175, 80, 0.6);
}

/* Operator Panel Styling */
#operatorOverlay {
  z-index: 300;
  display: none;
}

#audioOverlay {
  z-index: 310;
  display: none;
}

#audioOverlay .dialogContent {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

#motd {
  margin-bottom: 10px;
  font-size: 1.1em;
  color: #4CAF50;
}

.operatorRow {
  margin-bottom: 10px;
}

.operatorConfigRow {
  display: grid;
  grid-template-columns: 1fr;
  gap: 6px;
}

.operatorToggle {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}

#operatorOverlay input[type="number"] {
  width: 100%;
  padding: 10px;
  font-size: 1em;
  border: 2px solid #4CAF50;
  border-radius: 5px;
  background: rgba(0, 0, 0, 0.5);
  color: #fff;
  margin-bottom: 10px;
  box-sizing: border-box;
}

#operatorOverlay .dialogTitle {
  margin: 0 0 20px 0;
  color: #4CAF50;
  font-size: 2rem;
}

#operatorOverlay label {
  color: #fff;
  font-weight: bold;
}

#operatorOverlay input[type="text"] {
  width: 100%;
  padding: 10px;
  font-size: 1em;
  border: 2px solid #4CAF50;
  border-radius: 5px;
  background: rgba(0, 0, 0, 0.5);
  color: #fff;
  margin-bottom: 10px;
  box-sizing: border-box;
}

#operatorOverlay input[type="text"]:focus {
  outline: none;
  border-color: #66bb6a;
}

#operatorOverlay button {
  padding: 10px 18px;
  font-size: 1em;
  font-weight: bold;
  border: 2px solid #4CAF50;
  border-radius: 5px;
  background: rgba(76, 175, 80, 0.2);
  color: #4CAF50;
  cursor: pointer;
  transition: all 0.2s;
  margin-right: 6px;
}

#operatorOverlay button:hover {
  background: rgba(76, 175, 80, 0.4);
  border-color: #66bb6a;
}

#operatorOverlay button:active {
  background: rgba(76, 175, 80, 0.6);
}

#settingsHud button:focus-visible,
#settingsHud input:focus-visible,
#settingsHud select:focus-visible,
#audioOverlay button:focus-visible,
#audioOverlay input:focus-visible,
#audioOverlay select:focus-visible,
#helpPanel button:focus-visible,
#helpPanel input:focus-visible,
#helpPanel select:focus-visible,
#operatorOverlay button:focus-visible,
#operatorOverlay input:focus-visible,
#operatorOverlay select:focus-visible,
#entryDialog button:focus-visible,
#entryDialog input:focus-visible,
#entryDialog select:focus-visible {
  outline: 2px solid var(--color-btn-bg-focus);
  outline-offset: 2px;
}

#entryDialog {
  z-index: 300;
  display: none;
}

#loadingOverlay {
  position: fixed;
  inset: 0;
  z-index: 290;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.62);
  backdrop-filter: blur(4px);
}

.loadingPanel {
  width: min(420px, calc(100vw - 32px));
  padding: 22px 24px;
  border: 2px solid #4CAF50;
  border-radius: 14px;
  background: rgba(0, 0, 0, 0.9);
  box-shadow: 0 0 24px rgba(0, 0, 0, 0.35);
}

.loadingTitle {
  color: #4CAF50;
  font-size: 24px;
  font-weight: bold;
  text-align: center;
  margin-bottom: 12px;
}

#loadingStatus {
  color: #f5f8f0;
  font-size: 15px;
  text-align: center;
}

#loadingDetail {
  color: rgba(255, 255, 255, 0.72);
  font-size: 12px;
  text-align: center;
  min-height: 18px;
  margin-top: 6px;
}

.loadingBarTrack {
  width: 100%;
  height: 14px;
  margin-top: 16px;
  border-radius: 999px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(76, 175, 80, 0.45);
}

#loadingBarFill {
  width: 0%;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, #2d7d31 0%, #4CAF50 50%, #83d588 100%);
  transition: width 0.18s ease-out;
}

#serverInfo {
  margin-bottom: 10px;
  font-size: 1.1em;
  color: #333;
}

#serverMotd {
  color: #4CAF50;
}

#entryDialog h2 {
  margin: 0 0 20px 0;
  color: #4CAF50;
  text-align: center;
}

#entryInput {
  width: 100%;
  padding: 10px;
  font-size: 1em;
  border: 2px solid #4CAF50;
  border-radius: 5px;
  background: rgba(0, 0, 0, 0.5);
  color: #fff;
  margin-bottom: 20px;
  box-sizing: border-box;
}

#entryInput:focus {
  outline: none;
  border-color: #66bb6a;
}

.entryTeamSelector {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(10rem, 1fr);
  align-items: center;
  gap: 24px;
  width: min(100%, 560px);
  min-height: 58px;
  margin: 0 auto 20px;
  padding: 7px 10px;
  border: 1px solid transparent;
  border-radius: 3px;
  background: transparent;
  color: var(--color-fg);
  font: inherit;
  cursor: pointer;
}

.entryTeamLabel {
  font-weight: bold;
  text-align: right;
}

.entryTeamValue {
  color: var(--color-accent-light);
  font-weight: bold;
  text-align: left;
}

.entryTeamSelector[data-team="rogue"] .entryTeamValue { color: #ffff00; }
.entryTeamSelector[data-team="observer"] .entryTeamValue { color: #ffffff; }
.entryTeamSelector[data-team="red"] .entryTeamValue { color: #ff3333; }
.entryTeamSelector[data-team="blue"] .entryTeamValue { color: #6680ff; }
.entryTeamSelector[data-team="green"] .entryTeamValue { color: #33dd55; }
.entryTeamSelector[data-team="purple"] .entryTeamValue { color: #ff55ff; }

.entryTeamValue::before {
  content: "‹ ";
}

.entryTeamValue::after {
  content: " ›";
}

.entryTeamSelector:hover,
.entryTeamSelector:focus-visible {
  border-color: var(--color-accent);
}

.entryTeamSelector:disabled {
  cursor: not-allowed;
  opacity: 0.65;
}

#tankSelector {
  margin: 0 0 16px 0;
}

#tankSelectorTitle {
  color: #4CAF50;
  font-weight: bold;
  margin: 0 0 8px 0;
  text-align: center;
}

#tankSelectorCarousel {
  display: flex;
  gap: 10px;
  justify-content: center;
  align-items: center;
}

.tankOption {
  /* Fills whatever the dialog leaves between the two arrows, rather than a
     fixed 140px card: the preview is the only thing in this row worth space,
     and the dialog is laid out for phones through to headsets. */
  flex: 1 1 auto;
  min-width: 0;
  border: 2px solid #4CAF50;
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.45);
  color: #4CAF50;
  padding: 8px;
  cursor: pointer;
  transition: all 0.2s;
}

.tankOption.selected {
  border-color: #66bb6a;
  background: rgba(76, 175, 80, 0.28);
  box-shadow: 0 0 14px rgba(76, 175, 80, 0.4);
}

/* The confirm sits apart from the rows it commits, so it does not read as one
   more setting. */
.operatorConfirmRow {
  margin-top: 0.9em;
  padding-top: 0.7em;
  border-top: 1px solid rgba(76, 175, 80, 0.35);
  gap: 0.6em;
}

/* Reads Restart rather than Apply when a staged change starts a new game, and
   says so in the one place an operator is looking. */
#operatorApplyBtn.startsNewGame {
  border-color: #ffb347;
  color: #ffb347;
}

.tankArrowBtn {
  width: 46px;
  height: 46px;
  border: 2px solid #4CAF50;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.45);
  color: #4CAF50;
  font-size: 1em;
  font-weight: bold;
  cursor: pointer;
  transition: all 0.2s;
}

.tankArrowBtn:hover {
  border-color: #66bb6a;
  background: rgba(76, 175, 80, 0.2);
}

.tankArrowBtn:disabled {
  opacity: 0.5;
  cursor: default;
}

.tankPreviewCanvas {
  /* The drawing buffer follows this, not the other way round -- see
     `resizeTankPreview`. `aspect-ratio` keeps the frame the shape the preview
     camera was framed for, and the cap stops it eating a tall screen. */
  width: 100%;
  aspect-ratio: 3 / 2;
  max-height: 34vh;
  display: block;
  margin: 0 auto 6px auto;
  border-radius: 6px;
  background: radial-gradient(circle at 50% 65%, rgba(76, 175, 80, 0.2), rgba(0, 0, 0, 0.5));
}

.tankOptionLabel {
  display: block;
  text-align: center;
  font-weight: bold;
}

.entryDialogButtons {
  display: flex;
  gap: 10px;
  justify-content: center;
}

.entryDialogButtons button {
  padding: 10px 20px;
  font-size: 1em;
  font-weight: bold;
  border: 2px solid #4CAF50;
  border-radius: 5px;
  background: rgba(76, 175, 80, 0.2);
  color: #4CAF50;
  cursor: pointer;
  transition: all 0.2s;
}

.entryDialogButtons button:hover {
  background: rgba(76, 175, 80, 0.4);
  border-color: #66bb6a;
}

.entryDialogButtons button:active {
  background: rgba(76, 175, 80, 0.6);
}

#messages {
  margin-top: 15px;
  font-size: 14px;
  max-height: 100px;
  overflow-y: auto;
  border-top: 1px solid #444;
  padding-top: 10px;
}

.message {
  margin: 5px 0;
  padding: 5px;
  background: rgba(76, 175, 80, 0.2);
  border-radius: 3px;
  animation: fadeIn 0.3s;
}

.message.kill {
  background: rgba(255, 87, 34, 0.3);
  color: #ff5722;
}

#debugHud {
  position: fixed;
  bottom: var(--hud-edge);
  right: var(--hud-edge);
  z-index: 100;
  background: var(--color-panel-bg);
  padding: var(--hud-pad);
  border-radius: 10px;
  /* A phone override set this to 90vw, which on an 857px landscape screen is
     771px and stretched the panel across from its right anchor. As a ceiling on
     the one width it wants, it does that override's job on a narrow phone and
     nothing anywhere else. */
  width: min(320px, 90vw);
  font-family: 'Courier New', monospace;
  font-size: var(--hud-font-small);
  box-sizing: border-box;
  overflow-x: auto;
  /* Both are pinned to the right edge and the radar is on top, so the HUD stops
     below it instead of scrolling away underneath. It scrolls already, so the
     rows this costs are still reachable. */
  max-height: max(120px, calc(100vh
    - var(--hud-edge)
    - var(--radar-size)
    - var(--hud-edge-gap)
    - var(--hud-edge)));
  overflow-y: auto;
  display: none;
}

#debugHud h3 {
  font-size: 14px;
  margin-bottom: 10px;
  color: #FFD700;
  border-bottom: 1px solid #444;
  padding-bottom: 5px;
}

#debugContent {
  line-height: 1.6;
}

#debugContent div {
  margin: 3px 0;
  display: flex;
  justify-content: space-between;
}

#debugContent .label {
  color: #AAA;
}

#debugContent .value {
  color: #0F0;
  font-weight: bold;
}

.message.death {
  background: rgba(244, 67, 54, 0.3);
  color: #f44336;
}

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

canvas {
  display: block;
  width: 100%;
  height: 100%;
}

.player-label {
  color: #fff;
  font-family: Arial, sans-serif;
  font-size: 14px;
  font-weight: bold;
  padding: 4px 8px;
  background: rgba(0, 0, 0, 0.6);
  border-radius: 4px;
  white-space: nowrap;
  text-align: center;
  text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
  pointer-events: none;
  user-select: none;
}

/* An observer aims at nothing and reloads nothing, so the crosshair, the mouse
   motion box, the shot status and the altimeter all go. HUDRenderer::renderRoaming
   draws none of them either. */
body.observing #crosshair,
body.observing #controlBox,
body.observing #shotStatus,
body.observing #altimeter {
  display: none;
}

/* A tank that cannot leave the ground has no altitude to read, so the tape goes
   with the ability. updateFlag() sets the same rule upstream: the tape is up
   while the world allows jumping or the Jumping flag is in hand. */
body.no-jumping #altimeter {
  display: none;
}

/* Identify picks the roaming target for an observer and locks a guided missile
   for a tank, so the touch button is offered to both. Fire and Drop keep their
   own labels, because an observer spends them on the view and on descending. */

/* renderRoaming() draws both of these: renderStatus() puts the roaming label at
   the very top and renderAlerts() stacks the alert slots just under it. They are
   separate things -- the status line persists, an alert times out -- so they
   share a column rather than an element. Both deliberately sit over everything,
   dialogs included: an alert lasts a few seconds and reading it matters more
   than what it covers, and opening a menu is itself what starts the pause
   countdown that has to be read from in front of that menu. */
#hudNotices {
  position: fixed;
  top: max(1vh, 12px);
  left: 0;
  width: 100%;
  z-index: 1100;
  pointer-events: none;
  text-align: center;
}

.alertHudLine,
#roamStatus {
  font-size: clamp(1.2rem, 3.2vmin, 2.1rem);
  font-weight: bold;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.9), 0 0 3px rgba(0, 0, 0, 0.9);
  line-height: 1.25;
}

/* messageColor, which is what renderStatus uses for the roaming label. */
#roamStatus {
  color: #cfd8e6;
  display: none;
}

body.observing #roamStatus {
  display: block;
}

/* ScoreboardRenderer::drawRoamTarget brackets the row being watched. */
.scoreboardEntry.selectable {
  cursor: pointer;
}

.scoreboardEntry.roamTarget {
  background: rgba(255, 255, 255, 0.16);
  border-radius: 3px;
}

#crosshair {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 40px;
  height: 40px;
  pointer-events: none;
  z-index: 50;
}

#shotStatus {
  position: fixed;
  top: 50%;
  left: 50%;
  pointer-events: none;
  z-index: 50;
}

.voiceChannelHud {
  position: fixed;
  top: calc(50% + max(2.5rem, 5vmin));
  left: calc(50% + var(--motion-box) / 2 + max(16px, 2vw) + 16px);
  z-index: 60;
  min-width: 6.5rem;
  max-width: min(12rem, 30vw);
  padding: 0.45rem 0.75rem;
  border: 2px solid #f44336;
  border-radius: 0.35rem;
  background: rgba(0, 0, 0, 0.78);
  color: var(--color-fg);
  font-size: 0.78rem;
  font-weight: bold;
  line-height: 1.2;
  text-align: center;
  pointer-events: auto;
  box-sizing: border-box;
  text-shadow: 0 1px 2px #000;
}

.voiceChannelHud--active {
  border-color: var(--color-accent-light);
  box-shadow: 0 0 0.55rem rgba(102, 187, 106, 0.55);
}

.voiceChannelHud--muted {
  border-color: #f44336;
  box-shadow: 0 0 0.55rem rgba(244, 67, 54, 0.4);
}

.voiceChannelHudLabel,
.voiceChannelHudStatus {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.voiceChannelHudLabel {
  color: var(--color-fg);
  font-size: 0.95rem;
}

.voiceChannelHudStatus {
  margin-top: 0.15rem;
  color: rgba(255, 255, 255, 0.72);
  font-size: 0.7rem;
  font-weight: normal;
}

#controlBox {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: var(--motion-box);
  height: var(--motion-box);
  max-width: 100vw;
  max-height: 100vh;
  border: 2px solid rgba(76, 175, 80, 0.6);
  pointer-events: none;
  z-index: 49;
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
  transition: border-color 0.3s;
}

/* The dead zone: inside it the mouse asks for no motion at all. */
#noMotionBox {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: var(--motion-dead-box);
  height: var(--motion-dead-box);
  border: 2px solid;
  border-color: inherit;
}

#controlBox.keyboard-mode {
  border-color: rgba(255, 152, 0, 0.6);
}

#crosshair::before,
#crosshair::after {
  content: '';
  position: absolute;
  background: rgba(255, 255, 255, 0.8);
  box-shadow: 0 0 3px rgba(0, 0, 0, 0.8);
}

#crosshair::before {
  top: 50%;
  left: 0;
  width: 100%;
  height: 2px;
  transform: translateY(-50%);
}

#crosshair::after {
  left: 50%;
  top: 0;
  width: 2px;
  height: 100%;
  transform: translateX(-50%);
}

@media (max-width: 900px), (max-width: 600px) {
  #shotStatus {
  }
}

#crosshair .center {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 4px;
  height: 4px;
  border: 2px solid rgba(255, 255, 255, 0.8);
  border-radius: 50%;
  box-shadow: 0 0 3px rgba(0, 0, 0, 0.8);
}

#radar {
  position: fixed;
  top: var(--hud-edge);
  right: var(--hud-edge);
  z-index: 120;
  border: none;
  border-radius: 6px;
  box-shadow: 0 0 16px #222b, 0 0 0 4px rgba(0,0,0,0.5);
  background: none;
  opacity: 1;
  width: var(--radar-size);
  height: var(--radar-size);
  aspect-ratio: 1 / 1;
  box-sizing: border-box;
  pointer-events: none;
}

@media (max-width: 600px) {
  #radar {
    width: 48vw;
    height: 48vw;
    max-width: calc(100vw - 8vw);
    max-height: calc(100vw - 8vw);
    right: 2vw;
    min-width: 60px;
    min-height: 60px;
  }
}

#settingsHud,
#helpPanel,
#audioOverlay,
#operatorOverlay,
#entryDialog {
  /* `fixed`, not `absolute`. Body is not a positioned ancestor, so an absolute
     dialog centres on the *document* rather than on the window -- and a document
     taller than the window puts `top: 50%` below the middle of what is on
     screen, which is a dialog that opens off the bottom. Only `#settingsHud`
     had this right. */
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: min(820px, calc(100vw - 32px));
  height: min(86vh, 900px);
  /* svh, not vh, for the same reason `--mobile-btn-size` takes it: on mobile
     Chrome `vh` is the *large* viewport, the one with the URL bar retracted, so
     a dialog sized in vh is taller than the window it has to fit in. Declared
     second so a browser without svh keeps the vh line above. */
  height: min(86svh, 900px);
  padding: 24px;
  border: 1px solid rgba(76, 175, 80, 0.72);
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.82);
  box-shadow: 0 12px 36px #0009;
  box-sizing: border-box;
  overflow-y: auto;
  overflow-x: hidden;
  font-size: var(--hud-font-title);
  line-height: 1.45;
}

#settingsHud {
  z-index: 1000;
  text-align: left;
  display: none;
  /* The other dialogs hold prose or a form and want the width they are given.
     This one is a list of short label-and-value rows, and stretched to 820px it
     was mostly gap. It takes the width of its widest row instead, with the
     padding as the margin -- still centred, because the transform does that
     rather than the width. The floor keeps a short list from looking like a
     tooltip; the ceiling is the width the others use. A small screen has its own
     rule further down and still fills. */
  width: fit-content;
  /* A floor low enough that the rows decide the width rather than this does; it
     is here so a menu trimmed down to a couple of entries still reads as a
     dialog. */
  min-width: min(260px, calc(100vw - 32px));
  max-width: min(820px, calc(100vw - 32px));
}

#settingsHud .dialogTitle,
#helpPanel .dialogTitle,
#audioOverlay .dialogTitle,
#operatorOverlay .dialogTitle,
#entryDialog h2 {
  margin: 0 0 18px;
  color: var(--color-accent-light);
  font-size: 2rem;
  font-weight: bold;
  text-align: center;
}

/* Every dialog closes the same way, so every heading lays out the same way:
   the label, then the [X] against the right edge. */
#settingsHud .dialogTitle,
#helpPanel .dialogTitle,
#audioOverlay .dialogTitle,
#operatorOverlay .dialogTitle,
#entryDialog h2 {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--hud-gap);
}

.settingsHudContent {
  width: 100%;
}

.settingsMenuList {
  display: grid;
  gap: 3px;
  width: min(100%, 680px);
  max-width: 100%;
  margin: 0 auto;
}

.settingsMenuRow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  column-gap: 24px;
  align-items: center;
  width: 100%;
  min-height: 58px;
  padding: 10px 12px;
  border: 1px solid transparent;
  border-radius: 3px;
  background: transparent;
  color: var(--color-fg);
  font: inherit;
  text-align: left;
}

.settingsMenuRow:hover,
.settingsMenuRow:focus-visible {
  border-color: var(--color-accent);
  background: transparent;
}

.settingsMenuRow:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}

/* A HUD button for something this player may not do: plainly unavailable rather
   than missing, which is the same rule the renderer's capability gating uses for
   a feature the context cannot draw. */
.settingsHudBtn:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}

.settingsMenuLabel,
.settingsMenuValue {
  min-width: 0;
  overflow-wrap: anywhere;
}

.settingsMenuLabel {
  text-align: right;
}

.settingsMenuValue {
  color: var(--color-accent-light);
  font-weight: bold;
  text-align: left;
}

.settingsMenuRow.active .settingsMenuValue {
  color: var(--color-fg);
}

/* On and Off read as state, not as decoration, so they are the only values that
   get a colour. Set after the .active rule so an enabled toggle stays green. */
.settingsMenuRow[data-menu-state="off"] .settingsMenuValue {
  color: var(--color-state-off);
}

.settingsMenuRow[data-menu-state="on"] .settingsMenuValue {
  color: var(--color-state-on);
}

/* The one compact block keyed to height as well as width. Short counts as small
   here: a phone held in landscape is wide and short -- a Pixel 10 reports
   857x411 -- so a width-only rule hands it desktop-sized dialogs against the 411
   pixels that actually bind.

   Only here. The other compact blocks reflow for a narrow row -- a radar sized
   off vw alone, buttons at a 60vw minimum, audio rows stacking -- and a wide
   short screen wants none of it: 48vw there is 411px, the whole height. Being
   short and being narrow ask for different things, and the size of a dialog is
   what they have in common. */
@media (max-width: 600px), (max-height: 500px) {
  #settingsHud,
  #helpPanel,
  #audioOverlay,
  #operatorOverlay,
  #entryDialog {
    height: min(88vh, 900px);
    padding: var(--hud-gap);
    /* Not a fixed 20px: this block also catches a screen that is small because
       it is short, where 20px is what made a dialog outsized. */
    font-size: var(--hud-font-title);
  }

  .settingsMenuRow {
    column-gap: 16px;
    width: 100%;
    min-height: 58px;
    padding: 10px 8px;
  }

  .settingsMenuList {
    width: 100%;
  }
}

/* No column override anywhere. The base rule gives the label and the value an
   equal half each, and since the label is right aligned and the value left
   aligned the pair straddles the middle -- which is what reads as centred. There
   used to be a narrow-screen variant that let the value shrink to its content
   and handed the label all the slack; a right aligned label then hugs the value
   and the empty space piles up on the far side, in portrait as much as in
   landscape. The labels fit an equal half at every width bzo runs at, and
   overflow-wrap on them covers the rest. */
@media (max-width: 600px) {
  /* Filling the screen is worth it for a dialog holding prose or a form that
     would otherwise be squeezed. Settings is not one: it is a column of short
     rows, and it takes its content's width here as it does everywhere, so it is
     only as wide as its widest row needs. */
  #helpPanel,
  #audioOverlay,
  #operatorOverlay,
  #entryDialog {
    width: calc(100vw - 16px);
  }
}

.audioSettings {
  flex: 1 1 100%;
  width: 100%;
  max-width: 760px;
  margin-top: 1.25rem;
  padding: 1rem;
  border: 1px solid rgba(76, 175, 80, 0.55);
  border-radius: 0.75rem;
  background: rgba(0, 0, 0, 0.35);
  box-sizing: border-box;
  text-align: left;
}

.audioSettings h2 {
  margin: 0 0 0.5rem;
  color: var(--color-accent-light);
  font-size: 1.25rem;
}

.audioSettingsDescription,
.audioSettingHint,
.voicePermissionStatus {
  color: rgba(255, 255, 255, 0.75);
  font-size: 1em;
  line-height: 1.5;
}

.audioSettingsDescription {
  margin: 0 0 1rem;
}

.audioSettingRow {
  display: grid;
  grid-template-columns: minmax(8rem, 0.45fr) minmax(11rem, 1fr);
  gap: 0.35rem 0.75rem;
  align-items: center;
  margin: 0 0 0.85rem;
}

.audioSettingRow > label,
.audioSettingLabel {
  color: var(--color-fg);
  font-weight: bold;
}

.audioSettingRow select {
  width: 100%;
  min-height: 2.25rem;
  padding: 0.35rem 0.5rem;
  border: 1px solid rgba(76, 175, 80, 0.7);
  border-radius: 0.35rem;
  background: rgba(0, 0, 0, 0.75);
  color: var(--color-fg);
  font: inherit;
  box-sizing: border-box;
}

.volumeControl {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  min-width: 0;
}

.volumeSlider {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 2.25rem;
  margin: 0;
  accent-color: var(--color-accent);
  cursor: pointer;
  /* The dialog owns Left/Right on a focused row, so the browser must not also
     scroll the page or pan the canvas when a finger drags the thumb. */
  touch-action: none;
}

.volumeValue {
  flex: 0 0 3.5rem;
  color: var(--color-accent-light);
  font-weight: bold;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.audioSettingRow select:disabled {
  opacity: 0.75;
  cursor: not-allowed;
}

.audioSettingRow select:focus,
.volumeSlider:focus-visible,
.audioCheckboxLabel input:focus-visible,
.audioSettingActions button:focus-visible {
  outline: 2px solid var(--color-btn-bg-focus);
  outline-offset: 2px;
}

.audioSettingHint {
  grid-column: 2;
  margin: 0;
}

.audioProcessingOptions {
  grid-template-columns: 1fr;
  gap: 0.45rem;
  padding-top: 0.25rem;
}

.audioProcessingOptions .audioSettingHint {
  grid-column: 1;
}

.audioCheckboxLabel {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: rgba(255, 255, 255, 0.9);
  font-weight: normal;
  cursor: pointer;
}

.audioCheckboxLabel input {
  accent-color: var(--color-accent);
  inline-size: 1.3rem;
  block-size: 1.3rem;
  margin: 0;
}

.audioSettingActions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.65rem;
  align-items: center;
  margin-top: 0.5rem;
}

.audioSettingActions .settingsHudBtn {
  min-height: 3rem;
  padding: 0.55rem 0.9rem;
  border: 2px solid var(--color-accent);
  border-radius: 0.45rem;
  background: var(--color-btn-bg);
  color: var(--color-fg);
}

.audioSettingActions .settingsHudBtn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.audioSettingActions .voiceMicToggle[aria-pressed="true"] {
  border-color: var(--color-accent-light);
  background: var(--color-btn-bg-active);
}

.voicePermissionStatus {
  min-height: 1.2em;
  margin: 0.75rem 0 0;
}

.settingsHudBtn {
  font-size: 1em;
  border-radius: 4px;
  border: none;
  background: #444;
  color: #fff;
  cursor: pointer;
  transition: background 0.2s;
  box-sizing: border-box;
}

#settingsHud .settingsMenuRow,
#settingsHud .settingsMenuRow:hover,
#settingsHud .settingsMenuRow:focus-visible,
#settingsHud .settingsMenuRow.active {
  background: transparent;
}
@media (max-width: 600px) {
  .settingsHudBtn:not(.settingsMenuRow) {
    min-width: 60vw;
    max-width: 98vw;
    min-height: 40px;
    font-size: 1em;
  }
}

/* Attached to the targeting box's right edge, whatever the screen. The offset
   used to be a fixed 40px against a width in vmin, so the two only agreed at one
   size: 4vmin is 43px on a 1920x1080 desktop and the tape sat against the box,
   but 16px on a 857x411 phone, leaving a 24px gap. Both terms are the box's own
   unit now, so `right` puts the tape's left edge exactly on the box's right edge
   and it cannot drift again. */
#altimeter {
  position: fixed;
  top: 50%;
  right: calc(50% - var(--motion-box) / 2 - var(--altimeter-width));
  transform: translateY(-50%);
  width: var(--altimeter-width);
  height: var(--motion-box);
  z-index: 50;
  pointer-events: none;
  background: transparent;
}
#degreeBar {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  height: 4vmin;
  width: var(--motion-box);
  z-index: 51;
  pointer-events: none;
  background: transparent;
}

@media (max-width: 900px) {
  .voiceChannelHud {
    left: auto;
    right: 10px;
    max-width: 9rem;
  }
}

@media (max-width: 600px) {
  .audioSettings {
    padding: 0.75rem;
  }

  .audioSettingRow {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.35rem;
  }

  .audioSettingHint,
  .audioProcessingOptions .audioSettingHint {
    align-self: stretch;
  }

  .audioSettingActions {
    flex-direction: column;
    align-items: stretch;
  }

  .audioSettingActions .settingsHudBtn {
    width: 100%;
    min-height: 2.75rem;
  }

  .voiceChannelHud {
    top: calc(50% + 2.25rem);
    right: 8px;
    min-width: 5.75rem;
    max-width: 8rem;
    padding: 0.4rem 0.55rem;
    font-size: 0.72rem;
  }

  .entryTeamSelector {
    grid-template-columns: minmax(0, 1fr) minmax(8rem, 1fr);
    gap: 16px;
  }
}
