* {
  box-sizing: border-box;
}

:root {
  /* Defaults for custom properties set inline by React (player theming,
     clock progress, per-building modal theme) so they resolve even before
     JS applies them. */
  --player-color: #ffc107;
  --elapsed-angle: 0deg;
  --modal-bg: transparent;
  --building-accent: #ffc107;
  --game-scale: 1;
  --game-scale-inverse: 1;

  /* Shared design tokens for the in-game HUD/map/modal system - keeps
     hover timing, radii and glass-panel look consistent everywhere
     instead of every component re-declaring its own values. */
  --glass-bg: rgba(0, 0, 0, 0.45);
  --hud-blur: blur(6px);
  --glass-blur: var(--hud-blur);
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 12px;
  --radius-pill: 999px;
  --modal-card-height: 55px; /* compact shop-card height, shared by every modal-grid card that opts into it */
  --transition-fast: 0.15s ease; /* hover micro-interactions: lift, zoom, tint, fade */
  --transition-med: 0.3s ease; /* state fills/reveals: career/wealth bar height, hunger icon */
  --transition-slow: 0.4s ease; /* clock hand sweep */
  --disabled-opacity: 0.45;
  --hover-lift: -2px;
  --pill-zoom: 1.08;
  --label-zoom: 1.06;
  --icon-zoom: 1.25;
  --press-scale: 0.95; /* click/tap feedback on buttons - the only thing telling you a click actually registered */

  /* The rendered game scene: a 16:9 box fitted to whichever viewport
     dimension is limiting (contain), centered with letterbox/pillarbox
     around it. This is exactly .map-wrap's box, so anything sized off
     --scene-w tracks the map the same way the cqw-based HUD does. Crucially
     it's correct in BOTH regimes: width-limited (portrait/narrow, scene
     fitted to width) and height-limited (wide/landscape, scene fitted to
     height and pillarboxed). */
  --scene-w: min(100vw, calc(100vh * 16 / 9));
  --scene-h: calc(var(--scene-w) * 9 / 16);

  /* Z-index tiers. Everything in-game shares ONE stacking context (.game-main
     has isolation:isolate), so these values are directly comparable. Each row
     below is a GROUP; ordering *within* a group is handled by that group's own
     local z-indexes inside its own stacking context (the map layers, the HUD
     layer, and each modal shell are all their own contexts). See
     docs/z-index.md for the full map. Only cross-group boundaries live here. */
  --z-map-bg: 0;         /* map background strip */
  --z-map: 1;            /* map placement layer: buildings, signs, ball, tooltips */
  --z-modal: 100;        /* shared gameplay modal backdrop */
  --z-modal-top: 110;    /* gameplay modal opened as `top` */
  --z-modal-confirm: 120;/* confirm prompt opened from inside another modal */
  --z-shield: 200;       /* spectator shield - one layer covering map + modals */
  --z-hud: 300;          /* HUD bar - always interactive, above the shield */
  --z-modal-local: 400;  /* setup/menu/music - spectator-usable, above the shield */

  /* HUD keep-out zones (see .modal-backdrop / .modal-shell): vertical space
     reserved so no modal - or its footer, which straddles the modal's bottom
     edge - ever covers the HUD. The modal's top border lines up with the
     player turn-badge (top-left); the bottom clears the clock + hud-strip.

     Expressed against the SCENE, not the raw viewport, so they stay correct
     when the scene is pillarboxed on a wide screen - there 7vw of the
     *viewport* was far wider than 7% of the actual scene, which crushed the
     modal's max-height. --modal-hud-inset is the letterbox gap above/below
     the scene (0 on an exact-16:9 desktop); the label/clock heights track the
     scene width (the HUD itself is sized in cqw = % of scene width). The
     base bottom reserve clears the clock's 6.84% rise with a visible gap;
     footer modals add room for their action bar's overhang below. */
  --modal-hud-inset: max(0px, calc((100vh - var(--scene-h)) / 2));
  --modal-safe-top: calc(var(--modal-hud-inset) + var(--scene-w) * 0.07);
  --modal-safe-bottom: calc(var(--modal-hud-inset) + var(--scene-w) * 0.08);
}

html, body {
  height: 100%;
  margin: 0;
  overflow: hidden;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background: #14181d;
  color: #e9ecef;
}

.hud-top-actions { display: flex; align-items: center; }
.music-hud-button { font-size: 17px; }
.music-player {
  --music-controls-width: 154px;
  --music-playlist-row-height: 32px;
  width: 100%;
  max-width: 440px;
  height: auto;
  max-height: 520px;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding-bottom: 10px;
}
.modal:has(.music-player) .modal-body { min-height: 0; overflow: hidden; }
.music-player-error { margin: 0; padding: 9px 12px; border-radius: 7px; background: rgba(168, 50, 50, 0.28); color: #ffd8d8; }
.music-now-playing {
  min-width: 0;
  display: grid;
  grid-template-columns: 92px minmax(0, 1fr);
  align-items: center;
  gap: 13px;
  padding: 10px;
  border: 1px solid rgba(139, 92, 246, 0.3);
  border-radius: 12px;
  background: linear-gradient(135deg, rgba(139, 92, 246, 0.18), rgba(255, 255, 255, 0.035));
}
.music-thumbnail { width: 92px; height: 92px; display: grid; place-items: center; overflow: hidden; border-radius: 9px; background: rgba(139, 92, 246, 0.2); box-shadow: 0 5px 18px rgba(0, 0, 0, 0.28); font-size: 34px; }
.music-thumbnail img { width: 100%; height: 100%; object-fit: cover; }
.music-now-content { min-width: 0; align-self: stretch; display: flex; flex-direction: column; gap: 5px; }
.music-track-info { min-width: 0; display: grid; gap: 5px; }
.music-track-info strong { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 17px; }
.music-track-info span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: rgba(233, 236, 239, 0.68); font-size: 13px; }
.music-controls { width: var(--music-controls-width); margin-left: auto; display: flex; justify-content: flex-end; align-items: flex-end; gap: 6px; }
.music-controls button { width: 34px; height: 29px; padding: 0; border: 1px solid rgba(255,255,255,.16); border-radius: 7px; background: rgba(255,255,255,.08); color: #fff; cursor: pointer; transition: background var(--transition-fast), transform var(--transition-fast); }
.music-controls button:hover:not(:disabled) { background: rgba(139, 92, 246, .32); transform: translateY(-1px); }
.music-controls button.is-active { background: rgba(255,255,255,.08); border-color: rgba(255,255,255,.16); color: #c4b5fd; box-shadow: inset 0 -2px #a78bfa; }
.music-shuffle-button { font-family: 'Segoe UI Symbol', sans-serif; }
.music-controls button:active:not(:disabled) { transform: scale(var(--press-scale)); }
.music-controls button:disabled { opacity: .4; cursor: default; }
.music-volume { width: var(--music-controls-width); min-width: 0; margin-top: auto; margin-left: auto; display: flex; align-items: center; gap: 7px; font-size: 12px; }
.music-volume input { flex: 1; accent-color: #8b5cf6; }
.music-mute-button { flex: none; width: 18px; height: 18px; padding: 0; border: 0; background: transparent; color: inherit; font-size: 13px; line-height: 1; cursor: pointer; transition: transform var(--transition-fast); }
.music-mute-button:hover, .music-mute-button:focus-visible { transform: scale(var(--icon-zoom)); }
.music-progress { grid-column: 1 / -1; min-width: 0; display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 7px; color: rgba(233, 236, 239, 0.62); font-size: 10px; }
.music-progress input { width: 100%; min-width: 0; margin: 0; accent-color: #8b5cf6; cursor: pointer; }
.music-progress input:disabled { cursor: default; opacity: 0.45; }
.music-volume input,
.music-progress input {
  caret-color: transparent;
  -webkit-user-select: none;
  user-select: none;
}
.music-playlist { width: 100%; height: calc(var(--music-playlist-row-height) * 4.5); min-height: 0; flex: none; overflow-y: auto; margin: 0; padding: 0; list-style: none; border: 1px solid rgba(255,255,255,.1); border-radius: 9px; background: rgba(0, 0, 0, 0.12); }
.music-playlist li { min-width: 0; }
.music-playlist button { width: 100%; height: var(--music-playlist-row-height); min-width: 0; display: grid; grid-template-columns: 28px minmax(0, 1fr); gap: 7px; align-items: center; padding: 5px 9px; border: 0; border-bottom: 1px solid rgba(255,255,255,.08); background: transparent; color: #e9ecef; text-align: left; cursor: pointer; }
.music-playlist li:last-child button { border-bottom: 0; }
.music-playlist-info { min-width: 0; display: flex; align-items: baseline; gap: 6px; }
.music-playlist strong, .music-playlist small { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.music-playlist strong { flex: 0 1 auto; }
.music-playlist small { flex: 0 1 auto; margin-left: auto; text-align: right; }
.music-playlist button:hover { background: rgba(139, 92, 246, .16); }
.music-playlist button.is-current { background: rgba(139, 92, 246, .32); box-shadow: inset 3px 0 #a78bfa; }
.music-playlist button.is-current strong { color: #fff; }
.music-playlist small { color: rgba(233,236,239,.6); }

/* Music + SFX sliders shown in the in-game menus (see AudioControls). */
.audio-settings {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 14px 0 4px;
  padding: 12px 14px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: var(--radius-md);
  background: rgba(255, 255, 255, 0.04);
}

.audio-settings-title {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(233, 236, 239, 0.5);
}

.audio-setting-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.audio-setting-mute {
  flex: none;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  transition: transform var(--transition-fast);
}

.audio-setting-mute:hover,
.audio-setting-mute:focus-visible {
  transform: scale(var(--icon-zoom));
}

.audio-setting-label {
  flex: none;
  width: 96px;
  font-size: 13px;
  color: rgba(233, 236, 239, 0.85);
}

.audio-setting-row input[type="range"] {
  flex: 1;
  min-width: 0;
  accent-color: #4fd888;
  caret-color: transparent;
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
}

@media (max-width: 480px) {
  .music-player { --music-controls-width: 142px; max-width: 360px; gap: 10px; }
  .music-now-playing { grid-template-columns: 78px minmax(0, 1fr); padding: 8px; }
  .music-thumbnail { width: 78px; height: 78px; }
  .music-controls button { width: 31px; }
  .music-playlist button { grid-template-columns: 24px minmax(0, 1fr); }
  .music-playlist small { display: none; }
}

/* App-wide: this is a game UI, not a document, so nothing is text-selectable.
   A side effect we want: with selection off the browser never shows the text
   I-beam over plain text - you get the default arrow, and interactive
   elements still opt into their own `cursor: pointer`. Text inputs are the
   sole exception, where a caret and selection genuinely make sense. This
   replaces the per-element user-select:none workarounds we used to sprinkle
   around (modal icon, HUD tokens, map signs...). */
*,
*::before,
*::after {
  -webkit-user-select: none;
  user-select: none;
}

input,
textarea {
  -webkit-user-select: text;
  user-select: text;
}

#root {
  height: 100%;
}

/* Registering --player-color as a real <color> lets the browser smoothly
   interpolate it (and anything derived from it below) whenever a turn
   handoff changes the value, instead of snapping instantly. */
@property --player-color {
  syntax: '<color>';
  inherits: true;
  initial-value: #ffc107;
}

.container {
  position: relative;
  height: 100%;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: #0a0d12;
  transition: --player-color 1.4s ease;
}

/* Ambient page backdrop: soft, slow-drifting blobs tinted with the current
   player's color, sitting in the dead space around the game board. Kept
   delicate on purpose - this is atmosphere, not a focal point. */
.container::before,
.container::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.container::before {
  background:
    radial-gradient(ellipse 50% 42% at 12% 15%, color-mix(in srgb, var(--player-color) 30%, transparent), transparent 70%),
    radial-gradient(ellipse 46% 50% at 90% 88%, color-mix(in srgb, var(--player-color) 22%, transparent), transparent 72%),
    radial-gradient(ellipse 36% 36% at 88% 6%, color-mix(in srgb, var(--player-color) 14%, transparent), transparent 75%);
  filter: blur(40px);
  animation: ambient-drift 26s ease-in-out infinite alternate, ambient-breathe 7s ease-in-out infinite;
}

.container::after {
  background: radial-gradient(ellipse 85% 85% at 50% 50%, transparent 45%, rgba(0, 0, 0, 0.45) 100%);
}

@keyframes ambient-drift {
  0% { transform: translate(0, 0) scale(1); }
  100% { transform: translate(3%, -3%) scale(1.08); }
}

@keyframes ambient-breathe {
  0%, 100% { opacity: 0.65; }
  50% { opacity: 0.9; }
}

hr {
  border: none;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  margin: 12px 0;
}

/* buttons */

.btn {
  padding: 6px 14px;
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  margin: 2px 0;
  transition: transform var(--transition-fast);
}

.btn:active:not(:disabled) {
  transform: scale(var(--press-scale));
}

.btn-primary {
  background: #2c6e49;
  color: #fff;
}

.btn-primary:disabled {
  opacity: var(--disabled-opacity);
  cursor: default;
}

.btn-danger {
  background: #a83232;
  color: #fff;
}

.btn-danger:disabled {
  opacity: var(--disabled-opacity);
  cursor: default;
}

.btn-secondary {
  background: rgba(255, 255, 255, 0.08);
  color: #e9ecef;
  border: 1px solid rgba(255, 255, 255, 0.16);
}

.btn-secondary:hover {
  background: rgba(255, 255, 255, 0.14);
}

/* confirmation dialogs (e.g. quit-game) - a compact message plus a
   cancel/confirm pair, reusing Modal with the `confirm` tier so it stacks
   above an already-open Intro/Game Menu modal */

.confirm-message {
  margin: 0 0 18px;
  font-size: 14px;
  line-height: 1.5;
  color: rgba(233, 236, 239, 0.85);
}

/* SpendConfirmModal's price line - reuses .confirm-message's spacing rhythm
   but bigger/bolder, since the cost is the one number that actually needs
   a second look before confirming. */
.spend-confirm-price {
  margin: 0 0 18px;
  font-size: 20px;
  font-weight: 700;
}

/* SpendConfirmModal quantity selector - a segmented radio control (e.g. the
   lottery's x1 / x10 / x100). Sits between the description and the price line,
   which repaints as the pick changes. */
.quantity-select {
  display: flex;
  gap: 8px;
  margin: 0 0 18px;
}

.quantity-option {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 10px;
  border-radius: 6px;
  border: 1px solid rgba(233, 236, 239, 0.2);
  background: rgba(233, 236, 239, 0.04);
  color: rgba(233, 236, 239, 0.75);
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}

.quantity-option:hover {
  border-color: rgba(233, 236, 239, 0.4);
  color: #e9ecef;
}

.quantity-option.is-selected {
  border-color: #2c6e49;
  background: rgba(44, 110, 73, 0.25);
  color: #e9ecef;
}

/* the native radio is only there for semantics/keyboard - the label is the
   visible control */
.quantity-option input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.confirm-actions {
  display: flex;
  gap: 8px;
}

.confirm-actions .btn {
  flex: 1;
  padding: 10px;
}

/* alerts */

.alert {
  position: relative;
  z-index: 1;
  padding: 10px 14px;
  border-radius: 4px;
  margin: 10px 0;
  display: flex;
  align-items: center;
  gap: 10px;
}

.alert-warning {
  background: #fff3cd;
  color: #664d03;
  border: 1px solid #ffe69c;
}

/* layout */

/* Fills the viewport edge-to-edge on 16:9 and wider displays (21:9, 32:9
   ultrawide). A dedicated aspect-ratio query below handles taller/narrower
   viewports by fitting a complete 16:9 scene to their width, rather than
   letting the scene fill the viewport height and crop its sides. */
.game-main {
  position: relative;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  box-shadow: 0 0 18px -6px var(--player-color, transparent);
  transition: box-shadow var(--transition-fast);
  /* The single in-game stacking context. Modals portal in here (not to
     <body>), the spectator shield is a direct child, and the map/HUD layers
     stack in here too - so every --z-* tier above is comparable in one place.
     .map-wrap deliberately is NOT a stacking context (it dropped
     container-type) so its map + HUD layers participate directly at this
     level, letting the HUD sit above modals while the map sits below them. */
  isolation: isolate;
}

@supports (height: 100dvh) {
  .game-main {
    height: 100dvh;
  }
}

/* 4:3, 3:2, and every other viewport narrower than 16:9: zoom the whole
   scene out as one complete 16:9 canvas. .container centers the resulting
   shorter box vertically, leaving the surrounding ambient background as
   letterbox space instead of cropping any map or HUD content. */
@media (max-aspect-ratio: 16 / 9) {
  .game-main {
    width: 100vw;
    height: auto;
    aspect-ratio: 16 / 9;
    flex: none;
  }
}

/* name entry */

/* The fixed-size 16:9 stage the login/lobby is authored on. Centred in the
   game frame and scaled to fit by --login-scale (set from JS), so the whole
   scene zooms out to fit in portrait - the same treatment as the game view. */
/* No active player on the login/lobby, so drop .game-main's player-colour glow
   (it would fall back to the default yellow and read as bars around the scene). */
.login-frame {
  box-shadow: none;
}

.login-stage {
  /* declared here (rather than only via the inline style in NameEntry.tsx)
     so static CSS analysis can resolve the property - the real value is set
     from JS and overrides this fallback. */
  --login-scale: 1;
  position: absolute;
  top: 50%;
  left: 50%;
  width: 1280px;
  height: 720px;
  transform: translate(-50%, -50%) scale(var(--login-scale));
  transform-origin: center center;
  overflow: hidden;
}

.name-entry-page {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  display: flex;
  align-items: safe center;
  justify-content: safe center;
  padding: 24px;
  isolation: isolate;
}

/* Full-window map backdrop: covers the whole viewport (behind the scene AND
   its letterbox) so the ambient art always fills the screen, not just the
   16:9 stage. Sits above .container's plain fill and below .game-main. */
.login-backdrop {
  /* declared here (rather than only via the inline style on .container in
     NameEntry.tsx) so static CSS analysis can resolve the property - the real
     value comes from that inline style and inherits down to ::before below. */
  --login-map-image: none;
  --login-map-position: center;
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

/* the actual city map (see NameEntry.tsx's --login-map-image and
   --login-map-position - a random crop picked once per page load), blurred
   and slowly panning/zooming - a moody, on-theme backdrop instead of a
   generic gradient. Oversized (inset: -6%) so the pan never reveals a hard
   edge, and cover-sized so it always fills the window at any aspect. */
.login-backdrop::before {
  content: '';
  position: absolute;
  inset: -6%;
  background: var(--login-map-image) var(--login-map-position)/cover no-repeat;
  filter: blur(3px) saturate(0.7) brightness(0.42);
  animation: name-entry-pan 50s ease-in-out infinite alternate;
}

.login-backdrop::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle at 20% 15%, rgba(44, 110, 73, 0.4), transparent 45%),
    radial-gradient(circle at 85% 85%, rgba(87, 63, 168, 0.35), transparent 50%),
    linear-gradient(180deg, rgba(20, 24, 29, 0.55), rgba(20, 24, 29, 0.85));
}

@keyframes name-entry-pan {
  0% { transform: scale(1.06) translate(0, 0); }
  100% { transform: scale(1.14) translate(-2.5%, -2%); }
}

@media (prefers-reduced-motion: reduce) {
  .login-backdrop::before {
    animation: none;
  }
}

/* the animated ring is a classic two-layer background trick: a solid fill
   clipped to the padding-box (so it doesn't show past the border) stacked
   under a conic-gradient clipped to the border-box (so only the 1px border
   ring shows it) - cycling through the same accent colors the buildings
   use on the map, tying this screen to the game's own palette. Angle is a
   registered <angle> custom property (same technique as --player-color
   above) so the browser can animate it smoothly instead of snapping. */
@property --name-card-ring-angle {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}

.name-entry-card {
  position: relative;
  width: 100%;
  max-width: 400px;
  /* safety net: on the rare design that runs taller than the 720px stage the
     card scrolls internally rather than overflowing the scene. */
  max-height: 100%;
  overflow-y: auto;
  border: 1px solid transparent;
  border-radius: 16px;
  padding: 28px 26px;
  background:
    linear-gradient(rgba(20, 24, 29, 0.82), rgba(20, 24, 29, 0.82)) padding-box,
    conic-gradient(
      from var(--name-card-ring-angle),
      #d4af37, #e0522a, #3f9142, #2e86ab, #8a2e3b, #5b3a8a, #00c2e0, #c8ced6, #d4af37
    ) border-box;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.55);
  animation: name-card-ring-spin 16s linear infinite;
}

/* The post-login game browser needs more room than the credential form so
   status groups and their cards read as a small dashboard, not a select list.
   Flex column + clipped so the game list scrolls inside its own panel (bounded
   by the card's max-height) instead of growing the card past the scene. */
.game-setup-card {
  max-width: 560px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

@keyframes name-card-ring-spin {
  to { --name-card-ring-angle: 360deg; }
}

@media (prefers-reduced-motion: reduce) {
  .name-entry-card {
    animation: none;
  }
}

.name-entry-brand {
  text-align: center;
  margin-bottom: 22px;
}

.name-entry-logo {
  display: block;
  width: 54px;
  height: 62px;
  object-fit: contain;
  margin: -6px -3px -5px 0;
  filter:
    drop-shadow(0 0 4px rgba(224, 184, 92, 0.3))
    drop-shadow(0 0 11px rgba(48, 82, 139, 0.2))
    drop-shadow(0 8px 16px rgba(0, 0, 0, 0.42));
}

.name-entry-wordmark {
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
}

.name-entry-wordmark span {
  font-size: 32px;
  font-weight: 800;
  letter-spacing: 1px;
  background: linear-gradient(135deg, #ffc107, #4fd888 55%, #63a4ff);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.name-entry-tagline {
  margin: 4px 0 0;
  font-size: 13px;
  color: rgba(233, 236, 239, 0.65);
}

.name-entry-card form {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.name-entry-card .field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 13px;
  color: rgba(233, 236, 239, 0.75);
}

.name-entry-card input {
  padding: 10px 12px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.3);
  color: #e9ecef;
  font-size: 14px;
}

.name-entry-card input:focus {
  outline: none;
  border-color: #4fd888;
}

.name-locked {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 12px;
  margin-bottom: 16px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.3);
}

.name-locked-value {
  font-size: 14px;
  font-weight: 600;
  color: #e9ecef;
}

.login-error {
  margin: -6px 0 0;
  font-size: 13px;
  color: #ff8080;
}

.name-edit-btn {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 15px;
  padding: 2px 6px;
  border-radius: 6px;
  line-height: 1;
  opacity: 0.75;
  transition: opacity var(--transition-fast), background-color var(--transition-fast);
}

.name-edit-btn:hover {
  opacity: 1;
  background: rgba(255, 255, 255, 0.1);
}

/* the game list dims (but doesn't hide) once a code is typed - it's still
   there for reference, just not the thing that's about to happen. */
.running-games {
  transition: opacity var(--transition-fast);
}

.running-games-disabled {
  opacity: var(--disabled-opacity);
  pointer-events: none;
}

/* the grouped list scrolls as one, so a long "Other games" section can't
   push the footer actions off-screen. Individual sections/uls don't scroll. */
.running-games-list {
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-height: min(46vh, 430px);
  overflow-y: auto;
  padding-right: 4px;
}

.running-games-section {
  padding: 12px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 12px;
  background: rgba(0, 0, 0, 0.18);
}

.running-games-section-title {
  margin: 0 0 10px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(233, 236, 239, 0.5);
}

.running-games-section ul {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 10px;
}

.running-games-empty {
  font-size: 13px;
  color: rgba(233, 236, 239, 0.55);
  margin: 4px 0;
}

.empty-game-card {
  min-height: 74px;
  display: grid;
  grid-template-columns: 38px minmax(0, 1fr);
  align-items: center;
  gap: 10px;
  padding: 14px;
  border: 1px dashed rgba(255, 255, 255, 0.18);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.025);
  color: rgba(233, 236, 239, 0.48);
}

.empty-game-icon {
  font-size: 30px;
  filter: grayscale(1);
  opacity: 0.45;
}

.empty-game-card strong,
.empty-game-card small { display: block; }

.empty-game-card strong {
  margin-bottom: 4px;
  color: rgba(233, 236, 239, 0.68);
}

.empty-game-card small { font-size: 11px; }

.running-game-card {
  position: relative;
  overflow: hidden;
  height: 100%;
  width: 100%;
  display: grid;
  grid-template-columns: 38px minmax(0, 1fr);
  align-items: center;
  gap: 10px;
  padding: 14px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.05);
  color: #e9ecef;
  cursor: pointer;
  text-align: left;
  transition: background-color var(--transition-fast), border-color var(--transition-fast),
    transform var(--transition-fast), box-shadow var(--transition-fast);
}

.running-game-card:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.1);
  border-color: #4fd888;
  transform: translateY(var(--hover-lift));
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.35);
}

.running-game-card:disabled {
  opacity: var(--disabled-opacity);
  cursor: not-allowed;
}

.running-game-players {
  display: block;
  font-size: 14px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.running-game-icon {
  align-self: stretch;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 30px;
  line-height: 1;
}

.running-game-content {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding-top: 8px;
}

.running-game-meta {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 8px;
  font-size: 11px;
  color: rgba(233, 236, 239, 0.6);
}

.running-game-status {
  position: absolute;
  top: 0;
  right: 0;
  padding: 4px 7px;
  border-bottom-left-radius: 8px;
  background: rgba(33, 38, 45, 0.94);
  box-shadow: -2px 2px 8px rgba(0, 0, 0, 0.25);
  font-size: 10px;
  font-weight: 600;
  color: rgba(233, 236, 239, 0.8);
}

.running-game-status-rejoin {
  color: #8ab8ff;
  background: rgba(35, 65, 105, 0.96);
}

.running-game-status-open {
  color: #4fd888;
  text-shadow: 0 0 0 rgba(79, 216, 136, 0.7);
  animation: running-game-live-pulse 1.8s ease-in-out infinite;
}

@keyframes running-game-live-pulse {
  50% {
    text-shadow: 0 0 6px rgba(79, 216, 136, 0.7);
  }
}

@media (prefers-reduced-motion: reduce) {
  .running-game-status-open {
    animation: none;
  }
}

/* explicit actions grouped below the game list, set off by a top rule so
   "pick a running game" reads as separate from "or start / join by code". */
.name-entry-actions {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 4px;
  padding-top: 16px;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}

/* Login creation toggles: one option per row, with a shared label column
   and a fixed-width switch column. */
.name-entry-actions > .game-visibility-toggle {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 46px;
  align-items: center;
  justify-content: stretch;
  gap: 12px;
  text-align: left;
}

.name-entry-code-field {
  margin-top: 2px;
}

.name-entry-submit {
  width: 100%;
  padding: 12px;
  font-size: 15px;
  font-weight: 700;
  border-radius: 8px;
  background: linear-gradient(135deg, #2c6e49, #1f8a55);
}

/* clock */

.clock-wrap {
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  z-index: 21;
  display: flex;
  align-items: center;
  gap: 8px;
  width: 51px;
  height: 42px;
  padding: 0 13px 0 8px;
  color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-bottom: 0;
  border-radius: 15px 15px 0 0;
  background: var(--glass-bg);
  box-shadow: 0 -5px 18px rgba(0, 0, 0, 0.22), inset 0 1px rgba(255, 255, 255, 0.08);
  backdrop-filter: var(--hud-blur);
  -webkit-backdrop-filter: var(--hud-blur);
  transition: box-shadow var(--transition-fast), transform var(--transition-fast);
}

/* Map.tsx owns the clock. cqw keeps its dimensions tied to the map itself,
   matching the status posts and player vehicle on mobile. */
.map-hud-layer > .clock-wrap {
  bottom: 0;
  width: 7.49cqw;
  height: 6.84cqw;
  padding: 0 0.61cqw;
  border-radius: 3.42cqw 3.42cqw 0 0;
}

.map-wrap .clock-face {
  width: 6.05cqw;
  height: 6.05cqw;
  border-width: 0.11cqw;
}

.map-wrap .clock-face::before {
  inset: 0.33cqw;
}

.map-wrap .clock-hand {
  width: 0.28cqw;
  height: 2.01cqw;
  margin-left: -0.14cqw;
}

.map-wrap .clock-pin {
  width: 0.67cqw;
  height: 0.67cqw;
}

.map-wrap .day-badge {
  width: 2.11cqw;
  height: 1.78cqw;
  border-radius: 0.33cqw;
  border-top-width: 0.33cqw;
}

.map-wrap .day-badge-rings::before,
.map-wrap .day-badge-rings::after {
  top: -0.5cqw;
  width: 0.22cqw;
  height: 0.44cqw;
  border-radius: 0.22cqw;
}

.map-wrap .day-badge-rings::before {
  left: 0.56cqw;
}

.map-wrap .day-badge-rings::after {
  right: 0.56cqw;
}

.map-wrap .day-badge-number {
  font-size: 1cqw;
}

.map-wrap .hud-pill {
  gap: 0.67cqw;
  padding: 0.44cqw 1.11cqw;
  border-width: 0.11cqw;
  border-radius: 0.67cqw;
  font-size: 1.89cqw;
}

/* Work-experience progress spans the compact job module's bottom edge. */
.job-badge-progress-track {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 3px;
  background: rgba(255, 255, 255, 0.35);
}

.job-badge-progress-fill {
  display: block;
  height: 100%;
  background: var(--player-color, #4fa3ff);
  transition: width var(--transition-med);
}

.hud-strip .hud-pill.job-hud-module {
  display: inline-flex;
  align-items: center;
  height: 24px;
  padding: 0 7px;
  gap: 7px;
  overflow: visible;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.07);
  box-shadow: inset 0 1px rgba(255, 255, 255, 0.08), 0 2px 6px rgba(0, 0, 0, 0.16);
}

.job-hud-role,
.job-hud-salary {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
}

.job-hud-role {
  gap: 4px;
}

.job-hud-title {
  max-width: 115px;
  overflow: hidden;
  text-overflow: ellipsis;
}

.job-hud-salary {
  gap: 1px;
  color: #dff7e9;
  font-variant-numeric: tabular-nums;
}

.job-hud-salary small {
  color: rgba(255, 255, 255, 0.55);
  font-size: 0.72em;
  font-weight: 700;
}

.hud-strip .job-hud-module .job-badge-progress-track {
  height: 2px;
  border-radius: 0 0 6px 6px;
  overflow: hidden;
}

.map-wrap .hud-strip .job-hud-module {
  height: 2.67cqw;
  padding: 0 0.78cqw;
  gap: 0.78cqw;
  border-width: 0.11cqw;
  border-radius: 0.61cqw;
  font-size: 1.11cqw;
}

.map-wrap .job-hud-role {
  gap: 0.44cqw;
}

.map-wrap .job-hud-title {
  max-width: 12.78cqw;
}

.map-wrap .job-hud-salary {
  gap: 0.11cqw;
}

.map-wrap .hud-strip .job-hud-module .job-badge-progress-track {
  height: 0.22cqw;
}

.map-wrap .hud-corner {
  gap: 0.89cqw;
}

.map-wrap .hud-top-right {
  top: 0;
  right: 0;
  gap: 0;
}

.map-wrap .hud-strip {
  grid-template-columns: minmax(0, 1fr) 7.49cqw minmax(0, 1fr);
  padding: 0;
  gap: 0;
}

.map-wrap .hud-strip::before {
  border-radius: 0;
}

.map-wrap .hud-strip-group {
  gap: 0.5cqw;
}

.map-wrap .menu-btn {
  width: 6.2cqw;
  height: 6.2cqw;
  padding: 0;
  border-radius: 0;
}

.map-wrap .menu-btn-icon {
  top: 0.9cqw;
  right: 0.9cqw;
  font-size: 1.7cqw;
}

.map-wrap .hunger-icon {
  font-size: 1.89cqw;
}

.map-wrap .fridge-inventory-badge {
  gap: 0.44cqw;
}

.map-wrap .icon-tooltip {
  bottom: calc(100% + 0.67cqw);
  padding: 0.33cqw 0.78cqw;
  border-radius: 0.44cqw;
  font-size: 1.22cqw;
}

.map-wrap .player-stack {
  top: 0;
  left: 0;
  gap: 0.89cqw;
}

.map-wrap .turn-badge {
  gap: 1.11cqw;
  padding: 0.67cqw 1.56cqw 0.89cqw 0.67cqw;
  border-width: 0.11cqw;
  box-shadow: 0 0.22cqw 0.89cqw rgba(0, 0, 0, 0.4);
}

.map-wrap .turn-badge.inactive {
  padding: 0.67cqw 0.89cqw 0.89cqw 0.67cqw;
}

.map-wrap .turn-badge.active {
  gap: 0.78cqw;
  padding: 0.56cqw 2cqw 0.67cqw 0.56cqw;
}

.map-wrap .turn-badge-avatar {
  width: 2.78cqw;
  height: 2.78cqw;
  border-width: 0.17cqw;
  font-size: 1.22cqw;
}

.map-wrap .turn-badge.you .turn-badge-avatar {
  box-shadow: 0 0 0 0.22cqw rgba(255, 193, 7, 0.5);
}

.map-wrap .turn-badge-sub {
  bottom: -0.44cqw;
  min-width: 1.44cqw;
  height: 1.11cqw;
  padding: 0 0.17cqw;
  border-width: 0.11cqw;
  border-radius: 0.28cqw;
  font-size: 0.78cqw;
}

.map-wrap .turn-badge-progress {
  right: -0.33cqw;
  left: auto;
}

.map-wrap .turn-badge-rank {
  top: -0.44cqw;
  right: auto;
  bottom: auto;
  left: -0.33cqw;
}

.map-wrap .turn-badge-name {
  font-size: 1.22cqw;
  letter-spacing: 0.03cqw;
}

.map-wrap .turn-badge-you {
  margin-left: 0.67cqw;
  padding: 0.11cqw 0.67cqw;
  border-radius: 0.89cqw;
  font-size: 1.11cqw;
}

.clock-face {
  position: relative;
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: conic-gradient(var(--player-color, #ffc107) 0deg var(--elapsed-angle, 0deg), rgba(255, 255, 255, 0.13) var(--elapsed-angle, 0deg) 360deg);
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow: 0 0 12px color-mix(in srgb, var(--player-color, #ffc107) 22%, transparent);
}

.clock-face::before {
  content: '';
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  background: #1b1e23;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.65);
}

.clock-wrap:hover {
  transform: translateX(-50%) translateY(-2px);
  box-shadow: 0 -7px 22px rgba(0, 0, 0, 0.3), 0 0 16px -7px var(--player-color, transparent);
}

.clock-hand {
  position: absolute;
  bottom: 50%;
  left: 50%;
  width: 4px;
  height: 9px;
  margin-left: -2px;
  border-radius: 4px 4px 1px 1px;
  background: #fff;
  box-shadow: 0 0 3px rgba(255, 255, 255, 0.45);
  transform-origin: bottom center;
  transition: transform var(--transition-slow);
  z-index: 1;
}

.clock-pin {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 2;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #fff;
  transform: translate(-50%, -50%);
}

.day-badge {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 19px;
  height: 16px;
  border: 1px solid color-mix(in srgb, var(--player-color, #ffc107) 55%, rgba(255, 255, 255, 0.2));
  border-top: 3px solid var(--player-color, #ffc107);
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.09);
  color: #fff;
  cursor: default;
  box-shadow: inset 0 1px rgba(255, 255, 255, 0.12), 0 2px 6px rgba(0, 0, 0, 0.25);
}

.day-badge-rings::before,
.day-badge-rings::after {
  content: '';
  position: absolute;
  top: -5px;
  width: 2px;
  height: 5px;
  border-radius: 2px;
  background: #e9edf2;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

.day-badge-rings::before {
  left: 5px;
}

.day-badge-rings::after {
  right: 5px;
}

.day-badge-number {
  position: absolute;
  inset: 3px 0 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 9px;
  font-weight: 900;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.map-wrap .day-badge-number {
  inset: 0.33cqw 0 0;
}

.hunger-icon {
  display: inline-block;
  font-size: 17px;
  line-height: 1;
  opacity: 0.25;
  filter: grayscale(100%);
  transition: opacity var(--transition-med), filter var(--transition-med), transform var(--transition-fast);
}

.hunger-icon.filled {
  opacity: 1;
  filter: none;
}

.hud-pill:hover .hunger-icon,
.hud-pill:focus .hunger-icon {
  transform: scale(var(--icon-zoom));
}

.fridge-inventory-badge .pill-icon {
  font-size: 17px;
}

.map-wrap .fridge-inventory-badge .pill-icon {
  font-size: 1.89cqw;
}

/* the single icon inside a hud-pill that has its own hover-tooltip (mood
   face, lottery ticket count...) - zooms in when the pill is hovered */
.pill-icon {
  display: inline-block;
  transition: transform var(--transition-fast);
}

.hud-pill:hover .pill-icon,
.hud-pill:focus .pill-icon {
  transform: scale(var(--icon-zoom));
}

/* shared tooltip bubble - used by any hoverable/focusable icon (inventory,
   happiness) that needs a name/label popover above it */
.icon-tooltip {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  font-size: 11px;
  font-weight: 600;
  color: #fff;
  background: rgba(0, 0, 0, 0.85);
  padding: 3px 7px;
  border-radius: var(--radius-sm);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition-fast);
}

.hud-pill:hover > .icon-tooltip,
.hud-pill:focus > .icon-tooltip {
  opacity: 1;
}

/* Edge and clock-adjacent badges open their labels inward. Centering every
   tooltip makes the calendar spill past the left gameplay border and makes
   the split job labels collide with the raised clock. */
.hud-strip-group:first-child > .hud-pill:first-child > .icon-tooltip,
.job-hud-salary-module > .icon-tooltip {
  left: 0;
  transform: none;
}

.job-hud-role-module > .icon-tooltip {
  right: 0;
  left: auto;
  transform: none;
}

/* Cash is the bottom-right edge item, so its label opens inward instead of
   overflowing past the map border. */
.hud-strip-group:last-child > .hud-pill:last-child > .icon-tooltip {
  right: 0;
  left: auto;
  transform: none;
}

.hud-corner {
  position: absolute;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 8px;
}

/* the bottom HUD (hunger/fridge/lottery/happiness/career/cash) lives in one
   edge-to-edge strip rather than two floating pill clusters. */
.hud-strip {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 51px minmax(0, 1fr);
  align-items: stretch;
  justify-content: space-between;
  gap: 0;
  padding: 0;
}

/* The strip has no shared painted surface: its left/right segments sit above
   the clock so their tooltips remain visible, while the center segment sits
   below the clock. The three identical surfaces still read as one tray. */
.hud-strip::before {
  content: none;
}

.hud-strip-group {
  position: relative;
  z-index: 22;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 3px 6px;
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}

.hud-strip-center {
  position: relative;
  z-index: 20;
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}

.map-wrap .hud-strip-group {
  padding: 0.33cqw 0.67cqw;
}

.hud-strip-group:first-child .job-hud-role-module {
  margin-left: auto;
}

/* Keep the right group pinned to the screen edge even when the salary badge is
   absent (unemployed - JobSalaryBadge renders null). When it IS present, its
   margin-right:auto below still wins - auto margins consume free space before
   justify-content - so the employed layout (salary center-side, happiness/cash
   at the edge) is unchanged. Without this, losing the salary anchor let the
   group fall back to flex-start and dragged cash toward the center. */
.hud-strip-group:last-child {
  justify-content: flex-end;
}

.hud-strip-group:last-child .job-hud-salary-module {
  margin-right: auto;
}

.hud-strip-group,
.hud-strip .hud-pill {
  align-items: center;
}

/* the strip itself is the glass surface now, so the badges it holds shed
   their own chip background/border/blur - otherwise they'd double up. */
.hud-strip .hud-pill {
  background: none;
  border: none;
  padding: 0;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* Icon-only tray items share one footprint and surface so their varying
   emoji/art shapes don't create a ragged baseline. Text HUD items such as
   cash deliberately stay unsquared. */
.hud-strip .hud-icon-square {
  display: inline-flex;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 7px;
  background: rgba(255, 255, 255, 0.07);
  box-shadow: inset 0 1px rgba(255, 255, 255, 0.08), 0 2px 6px rgba(0, 0, 0, 0.16);
}

.map-wrap .hud-strip .hud-icon-square {
  width: 2.67cqw;
  height: 2.67cqw;
  border-width: 0.11cqw;
  border-radius: 0.61cqw;
}

.hud-strip .hud-cash-box {
  display: inline-flex;
  justify-content: center;
  min-width: 24px;
  height: 24px;
  padding: 0 6px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.07);
  box-shadow: inset 0 1px rgba(255, 255, 255, 0.08), 0 2px 6px rgba(0, 0, 0, 0.16);
}

.map-wrap .hud-strip .hud-cash-box {
  min-width: 2.67cqw;
  height: 2.67cqw;
  padding: 0 0.67cqw;
  border-width: 0.11cqw;
  border-radius: 0.61cqw;
}

.hud-top-right {
  top: 0;
  right: 0;
  flex-direction: row;
  align-items: center;
  gap: 0;
}

.hud-top-center {
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
}

.pill-cursor {
  cursor: default;
}

.hud-pill {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-size: 17px;
  font-weight: 600;
  line-height: 1;
  color: #fff;
  white-space: nowrap;
  background: var(--glass-bg);
  padding: 4px 10px;
  border-radius: var(--radius-md);
  border: 1px solid var(--player-color, transparent);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  transition: transform var(--transition-fast);
}

.hud-pill:hover,
.hud-pill:focus-within {
  transform: scale(var(--pill-zoom));
}

/* corner-flap shape (same glass-strip theme as .hud-strip)
   rather than a floating round pill - flush against the map's top-right
   corner, clipped to a right triangle so it reads as a folded-over corner
   rather than a rectangular chip. drop-shadow (not box-shadow) is used for
   depth since box-shadow ignores clip-path and would draw a square shadow
   behind the triangle.
   clip-path alone doesn't reliably clip backdrop-filter in every browser -
   the blur bleeds across the whole square instead of stopping at the
   diagonal - so mask-image carries the same diagonal split for the blur
   specifically, while clip-path is kept so clicks/hovers on the "invisible"
   half of the square correctly miss the button. Requires width === height
   (it is, both 56px) since the 50%/50% hard stop assumes a square box. */
.menu-btn {
  position: relative;
  width: 56px;
  height: 56px;
  padding: 0;
  border: none;
  border-radius: 0 !important;
  -webkit-border-radius: 0 !important;
  appearance: none;
  -webkit-appearance: none;
  clip-path: polygon(0 0, 100% 0, 100% 100%);
  mask-image: linear-gradient(to top right, transparent 50%, black 50%);
  -webkit-mask-image: linear-gradient(to top right, transparent 50%, black 50%);
  filter: drop-shadow(-1px 1px 2px rgba(0, 0, 0, 0.35));
}

/* the triangle clip leaves the box's geometric center sitting right on the
   diagonal cut, so the icon can't just be flex-centered - it's pinned
   near the top-right corner instead, the "thick" part of the triangle. */
.menu-btn-icon {
  position: absolute;
  top: 8px;
  right: 8px;
  font-size: 17px;
  line-height: 1;
}

.menu-code {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  margin: 0 0 6px;
}

/* Inline underline tab nav for the Game Menu sections (Players, Audio, ...).
   The active tab is marked by an accent underline that sits on the shared
   bottom rule, so adding more tabs just extends the row. */
.menu-tabs {
  display: flex;
  gap: 4px;
  margin: 0 0 14px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
}

.menu-tab {
  flex: 0 0 auto;
  padding: 8px 16px;
  border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  background: none;
  color: rgba(233, 236, 239, 0.6);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.3px;
  cursor: pointer;
  transition: color var(--transition-fast), border-color var(--transition-fast);
}

.menu-tab:hover {
  color: rgba(233, 236, 239, 0.9);
}

.menu-tab.active {
  color: #fff;
  border-bottom-color: var(--modal-accent, #4fd888);
}

/* Holds the height of the 8-slot (two-row) Players grid so the shorter tabs
   (Audio, ...) fill to the same height and the modal doesn't shrink on switch. */
.menu-tab-panel {
  min-height: 180px;
}

.menu-code-label {
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  opacity: 0.7;
}

/* No plate - the code reads flush against the modal; relative only so the
   hollow copy icon can perch at its top-right corner. Centred by .menu-code. */
.menu-code-display {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.menu-code-value {
  font-size: 26px;
  font-weight: 700;
  letter-spacing: 4px;
  /* the trailing letter-spacing pads the right edge; pull it back so the
     glyphs read as optically centred on the plate */
  text-indent: 4px;
  text-align: center;
  color: #f4f6f8;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

/* Hollow (stroked) icon that inherits the code's light colour rather than
   sitting in its own button chrome, so it reads as part of the plate. */
.menu-code-copy {
  position: absolute;
  top: -6px;
  right: -20px;
  width: 24px;
  height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: none;
  color: #f4f6f8;
  opacity: 0.55;
  cursor: pointer;
  transition: opacity var(--transition-fast), transform var(--transition-fast), color var(--transition-fast);
}

.menu-code-copy svg {
  width: 15px;
  height: 15px;
}

.menu-code-copy:hover {
  opacity: 1;
  transform: scale(1.18);
}

.menu-code-copy:active {
  transform: scale(1.05);
}

.menu-code-copy.is-copied {
  opacity: 1;
  color: #4fd888;
}

.color-swatch {
  width: 24px;
  height: 24px;
  padding: 0;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.25);
  cursor: pointer;
  transition: transform var(--transition-fast), border-color var(--transition-fast);
}

.color-swatch:hover:not(:disabled) {
  transform: scale(1.12);
}

.color-swatch.selected {
  border-color: #fff;
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.35);
}

.color-swatch:disabled {
  opacity: 0.25;
  cursor: not-allowed;
}

.add-player-form {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  margin: 0 0 16px;
}

.add-player-form .field {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 13px;
  color: rgba(233, 236, 239, 0.75);
}

.add-player-form input {
  padding: 10px 12px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.3);
  color: #e9ecef;
  font-size: 14px;
}

.add-player-form input:focus {
  outline: none;
  border-color: #4fd888;
}

.add-player-form button {
  flex-shrink: 0;
  width: 110px;
}

.intro-roster {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0 0 16px;
}

.intro-roster-list {
  list-style: none;
  margin: 0;
  padding: 0 6px 0 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 180px;
  overflow-y: auto;
}

/* thin, dark, rounded scrollbar for scroll panels inside the glass modals -
   the browser default (chunky, light track) clashed hard with the theme */
.intro-roster-list,
.running-games-section,
.running-games-section ul,
.modal-body {
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
}

.intro-roster-list::-webkit-scrollbar,
.running-games-section::-webkit-scrollbar,
.running-games-section ul::-webkit-scrollbar,
.modal-body::-webkit-scrollbar {
  width: 6px;
}

.intro-roster-list::-webkit-scrollbar-track,
.running-games-section::-webkit-scrollbar-track,
.running-games-section ul::-webkit-scrollbar-track,
.modal-body::-webkit-scrollbar-track {
  background: transparent;
}

.intro-roster-list::-webkit-scrollbar-thumb,
.running-games-section::-webkit-scrollbar-thumb,
.running-games-section ul::-webkit-scrollbar-thumb,
.modal-body::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.2);
  border-radius: var(--radius-pill);
}

.intro-roster-list::-webkit-scrollbar-thumb:hover,
.running-games-section::-webkit-scrollbar-thumb:hover,
.running-games-section ul::-webkit-scrollbar-thumb:hover,
.modal-body::-webkit-scrollbar-thumb:hover {
  background: rgba(255, 255, 255, 0.35);
}

.intro-roster-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: var(--radius-md);
}

.intro-roster-avatar-wrap {
  position: relative;
  flex-shrink: 0;
}

.intro-roster-avatar {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  font-size: 12px;
}

.intro-roster-avatar-btn {
  appearance: none;
  -webkit-appearance: none;
  padding: 0;
  font: inherit;
  cursor: pointer;
}

.color-picker-backdrop {
  position: fixed;
  inset: 0;
  z-index: 25;
}

/* opens to the right of the avatar, vertically centered on it - level with
   the player name rather than dropping down below the row. Single row, no
   wrap: this sits inside .intro-roster-list (a scrollable, max-height
   panel), so a popover that wraps to multiple rows can grow tall enough to
   get clipped by that scroll container instead of just floating over it. */
.color-picker-popover {
  position: absolute;
  top: 50%;
  left: calc(100% + 8px);
  transform: translateY(-50%);
  z-index: 30;
  display: flex;
  flex-wrap: nowrap;
  gap: 6px;
  width: max-content;
  padding: 8px;
  background: rgba(20, 20, 24, 0.97);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: var(--radius-md);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.45);
}

.intro-roster-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.intro-roster-name {
  font-size: 14px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.intro-roster-controller {
  font-size: 11px;
  color: rgba(233, 236, 239, 0.55);
}

.intro-roster-you {
  flex-shrink: 0;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: #4fd888;
  border: 1px solid rgba(79, 216, 136, 0.5);
  border-radius: var(--radius-pill);
  padding: 2px 8px;
}

.intro-roster-remove {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  border-radius: 50%;
  color: rgba(233, 236, 239, 0.5);
  font-size: 12px;
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}

.intro-roster-remove:hover {
  background: rgba(168, 50, 50, 0.25);
  color: #ff8080;
}

.player-stack {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.turn-badge {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  padding: 6px 14px 8px 6px;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
  transition: opacity var(--transition-fast), transform var(--transition-fast);
}

/* active turn gets the same flush-corner theme as .menu-btn/.hud-strip
   instead of staying a rounded pill (inactive badges keep their pill look
   untouched, see .turn-badge.inactive below) - no border, flat rectangle,
   sitting flush against the map's top-left corner. The "distance" from
   the corner lives as the badge's own padding (pushing the avatar/name
   in) rather than as a gap between the badge and the corner, so the glass
   background itself still runs all the way into the corner - see
   .map-wrap .turn-badge.active below for the actual values (always
   overridden there, since .map-wrap is the real display context here
   regardless of desktop/mobile).
   Only the bottom-right corner, facing into the scene, is rounded; all
   three corners touching or running parallel to the map edges stay square.
   The blurred glass background lives on ::before (its own overflow:hidden
   + border-radius, same trick as .hud-strip::before) rather than directly
   on the badge - the badge itself holds real content (avatar w/ its
   overflowing rank/progress sub-badges, name) that can't take
   overflow:hidden without clipping those. */
.turn-badge.active {
  position: relative;
  border: none;
  border-radius: 0;
  box-shadow: none;
  background: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* without this, the avatar/name are non-positioned (static) elements,
   which paint in an earlier stacking step than ::before's absolutely
   positioned background - meaning the background actually paints on top
   of them, hiding the name text entirely until something (like the
   :hover rule's transform on .turn-badge-name) incidentally promotes it
   into its own stacking context. Explicitly positioning all direct
   children puts them in the same stacking step as ::before, ordered
   after it by DOM position, so they always render above it. */
.turn-badge.active > * {
  position: relative;
  z-index: 1;
}

.turn-badge.active::before {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-radius: 0;
  clip-path: polygon(0 0, 100% 0, calc(100% - 14px) 100%, 0 100%);
  overflow: hidden;
}

.map-wrap .turn-badge.active::before {
  clip-path: polygon(0 0, 100% 0, calc(100% - 1.56cqw) 100%, 0 100%);
}

/* Top-right actions reuse the active player-label surface, mirrored so its
   diagonal edge faces into the map. The shell never scales on hover; only
   the individual icons do, keeping both top corners equal in height. */
.turn-badge.active.turn-badge-right::before {
  clip-path: polygon(0 0, 100% 0, 100% 100%, 14px 100%);
}

.map-wrap .turn-badge.active.turn-badge-right::before {
  clip-path: polygon(0 0, 100% 0, 100% 100%, 1.56cqw 100%);
}

.map-wrap .hud-top-right .hud-top-actions {
  gap: 0.44cqw;
  padding: 0.56cqw 0.56cqw 0.67cqw 2cqw;
}

.hud-action-icon {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: 7px;
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  transition: transform var(--transition-fast), background var(--transition-fast);
}

.map-wrap .hud-action-icon {
  width: 2.78cqw;
  height: 2.78cqw;
  border-radius: 0.61cqw;
  font-size: 1.7cqw;
}

.hud-action-icon:hover,
.hud-action-icon:focus-visible {
  z-index: 2;
  background: rgba(255, 255, 255, 0.17);
  transform: scale(var(--icon-zoom));
}

.hud-top-actions .menu-btn {
  width: 28px;
  height: 28px;
  clip-path: none;
  mask-image: none;
  -webkit-mask-image: none;
  filter: none;
}

.map-wrap .hud-top-actions .menu-btn {
  width: 2.78cqw;
  height: 2.78cqw;
  border-radius: 0.61cqw !important;
}

.hud-top-actions .menu-btn-icon {
  position: static;
  font-size: inherit;
}

/* Wealth progress follows the active label up to its diagonal cut. */
.turn-badge-wealth-track {
  position: absolute !important;
  left: 0;
  right: 14px;
  bottom: 0;
  height: 3px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.35);
  border-radius: 0;
  clip-path: polygon(0 0, 100% 0, calc(100% - 1px) 100%, 0 100%);
}

.map-wrap .turn-badge-wealth-track {
  right: 1.56cqw;
  clip-path: polygon(0 0, 100% 0, calc(100% - 0.11cqw) 100%, 0 100%);
}

.turn-badge-wealth-fill {
  display: block;
  height: 100%;
  background: #d4af37;
  transition: width var(--transition-med);
}

.map-wrap .turn-badge-wealth-track {
  height: 0.33cqw;
  border-radius: 0;
}

.turn-badge.inactive {
  align-self: flex-start;
  padding: 6px 8px 8px 6px;
  opacity: var(--disabled-opacity);
  transform: scale(0.85);
  transform-origin: left center;
}

/* shared circular avatar look - player turn badge and the winners list both
   show an initial in a colored circle */
.avatar-circle {
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  font-weight: 700;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
  border: 2px solid rgba(255, 255, 255, 0.6);
}

.turn-badge-avatar {
  position: relative;
  width: 30px;
  height: 30px;
  font-size: 13px;
}

.turn-badge.you .turn-badge-avatar {
  border-color: #ffc107;
  box-shadow: 0 0 0 2px rgba(255, 193, 7, 0.5);
}

.turn-badge-sub {
  position: absolute;
  bottom: -4px;
  min-width: 13px;
  height: 10px;
  padding: 0 2px;
  border-radius: 3px;
  border: 1px solid rgba(255, 255, 255, 0.6);
  font-size: 7px;
  font-weight: 700;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}

.turn-badge-progress {
  right: -3px;
  left: auto;
  background: #2c6e49;
  color: #fff;
}

.turn-badge-rank {
  top: -4px;
  right: auto;
  bottom: auto;
  left: -3px;
  background: #ffc107;
  color: #1b1f24;
}

.turn-badge-name {
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.3px;
  color: #fff;
  transition: transform var(--transition-fast), color var(--transition-fast);
}

.turn-badge:hover .turn-badge-name {
  transform: scale(var(--label-zoom));
  color: var(--player-color, #ffc107);
}

.turn-badge-you {
  margin-left: 6px;
  font-size: 10px;
  font-weight: 700;
  color: #1b1f24;
  background: #ffc107;
  padding: 1px 6px;
  border-radius: 8px;
}

/* new-turn summary / messages notifications */

.new-turn-lead {
  margin: 0 0 12px;
  font-size: 14px;
}

/* Between-turns events as a grid of compact cards (see NotificationCard),
   not a vertical list of rows: auto-fit means one event fills the width, two
   sit side by side, more wrap into a tidy grid - so the summary spreads
   sideways and stays clear of a scrollbar far longer than a stacked list. */
/* Notification cards always sit in a single centered row (max 3 per page -
   the modal paginates rather than wrapping/stacking), so they never fall
   into a vertical list. See NewTurn / MessagesPresenter for the pager. */
.notification-grid {
  display: flex;
  flex-wrap: nowrap;
  justify-content: center;
  gap: 10px;
  margin: 0 0 16px;
}

.notification-card {
  flex: 1 1 0;
  min-width: 0;
  max-width: 200px;
  min-height: 132px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 12px;
  padding: 18px 12px 16px;
  border-radius: var(--radius-md);
  /* Severity reads as a soft blurred glow, not a flat overlay: a radial
     wash pooling from behind the icon fading down, an inset colored haze
     from the edges, and a light backdrop blur so it frosts what's behind.
     All driven by --notif-color, set per-severity below. */
  border: 1px solid color-mix(in srgb, var(--notif-color, rgba(255, 255, 255, 0.18)) 42%, transparent);
  background:
    radial-gradient(135% 100% at 50% -15%, color-mix(in srgb, var(--notif-color, #ffffff) 28%, transparent), transparent 64%),
    rgba(255, 255, 255, 0.03);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.08),
    inset 0 0 26px -8px color-mix(in srgb, var(--notif-color, transparent) 55%, transparent);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  font-size: 12.5px;
  line-height: 1.4;
}

/* icon in a big round severity-tinted badge - the card's anchor, so it's
   sized up well past the header/action-button chips it echoes. */
.notification-card-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  font-size: 30px;
  line-height: 1;
  background: color-mix(in srgb, var(--notif-color, #ffffff) 32%, rgba(255, 255, 255, 0.08));
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), inset 0 -2px 4px rgba(0, 0, 0, 0.22), 0 2px 6px rgba(0, 0, 0, 0.2);
}

/* text fills the space under the icon and centers in it, so short and long
   messages both sit balanced across cards of equal height. */
.notification-card-text {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  color: rgba(255, 255, 255, 0.92);
  font-weight: 500;
  text-wrap: balance;
}

/* Solo card (one notice per page in NewTurn, or a lone message) gets a
   bigger, more personal presentation - roomier card, larger icon and text -
   since it's the sole focus rather than one of a row. */
.notification-card:only-child {
  max-width: 340px;
  min-height: 168px;
  gap: 16px;
  padding: 26px 22px 22px;
  font-size: 14.5px;
}

.notification-card:only-child .notification-card-icon {
  width: 72px;
  height: 72px;
  font-size: 40px;
}

/* Severity is just a color now - the shared .notification-card / -icon rules
   above turn it into the glow, border and icon tint. */
.notification-good { --notif-color: #4fd888; }
.notification-bad { --notif-color: #ff5252; }
/* warn (yellow): needs action but not yet a loss - e.g. rent due */
.notification-warn { --notif-color: #ffc107; }
/* info (blue): FYI, nothing to do - someone won, lottery lost, Willy empty */
.notification-info { --notif-color: #4d9fff; }

.rent-note {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin: 10px 0 8px;
  padding: 6px 10px;
  background: color-mix(in srgb, var(--modal-accent, #2c6e49) 12%, rgba(255, 255, 255, 0.05));
  border: 1px solid color-mix(in srgb, var(--modal-accent, #2c6e49) 35%, transparent);
  border-radius: var(--radius-md);
  font-size: 12px;
  font-weight: 600;
  opacity: 0.9;
}

/* Bank account overview: Wallet + Savings read as one recessed "ledger"
   panel embedded into the modal (inset shadow, no per-tile borders) rather
   than two raised buttons - the deposit/withdraw transfer below is what's
   actually clickable. */
.bank-overview {
  display: grid;
  grid-template-columns: 1fr 1fr;
  margin: 0 0 12px;
  background: rgba(0, 0, 0, 0.22);
  border-radius: var(--radius-md);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.35);
}

.balance-note {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 12px 14px;
  text-align: center;
}

/* a hairline splits the two accounts instead of framing each one, so the
   pair stays one embedded panel. */
.balance-note + .balance-note {
  border-left: 1px solid rgba(255, 255, 255, 0.08);
}

.balance-note-label {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  opacity: 0.65;
}

.balance-note-amount {
  font-size: 22px;
  font-weight: 800;
}

/* savings drives the win goal and the credit-card unlock, so its figure
   carries the modal accent - the only emphasis the pair needs now. */
.balance-note-primary .balance-note-amount {
  color: color-mix(in srgb, var(--modal-accent, #2c6e49) 45%, #fff);
}

/* Progress toward the $1000 savings that unlocks the Credit Card - a meter
   instead of a bare hint, so the goal feels reachable. */
.bank-goal {
  margin: 12px 0 0;
  padding: 10px 12px;
  background: rgba(0, 0, 0, 0.22);
  border-radius: var(--radius-md);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.35);
}

.bank-goal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 8px;
  font-size: 12px;
  font-weight: 600;
}

.bank-goal-label {
  opacity: 0.85;
}

.bank-goal-value {
  font-weight: 700;
  color: color-mix(in srgb, var(--modal-accent, #2c6e49) 55%, #fff);
}

.bank-goal-track {
  height: 6px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.12);
  overflow: hidden;
}

.bank-goal-fill {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: color-mix(in srgb, var(--modal-accent, #2c6e49) 70%, #fff);
  transition: width var(--transition-med);
}

.bank-transfer {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 32px minmax(0, 1fr);
  align-items: center;
  gap: 4px;
  max-width: 300px;
  margin: 10px auto;
  padding: 4px;
  background: color-mix(in srgb, var(--modal-accent, #5b3a8a) 12%, rgba(255, 255, 255, 0.04));
  border: 1px solid color-mix(in srgb, var(--modal-accent, #5b3a8a) 35%, rgba(255, 255, 255, 0.1));
  border-radius: var(--radius-md);
}

.bank-transfer .modal-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: 1fr 1fr;
  column-gap: 8px;
  width: 100%;
  max-width: none;
  height: 55px;
  padding: 7px 9px;
  background: rgba(255, 255, 255, 0.055);
  border-color: rgba(255, 255, 255, 0.12);
}

.bank-transfer .modal-card-icon {
  grid-row: 1 / 3;
  align-self: center;
  font-size: 23px;
}

.bank-transfer .modal-card-label,
.bank-transfer .modal-card-sub {
  justify-self: start;
  text-align: left;
}

.bank-transfer .modal-card-label {
  align-self: end;
  font-size: 11px;
}

.bank-transfer .modal-card-sub {
  align-self: start;
  color: color-mix(in srgb, var(--modal-accent, #5b3a8a) 55%, #fff);
  font-size: 10px;
  font-weight: 700;
  opacity: 1;
}

.bank-transfer-icon {
  position: relative;
  z-index: 1;
  width: 30px;
  height: 30px;
  display: grid;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: #fff;
  background: color-mix(in srgb, var(--modal-accent, #5b3a8a) 65%, #17131f);
  border: 1px solid color-mix(in srgb, var(--modal-accent, #5b3a8a) 75%, #fff);
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.3);
  font-size: 18px;
  font-weight: 800;
  line-height: 1;
}

/* the login screen's public/private toggle only applies when you're about
   to create a game - dimmed (not hidden, so the layout doesn't jump) once
   a code's typed or a running game's selected instead. */
.toggle-inactive {
  opacity: var(--disabled-opacity);
  cursor: not-allowed;
  pointer-events: none;
}

/* simple "Public game" on/off - the switch alone carries the state. */
.game-visibility-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  cursor: pointer;
  font-size: 13px;
  font-weight: 600;
  color: rgba(233, 236, 239, 0.85);
}

/* read-only counterpart shown in the in-game Menu (GameMenuModal) - the
   Intro screen's .game-visibility-toggle is the only place this can
   actually be changed, and only while still joinable. A pill badge, not
   a bare sentence, so it reads as a status chip rather than a stray line
   of text floating between the roster and the Pause button. */
.bank-hint {
  margin: 0;
  padding: 8px 10px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px dashed rgba(255, 255, 255, 0.25);
  border-radius: var(--radius-md);
  font-size: 12px;
  font-style: italic;
  opacity: 0.8;
}

.nothing-to-do {
  margin: 16px 0;
  padding: 18px 16px;
  text-align: center;
  font-size: 13px;
  font-style: italic;
  opacity: 0.75;
}

/* Chunky square toggle - matches the app's hard-corner theme and reads like
   a modern game setting rather than an iOS pill. Track carries an inset well;
   the square knob slides between two notched ends and lights up on the accent
   when on. Geometry: 46px track, 20px knob, 3px inset -> 20px of travel. */
.switch {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
  width: 46px;
  height: 26px;
}

.switch input {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  cursor: pointer;
  z-index: 1;
}

.switch-track {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.35);
  border: 1px solid rgba(255, 255, 255, 0.22);
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.45);
  transition: background var(--transition-fast), border-color var(--transition-fast),
    box-shadow var(--transition-fast);
}

.switch-thumb {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  background: linear-gradient(160deg, #f4f6f8, #c3c9d1);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.7);
  transition: transform var(--transition-fast), background var(--transition-fast);
}

.switch input:checked ~ .switch-track {
  background: color-mix(in srgb, var(--modal-accent, #2c6e49) 45%, transparent);
  border-color: var(--modal-accent, #2c6e49);
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3),
    0 0 8px color-mix(in srgb, var(--modal-accent, #2c6e49) 55%, transparent);
}

.switch input:checked ~ .switch-track .switch-thumb {
  transform: translateX(20px);
  background: linear-gradient(160deg, #ffffff, #dfe6ec);
}

.switch input:focus-visible ~ .switch-track {
  outline: 2px solid var(--modal-accent, #2c6e49);
  outline-offset: 2px;
}


/* card grids inside modals (buildings, inventory) */

.modal-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  margin: 10px 0;
}

.modal-card {
  position: relative; /* anchors the .icon-tooltip item description below */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: var(--radius-md);
  padding: 10px 4px;
  color: #fff;
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast), transform var(--transition-fast);
}

.modal-card:hover > .icon-tooltip,
.modal-card:focus > .icon-tooltip {
  opacity: 1;
}

.modal-card-priced-item,
.modal-card-compact {
  height: var(--modal-card-height);
  min-height: var(--modal-card-height);
  gap: 2px;
  padding: 4px;
}

/* Tagged shop cards keep prices out of the content stack: a compact
   attached tag carries the cost without adding a third text row. */
.modal-card-price-tag {
  position: absolute;
  z-index: 2;
  top: 0;
  left: 0;
  padding: 3px 4px 3px 5px;
  color: #202026;
  background: #f3d36a;
  border-radius: 5px 0 0 0;
  font-size: 8px;
  font-weight: 800;
  line-height: 1.2;
  white-space: nowrap;
}

.modal-card-price-tag::after {
  content: '';
  position: absolute;
  top: 0;
  left: 100%;
  width: 0;
  height: 0;
  border-top: 7px solid transparent;
  border-bottom: 7px solid transparent;
  border-left: 5px solid #f3d36a;
}

.modal-card-price-tag-unaffordable {
  color: #fff;
  background: #b94747;
}

.modal-card-price-tag-unaffordable::after {
  border-left-color: #b94747;
}

.modal-card-effect-tag {
  position: absolute;
  z-index: 2;
  top: 0;
  right: 0;
  min-width: 29px;
  padding: 3px 5px 3px 4px;
  color: #17171b;
  background: var(--modal-accent, #ffc107);
  border-radius: 0 5px 0 7px;
  box-shadow: -1px 1px 0 rgba(0, 0, 0, 0.24);
  font-size: 8px;
  font-weight: 800;
  line-height: 1.2;
  white-space: nowrap;
}

/* item-effect descriptions run longer than the other one-word .icon-tooltip
   uses elsewhere, so unlike the shared base rule's nowrap default, these
   wrap normally - confined to the card's own width (not a fixed px value)
   so it can never spill past the card's left/right edges into the page,
   however narrow the grid column happens to be. */
.item-description-tooltip {
  z-index: 10;
  top: 16px;
  right: 0;
  bottom: 0;
  left: 0;
  display: flex;
  align-items: center;
  white-space: normal;
  text-align: left;
  width: 100%;
  max-width: 100%;
  height: auto;
  padding: 3px 4px;
  border-radius: 0 0 9px 9px;
  background: rgba(0, 0, 0, 0.76);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  font-size: 8px;
  line-height: 1.25;
  transform: none;
}

.modal-card-priced-item .item-description-tooltip {
  font-family: inherit;
  font-weight: 400;
}

/* Tagged shop cards reveal their effect only from the explicit top-right
   effect badge, not from hovering anywhere on the purchase card. */
.modal-card-priced-item:hover > .item-description-tooltip,
.modal-card-priced-item:focus > .item-description-tooltip {
  opacity: 0;
}

.modal-card-priced-item .modal-card-effect-tag:hover ~ .item-description-tooltip {
  opacity: 1;
}

.modal-card:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.14);
  border-color: var(--modal-accent, rgba(255, 255, 255, 0.4));
  transform: translateY(var(--hover-lift));
}

.modal-card:active:not(:disabled) {
  background: rgba(255, 255, 255, 0.22);
  transform: scale(var(--press-scale));
}

.modal-card:disabled {
  opacity: var(--disabled-opacity);
  cursor: not-allowed;
}

.modal-card-static {
  cursor: default;
}

.modal-card-current:disabled {
  opacity: 1;
  cursor: default;
  background: color-mix(in srgb, var(--modal-accent, #4fd888) 18%, rgba(255, 255, 255, 0.06));
  border-color: var(--modal-accent, #4fd888);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--modal-accent, #4fd888) 35%, transparent);
}

.modal-card-icon {
  font-size: 26px;
  line-height: 1;
}

.modal-card-label {
  font-size: 11px;
  font-weight: 600;
  text-align: center;
  line-height: 1.25;
}

.modal-card-sub {
  font-size: 10px;
  opacity: 0.75;
}

/* any displayed price (Card sub, ActionButton sub, university fees...) -
   red whenever the current player can't afford it, see Price.tsx */
.price-cant-afford {
  color: #ff6b6b;
  opacity: 1;
}

/* University course tree (BuildingView -> UniversityFlowchart) - 3 track
   columns (Life/Economics/STEM) laid out as rows of nodes with thin
   connector lines between tiers, instead of a flat grid of buttons. */

.uni-tree {
  display: flex;
  flex-direction: column;
  margin: 6px 0 4px;
}

.uni-tree-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}

.uni-library {
  position: relative;
  display: grid;
  grid-template-rows: auto 1fr;
  align-items: center;
  min-height: 52px;
  padding: 4px 8px 2px;
}

.uni-library-title {
  text-align: center;
  font-size: 8px;
  font-weight: 800;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  opacity: 0.65;
}

.uni-library-shelves {
  align-self: end;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 2px;
  height: 29px;
  padding: 2px 3px 3px;
  border-bottom: 3px solid #795638;
}

.uni-library-book {
  width: 7px;
  height: 21px;
  border: 1px dashed rgba(255, 255, 255, 0.12);
  border-radius: 1px 1px 0 0;
  background: rgba(255, 255, 255, 0.025);
  transform-origin: bottom;
  transition: background var(--transition-med), border-color var(--transition-med), transform var(--transition-med);
}

.uni-library-book:nth-child(3n) {
  height: 25px;
  transform: rotate(3deg);
}

.uni-library-book:nth-child(3n + 2) {
  height: 23px;
}

.uni-library-book.filled {
  border-style: solid;
  border-color: rgba(255, 255, 255, 0.28);
  background: var(--uni-book-color, #4fd888);
  box-shadow: inset -1px 0 rgba(0, 0, 0, 0.2);
}

.uni-library-book.filled:nth-child(3n + 1) { --uni-book-color: #4fd888; }
.uni-library-book.filled:nth-child(3n + 2) { --uni-book-color: #ffc107; }
.uni-library-book.filled:nth-child(3n) { --uni-book-color: #4fa3ff; }

.uni-tree-connector {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  height: 22px;
}

.uni-tree-line {
  width: 2px;
  height: 100%;
  margin: 0 auto;
  background: rgba(255, 255, 255, 0.18);
  transition: background var(--transition-med), box-shadow var(--transition-med);
}

.uni-tree-line.active {
  background: var(--modal-accent, #4fd888);
  box-shadow: 0 0 5px color-mix(in srgb, var(--modal-accent, #4fd888) 65%, transparent);
}

.uni-tree-connector-fork {
  display: block;
  height: 22px;
}

.uni-tree-connector-fork svg {
  width: 100%;
  height: 100%;
  overflow: visible;
}

.uni-tree-connector-fork line {
  stroke: rgba(255, 255, 255, 0.18);
  stroke-width: 2;
  transition: stroke var(--transition-med), filter var(--transition-med);
}

.uni-tree-connector-fork line.active {
  stroke: var(--modal-accent, #4fd888);
  filter: drop-shadow(0 0 2px color-mix(in srgb, var(--modal-accent, #4fd888) 70%, transparent));
}

.uni-node {
  --uni-step-width: 27%;
  position: relative;
  overflow: visible;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-height: 52px;
  padding: 7px 4px 6px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 6px;
  color: #fff;
  font-family: inherit;
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast), transform var(--transition-fast), filter var(--transition-fast);
}

.uni-node-icon,
.uni-node-label {
  position: relative;
  z-index: 1;
}

.uni-node-state-tag {
  position: absolute !important;
  z-index: 0;
  bottom: 0;
  left: 0;
  display: flex;
  min-width: 27px;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  padding: 3px 2px 3px 4px;
  box-sizing: border-box;
  width: var(--uni-step-width);
  border-radius: 0 0 0 5px;
  font-size: 8px;
  font-weight: 800;
  line-height: 1;
  text-align: left;
  white-space: nowrap;
  transition: width 300ms ease;
}

.uni-node-state-tag > span {
  position: relative;
  z-index: 1;
  font-size: 7px;
  font-weight: 600;
  opacity: 0.8;
}

.uni-node-state-tag > strong {
  position: relative;
  z-index: 1;
  font-size: 8px;
  font-weight: 800;
}

.uni-node-state-tag::after {
  content: '';
  position: absolute;
  top: 0;
  left: 100%;
  width: 0;
  height: 0;
  border-top: 11px solid transparent;
  border-bottom: 11px solid transparent;
  border-left: 5px solid var(--uni-tag-color);
}

.uni-node-price-tag {
  --uni-tag-color: #f3d36a;
  color: #202026;
  background: var(--uni-tag-color);
}

.uni-node-price-tag.price-cant-afford {
  --uni-tag-color: #b94747;
  color: #fff;
}

.uni-node-step-tag {
  --uni-tag-color: color-mix(in srgb, var(--uni-track-color) 65%, #202026);
  color: #fff;
  background: var(--uni-tag-color);
}

.uni-node-graduated .uni-node-step-tag {
  right: 0;
  width: auto;
  border-radius: 0 0 5px 5px;
}

.uni-node-graduated .uni-node-step-tag::after {
  display: none;
}

.uni-node-badge {
  position: absolute;
  z-index: 2;
  top: 0;
  right: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 29px;
  padding: 3px 4px 3px 5px;
  font-size: 8px;
  font-weight: 800;
  line-height: 1.2;
  white-space: nowrap;
  color: #17171b;
  background: var(--uni-track-color, rgba(255, 255, 255, 0.7));
  border: 0;
  border-radius: 0 5px 0 7px;
  box-shadow: -1px 1px 0 rgba(0, 0, 0, 0.24);
}

.uni-node-effect-tooltip {
  z-index: 10;
  top: 16px;
  right: 0;
  bottom: 0;
  left: 0;
  display: flex;
  width: 100%;
  height: auto;
  align-items: center;
  padding: 3px 4px;
  border-radius: 0 0 5px 5px;
  background: rgba(0, 0, 0, 0.76);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  transform: none;
  white-space: normal;
  font-size: 8px;
  line-height: 1.25;
  text-align: left;
}

.uni-node-badge:hover ~ .uni-node-effect-tooltip {
  opacity: 1;
}

.uni-node:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.14);
  transform: translateY(var(--hover-lift));
}

.uni-node:active:not(:disabled) {
  background: rgba(255, 255, 255, 0.22);
  transform: scale(var(--press-scale));
}

.uni-node:disabled:not(.uni-node-locked) {
  opacity: var(--disabled-opacity);
  cursor: not-allowed;
}

.uni-node-locked {
  opacity: 0.5;
  filter: grayscale(0.7);
  cursor: not-allowed;
}

.uni-node-graduated {
  cursor: not-allowed;
  border-color: color-mix(in srgb, var(--uni-track-color) 55%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--uni-track-color) 30%, transparent);
}

.uni-node-enrollable {
  border-color: color-mix(in srgb, var(--uni-track-color) 45%, rgba(255, 255, 255, 0.15));
}

.uni-track-life {
  --uni-track-color: #4fd888;
}

.uni-track-economics {
  --uni-track-color: #ffc107;
}

.uni-track-stem {
  --uni-track-color: #4fa3ff;
}

.uni-node-icon {
  position: absolute;
  bottom: 28px;
  left: 2px;
  font-size: 20px;
  line-height: 0.75;
}

.uni-node-label {
  position: absolute;
  right: 6px;
  bottom: 5px;
  left: 34px;
  font-size: 10px;
  font-weight: 600;
  text-align: right;
  line-height: 1.2;
}

/* action buttons (Rest, Pay Rent, Work, Job offers, Pay Debt...) - the primary
   thing to do at this location right now, as opposed to item buttons (below)
   which are a grid of interchangeable choices (shop items, food, schools,
   jobs). They live in an .action-footer pinned to the modal's bottom border
   (see the ActionFooter component): secondary action left, primary/hero right. */

.action-footer {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* Straddles the modal's bottom border, 20% in/80% out (see Modal's
   `footer` prop) - holds an .action-footer (secondary action bottom-left,
   primary/hero bottom-right). A sibling of .modal rather than a child of it
   specifically so .modal's own
   overflow:hidden (which clips header/body to the rounded corners as
   normal) doesn't also clip this - it's positioned against .modal-shell
   instead, which wraps .modal without clipping anything. `bottom: 0` lines
   its own bottom edge up with .modal's (fully inside, 0% out); each 1% of
   translateY then moves 1% of its own height past that line, so
   translateY(68%) keeps roughly one third inside the modal and two thirds
   hanging below it. */
.modal-footer {
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 0;
  transform: translateY(68%);
}

/* footer actions visually belong to the building modal even while straddling
   its edge: a fully opaque glossy accent fill (gloss = the white top gradient
   over solid accent) so the map cannot show through the part hanging below the
   modal, while still getting the tactile lip/press from the base .action-btn
   rule above. */
.modal-footer .action-btn {
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.12), transparent 58%),
    color-mix(in srgb, var(--modal-accent, #2c6e49) 72%, #20252b);
}

/* bottom-right: margin-left:auto pins it to the row's right edge whether or
   not a left action precedes it - the standard flexbox "push this item and
   everything after it to the far end" trick. */
.action-footer .action-btn-wrap-right {
  margin-left: auto;
}

/* bottom-middle: a lone standalone action (Start turn, Ok) centered on the
   modal's bottom border. margin-inline:auto centers it in the free space. */
.action-footer .action-btn-wrap-center {
  margin-inline: auto;
}

/* content-sized (not stretched): buttons sit at their own corners rather than
   filling the row. */
.action-btn-wrap {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* destructive footer action (Quit...) - same #a83232 as .btn-danger; overrides
   --modal-accent on this wrap so the button's fill/lip/shadow all go red
   without duplicating the toy-button shadow rules. */
.action-btn-wrap-danger {
  --modal-accent: #a83232;
}

/* Compact game-panel control: still tactile, but closer to a board-game tab
   than the previous oversized glossy toy key. */
.action-btn {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 30px;
  padding: 4px 9px;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0) 55%),
    color-mix(in srgb, var(--modal-accent, #2c6e49) 26%, rgba(28, 32, 38, 0.92));
  border: 1px solid color-mix(in srgb, var(--modal-accent, #2c6e49) 58%, rgba(255, 255, 255, 0.14));
  border-radius: 8px;
  color: #fff;
  font-weight: 700;
  font-size: 13px;
  cursor: pointer;
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.35);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.16),
    0 2px 0 color-mix(in srgb, var(--modal-accent, #2c6e49) 38%, black),
    0 3px 7px rgba(0, 0, 0, 0.24);
  transition: transform 0.08s ease, box-shadow 0.08s ease, filter var(--transition-fast);
}

.action-btn:hover:not(:disabled) {
  filter: brightness(1.1);
  transform: translateY(-1px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.2),
    0 2px 0 color-mix(in srgb, var(--modal-accent, #2c6e49) 42%, black),
    0 4px 8px rgba(0, 0, 0, 0.28);
}

/* Press sinks the compact control onto its shallow lower lip. */
.action-btn:active:not(:disabled) {
  transform: translateY(2px);
  filter: brightness(0.97);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.2),
    0 1px 0 color-mix(in srgb, var(--modal-accent, #2c6e49) 45%, black),
    0 2px 5px rgba(0, 0, 0, 0.3);
}

.action-btn:disabled {
  opacity: 1;
  cursor: not-allowed;
  filter: saturate(0.45) brightness(0.72);
  box-shadow:
    0 2px 0 color-mix(in srgb, var(--modal-accent, #2c6e49) 30%, black),
    0 3px 6px rgba(0, 0, 0, 0.22);
}

/* Rounded-square emblem matching the modal header plaque. */
.action-icon {
  font-size: 12px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 5px;
  background: rgba(255, 255, 255, 0.11);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16);
}

.action-label {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.3px;
  transition: transform var(--transition-fast), color var(--transition-fast);
}

.action-btn:hover:not(:disabled) .action-label {
  transform: scale(var(--label-zoom));
  /* stays white - the button fill is now the accent itself, so tinting the
     label the accent colour would make it vanish into the background */
  color: #fff;
}

.action-sub {
  font-size: 10px;
  font-weight: 700;
  opacity: 0.85;
}

/* Work's per-hour garnishment (see WorkActionsFooter/garnishedPerHour) -
   red instead of a spelled-out "debt" label, same tone .debt-badge used to
   use before it moved onto this button. */
.garnish-amount {
  color: #ff5252;
  opacity: 1;
}

/* modal (building view, new turn, etc.) */

/* every modal portals to document.body (see Modal.tsx) - true fixed,
   viewport-relative overlay, flush to the real screen edges regardless of
   confirm tier. The asymmetric padding keeps every centered modal inside the
   safe band between the top player label and the bottom clock/hud-strip
   (see --modal-safe-* tokens), so a tall modal never rides up over the tab
   or down onto the clock. */
.modal-backdrop {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.5);
  padding: var(--modal-safe-top) 20px var(--modal-safe-bottom);
  z-index: var(--z-modal);
}

.new-game-options {
  min-width: 280px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.lobby-menu { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin-bottom: 14px; }
.lobby-menu button { padding: 10px 6px; border: 1px solid rgba(255,255,255,.14); border-radius: 8px; background: rgba(255,255,255,.05); color: rgba(233,236,239,.72); cursor: pointer; font-weight: 700; }
.lobby-menu button.active { border-color: #4fd888; background: rgba(79,216,136,.14); color: #fff; }
.lobby-slider { overflow: hidden; flex: 1 1 auto; min-height: 0; display: flex; }
.lobby-track { flex: 1 1 auto; min-width: 0; display: flex; align-items: stretch; transition: transform 180ms ease-out; }
.lobby-panel { flex: 0 0 100%; min-width: 0; min-height: 0; }
.lobby-panel h2 { margin: 0 0 14px; text-align: center; font-size: 18px; }
.lobby-panel .running-games-section { max-height: min(38vh, 330px); overflow-y: auto; }

/* Join/Continue panels stretch to the slider's full height so the games list
   grows to fill the space (in Join, up to the bottom-pinned invite box) and
   scrolls automatically once the games overflow that height. */
.lobby-join-step,
.lobby-continue-step,
.lobby-all-step {
  align-self: stretch;
  display: flex;
  flex-direction: column;
}

.lobby-join-step .running-games-section,
.lobby-continue-step .running-games-section,
.lobby-all-step .running-games-section {
  flex: 1 1 auto;
  min-height: 0;
  max-height: none;
  overflow-y: auto;
}

/* Admin All Games tab: each row is a read-only game summary plus a kill control. */
.ongoing-game-row {
  display: flex;
  align-items: stretch;
  gap: 8px;
}

.ongoing-game-row .running-game-card {
  flex: 1 1 auto;
}

/* the summary card here isn't a join button - neutralise the interactive
   hover/press styling the shared .running-game-card carries. */
.ongoing-game-info,
.ongoing-game-info:hover {
  cursor: default;
  transform: none;
  background: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.14);
  box-shadow: none;
}

.ongoing-kill-btn {
  flex: 0 0 auto;
  width: 46px;
  border: 1px solid rgba(217, 54, 54, 0.5);
  border-radius: 8px;
  background: rgba(217, 54, 54, 0.14);
  color: #ff8b8b;
  font-size: 18px;
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast);
}

.ongoing-kill-btn:hover {
  background: rgba(217, 54, 54, 0.28);
  border-color: #d93636;
}

.ongoing-kill-confirm {
  flex: 0 0 auto;
  display: flex;
  align-items: stretch;
  gap: 6px;
}

.ongoing-kill-yes {
  padding: 0 14px;
  border: 1px solid #d93636;
  border-radius: 8px;
  background: #d93636;
  color: #fff;
  font-weight: 800;
  cursor: pointer;
}

.ongoing-kill-yes:hover {
  background: #e24444;
}

.ongoing-kill-no {
  width: 38px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.06);
  color: #fff;
  cursor: pointer;
}

.ongoing-kill-no:hover {
  background: rgba(255, 255, 255, 0.14);
}

@media (prefers-reduced-motion: reduce) {
  .lobby-track { transition: none; }
}

.new-game-options .game-visibility-toggle {
  justify-content: space-between;
  padding: 10px 12px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.04);
}

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

.lobby-local-players .field {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 13px;
  color: rgba(233, 236, 239, 0.75);
}

.lobby-local-players input {
  min-width: 0;
  padding: 10px 12px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.3);
  color: #e9ecef;
}

.lobby-local-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.lobby-local-list button {
  padding: 6px 9px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  color: #e9ecef;
  cursor: pointer;
}

.lobby-local-list button span { color: #ff8080; }

.lobby-new-step {
  min-height: 310px;
  display: flex;
  flex-direction: column;
}

/* Game-created modal roster: same four-across square tile language as the
   lobby's player setup, while the ordinary Game Menu keeps the compact rows. */
.intro-roster-tiles .intro-roster-list {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px 8px;
  max-height: none;
  padding: 2px;
  overflow: visible;
}

.intro-roster-tiles .intro-roster-row {
  position: relative;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 0;
  border: 0;
  background: none;
}

.intro-roster-tiles .intro-roster-avatar {
  width: 58px;
  height: 58px;
  border-radius: 0;
  color: #20252b;
  font-size: 23px;
  font-weight: 800;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.4), 0 6px 14px rgba(0,0,0,.3);
}

.intro-roster-tiles .intro-roster-info {
  width: 100%;
  text-align: center;
}

.intro-roster-tiles .intro-roster-name {
  display: block;
  font-size: 11px;
}

.intro-roster-tiles .intro-roster-controller,
.intro-roster-tiles .intro-roster-you { display: none; }

/* Vacant seat placeholders (Game Menu): a dashed, dimmed square with a faint
   silhouette - reads as an open slot without competing with real players. */
.intro-roster-tiles .intro-roster-avatar-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.03);
  border: 1px dashed rgba(255, 255, 255, 0.12);
  box-shadow: none;
  color: rgba(233, 236, 239, 0.18);
}

.intro-roster-tiles .intro-roster-avatar-empty svg {
  width: 30px;
  height: 30px;
}

.intro-roster-tiles .intro-roster-remove {
  position: absolute;
  top: -5px;
  left: calc(50% + 18px);
  z-index: 2;
  width: 19px;
  height: 19px;
  border: 2px solid #171b20;
  background: #d93636;
  color: #fff;
  font-size: 10px;
}

.new-game-toggle-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 10px;
}

.new-game-toggle-row.has-cheats {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.lobby-new-step-content { flex: 1; }

/* Absorbs leftover height in .new-game-options so the toggle row + Create
   button sit together at the bottom instead of the toggles hugging the roster. */
.new-game-fill {
  flex: 1 1 auto;
  min-height: 0;
}

.new-game-player-roster {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  align-items: flex-start;
  gap: 18px 10px;
  min-height: 96px;
}

.new-game-player-tile {
  position: relative;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
}

button.new-game-player-tile {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
}

.new-game-add-player-icon {
  border-style: dashed;
  background: rgba(255, 255, 255, 0.035);
  color: rgba(233, 236, 239, 0.62);
  font-size: 20px;
}

.new-game-add-player:hover .new-game-add-player-icon {
  border-color: #4fd888;
  background: rgba(79, 216, 136, 0.1);
}

.new-game-add-badge {
  position: absolute;
  top: -5px;
  left: calc(50% + 19px);
  width: 20px;
  height: 20px;
  display: grid;
  place-items: center;
  border: 2px solid #171b20;
  border-radius: 50%;
  background: #2c9a61;
  color: #fff;
  font-size: 15px;
  font-weight: 900;
  line-height: 1;
}

.local-player-editor {
  padding: 12px;
  border: 1px solid rgba(79, 216, 136, 0.3);
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.2);
  box-shadow: inset 3px 0 0 rgba(79, 216, 136, 0.65);
}

.local-player-editor .field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  color: rgba(233, 236, 239, 0.75);
  font-size: 12px;
}

.local-player-editor input {
  padding: 9px 11px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.3);
  color: #e9ecef;
}

.local-player-editor-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 10px;
}

.local-player-modal {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.lobby-modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: grid;
  place-items: center;
  padding: 20px;
  background: rgba(6, 9, 13, 0.68);
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);
}

.lobby-modal {
  width: min(100%, 390px);
  overflow: hidden;
  border: 1px solid rgba(79, 216, 136, 0.55);
  border-radius: 14px;
  background: linear-gradient(145deg, rgba(25, 31, 38, 0.98), rgba(17, 22, 28, 0.98));
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.62), 0 0 30px rgba(79, 216, 136, 0.1);
  animation: lobby-modal-enter 140ms ease-out;
}

@keyframes lobby-modal-enter {
  from { opacity: 0; transform: translateY(8px) scale(0.98); }
}

.lobby-modal-header {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr) 28px;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  background: rgba(79, 216, 136, 0.06);
}

.lobby-modal-header h2 { margin: 0; font-size: 15px; }
.lobby-modal-icon { font-size: 22px; text-align: center; }
.lobby-modal-close { width: 28px; height: 28px; padding: 0; border: 0; border-radius: 8px; background: rgba(255,255,255,.08); color: #fff; cursor: pointer; font-size: 18px; }
.lobby-modal-close:hover { background: rgba(255,255,255,.16); }
.lobby-modal-body { padding: 18px; }

.local-player-modal .field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  color: rgba(233, 236, 239, 0.78);
  font-size: 12px;
}

.local-player-modal input {
  padding: 10px 12px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.3);
  color: #e9ecef;
}

.local-player-color-picker > span {
  display: block;
  margin-bottom: 8px;
  color: rgba(233, 236, 239, 0.78);
  font-size: 12px;
}

.local-player-color-picker > div {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  gap: 7px;
}

.local-player-color-picker button {
  aspect-ratio: 1;
  border: 2px solid transparent;
  border-radius: 8px;
  cursor: pointer;
}

.local-player-color-picker button.selected {
  border-color: #fff;
  box-shadow: 0 0 0 2px #4fd888;
}

.local-player-color-picker button:disabled {
  opacity: 0.2;
  cursor: not-allowed;
}

.new-game-player-initial {
  --preview-player-color: #aec6cf;
  width: 64px;
  height: 64px;
  display: grid;
  place-items: center;
  border: 2px solid color-mix(in srgb, var(--preview-player-color) 78%, #fff);
  border-radius: 12px;
  background: linear-gradient(145deg, color-mix(in srgb, var(--preview-player-color) 88%, #fff), var(--preview-player-color));
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.32), inset 0 1px 0 rgba(255, 255, 255, 0.4);
  color: #20252b;
  font-size: 26px;
  font-weight: 800;
}

.new-game-player-main {
  box-shadow: 0 0 0 2px #4fd888, 0 6px 16px rgba(0, 0, 0, 0.32), inset 0 1px 0 rgba(255, 255, 255, 0.4);
}

/* Vacant seat placeholders in the new-game roster - dimmed dashed squares with
   a faint silhouette showing the game's remaining capacity, up to MAX_PLAYERS. */
.new-game-empty-seat {
  border-style: dashed;
  border-color: rgba(255, 255, 255, 0.12);
  background: rgba(255, 255, 255, 0.03);
  box-shadow: none;
  color: rgba(233, 236, 239, 0.18);
}

.new-game-empty-seat svg {
  width: 32px;
  height: 32px;
}

.new-game-player-name {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: rgba(233, 236, 239, 0.85);
  font-size: 11px;
  font-weight: 700;
}

.new-game-player-remove {
  position: absolute;
  top: -5px;
  left: calc(50% + 19px);
  width: 19px;
  height: 19px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 2px solid #171b20;
  border-radius: 50%;
  background: #d93636;
  color: #fff;
  cursor: pointer;
  font-size: 13px;
  font-weight: 900;
  line-height: 1;
}

.new-game-step-actions {
  padding-top: 18px;
}

/* Invite-code box: a self-contained panel pinned to the bottom of the Join
   panel (margin-top:auto), matching the games-list card surface above it. */
.new-game-invite {
  margin-top: auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: 10px;
  padding: 14px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 12px;
  background: rgba(0, 0, 0, 0.18);
}

.new-game-invite .field {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: rgba(233, 236, 239, 0.6);
}

.new-game-invite input {
  min-width: 0;
  padding: 11px 14px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.3);
  color: #e9ecef;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 3px;
  text-transform: uppercase;
}

.new-game-invite input::placeholder {
  color: rgba(233, 236, 239, 0.3);
  letter-spacing: 3px;
}

.new-game-invite input:focus {
  outline: none;
  border-color: #4fd888;
  box-shadow: 0 0 0 2px rgba(79, 216, 136, 0.25);
}


/* A nested modal replaces its parent while it is open. The parent stays
   mounted so its local state and scroll position are restored on child close. */
.modal-backdrop-suspended {
  display: none;
}

/* Action footers hang 68% below the modal border. Reserve that extra space
   in the shared height limit so the visible footer - not merely the modal
   box - ends above the raised clock with a comfortable gap. */
.modal-backdrop:has(.modal-footer > *) {
  --modal-safe-bottom: calc(var(--modal-hud-inset) + var(--scene-w) * 0.115);
}

.modal-backdrop-top {
  z-index: var(--z-modal-top);
}

/* Confirmation prompts replace their parent modal, with a darker backdrop to
   read as an even-more-unmissable "this can't be undone" prompt. */
.modal-backdrop-confirm {
  z-index: var(--z-modal-confirm);
  background: rgba(0, 0, 0, 0.65);
}

/* Positioning context for .modal + its optional straddle-the-border
   .modal-footer (see Modal.tsx) - sized+scaled the same way regardless of
   confirm tier, one shared rule, not two separately-maintained ones.
   transform: scale(var(--game-scale)) is what keeps every modal in step
   with the map/pins/car/tooltip as the window resizes; --game-scale is
   mirrored onto <html> in App.tsx specifically so this reaches modals here
   even though they're portalled outside .game-main's own subtree.
   Flexbox already centers the pre-transform box, so the default center
   transform-origin keeps it centered as it scales. Lives on the shell
   rather than .modal itself so .modal-footer (a sibling, not a child of
   .modal - see below) scales and tracks position in lockstep with it. */
.modal-shell {
  position: relative;
  /* Fixed native design width, NOT width:100%/max-width - the whole modal is
     a rigid box that only transform:scale() below resizes, exactly like the
     16:9 map canvas. Tying width to the viewport (as 100% did) made the shell
     shrink with the screen *and* get scaled on top of that below ~458px (the
     backdrop's 20px side padding + this 418px), so it collapsed in width
     instead of zooming out with everything else. Rendered width is always
     418 * --game-scale = a fixed 46% of the 16:9 frame, so it never reaches
     the screen edges and never needs a viewport cap. flex-shrink:0 is
     essential: .modal-backdrop is a flexbox, so the default flex-shrink:1
     would let this collapse below 418px once the viewport (minus the
     backdrop's side padding) drops under ~458px - shrinking its width on top
     of the transform scale, exactly the quadratic collapse we're avoiding. */
  width: 418px;
  flex-shrink: 0;
  /* transform scaling happens after layout, so compensate with the inverse
     scale here. The rendered shell is capped to the safe band between the HUD
     keep-out zones (see --modal-safe-* tokens) so it - and its straddling
     footer - stay clear of the top player label and the bottom clock;
     overflowing modal bodies scroll inside. */
  max-height: calc((100vh - var(--modal-safe-top) - var(--modal-safe-bottom)) * var(--game-scale-inverse, 1));
  display: flex;
  flex-direction: column;
  transform: scale(var(--game-scale, 1));
}

/* Local/session modals (setup/menu/music) live above the spectator shield -
   a spectator can still open the menu or change music while shielded out of
   the game itself. */
.modal-backdrop-local {
  z-index: var(--z-modal-local);
}

/* The spectator shield: ONE layer, a child of .map-wrap (see Map.tsx), shown
   whenever the viewer is not the current turn's controller. It uses the SAME
   centered 16:9 scene frame as .map-placement-layer / .map-hud-layer, so it is
   sized by that frame automatically at every viewport - it hugs the actual
   gameplay plane, not the pillarboxed full width. .map-wrap is not a stacking
   context, so --z-shield still slots it in .game-main's context: above the map
   placement layer (--z-map) and any shared gameplay modal (--z-modal*), below
   the HUD (--z-hud) and local modals (--z-modal-local). It blocks
   hover/active/focus/click and shows the not-allowed cursor; the modal's own
   inert + capture guards (Modal.tsx) still enforce turn ownership; this layer
   is the visible affordance + wheel-forwarding for scrolling a locked modal. */
.spectator-shield {
  position: absolute;
  top: 0;
  left: 50%;
  width: auto;
  height: 100%;
  aspect-ratio: 16 / 9;
  transform: translateX(-50%);
  z-index: var(--z-shield);
  pointer-events: auto;
  cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='28' viewBox='0 0 28 28'%3E%3Ccircle cx='14' cy='14' r='10' fill='%23d93636' stroke='white' stroke-width='2'/%3E%3Cpath d='M7 7l14 14' stroke='white' stroke-width='3' stroke-linecap='round'/%3E%3C/svg%3E") 14 14, not-allowed;
  background: transparent;
}

/* Opt-in debug visualization for the otherwise-invisible shield: a faint red
   tint + 1px blur so you can see its exact footprint and how the cursor behaves
   over it. Enabled in the development client (see SpectatorShield.tsx and
   docs/z-index.md). */
.spectator-shield-debug {
  background: rgba(217, 54, 54, 0.12);
  backdrop-filter: blur(1px);
  -webkit-backdrop-filter: blur(1px);
}

.modal {
  --modal-accent: #2c6e49;
  width: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: rgba(24, 28, 33, 0.8);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: 1px solid var(--modal-accent);
  border-radius: var(--radius-lg);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.55), 0 0 24px -8px var(--modal-accent);
  overflow: hidden;
}

/* Modals that opt into `fill` (see Modal.tsx - currently NewTurn and the
   Game Menu) take up the whole safe band between the HUD keep-out zones, so
   they present at a consistent height no matter how much content they hold -
   a sparse panel gets trailing space rather than shrinking. Modals without
   the prop keep the original content-hugging behaviour. */
.modal-shell-fill {
  height: calc((100vh - var(--modal-safe-top) - var(--modal-safe-bottom)) * var(--game-scale-inverse, 1));
}

.modal-shell-fill .modal {
  flex: 1;
}

/* body grows to fill the now-taller modal and scrolls within it, so the
   header stays put and only the content area scrolls when it overflows. */
.modal-shell-fill .modal-body {
  flex: 1;
  min-height: 0;
}

/* Compact location-plaque header shared by every modal. It deliberately
   avoids a full-width website-style masthead: the emblem and two-line text
   stack identify the panel without consuming a large vertical band. */
.modal-header {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 7px;
  padding: 6px 8px 4px;
  background: color-mix(in srgb, var(--modal-accent) 7%, transparent);
}

.uni-node-lock-tag {
  --uni-tag-color: rgba(74, 78, 88, 0.92);
  width: auto;
  padding-right: 5px;
  color: rgba(255, 255, 255, 0.82);
  background: var(--uni-tag-color);
}

/* Small sign emblem rather than a large app-icon tile. */
.modal-icon {
  font-size: 15px;
  line-height: 1;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: 1px solid color-mix(in srgb, var(--modal-accent) 55%, rgba(255, 255, 255, 0.12));
  border-radius: 0;
  background: color-mix(in srgb, var(--modal-accent) 22%, rgba(255, 255, 255, 0.05));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 2px 5px rgba(0, 0, 0, 0.22);
}

.modal-heading {
  flex: 1;
  min-width: 0;
  padding-top: 0;
}

.modal-heading h3 {
  margin: 0;
  font-size: 12px;
  line-height: 15px;
  font-weight: 800;
  letter-spacing: 0.2px;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
}

.modal-tagline {
  margin: 0;
  font-size: 9px;
  line-height: 11px;
  font-style: italic;
  opacity: 0.75;
}

/* Quiet corner control that doesn't compete with the plaque title. */
.modal-header-actions { flex-shrink: 0; display: inline-flex; align-items: center; gap: 5px; }
.modal-header-icon { width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center; border-radius: 7px; background: rgba(255,255,255,.08); font-size: 12px; cursor: help; }

.modal-close {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  background: rgba(255, 255, 255, 0.08);
  border: none;
  border-radius: 7px;
  color: #fff;
  font-size: 11px;
  cursor: pointer;
  opacity: 0.75;
  transition: background var(--transition-fast), opacity var(--transition-fast), transform var(--transition-fast);
}

.modal-close:hover:not(:disabled) {
  opacity: 1;
  background: rgba(255, 255, 255, 0.18);
  transform: scale(1.08);
}

.modal-close:disabled {
  opacity: var(--disabled-opacity);
  cursor: not-allowed;
}

.modal-body {
  padding: 6px 16px 14px;
  overflow-y: auto;
  background: var(--modal-bg, transparent);
}

.modal-body p:first-child {
  margin-top: 0;
}

/* Modal CTAs (confirm/quit, Winners...) share the
   game's tactile button look - rounder corners, a glossy fill, a soft depth
   shadow and press feedback - instead of the flat 4px web-button base .btn.
   Only additive properties (radius/shadow/gloss) are set on .modal .btn so each
   button's own size modifier (.confirm-actions .btn) stays
   intact. Scoped to .modal so NameEntry / the app-error button stay flat.
   (Start turn / Ok are ActionButtons in a footer now, not .btn - see
   NewTurn / MessagesPresenter.) */
.modal .btn {
  border-radius: 10px;
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.3);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22), 0 3px 8px rgba(0, 0, 0, 0.28);
  transition: transform var(--transition-fast), box-shadow var(--transition-fast), filter var(--transition-fast);
}

.modal .btn:hover:not(:disabled) {
  filter: brightness(1.08);
  transform: translateY(-1px);
}

.modal .btn:active:not(:disabled) {
  transform: translateY(1px) scale(var(--press-scale));
}

.modal .btn-primary {
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.24), rgba(255, 255, 255, 0) 55%),
    var(--modal-accent, #2c6e49);
  color: #fff;
}

.modal .btn-danger {
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0) 55%),
    #a83232;
  color: #fff;
}

.modal .btn-secondary {
  background: rgba(255, 255, 255, 0.1);
}

/* winners modal */

.winners-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 4px 0 16px;
}

.winner-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  background: color-mix(in srgb, var(--modal-accent) 14%, rgba(255, 255, 255, 0.05));
  border: 1px solid color-mix(in srgb, var(--modal-accent) 45%, transparent);
  border-radius: var(--radius-md);
}

.winner-avatar {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  font-size: 14px;
}

.winner-name {
  flex: 1;
  font-weight: 700;
  font-size: 14px;
}

.winner-stats {
  font-size: 12px;
  opacity: 0.8;
  white-space: nowrap;
}

.winners-leave {
  width: 100%;
}

/* map */

.map-wrap {
  position: relative;
  width: 100%;
  /* fills whatever shape .game-main actually is - exactly 16:9 in the
     letterboxed/capped edge case, wider on anything above that (21:9, 32:9)
     - rather than forcing its own fixed ratio via the old padding-top
     hack. map.png is always fitted by height and centered (see .map-bg). */
  height: 100%;
  overflow: hidden;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
  /* NOT a container / stacking context on purpose: cqw sizing moved down onto
     the two inner layers (.map-placement-layer and .map-hud-layer, same width
     as this box), so those layers stack directly in .game-main's context. That
     is what lets the HUD (--z-hud) sit ABOVE portalled modals (--z-modal) while
     the map placement layer (--z-map) sits below them - impossible while this
     wrapper was one container trapping both. */
  /* Only visible beyond the three-panel background strip on fantastically
     wide displays; each map panel itself is always fitted by height below. */
  background: #0a0d12;
}

.map-bg-strip {
  position: absolute;
  top: 0;
  left: 50%;
  height: 100%;
  display: flex;
  transform: translateX(-50%);
  z-index: var(--z-map-bg);
}

.map-bg {
  width: auto;
  height: 100%;
  display: block;
  max-width: none;
  flex: none;
}

.map-bg-mirrored {
  transform: scaleX(-1) scale(1.012);
}

/* On layouts wider than the centered 16:9 gameplay plane, soften every
   visible peripheral pixel—from the viewport edge precisely up to each
   colored HUD border. This includes the wide edges of the center artwork,
   not only the reflected copies beyond it. */
.map-side-depth {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 100vw;
  background: rgba(5, 9, 15, 0.1);
  backdrop-filter: blur(3px) saturate(0.82) brightness(0.88);
  -webkit-backdrop-filter: blur(3px) saturate(0.82) brightness(0.88);
  pointer-events: none;
}

.map-side-depth-left {
  right: 100%;
}

.map-side-depth-right {
  left: 100%;
}

/* Building bounds, pins, paths, and sprites are generated in the centered
   16:9 window used before .map-wrap became fluid. Keep those placements on
   that same coordinate plane while the full-height background reveals more
   of its wide source image on wider screens. The image never scales by width,
   so it cannot crop vertically; mirrored copies continue its scenery beyond
   both outside edges. HUD elements remain direct children of
   .map-wrap so they continue to hug the actual viewport edges. */
.map-placement-layer,
.map-hud-layer {
  position: absolute;
  top: 0;
  left: 50%;
  width: auto;
  height: 100%;
  aspect-ratio: 16 / 9;
  transform: translateX(-50%);
  pointer-events: none;
  /* Map group. Its own container-type (below) makes each layer a stacking
     context, so their internal z-indexes stay self-contained; this value is
     only where the whole map layer sits in .game-main - below every modal. */
  z-index: var(--z-map);
}

.map-placement-layer {
  /* Tooltips, status signs, and the player vehicle use cqw. Resolve those
     units against the centered placement frame, not the ultrawide map-wrap. */
  container-type: inline-size;
}

.map-hud-layer {
  container-type: inline-size;
  /* HUD group sits above the shield and modals (see --z-* scale). Its own
     internal z-indexes (clock, corner, strip) stay contained by container-type
     above, so "order inside the group" is untouched. */
  z-index: var(--z-hud);
}

.map-hud-layer::before,
.map-hud-layer::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--player-color, rgba(255, 255, 255, 0.35));
  box-shadow: 0 0 12px -2px var(--player-color, transparent);
  pointer-events: none;
}

.map-hud-layer::before {
  left: 0;
}

.map-hud-layer::after {
  right: 0;
}

/* The HUD layer is pointer-events:none so its transparent gaps let clicks fall
   through to the map below (or, while spectating, to the shield). Its actual
   painted surfaces must opt back in, otherwise hovering their non-button areas
   falls through to the spectator shield and shows its not-allowed cursor. The
   player label (.player-stack > .turn-badge) already does this - the top-right
   menu/music cluster (.hud-corner) and the bottom strip's glass segments
   (.hud-strip-group / -center) get the same treatment so they read as normal,
   interactive HUD rather than shielded-out game surface. */
.map-hud-layer button,
.map-hud-layer [role="button"],
.map-hud-layer > .clock-wrap,
.map-hud-layer > .player-stack > .turn-badge,
.map-hud-layer > .hud-corner,
.map-hud-layer > .hud-strip .hud-strip-group,
.map-hud-layer > .hud-strip .hud-strip-center,
.map-hud-layer > .hud-strip .hud-pill {
  pointer-events: auto;
}

/* A quiet publisher-seal treatment above the lower HUD edge. It is part of
   the fixed 16:9 HUD plane, so it keeps the same relationship to the board
   at every viewport size without becoming another control. */
.map-brand-mark {
  position: absolute;
  right: 1.15cqw;
  bottom: 5.1cqw;
  width: 3.8cqw;
  height: 4.35cqw;
  object-fit: contain;
  opacity: 0.38;
  filter:
    drop-shadow(0 0 0.35cqw rgba(224, 184, 92, 0.28))
    drop-shadow(0 0 0.85cqw rgba(48, 82, 139, 0.18))
    drop-shadow(0 0.35cqw 0.65cqw rgba(0, 0, 0, 0.38));
  pointer-events: none;
}

.map-placement-layer > .map-building {
  pointer-events: auto;
}

.map-building {
  position: absolute;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  z-index: 1;
}

.map-building:disabled {
  cursor: not-allowed;
}

/* the exact-silhouette hover/active highlight - map-glow is a plain colored
   layer (the building's own accent) masked down to just its opaque pixels
   (src/assets/buildings/*.png, see split-building-sprites.mjs), instead of
   lighting up the whole rectangular hit-box. Invisible until hovered/here.
   Two copies of the same mask are layered (see the inline maskImage in
   Map.tsx), the second one scaled up 10% around the center - `exclude`/
   `xor` keeps only the sliver where just one of the two overlaps, i.e. a
   thin ring along the silhouette's edge, so the building's own art shows
   through the middle instead of getting a flat color wash over the whole
   shape. The drop-shadow blur then turns that ring into a soft glow. */
.map-glow {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: var(--building-accent, #ffc107);
  mask-repeat: no-repeat, no-repeat;
  mask-position: center, center;
  mask-size: 100% 100%, 104% 104%;
  mask-composite: exclude;
  -webkit-mask-repeat: no-repeat, no-repeat;
  -webkit-mask-position: center, center;
  -webkit-mask-size: 100% 100%, 104% 104%;
  -webkit-mask-composite: xor;
  mix-blend-mode: screen;
  opacity: 0;
  filter: drop-shadow(0 0 6px var(--building-accent, #ffc107)) drop-shadow(0 0 10px var(--building-accent, #ffc107));
  transition: opacity var(--transition-fast);
}

.map-building:hover:not(:disabled) .map-glow,
.map-building:focus-visible .map-glow {
  opacity: 0.85;
}

.map-building.here .map-glow {
  opacity: 0.65;
}

/* raise the hovered/focused building's glow above its neighbors so it never
   ends up peeking out from underneath a building that happens to overlap
   it further down the map. The tooltip itself is a separate top-level
   layer (see .map-building-tooltip below), not a descendant of this
   button, so it isn't capped by this z-index. */
.map-building:hover,
.map-building:focus-visible {
  z-index: 2;
}

/* the building's own nice hover/focus bubble - name plus whatever status
   badges apply (home/job/hiring/studying, shared with the pin's
   StatusSigns via buildingStatusBadges) - replacing the plain browser
   title tooltip. Shown even when the building is disabled (not your turn)
   so that's still communicated, just without the status list swapped in
   for a one-line note instead. Rendered as a top-level sibling of the
   buildings/pins/car (positioned via inline left/top computed from the
   hovered building's BUILDING_BOUNDS box in Map.tsx, since it's no longer
   a descendant of the button - see the hoveredBuilding state there) so it
   can sit above *everything* on the map, car included, rather than being
   capped by its old parent building's z-index. Sized in cqw (a % of
   .map-wrap's own width, via its container-type: inline-size above)
   rather than px, same idea as the pin signs/player-ball's %-of-.map-wrap
   sizing - it needs to stick to the *map's* size, not the surrounding
   page/HUD, so it doesn't end up looming over a shrunk-down map the way a
   fixed-px HUD pill/tooltip would. */
.map-building-tooltip {
  position: absolute;
  transform: translate(-50%, -50%);
  /* above the player ball (10) and status signs - a hovered building's
     info bubble should never be hidden underneath either of those. */
  z-index: 20;
  width: max-content;
  max-width: 20cqw;
  padding: 0.9cqw 1.2cqw;
  border-radius: 1.1cqw;
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 0.11cqw solid rgba(255, 255, 255, 0.14);
  box-shadow: 0 8px 18px rgba(0, 0, 0, 0.45);
  color: #fff;
  text-align: left;
  pointer-events: none;
}

.map-building-tooltip-name {
  display: block;
  font-size: 1.45cqw;
  font-weight: 700;
  letter-spacing: 0.01em;
  white-space: nowrap;
}

.map-building-tooltip-description {
  display: block;
  margin-top: 0.35cqw;
  font-size: 1.2cqw;
  line-height: 1.35;
  opacity: 0.85;
  white-space: normal;
}

.map-building-tooltip-not-your-turn {
  display: block;
  margin-top: 0.45cqw;
  font-size: 1.25cqw;
  font-weight: 600;
  color: #ff9c9c;
  white-space: nowrap;
}

.map-building-tooltip-statuses {
  display: flex;
  flex-direction: column;
  gap: 0.45cqw;
  margin-top: 0.65cqw;
  padding-top: 0.65cqw;
  border-top: 0.11cqw solid rgba(255, 255, 255, 0.16);
}

.map-building-tooltip-status {
  --status-color: #fff; /* overridden inline per-badge in Map.tsx */
  display: flex;
  align-items: center;
  gap: 0.65cqw;
  font-size: 1.25cqw;
  font-weight: 600;
  color: var(--status-color);
  white-space: nowrap;
}

.map-building-tooltip-status-icon {
  filter: drop-shadow(0 0 3px var(--status-color, transparent));
}

/* a small tilted signboard on a post, planted at the building's pin spot -
   the border color codes the status (home/job/hiring/study, see the
   per-sign `color` prop in Map.tsx) so it reads at a glance even before
   the emoji does. The name/icon signage itself is baked into the map art
   (map_layer_2.png), so this carries no background of its own.
   Positioned and sized exactly like .player-ball below - a direct cqw of
   .map-wrap, its containing block, since it's absolutely positioned
   straight inside it (no intermediate auto-sized wrapper, which is what
   made an earlier attempt at this collapse to ~0 on some layouts: a
   percentage can't resolve against a shrink-wrapped ancestor). cqw rather
   than plain % for *both* dimensions - including height - so the sign's
   own aspect ratio stays fixed regardless of .map-wrap's height:width
   ratio (a plain height:% would resolve against .map-wrap's height, which
   changes independently of its width whenever that ratio changes; cqw
   only ever tracks width, container-type: inline-size doesn't expose a
   block/height-based query unit at all). 4.44/5.78 matches the old
   40px/52px at the game's native 900-wide canvas (40/900, 52/900).
   --badge-index (set in Map.tsx) cascades multiple
   simultaneous badges at the same pin into a down-right fan, like a hand
   of cards, each one drawn in front of the last (see z-index below) -
   replaces an earlier attempt that centered them in a single row, which
   read as one blob rather than distinct signs once two badges' signboard
   art (which nearly fills its box - see the SVG in StatusSign.tsx)
   overlapped at anything less than a full sign-width of gap. */
.map-status-sign {
  --sign-color: #ffc107;
  --sign-w: 4.44cqw;
  --badge-index: 0; /* overridden inline per-badge in Map.tsx */
  position: absolute;
  width: var(--sign-w);
  height: 5.78cqw;
  /* cqw, not %, for the offset itself - translate()'s percentages resolve
     against this element's own (tiny) box, not .map-wrap, so a %-based
     offset here would be a fraction of a fraction and barely move at all.
     cqw resolves against .map-wrap (container-type: inline-size above),
     same unit the tooltip uses for everything including vertical spacing. */
  transform: translate(calc(-50% + var(--badge-index, 0) * 1.1cqw), calc(-50% + var(--badge-index, 0) * 0.8cqw));
  z-index: calc(4 + var(--badge-index, 0));
  filter: drop-shadow(0 0 3px var(--sign-color)) drop-shadow(0 0 6px var(--sign-color)) drop-shadow(0 2px 4px rgba(0, 0, 0, 0.5));
  cursor: default;
}

.map-status-sign svg {
  display: block;
  width: 100%;
  height: 100%;
  cursor: default;
}

/* cqw, not fixed px - the whole 16:9 canvas zooms to fit by resizing
   .map-wrap's actual box dimensions (see .game-main), so cqw sizes ride
   along with it at every viewport size; a fixed px car would stay full-size
   while the map around it shrank, looking oversized.
   Height is cqw too (not %) so the car's own aspect ratio stays fixed
   regardless of .map-wrap's height:width ratio - see .map-status-sign's
   comment above for why plain height:% is the wrong tool here. 2/3.33
   matches the old 18px/30px at the game's native 900-wide canvas
   (18/900, 30/900). */
.player-ball {
  position: absolute;
  width: 2cqw;
  height: 3.33cqw;
  /* above every status sign (see their z-index, capped at 4 badge types
     i.e. max 4 + 3) so the car is never hidden underneath a stacked-up
     building's posts. */
  z-index: 10;
  pointer-events: none;
  /* ground-contact shadow, plus a very faint halo in the player's own
     color (color-mix keeps it a soft, translucent tint rather than a
     hard-edged colored shadow) so the car reads as "theirs" without
     drawing attention away from the sprite itself. */
  filter:
    drop-shadow(0 2px 3px rgba(0, 0, 0, 0.5))
    drop-shadow(0 0 3px color-mix(in srgb, var(--player-color) 45%, transparent));
}

.player-ball svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* Application frames and controls use hard corners throughout. */
*,
*::before,
*::after {
  border-radius: 0 !important;
}

/* Keep genuinely circular indicators circular. */
.clock-face,
.clock-face::before,
.clock-center,
.clock-pin,
.avatar-circle,
.color-swatch {
  border-radius: 50% !important;
}

/* Override the menu button's older high-specificity responsive radius. */
.map-wrap .hud-top-actions .menu-btn {
  border-radius: 0 !important;
}

/* Player identity tiles are square everywhere: the in-game top-left label and
   the game-creation roster deliberately share this shape. These overrides
   come after the generic circular-avatar exception above. */
.turn-badge-avatar,
.intro-roster-tiles .intro-roster-avatar {
  border-radius: 0 !important;
}

.turn-badge-rank {
  width: 13px;
  min-width: 13px;
  height: 13px;
  padding: 0;
  border-radius: 0 !important;
}

.map-wrap .turn-badge-rank {
  width: 1.44cqw;
  min-width: 1.44cqw;
  height: 1.44cqw;
  padding: 0;
  border-radius: 0 !important;
}

/* The clock dock alone keeps rounded top shoulders where it rises from the
   square bottom HUD strip. */
.clock-wrap {
  border-radius: 15px 15px 0 0 !important;
}

.map-wrap .clock-wrap {
  border-radius: 3.42cqw 3.42cqw 0 0 !important;
}

