:root {
  --amber: #e8b23d;
  --cream: #e9dcb9;
  --panel: rgba(10, 10, 8, 0.9);
  --line: #55523f;
}
html,
body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  background: #161613;
  color: var(--cream);
  font-family: "Courier New", ui-monospace, monospace;
  /* On iOS, a double tap on a normal interface element (not canvas) — the
       settings panel, buttons, labels — can cause a problem. It can start
       the page zoom gesture. It can also select text. Both of these can
       open the system menu. The viewport setting maximum-scale=1 already
       stops double-tap zoom. This rule only stops text selection and
       callouts. It applies to everything except input fields.
       user-select:none does not work on <input>/<textarea>. So users can
       still edit text in them. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
.stage {
  position: fixed;
  inset: 0;
}
h1 {
  font-size: 14px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin: 0;
  color: var(--amber);
  white-space: nowrap;
}
.sub {
  display: none;
}

/* The cursor hides when the mouse is idle. While driving, it only gets in the way. */
body.cursor-idle,
body.cursor-idle * {
  cursor: none !important;
}

.topbar {
  position: absolute;
  /* This z-index is higher than .touchControls (z-index:6). If it were
     not, the touch joysticks and buttons would draw on top of the open
     settings panel. Taps would then reach them through the open menu. */
  z-index: 7;
  top: 0;
  left: 0;
  bottom: 0;
  width: 300px;
  max-width: 85vw;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 10px 12px;
  box-sizing: border-box;
  background: var(--panel);
  border-right: 1px solid var(--line);
  backdrop-filter: blur(3px);
}
.closeUiBtn {
  position: sticky;
  top: 0;
  align-self: flex-end;
  z-index: 2;
  background: rgba(10, 10, 8, 0.7);
  border: 1px solid var(--line);
  color: var(--cream);
  font-family: inherit;
  font-size: 22px;
  line-height: 1;
  width: 44px;
  height: 44px;
  border-radius: 8px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.closeUiBtn:hover {
  background: #26261f;
}
.controls {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  margin: 0;
  font-size: 11px;
}
.controls label {
  opacity: 1;
}
.controls input[type="text"] {
  background: #0e0e0c;
  border: 1px solid var(--line);
  color: var(--cream);
  font-family: inherit;
  font-size: 11px;
  padding: 5px 7px;
  border-radius: 5px;
  width: 88px;
}
.controls input[type="range"] {
  width: 110px;
  accent-color: var(--amber);
}
.sliderBlock {
  display: flex;
  flex-direction: column;
  width: 100%;
  gap: 5px;
}
.sliderBlock label {
  display: flex;
  justify-content: space-between;
  width: 100%;
}
.sliderBlock input[type="range"] {
  width: 100%;
}
.controls label.chk {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  opacity: 1;
  cursor: pointer;
}
.controls input[type="checkbox"] {
  accent-color: var(--amber);
  margin: 0;
  cursor: pointer;
}
.controls .geoSourceRadios {
  display: flex;
  align-items: center;
  gap: 10px;
}
.controls .geoSourceRadios label {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  opacity: 1;
  cursor: pointer;
}
.controls input[type="radio"] {
  accent-color: var(--amber);
  margin: 0;
  cursor: pointer;
}
.controls button {
  background: #0e0e0c;
  border: 1px solid var(--line);
  color: var(--cream);
  font-family: inherit;
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 6px 10px;
  border-radius: 5px;
  cursor: pointer;
}
.controls button:hover {
  background: #26261f;
}

/* Settings groups use collapsible <details> elements. This keeps the long
     list small. It also makes it easier to find things. The app remembers
     if each group is open. This is stored separately from the rest of the
     state. See the script — it uses its own localStorage key. */
.quickRow {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.settingsGroup {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.02);
}
.settingsGroup summary {
  cursor: pointer;
  padding: 8px 10px;
  font-size: 11px;
  font-weight: bold;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--amber);
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  list-style: none;
}
.settingsGroup summary::-webkit-details-marker {
  display: none;
}
.settingsGroup summary::before {
  content: "▸";
  display: inline-block;
  width: 1em;
  margin-right: 2px;
  transition: transform 0.15s;
}
.settingsGroup[open] summary::before {
  transform: rotate(90deg);
}
.groupLockBtn {
  display: none;
  float: right;
  align-items: center;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 4px;
  color: var(--cream);
  padding: 4px 7px;
  cursor: pointer;
}
.groupLockBtn svg {
  display: block;
}
.settingsGroup[open] .groupLockBtn {
  display: inline-flex;
}
.groupLockBtn:hover {
  background: #26261f;
}
.settingsGroup.locked .groupBody button:disabled,
.settingsGroup.locked .groupBody input:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}
.settingsGroup .groupBody {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 8px;
  padding: 6px 10px 14px 10px;
}
.settingsGroup .statsGrid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: stretch;
}
.statsGrid > div {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 7px 8px;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.025);
}
.statsGrid span {
  font-size: 10px;
  color: var(--muted);
  text-transform: uppercase;
}
.statsGrid strong {
  color: var(--amber);
  font-size: 14px;
}
.statsGrid button {
  grid-column: 1 / -1;
}
.teleportBtns {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  width: 100%;
}

.zoomsel {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0;
  width: 100%;
}
.zoomsel button {
  border-radius: 0;
  margin-left: -1px;
  padding: 6px 8px;
}
.zoomsel button:first-of-type {
  border-radius: 5px 0 0 5px;
  margin-left: 0;
}
.zoomsel button:last-of-type {
  border-radius: 0 5px 5px 0;
}
.zoomsel button.on {
  background: var(--amber);
  border-color: var(--amber);
  color: #141410;
}
.zoomsel button.on:hover {
  background: var(--amber);
}
.controls button.on {
  background: var(--amber);
  border-color: var(--amber);
  color: #141410;
}

.frame {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: #e9e6de;
}
canvas {
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none;
  /* iOS: without this, a long press on the canvas opens the system menu
       (select, search, save image). That menu would cover the car
       controls. */
  -webkit-touch-callout: none;
  user-select: none;
  -webkit-user-select: none;
}

.status {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #e9e6de;
  color: #333;
  font-size: 12px;
  letter-spacing: 0.06em;
  text-align: center;
  padding: 24px;
  box-sizing: border-box;
  line-height: 1.6;
}
.status.err {
  color: #8a2018;
}
.status.hidden {
  display: none;
}

.hud {
  position: absolute;
  bottom: 14px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 6px 10px;
  display: none;
  align-items: baseline;
  gap: 5px;
  opacity: calc(var(--speedoOpacity, 20) / 100);
  /* We do not set pointer-events:none here. Combined with user-select:none,
       it triggers a known WebKit bug. On iOS, user-select then stops
       working. The text still gets selected on a long press. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
.hud.show {
  display: flex;
}
.km {
  font-size: calc(22px * var(--speedoScale, 1));
  font-weight: bold;
  color: var(--amber);
}
.coords,
#geoVal {
  font-size: 11px;
  cursor: pointer;
  width: 100%;
  opacity: 1 !important;
}
.coords:hover {
  text-decoration: underline;
}
.targetPanel {
  position: absolute;
  z-index: 4;
  top: 48px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 5px 12px;
  font-size: 12px;
  letter-spacing: 0.03em;
  color: #3fc7ff;
  display: none;
  white-space: nowrap;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
.targetPanel.show {
  display: block;
}

.msg {
  position: absolute;
  /* above .topbar (z-index:7, same .stage stacking context, see its comment) —
     a toast telling the player something went wrong is useless hidden behind
     the open settings panel */
  z-index: 8;
  top: auto;
  bottom: 64px;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(150, 30, 20, 0.85);
  border-radius: 6px;
  padding: 5px 9px;
  font-size: 11px;
  letter-spacing: 0.06em;
  opacity: 0;
  transition: opacity 0.15s;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
.msg.show {
  opacity: 1;
}

/* This is a full-screen blue flash. It shows when the player reaches the
     mini-game target (see flashScreen). It uses a radial gradient, not a
     solid fill. A solid fill would look like a flickering haze. A gradient
     from center to edges looks like a real flash. */
.screenFlash {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 5;
  background: radial-gradient(ellipse at center, rgba(63, 199, 255, 0.55), rgba(63, 199, 255, 0) 70%);
  opacity: 0;
}
.screenFlash.show {
  animation: screenFlashPulse 0.6s ease-out;
}
@keyframes screenFlashPulse {
  0% {
    opacity: 0;
  }
  15% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}

/* This is the red flash. It uses the same shape and animation as
     .screenFlash above, just a different color. It marks the police chase
     (see updatePoliceGame). It blinks once every second during the chase,
     not only at the moment of capture. */
.screenFlash.police {
  background: radial-gradient(ellipse at center, rgba(255, 40, 40, 0.6), rgba(255, 40, 40, 0) 70%);
}

/* This is the large countdown of seconds until capture or arrest (see
     updatePoliceGame). It sits in the center of the screen, above all
     other HUD elements. */
.policeCountdown {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 6;
  font-size: 22vh;
  font-weight: 900;
  color: #ff3b3b;
  text-shadow: 0 0 30px rgba(255, 0, 0, 0.85);
  opacity: 0;
}
.policeCountdown.show {
  animation: policeCountdownPulse 1s ease-out;
}
@keyframes policeCountdownPulse {
  0% {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1.35);
  }
  30% {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
  }
  100% {
    opacity: 0.85;
    transform: translate(-50%, -50%) scale(1);
  }
}

.fpsCounter {
  position: absolute;
  bottom: 14px;
  right: 12px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 4px 8px;
  font-size: calc(11px * var(--fpsScale, 1));
  color: var(--amber);
  display: none;
  opacity: calc(var(--fpsOpacity, 100) / 100);
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
.fpsCounter.show {
  display: block;
}

.diagPanel {
  position: absolute;
  /* top is 44px, not 8px. The top 8px to about 34px on the left is used
     by .showUiBtn. That button is visible only when the UI is hidden, but
     it still takes up this corner. .playerList uses the same offset in
     its body.ui-hidden state. */
  top: 44px;
  left: 8px;
  /* This width is fixed. Without it, the diagnostic line about a road
     barrier would change the panel's width on every update. That line is
     longer than the others — it has coordinates and distances. We use
     white-space:pre-wrap, not pre. This keeps the line breaks between
     diagnostic lines. It also wraps long words inside that long line.
     Without it, the line would run past the edge of the panel. */
  width: 300px;
  /* This is always less than half the screen width, even on wide screens.
     The subtracted 16px already includes the left:8px offset. */
  max-width: min(calc(100vw - 16px), calc(50vw - 8px));
  box-sizing: border-box;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 6px 9px;
  font-size: calc(11px * var(--diagScale, 1));
  line-height: 1.5;
  color: var(--amber);
  opacity: calc(var(--diagOpacity, 100) / 100);
  white-space: pre-wrap;
  display: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
.diagPanel.show {
  display: block;
}

/* These are indicators for other players beyond the edge of the screen
     (see updateOffscreenIndicators). The container covers the whole
     canvas. JS sets each circle's position every frame, using
     style.left/top. CSS layout does not position them. */
.offscreenIndicators {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
}
.offIndicator {
  position: absolute;
  width: 30px;
  height: 30px;
  margin: -15px 0 0 -15px;
  border-radius: 50%;
  background: rgba(20, 18, 14, 0.8);
  border: 2px solid #e63946;
  color: #fff;
  font:
    600 11px / 1 system-ui,
    sans-serif;
  display: flex;
  align-items: center;
  justify-content: center;
  white-space: nowrap;
}
.offIndicator.target {
  border-color: #3fc7ff;
} /* This is the mini-game target. It uses the same blue as its beacon on the map and minimap. */

.geoToast {
  position: absolute;
  /* above .topbar (z-index:7) — same reasoning as .msg above */
  z-index: 8;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(150, 30, 20, 0.88);
  color: var(--cream);
  border-radius: 6px;
  padding: 7px 12px;
  display: none;
  align-items: center;
  gap: 10px;
  font-size: 11px;
  letter-spacing: 0.03em;
  white-space: nowrap;
  pointer-events: auto;
}
.geoToast.show {
  display: flex;
}
.geoToast button {
  background: #0e0e0c;
  border: 1px solid var(--line);
  color: var(--cream);
  font-family: inherit;
  font-size: 10px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 4px 9px;
  border-radius: 5px;
  cursor: pointer;
}
.geoToast button:hover {
  background: #26261f;
}

.shareToast {
  background: rgba(30, 40, 60, 0.9);
  white-space: normal;
  max-width: 80vw;
}
.shareToastNames {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.hint {
  font-size: 10px;
  opacity: 0.5;
  margin: 0;
  line-height: 1.5;
}

@media (max-width: 720px) {
  h1 {
    display: none;
  }
}

/* "Empty screen": hide the whole interface. Keep only the speedometer and the minimap. */
body.ui-hidden .topbar,
body.ui-hidden .msg,
body.ui-hidden .status,
body.ui-hidden > .stage > h1,
body.ui-hidden .sub {
  display: none !important;
}
.showUiBtn {
  position: fixed;
  z-index: 5;
  top: 8px;
  left: 8px;
  width: 34px;
  height: 34px;
  padding: 0;
  background: rgba(10, 10, 8, 0.5);
  border: 1px solid var(--line);
  border-radius: 6px;
  cursor: pointer;
  opacity: 0.35;
  display: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
.showUiBtn::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 16px;
  height: 2px;
  background: var(--cream);
  box-shadow:
    0 -5px 0 var(--cream),
    0 5px 0 var(--cream);
  transform: translate(-50%, -50%);
}
.showUiBtn:hover {
  opacity: 1;
  background: rgba(20, 20, 16, 0.75);
}
body.ui-hidden .showUiBtn {
  display: block;
}

/* Record/stop button for ghost-replay recording (see setGhostMode/startGhostRecording
     in game.js). Own visibility rule (.show, toggled by ghost mode itself, not by
     ui-hidden) — it must stay reachable whether or not the settings panel is open.
     Lives inside .stage (not a direct body child) so its z-index competes with
     .topbar's (7) in the SAME stacking context: .stage is position:fixed, which
     always opens its own stacking context regardless of z-index, so a sibling of
     .stage at the body level would out-rank .topbar's z-index no matter how low
     its own value — it'd paint over the open settings panel instead of under it.
     Vertically centered, left edge clear of the steer thumb's corner. */
.ghostRecordBtn {
  position: fixed;
  z-index: 5;
  left: 18px;
  top: 50%;
  transform: translateY(-50%);
  width: calc(52px * var(--ghostRecordScale, 1));
  height: calc(52px * var(--ghostRecordScale, 1));
  background: rgba(40, 8, 8, 0.6);
  border: 2px solid #ff3b3b;
  border-radius: 50%;
  color: #ff6b6b;
  font-family: inherit;
  font-size: calc(20px * var(--ghostRecordScale, 1));
  font-weight: bold;
  padding: 0;
  display: none;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  opacity: calc(var(--ghostRecordOpacity, 85) / 100);
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
.ghostRecordBtn.show {
  display: flex;
}
.ghostRecordBtn:hover {
  opacity: 1;
  background: rgba(60, 10, 10, 0.8);
}
.ghostRecordBtn.recording {
  opacity: 1;
  animation: ghostRecordPulse 1s ease-in-out infinite;
}
@keyframes ghostRecordPulse {
  0%,
  100% {
    background: rgba(60, 10, 10, 0.8);
    box-shadow: 0 0 0 0 rgba(255, 59, 59, 0.6);
  }
  50% {
    background: rgba(255, 59, 59, 0.55);
    box-shadow: 0 0 14px 4px rgba(255, 59, 59, 0.5);
  }
}

/* Список сохранённых ghost-заездов внутри группы настроек — см.
     renderGhostList()/toggleGhostPlaying()/deleteGhostRecording() в game.js.
     Кружок — тот же случайный hue, что и hue-rotate() на самом маркере
     (spawnGhostMarker), чтобы строка в списке узнаваемо соответствовала
     машинке на карте. */
.ghostList {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
}
.ghostEmpty {
  font-size: 10px;
  opacity: 0.5;
  padding: 4px 2px;
}
.ghostRow {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 5px 7px;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: rgba(255, 255, 255, 0.025);
}
.ghostSwatch {
  flex: 0 0 auto;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, 0.4);
}
.ghostLabel {
  flex: 1 1 auto;
  font-size: 10px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ghostRow button {
  flex: 0 0 auto;
  padding: 4px 8px;
  font-size: 9px;
}
/* Значок вместо кнопки "залить" — запись уже в общей галерее Firebase
     (см. uploadGhostToFirebase/applyRemoteGhosts в game.js), заливать нечего.
     Тот же слот в строке, что занимала кнопка ⬆, чтобы список не прыгал. */
.ghostBadge {
  flex: 0 0 auto;
  font-size: 12px;
  opacity: 0.8;
  line-height: 1;
}

/* This is the multiplayer player list. It is its own floating block under
     the "show UI" button. It works the same way whether or not the main
     settings panel is hidden. */
.playerList {
  /* The settings panel (.topbar) is up to 300px wide. It would overlap
       the list on the left. While it is open, we move the list to the
       right of it. When the panel is hidden (ui-hidden, see below), the
       list moves back under the "show UI" button. */
  position: fixed;
  z-index: 5;
  top: 44px;
  left: calc(min(300px, 85vw) + 16px);
  display: none;
  background: rgba(10, 10, 8, 0.5);
  border: 1px solid var(--line);
  color: var(--cream);
  font-family: inherit;
  font-size: calc(13px * var(--plScale, 1));
  letter-spacing: 0.02em;
  line-height: 1.5;
  padding: 8px 11px;
  border-radius: 6px;
  /* The leaderboard is never wider than half the screen. This is true even at --plScale 200%. */
  max-width: min(calc(220px * var(--plScale, 1)), 50vw);
  opacity: calc(var(--plOpacity, 70) / 100);
  user-select: none;
  -webkit-user-select: none;
}
.playerList.show {
  display: block;
}
body.ui-hidden .playerList {
  left: 8px;
}
.playerList .plTitle {
  opacity: 0.7;
  margin-bottom: 4px;
}
.playerList .plMe {
  color: var(--amber);
}

/* Touch controls show only on devices without a mouse, or with coarse
     finger input (phones and tablets). On desktop with a mouse, this
     stays display:none. */
.touchControls {
  position: fixed;
  inset: 0;
  z-index: 6;
  pointer-events: none;
  display: none;
}
@media (hover: none) and (pointer: coarse) {
  .touchControls {
    display: block;
  }
}
/* While the settings panel is open (.topbar, see its z-index:7 above), the
   joysticks and buttons must not catch taps. Without this rule, they
   would only look hidden under the panel. The touch zones are fixed
   across the whole screen (position:fixed, pointer-events:auto on
   .touchPad/.joyZone below). The panel is up to 300px wide, but the
   throttle joystick sits on the right. So in some places the panel does
   not cover the zones on screen. A tap there still reached the steering
   wheel or throttle through the open menu. The joysticks must also stay
   invisible through the panel. backdrop-filter:blur on .topbar blurs what
   is behind it. But it does not remove brightness or outlines. So the
   joystick still showed through the blur, even with an opaque panel
   background. */
body:not(.ui-hidden) .touchControls * {
  pointer-events: none;
  opacity: 0 !important;
}
/* This covers throttle, brake, and (in "buttons" mode) steering
     left/right. They share one size and opacity: --touchScale/
     --touchOpacity. This is separate from the joystick. */
.touchPad {
  position: fixed;
  display: flex;
  gap: calc(18px * var(--touchScale, 0.9));
  pointer-events: auto;
  opacity: calc(var(--touchOpacity, 20) / 100);
}
.joyZone,
.joyZoneV {
  display: none; /* setSteerMode/setThrottleMode show when mode=joystick */
}
.touchPad.left {
  left: 18px;
  bottom: calc(110px + var(--steerPadY, 0px));
}
.touchPad.right {
  right: 18px;
  bottom: calc(110px + var(--throttlePadY, 0px));
  flex-direction: column;
}
/* Drone controls (droneControlsOn in game.js): left pad grows from a 2-button
   row into a real 4-way dpad — forward/back stay hidden (.dpadOnly) and out of
   the flex layout until #steerButtons gets .dpad, at which point the 4 buttons
   are laid out as a diamond (each on its own N/E/S/W point around a shared
   center), not the plain left/right flex row. */
/* .touchBtn.dpadOnly (two classes), not .dpadOnly alone: .touchBtn itself
   sets display:flex further down this file, and a same-specificity rule
   earlier in source order loses that cascade tie regardless of what it says */
.touchBtn.dpadOnly {
  display: none;
}
/* Absolute positioning, not CSS grid: a first attempt used a 3x3 grid with a
   narrow (14px) center track to pull the 4 buttons close together — but each
   button keeps its own explicit 88px width/height (needed outside dpad mode
   too), and a grid item wider than its own track doesn't get clipped to it,
   it overflows unpredictably into whatever is next door. droneFwd/droneBack
   (both assigned the narrow center COLUMN) visually bled sideways into the
   right button's column. Percentage top/left off a sized, position:relative
   container sidesteps that: each button just sits at a fixed pixel offset
   from a shared center point, independent of any track width. */
.touchPad.left.dpad {
  display: block;
  position: fixed;
  width: calc(216px * var(--touchScale, 0.9));
  height: calc(216px * var(--touchScale, 0.9));
  bottom: calc(60px + var(--steerPadY, 0px)); /* ниже, чем обычный .touchPad.left (110px) — просили опустить сам дипад */
}
.touchPad.left.dpad .touchBtn {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
.touchPad.left.dpad .touchBtn.dpadOnly {
  display: flex;
}
/* половина расстояния между противоположными кнопками (64px*scale) выбрана
   так, что по диагонали (напр. droneFwd-right, 90° друг от друга) центры
   расходятся на 64*scale*√2 ≈ 90.5*scale — чуть больше диаметра кнопки
   (88*scale), кружки почти соприкасаются, но не наезжают друг на друга */
.touchPad.left.dpad [data-key="droneFwd"] {
  top: calc(50% - 64px * var(--touchScale, 0.9));
}
.touchPad.left.dpad [data-key="droneBack"] {
  top: calc(50% + 64px * var(--touchScale, 0.9));
}
.touchPad.left.dpad [data-key="left"] {
  left: calc(50% - 64px * var(--touchScale, 0.9));
}
.touchPad.left.dpad [data-key="right"] {
  left: calc(50% + 64px * var(--touchScale, 0.9));
}
.touchBtn {
  width: calc(88px * var(--touchScale, 0.9));
  height: calc(88px * var(--touchScale, 0.9));
  border-radius: 50%;
  padding: 0;
  background: rgba(10, 10, 8, 0.55);
  border: 2px solid var(--line);
  color: var(--amber);
  font-size: calc(36px * var(--touchScale, 0.9));
  line-height: 1;
  font-family: inherit;
  display: flex;
  align-items: center;
  justify-content: center;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  cursor: pointer;
}
.touchBtn.active {
  background: rgba(232, 178, 61, 0.35);
  border-color: var(--amber);
}

/* This is the slow-motion button. It sits above the throttle button
     (.touchPad.right/.joyZoneV above; both use right:18px, with enough
     height for both at the default scale). It is not inside .touchPad. It
     has its own position and its own size/opacity: --slowmoScale/
     --slowmoOpacity. This is separate from the buttons and from both
     joysticks. The refuel button in maplibre/index.html no longer shares
     this zone. It now sits over the real gas station on the map (see
     CLAUDE-maplibre.md). */
.touchBtn.slowmoBtn {
  position: fixed;
  right: 18px;
  bottom: 320px;
  width: calc(88px * var(--slowmoScale, 1));
  height: calc(88px * var(--slowmoScale, 1));
  font-size: calc(30px * var(--slowmoScale, 1));
  opacity: calc(var(--slowmoOpacity, 100) / 100);
  pointer-events: auto;
}

/* This is the steering joystick zone. It is an alternative to the ◀▶
     buttons, chosen by a setting. It is one large zone. The finger can
     start anywhere inside it and drag left or right from that point. The
     finger does not need to aim at the small circle. JS computes the
     turning threshold and the handle's movement limit from the zone's
     real radius (see joyZone in the script). So there is no need to set
     those values here in advance. Size and opacity are its own:
     --joyScale/--joyOpacity. This is separate from the buttons. */
.joyZone {
  position: fixed;
  left: 18px;
  bottom: calc(90px + var(--steerPadY, 0px));
  width: calc(170px * var(--joyScale, 1));
  height: calc(170px * var(--joyScale, 1));
  border-radius: 50%;
  background: rgba(10, 10, 8, 0.35);
  border: 2px solid var(--line);
  opacity: calc(var(--joyOpacity, 100) / 100);
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  pointer-events: auto;
  cursor: pointer;
}
.joyHandle {
  position: absolute;
  left: 50%;
  top: 50%;
  width: calc(64px * var(--joyScale, 1));
  height: calc(64px * var(--joyScale, 1));
  border-radius: 50%;
  background: rgba(10, 10, 8, 0.55);
  border: 2px solid var(--line);
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.joyZone.active .joyHandle {
  background: rgba(232, 178, 61, 0.35);
  border-color: var(--amber);
}

/* This is the vertical throttle/brake joystick. It is an alternative to
     the ▲▼ buttons. The handle's vertical position sets the target speed
     directly (see throttleVal in the script). Up means a fraction of
     maximum forward speed. Down means a fraction of maximum reverse
     speed. The center means released pedals. Size and opacity are its
     own: --joyVScale/--joyVOpacity. This is separate from both the
     buttons and the steering joystick. */
.joyZoneV {
  position: fixed;
  right: 18px;
  bottom: calc(90px + var(--throttlePadY, 0px));
  width: calc(90px * var(--joyVScale, 1));
  height: calc(220px * var(--joyVScale, 1));
  border-radius: 999px;
  background: rgba(10, 10, 8, 0.35);
  border: 2px solid var(--line);
  opacity: calc(var(--joyVOpacity, 100) / 100);
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  pointer-events: auto;
  cursor: pointer;
}
.joyHandleV {
  position: absolute;
  left: 50%;
  top: 50%;
  width: calc(64px * var(--joyVScale, 1));
  height: calc(64px * var(--joyVScale, 1));
  border-radius: 50%;
  background: rgba(10, 10, 8, 0.55);
  border: 2px solid var(--line);
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.joyZoneV.active .joyHandleV {
  background: rgba(232, 178, 61, 0.35);
  border-color: var(--amber);
}
