/* Design system - all values defined once, used everywhere */
:root {
  --bg-primary:    #0d0f0d;
  --bg-surface:    #141714;
  --bg-elevated:   #1c201c;
  --bg-panel:      #141714;
  --border:        #2a302a;
  --border-strong: #3a443a;

  --text-primary:  #c8d4c8;
  --text-secondary:#7a8c7a;
  --text-dim:      #4a5a4a;

  --accent:        #b8860b;
  --accent-dim:    #7a5a08;

  --tier-scouted:   #4a6741;
  --tier-claimed:   #2e6b8a;
  --tier-hardened:  #7a4a2e;
  --tier-fortified: #6b2e6b;

  --font-mono: "JetBrains Mono", "Fira Code", "Cascadia Code", monospace;
  --font-ui:   "Inter", system-ui, sans-serif;

  --radius:   4px;
  --gap:      8px;
}

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

/* Custom scrollbars — in-game style (Filip 2026-08-10): the default
   white/grey bars clashed with the dark UI. Thin, dark track, border-toned
   thumb that warms to the accent on hover. */
::-webkit-scrollbar {
  height: 8px;
  width: 8px;
}
::-webkit-scrollbar-track {
  background: rgba(0, 0, 0, 0.25);
}
::-webkit-scrollbar-thumb {
  background: var(--border-strong, #3a443a);
  border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
  background: var(--accent-dim, #7a5a08);
}
::-webkit-scrollbar-corner {
  background: transparent;
}

/* In-app update CHECK banner (Claude's spec 2026-08-05): a slim strip at the
   top, native-only, never modal. YELLOW so it reads as a notice (Filip
   2026-08-05); no dismiss X. The [hidden] guard is REQUIRED — the flex
   display otherwise overrides the hidden attribute (the 'X did nothing'
   symptom: hiding set hidden=true but the banner stayed visible). */
.update-banner {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1100;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  background: #c9a227;
  border-bottom: 1px solid #a8861c;
  font-size: 13px;
  font-weight: 600;
  color: #1a1505;
}
.update-banner[hidden] { display: none; }
.update-banner a {
  color: #1a1505;
  text-decoration: underline;
}
.update-banner a:hover { text-decoration: none; }
.update-banner-dismiss {
  margin-left: auto;
  background: none;
  border: none;
  color: #1a1505;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  padding: 0 4px;
}
.update-banner-dismiss:hover { opacity: 0.7; }

html, body {
  height: 100%;
  width: 100%;
  overflow: hidden;
  background: var(--bg-primary);
  color: var(--text-primary);
  font-family: var(--font-ui);
  font-size: 14px;
}

#app {
  display: flex;
  flex-direction: column;
  height: 100%;
  width: 100%;
  overflow: hidden;
  position: relative; /* anchors the coverage panel to the map area */
}

#map { flex: 1; }

/* Leaflet overrides */
.leaflet-container { background: var(--bg-primary); }
/* Android nav-bar safe area (tester ccchdro 2026-08-06): with viewport-fit=cover
   and MainActivity no longer consuming insets, env(safe-area-inset-bottom) carries
   the real system-bar height. Lift the bottom-right control stack above it —
   3-button nav used to swallow it (~48dp); gesture nav reports ~0 so nothing moves. */
.leaflet-container .leaflet-bottom { bottom: max(env(safe-area-inset-bottom, 0px), 0px); }
body.picking-location .leaflet-container { cursor: crosshair; }

.territory-frontier-edge {
  pointer-events: none;
  stroke-linecap: butt;
  stroke-linejoin: round;
}

.leaflet-position-pane svg,
.leaflet-position-pane path {
  mix-blend-mode: normal;
  isolation: isolate;
}

.leaflet-popup { margin-bottom: 16px; }

.leaflet-popup-content-wrapper {
  overflow: hidden;
  padding: 0;
  background: var(--bg-panel);
  color: var(--text-primary);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: 0 18px 55px rgba(0, 0, 0, 0.48);
}

.leaflet-popup-content {
  min-width: min(260px, calc(100vw - 42px));
  max-width: min(360px, calc(100vw - 42px));
  margin: 0;
  /* Filip 2026-08-05: the hex popup must scroll — the soldiers command section
     + picker can exceed the viewport (esp. on phones). Cap the height and let
     the content scroll inside the popup. */
  max-height: min(70vh, 560px);
  overflow-y: auto;
}

.leaflet-popup-tip {
  background: var(--bg-panel);
  border: 1px solid var(--border-strong);
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.34);
}

.leaflet-container a.leaflet-popup-close-button {
  top: 7px;
  right: 7px;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-elevated);
  color: var(--text-dim);
  font: 700 16px/26px var(--font-mono);
  text-align: center;
}

.leaflet-container a.leaflet-popup-close-button:hover {
  border-color: var(--accent-dim);
  color: var(--text-primary);
}

.leaflet-control-zoom a,
.leaflet-control-attribution {
  -webkit-tap-highlight-color: rgba(184, 134, 11, 0.28);
  background: rgba(20, 23, 20, 0.72) !important;
  border-color: rgba(42, 48, 42, 0.78) !important;
  color: rgba(200, 212, 200, 0.52) !important;
}

.leaflet-control-attribution a { color: rgba(200, 212, 200, 0.56) !important; }
.leaflet-control-zoom a:hover { color: rgba(200, 212, 200, 0.82) !important; }
.leaflet-control-zoom a:focus,
.leaflet-control-zoom a:active {
  background: var(--accent-dim) !important;
  border-color: var(--accent) !important;
  color: #fff !important;
  outline: none !important;
}

.gps-target-icon {
  position: relative;
  display: inline-block;
  width: 15px;
  height: 15px;
  border: 2px solid currentColor;
  border-radius: 50%;
  vertical-align: middle;
}

.gps-target-icon::before,
.gps-target-icon::after {
  content: "";
  position: absolute;
  background: currentColor;
}

.gps-target-icon::before {
  left: 50%;
  top: -5px;
  bottom: -5px;
  width: 2px;
  transform: translateX(-50%);
}

.gps-target-icon::after {
  top: 50%;
  left: -5px;
  right: -5px;
  height: 2px;
  transform: translateY(-50%);
}

.gps-follow-control button,
.manual-ping-control button,
.layer-toggle-control button {
  position: relative;
  overflow: hidden;
  display: block;
  width: 26px;
  height: 26px;
  padding: 0;
  background: rgba(20, 23, 20, 0.72);
  border: 0;
  border-radius: 2px;
  color: rgba(200, 212, 200, 0.52);
  line-height: 26px;
  cursor: pointer;
  -webkit-tap-highlight-color: rgba(184, 134, 11, 0.28);
}

/* Only the real .active (follow-on) state colors the GPS button. Previously
   :focus/:active reused this same style, so after tapping follow OFF the
   lingering focus made it look still-on until you tapped the map (Filip, S384) —
   a phantom third state. */
.gps-follow-control button.active,
.manual-ping-control button:active {
  background: var(--accent-dim);
  color: #fff;
  outline: none;
}

.manual-ping-control button:focus { outline: none; }

.manual-ping-control button.cooldown {
  background: rgba(20, 23, 20, 0.78);
  color: rgba(184, 134, 11, 0.72);
}

/* Cooldown countdown ring (Filip 2026-08-07): NOT a radar beam — a full
   circle that fills as the line sweeps clockwise, one complete circle in the
   cooldown duration (2 minutes) = the time until the next manual ping. The
   line moves continuously, "smooth like a Rolex" — a single CSS animation on
   the registered --sweep angle, so there are no per-second jumps (the old
   version re-seeded the animation every tick AND fell back to a stepped hand
   under reduced-motion — both are gone). The swept arc accumulates from the
   12 o'clock start, faint at the start and brightest right behind the line,
   with a crisp leading edge at --sweep. The negative animation-delay (seeded
   from --cooldown-progress, written ONCE per cooldown in map.js) lands the
   sweep at the right angle after a mid-cooldown page reload. The static ring
   stays; the grey-out when not applicable (no node / no position) is the
   button's normal :disabled state, untouched. */

@property --sweep {
  /* inherits: true — the animation runs on the BUTTON so both the countdown
     arc (button::before) and the target wipe (the icon's dot + ring) see the
     same angle. 2026-08-07. */
  syntax: "<angle>";
  inherits: true;
  initial-value: 0deg;
}

.manual-ping-control button.cooldown {
  background: rgba(20, 23, 20, 0.78);
  color: rgba(184, 134, 11, 0.72);
  /* The single animation drives the whole cooldown UI (arc + target wipe):
     one full clockwise circle in the cooldown duration (2 minutes). Smooth by
     construction — no JS ticks; the negative delay (seeded ONCE per cooldown
     in map.js) lands mid-cooldown reloads at the right angle. */
  animation: sweep-countdown var(--cooldown-duration, 120000ms) linear forwards;
  animation-delay: calc(var(--cooldown-progress, 0) * -1 * var(--cooldown-duration, 120000ms));
}

/* The countdown arc: fills from 12 o'clock as the line sweeps clockwise,
   faint at the start, brightest right behind the line, crisp leading edge at
   --sweep. (The static gold ring that used to sit behind the target is gone —
   Filip 2026-08-07.) */
.manual-ping-control button.cooldown::before {
  content: "";
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  background: conic-gradient(
    from 0deg,
    rgba(184, 134, 11, 0.05) 0deg,
    rgba(184, 134, 11, 0.30) calc(var(--sweep, 0deg) - 16deg),
    var(--accent) var(--sweep, 0deg) calc(var(--sweep, 0deg) + 3deg),
    transparent calc(var(--sweep, 0deg) + 3deg) 360deg
  );
  pointer-events: none;
}

@keyframes sweep-countdown {
  from { --sweep: 0deg; }
  to { --sweep: 360deg; }
}

/* Reduced motion: one rotation per 2 minutes is far below the motion-
   sensitivity thresholds — the smooth sweep stays for everyone (Filip
   2026-08-07). The old stepped hand (60 jumps over the cooldown) is gone. */

/* The TARGET wipes as the radar line passes (Filip 2026-08-07): AHEAD of the
   line (unswept / remaining time) the ring is the player's custom colour;
   BEHIND the line (swept / elapsed time) it slowly reverts to the default
   icon colour — the cooldown counts down in colour as well as by the arc.
   The ring is a conic DONUT: the conic can't be a border (a border can't vary
   around the circle), so it's a conic background + a ring-shaped mask. The
   mask MUST use `closest-side` — the default farthest-corner puts the opaque
   band outside the element's own circle (edges at 7px vs band at 7.2-9.9px),
   which rendered the ring invisible (Filip 2026-08-07). The dot is a solid
   accent — a conic wipe on a 4px dot is invisible anyway. */
.manual-ping-control button.cooldown .manual-ping-icon::before {
  background: var(--accent);
}

.manual-ping-control button.cooldown .manual-ping-icon::after {
  border: none;
  background: conic-gradient(
    from 0deg,
    currentColor 0deg,
    currentColor var(--sweep, 0deg),
    var(--accent) var(--sweep, 0deg) 360deg
  );
  /* closest-side: radius = half the element (7px) — transparent to 71% (5px),
     opaque from 74% (5.2px) to the edge = a proper ~1.8px ring. */
  -webkit-mask: radial-gradient(circle closest-side, transparent 71%, #000 74%);
  mask: radial-gradient(circle closest-side, transparent 71%, #000 74%);
}

.manual-ping-control button.ping-sent::after,
.manual-ping-control button.ping-blocked::after {
  content: "";
  position: absolute;
  inset: 7px;
  border-radius: 50%;
  border: 2px solid currentColor;
  pointer-events: none;
  animation: manual-ping-sonar 700ms ease-out;
}

.manual-ping-control button.ping-blocked::after { border-color: #c86a4a; }

.gps-follow-control button:hover,
.manual-ping-control button:hover,
.layer-toggle-control button:hover { color: rgba(200, 212, 200, 0.82); }

.layer-toggle-control { position: relative; }

.layer-toggle-icon {
  display: block;
  width: 100%;
  height: 100%;
  position: relative;
  color: rgba(200, 212, 200, 0.72);
}
.layer-toggle-icon::before,
.layer-toggle-icon::after {
  content: "";
  position: absolute;
  left: 5px;
  right: 5px;
  height: 1.5px;
  background: currentColor;
}
.layer-toggle-icon::before { top: 9px; }
.layer-toggle-icon::after { top: 15px; }

.layer-toggle-panel[hidden] { display: none; }

.layer-toggle-panel {
  position: absolute;
  /* Control now lives in the bottom-right corner, so the panel opens UP and
     to the LEFT (inward) — anchored to the button's bottom/inner edge so it's
     never clipped by the map's right or bottom edge (Filip S393). */
  bottom: 0;
  right: 32px;
  z-index: 900;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 190px;
  /* Landscape phones are short: cap height and scroll so no option is ever
     pushed off the top of the screen (Filip S393). */
  max-height: 70vh;
  overflow-y: auto;
  padding: 8px 10px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-primary);
}

.layer-toggle-panel label {
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  white-space: nowrap;
}

/* The player's own CARTO key field (2026-09-25). The app ships no tile key any more, so a player who
   wants the CARTO basemaps pastes theirs here; without one the key-free default layer is used.
   Styled like the panel's own rows rather than the OS default. */
.layer-toggle-panel .layer-key-row {
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
  cursor: default;
  white-space: normal;
}

.layer-toggle-panel .layer-key-row input {
  box-sizing: border-box;
  width: 100%;
  padding: 4px 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-primary);
  background: var(--bg-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.layer-toggle-panel .layer-key-row input:focus {
  outline: none;
  border-color: var(--accent);
}

.layer-toggle-divider {
  height: 1px;
  margin: 2px 0;
  background: var(--border);
}

/* Territory ↔ Coverage segmented switch at the top of the layer panel. */
.layer-mode-switch {
  display: flex;
  gap: 0;
  margin-bottom: 2px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.layer-mode-btn {
  flex: 1;
  padding: 4px 6px;
  border: none;
  background: transparent;
  color: var(--text-secondary, rgba(200, 212, 200, 0.7));
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.03em;
  cursor: pointer;
}
.layer-mode-btn + .layer-mode-btn { border-left: 1px solid var(--border); }
.layer-mode-btn:hover { color: var(--text-primary); }
.layer-mode-btn.active {
  background: var(--accent-dim);
  color: #fff;
}
.layer-row-territory[hidden] { display: none; }

/* Coverage stats/legend panel — bottom-left, coverage mode only. */
.coverage-panel[hidden] { display: none; }
.coverage-panel {
  position: absolute;
  left: 10px;
  bottom: 22px;
  z-index: 600;
  padding: 8px 10px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-primary);
  pointer-events: none; /* purely informational — never eats a map drag */
}
.coverage-panel-title {
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-secondary, rgba(200, 212, 200, 0.7));
  margin-bottom: 5px;
}
/* Filip 2026-08-10: the signal-strength legend JOINED this panel (it used to
   float bottom-right) — a header row with the single dismiss ✕, and the
   legend renders inline below the stats. The panel itself is pointer-events:
   none (informational), so the ✕ must opt back in. */
.coverage-panel-header {
  align-items: center;
  display: flex;
  justify-content: space-between;
  margin-bottom: 5px;
}
.coverage-panel-header .coverage-panel-title { margin-bottom: 0; }
.coverage-panel .mt-coverage-legend-close { pointer-events: auto; }
.coverage-panel .mt-coverage-legend {
  background: transparent;
  border: 0;
  border-radius: 0;
  border-top: 1px solid var(--border);
  bottom: auto;
  font-size: 10px;
  margin-top: 6px;
  padding: 6px 0 0;
  position: static;
  right: auto;
  width: auto;
  z-index: auto;
}
.coverage-legend {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin-bottom: 6px;
}
.coverage-legend-item {
  display: flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}
.coverage-pct { color: var(--text-secondary, rgba(200, 212, 200, 0.7)); }
.coverage-swatch {
  width: 12px;
  height: 12px;
  border-radius: 3px;
  flex: 0 0 auto;
}
.coverage-swatch-mt { background: #5fd768; }
.coverage-swatch-mc { background: #4a90e2; }
/* Half green / half blue = the both-network split hex, matched on the map. */
.coverage-swatch-both { background: linear-gradient(90deg, #5fd768 0 50%, #4a90e2 50% 100%); }
.coverage-stats { color: var(--text-secondary, rgba(200, 212, 200, 0.7)); }

/* Permanent heard-node name labels in Coverage mode (the "Meshmap look"). */
.heard-node-label.leaflet-tooltip {
  background: rgba(20, 23, 20, 0.82);
  border: none;
  box-shadow: none;
  color: #e6e9e6;
  font-family: var(--font-mono);
  font-size: 10px;
  padding: 1px 5px;
}
.heard-node-label.leaflet-tooltip::before { display: none; }

.main-menu-accent {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  font-size: 13px;
  color: var(--text-primary);
}
.main-menu-accent-label { flex: 1; }
.main-menu-accent input[type="color"] {
  width: 32px;
  height: 26px;
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-elevated);
  cursor: pointer;
}
.main-menu-accent button {
  font-size: 11px;
  padding: 4px 9px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: transparent;
  color: var(--text-primary);
  opacity: 0.8;
  cursor: pointer;
}
.main-menu-accent button:hover {
  color: var(--accent);
  border-color: var(--accent);
  opacity: 1;
}

.manual-ping-control button:disabled {
  cursor: default;
  color: rgba(200, 212, 200, 0.28);
}

.manual-ping-control button.cooldown:disabled { color: rgba(184, 134, 11, 0.72); }

.manual-ping-icon {
  position: relative;
  display: inline-block;
  width: 16px;
  height: 16px;
  vertical-align: middle;
  z-index: 1;
}

.manual-ping-icon::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: currentColor; /* default: same colour as the other icons/buttons */
  transform: translate(-50%, -50%);
}

.manual-ping-icon::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 14px;
  height: 14px;
  border: 2px solid currentColor; /* default: same colour as the other icons/buttons */
  border-radius: 50%;
  box-sizing: border-box;
  transform: translate(-50%, -50%);
}

/* Cooldown: the pressed ping turns the TARGET into the player's custom colour
   for the whole cooldown, then back to the default icon colour (Filip
   2026-08-07). The grey-out when not applicable (no node / no position) is the
   button's normal :disabled colour flowing through currentColor, untouched. */
.manual-ping-control button.cooldown .manual-ping-icon::before,
.manual-ping-control button.cooldown .manual-ping-icon::after {
  background: var(--accent);
  border-color: var(--accent);
}

@keyframes manual-ping-sonar {
  from {
    opacity: 0.9;
    transform: scale(0.45);
  }
  to {
    opacity: 0;
    transform: scale(2.1);
  }
}

/* Hex popup */
.hex-popup {
  padding: 10px 14px 14px;
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.6;
}

.tier-badge {
  display: inline-block;
  padding: 1px 6px;
  border-radius: var(--radius);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  margin-bottom: 4px;
}

.tier-scouted   { background: var(--tier-scouted);   color: #c8d4c8; }
.tier-claimed   { background: var(--tier-claimed);   color: #c8d4c8; }
.tier-hardened  { background: var(--tier-hardened);  color: #c8d4c8; }
.tier-fortified { background: var(--tier-fortified); color: #c8d4c8; }
.tier-surveyed  { background: #555a5e; color: #c8d4c8; }

.hex-popup-meta {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 12px;
  align-items: baseline;
  margin: 0 0 8px;
  padding-right: 34px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border);
}

.hex-popup > .owner {
  margin: 0 0 8px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border);
}

.hex-popup .owner {
  color: var(--accent);
  font-weight: 700;
  text-transform: uppercase;
  overflow-wrap: anywhere;
}

.hex-popup .hex-id {
  color: var(--text-secondary);
  font-size: 10px;
  text-align: right;
  overflow-wrap: anywhere;
}
.hex-popup .discovery { color: var(--text-secondary); font-size: 11px; margin-top: 2px; }

.node-popup {
  min-width: 150px;
  padding: 10px 12px 12px;
  line-height: 1.45;
}

.asset-map-popup .leaflet-popup-content {
  min-width: min(210px, calc(100vw - 42px));
}

.asset-map-popup .hex-popup {
  padding: 9px 12px 10px;
  line-height: 1.42;
}

.asset-map-popup .hex-popup > .owner {
  margin-bottom: 5px;
  padding-bottom: 5px;
  padding-right: 28px;
}

.leaflet-container .asset-map-popup a.leaflet-popup-close-button {
  top: 5px;
  right: 5px;
  width: 22px;
  height: 22px;
  font: 700 14px/20px var(--font-mono);
}

.node-popup .owner {
  margin-bottom: 6px;
  padding-right: 34px;
  padding-bottom: 6px;
  overflow-wrap: anywhere;
}

.node-popup .node-id {
  color: var(--text-secondary);
  font-size: 10px;
  overflow-wrap: anywhere;
}

.compact-map-popup .leaflet-popup-content {
  min-width: 0;
  width: auto !important;
  max-width: min(220px, calc(100vw - 42px));
}

.phone-position-popup {
  min-width: 132px;
  padding: 10px 12px 12px;
}

.phone-position-popup .owner {
  margin-bottom: 5px;
  padding-right: 28px;
  padding-bottom: 5px;
}

.leaflet-container .compact-map-popup a.leaflet-popup-close-button {
  top: 5px;
  right: 5px;
  width: 22px;
  height: 22px;
  font: 700 14px/20px var(--font-mono);
}

/* Status bar */
#status-bar {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 6px 10px;
  background: var(--bg-surface);
  border-bottom: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: 12px;
  max-width: 100vw;
}

.status-main-row,
.node-state-row {
  display: flex;
  align-items: center;
  min-width: 0;
  width: 100%;
}

.status-main-row {
  flex-wrap: wrap;
  gap: var(--gap);
}

.node-state-row {
  gap: 6px;
  padding-top: 4px;
  border-top: 1px solid rgba(42, 48, 42, 0.62);
}

.node-state-label[hidden] { display: none; }

.node-state-label {
  flex: 0 0 auto;
  color: var(--accent);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
}

.node-state-chip {
  flex: 0 0 auto;
  min-width: 26px;
  padding: 1px 5px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-dim);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.06em;
  line-height: 1.35;
  text-align: center;
}

.node-state-chip[data-state="connected"] { color: #8fd3ff; border-color: rgba(143, 211, 255, 0.45); }
.node-state-chip[data-state="running"] { color: #9bd08f; border-color: rgba(155, 208, 143, 0.48); }
.node-state-chip[data-state="silent"] { color: #b6a2d8; border-color: rgba(182, 162, 216, 0.48); min-width: 22px; padding-inline: 4px; }
.node-state-chip[data-state="listening"] { color: var(--accent); border-color: var(--accent-dim); }

/* Dedicated ping row (Filip S392) — a separate status line under the node-info
   row that shows ONLY ping activity/replies. Appears on any ping (manual/auto)
   and auto-hides 25s after the most recent one, so a real ping response is never
   confused with ambient traffic. Dashed separator + blue label set it apart. */
.ping-row {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  width: 100%;
  padding-top: 4px;
  margin-top: 4px;
  border-top: 1px dashed rgba(90, 110, 130, 0.5);
}
.ping-row[hidden] { display: none; }
.ping-row-label { color: #8fd3ff; }
.ping-status {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 11px;
  font-weight: 600;
  color: var(--text-secondary);
}
.ping-status.ping-sent { color: var(--accent); }
.ping-status.ping-response { color: #9bd08f; font-weight: 800; }
.ping-status.ping-dark { color: #b56b5a; }
.ping-status.ping-traffic { color: #8fd3ff; }
.ping-status.ping-error { color: #d0a536; } /* 2026-08-27: local send failed — amber, the "needs attention" tone */

.status-item { color: var(--text-secondary); white-space: nowrap; }

.status-event {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--text-secondary);
}

/* Long status sentences scroll back and forth so nothing is clipped in portrait
   (Filip #45). Only applied when JS (applyEventText) detects real overflow. */
.status-event-text { display: inline-block; white-space: nowrap; }
.status-event.is-marquee .status-event-text {
  animation: status-event-marquee var(--marquee-duration, 8s) ease-in-out infinite alternate;
}
@keyframes status-event-marquee {
  from { transform: translateX(0); }
  to { transform: translateX(var(--marquee-distance, 0)); }
}
@media (prefers-reduced-motion: reduce) {
  .status-event.is-marquee .status-event-text { animation: none; }
}

#source-select,
.mode-action-btn,
#connect-btn,
#recon-btn,
#deploy-btn,
#harden-btn,
#menu-btn {
  background: var(--bg-elevated);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 4px 10px;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  cursor: pointer;
}

#source-select { max-width: 150px; padding-right: 4px; }

/* Connected node name — first in the header (Filip 2026-08-03). The hex id is
   deliberately NOT shown; the name (or "-") is. NO flex-grow (a long name must
   not push the buttons out — Filip 2026-08-03: keep room for Deploy/Attack);
   truncates with ellipsis instead. The "|" separator is its own element. */
.status-node {
  flex: 0 1 auto;
  max-width: 120px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.status-sep {
  color: var(--text-dim);
  padding: 0 4px;
  flex: 0 0 auto;
  white-space: nowrap;
}

/* Context "activate" button — only shown on a besiegeable enemy hex (or as a
   status chip once the attack is underway). SAME size as the other header
   buttons (Filip 2026-08-03: it rendered bigger) — no uppercase, no inflated
   padding; the red stays so it still reads as the attack button. */
.mode-action-btn {
  background: #e23b2e;
  border-color: #e23b2e;
  color: #fff;
  height: auto;
  text-transform: none;
  white-space: nowrap;
}
.mode-action-btn:hover { background: #c8342a; }
.mode-action-btn:disabled {
  background: var(--bg-elevated);
  border-color: var(--border);
  color: var(--text-secondary);
  cursor: default;
}
.mode-action-btn:disabled:hover { background: var(--bg-elevated); }

/* Sentry (Filip 2026-08-03): stationary listening session. Styled exactly
   like the other header buttons (Filip: buttons don't take the player colour)
   — the countdown text IS the status. Cooling down = dimmed like a disabled
   control. */
/* Attack status chip in the hex popup (Filip 2026-08-03): a non-button line
   replacing the attack button once the siege is running / cap is spent. */
.hex-popup-actions.siege-status {
  color: var(--text-secondary);
  font-size: 12px;
  padding: 4px 0;
  white-space: normal;
}

#recon-btn:hover,
#source-select:hover,
.mode-segment:hover,
#connect-btn:hover,
#deploy-btn:hover,
#harden-btn:hover,
#menu-btn:hover { border-color: var(--accent-dim); }

#recon-btn.active,
#source-select.active,
#connect-btn.active,
#deploy-btn.active,
#harden-btn.active {
  background: var(--accent-dim);
  border-color: var(--accent);
  color: #fff;
}

#connect-btn:disabled.active {
  cursor: default;
  opacity: 1;
}

.status-item-btn {
  background: none;
  border: none;
  color: var(--text-dim);
  cursor: pointer;
  font-size: 13px;
  padding: 0 2px;
}

.status-item-btn:hover { color: var(--text-secondary); }

#menu-btn { flex: 0 0 auto; margin-left: auto; font-size: 15px; line-height: 1; padding: 4px 9px; }
#menu-btn[aria-expanded="true"] { background: var(--accent-dim); border-color: var(--accent); color: #fff; }

.main-menu[hidden] { display: none; }

.main-menu {
  position: fixed;
  top: 52px;
  right: 8px;
  z-index: 900;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 160px;
  padding: 8px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
  font-family: var(--font-mono);
}

.main-menu-info {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 2px 6px 8px;
  margin-bottom: 4px;
  border-bottom: 1px solid rgba(42, 48, 42, 0.62);
  font-size: 11px;
}

.menu-points { color: var(--accent); font-weight: 700; }
.menu-node-status { color: var(--text-secondary); }
.menu-node-status.connected { color: #8fd3ff; }
.menu-node-status.running { color: #9bd08f; }

.main-menu-item {
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius);
  color: var(--text-primary);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  text-align: left;
  padding: 6px 8px;
}

.main-menu-item:hover { background: var(--bg-surface); border-color: var(--border); }

/* Profile entry gets its own thin accent outline (Filip, S383: "circled...
   making it stand out as a Profile button") - a pill shape, distinct from
   Fleet's plain hover-only look, since it's the one item that's really an
   account/identity control rather than a navigation link. */
#status-callsign.main-menu-item {
  border-color: var(--accent-dim);
  border-radius: 999px;
  color: var(--accent);
}
#status-callsign.main-menu-item:hover { border-color: var(--accent); }

.main-menu-logout {
  margin-top: 4px;
  padding-top: 8px;
  border-top: 1px solid rgba(42, 48, 42, 0.62);
  color: #c86a4a;
}

@media (max-width: 640px) {
  #status-bar {
    gap: 5px;
    padding: 5px 5px;
    font-size: 11px;
  }

  .status-main-row {
    flex-wrap: nowrap;
    gap: 4px;
  }

  .status-node {
    overflow: hidden;
    text-overflow: ellipsis;
  }

  #status-tx {
    flex: 0 1 38px;
    min-width: 34px;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: 10px;
  }

  #source-select,
  .mode-segment,
  #connect-btn,
  #recon-btn,
  #deploy-btn,
  #harden-btn,
  #menu-btn {
    padding: 3px 5px;
    font-size: 10px;
    letter-spacing: 0;
  }

  #recon-btn,
  #deploy-btn,
  #harden-btn {
    flex: 0 0 auto;
  }

  .notif-btn {
    padding: 3px 6px;
    font-size: 12px;
  }

  #menu-btn {
    padding: 3px 6px;
    font-size: 13px;
  }

  #source-select { max-width: 118px; }
  .mode-segment { flex: 0 0 auto; }
  .mode-segment button { min-width: 27px; padding-inline: 4px; }
}

/* Auth overlay */
.auth-overlay {
  /* Fixed, full-viewport, above everything. Explicit top/right/bottom/left
     (not the inset shorthand) + width/height: the old shorthand was a
     suspect for the "pushes the app down" report on Android WebView; the
     explicit form is bulletproof. Sits ABOVE the app (z-index 1000 > all
     app/leaflet panes), so it can never push the app or sit under the header.
     background is translucent + backdrop-filter so the map shows BLURRED
     behind the login card (Filip 2026-08-05). */
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(13, 15, 13, 0.6);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  z-index: 1000;
}

.auth-overlay[hidden] { display: none; }

.auth-form {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  width: min(300px, 90vw);
  padding: 24px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.auth-form h1 {
  font-family: var(--font-mono);
  font-size: 18px;
  color: var(--accent);
  text-align: center;
  margin-bottom: 8px;
  letter-spacing: 0.04em;
}

.auth-form input {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-primary);
  font-family: var(--font-ui);
  font-size: 14px;
  padding: 8px 10px;
}

.auth-form input:focus { outline: none; border-color: var(--accent-dim); }

.auth-remember {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--text-secondary);
  font-family: var(--font-ui);
  font-size: 12px;
  cursor: pointer;
}

.auth-actions { display: flex; gap: var(--gap); margin-top: 4px; }

.auth-actions button {
  flex: 1;
  background: var(--bg-elevated);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  cursor: pointer;
}

.auth-actions button:hover { border-color: var(--accent-dim); }
#auth-login-btn { background: var(--accent-dim); border-color: var(--accent); color: #fff; }

.auth-error {
  color: #c86a4a;
  font-size: 12px;
  min-height: 14px;
  text-align: center;
}

/* Awaiting approval / rejected (S421). A full-screen overlay, like the auth overlay it mirrors: the app behind it
   is never booted for such an account, so this has to stand on its own. */
.awaiting-overlay {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--bg, #0a0d0e);
  z-index: 1000;
}
.awaiting-overlay[hidden] { display: none; }

.awaiting-box {
  max-width: 460px;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 10px;
  text-align: center;
}
.awaiting-box h1 { font-size: 20px; margin: 0; }
.awaiting-message { color: var(--muted, #8a9a9a); font-size: 13px; margin: 0; }
.awaiting-reason { color: #c86a4a; font-size: 13px; margin: 0; }
.awaiting-notifications { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.awaiting-note {
  font-size: 12px;
  text-align: left;
  padding: 8px 10px;
  border: 1px solid var(--border, #223034);
  border-radius: 6px;
  color: var(--text, #c8d4d0);
}

/* The link to the public site from the login/register screen (Claude S421): the one place a visitor without an
   invite already looks. Quiet by design — it must not compete with Login/Register. */
.auth-site-link {
  margin: 2px 0 0;
  font-size: 12px;
  text-align: center;
  color: var(--muted, #8a9a9a);
}

.auth-site-link a { color: var(--accent, #3fa3b8); }

/* Radio Room / Profile slide-in panels */
.radio-room {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(340px, 90vw);
  background: var(--bg-surface);
  border-left: 1px solid var(--border);
  box-shadow: -8px 0 24px rgba(0, 0, 0, 0.5);
  z-index: 900;
  overflow-y: auto;
  padding: 16px 16px max(env(safe-area-inset-bottom, 0px), 16px);
  font-family: var(--font-mono);
  font-size: 12px;
}

.radio-room[hidden] { display: none; }

.radio-room-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
}

.radio-room-header h2 {
  font-size: 14px;
  color: var(--accent);
  letter-spacing: 0.06em;
}

.radio-room-section { margin-bottom: 20px; }

.connect-row {
  display: flex;
  gap: 8px;
}

.connect-row #source-select { flex: 1 1 auto; max-width: none; }
.connect-row #connect-btn { flex: 0 0 auto; }

.radio-room-section h3 {
  font-size: 11px;
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin-bottom: 8px;
  border-bottom: 1px solid var(--border);
  padding-bottom: 4px;
}

.node-card {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px 10px;
  margin-bottom: 8px;
}

.node-card-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.node-card .node-type {
  color: var(--accent);
  font-weight: 600;
  text-transform: uppercase;
  font-size: 10px;
}

.node-card .node-id { color: var(--text-dim); font-size: 10px; }
.node-card > .node-id { display: block; margin: 2px 0 4px; }

.profile-callsign {
  color: var(--text-primary);
  font-size: 16px;
  font-weight: 700;
  margin: 6px 0 10px;
  overflow-wrap: anywhere;
}

.profile-stat {
  border-top: 1px solid rgba(42, 48, 42, 0.78);
  padding: 8px 0 6px;
}

.profile-stat-value {
  font-family: var(--font-mono, monospace);
  color: var(--text-primary, #ddd);
  font-size: 13px;
}

.profile-stat:first-of-type { border-top: 0; }
.profile-stat.compact { padding-bottom: 2px; }

.profile-stat-label {
  display: block;
  color: var(--text-secondary);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.profile-stat strong {
  display: block;
  color: var(--text-primary);
  font-size: 18px;
  line-height: 1.25;
  margin-top: 2px;
  overflow-wrap: anywhere;
}

.profile-stat-note {
  display: block;
  color: var(--text-dim);
  font-size: 10px;
  line-height: 1.35;
  margin-top: 2px;
}

.node-card .node-status { font-size: 10px; }
.node-card .node-status.online,
.node-card .node-status.connected { color: var(--tier-scouted); }
.node-card .node-status.offline,
.node-card .node-status.disconnected { color: var(--text-dim); }

.node-card select,
.node-card input {
  width: 100%;
  background: var(--bg-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: 11px;
  padding: 4px 6px;
  margin-top: 6px;
}

.node-card button {
  margin-top: 6px;
  width: 100%;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  padding: 5px;
  cursor: pointer;
}

.node-card button:hover { border-color: var(--accent-dim); }
.node-card button.claim-btn { background: var(--accent-dim); border-color: var(--accent); color: #fff; }
.node-card button.bt-connect-btn.active { background: var(--tier-claimed); border-color: var(--accent-dim); color: #fff; }
.node-card button.track-btn.active { background: var(--tier-scouted); border-color: var(--accent-dim); color: #fff; }
.node-card button[data-deploy-node-role].active { background: var(--tier-hardened); border-color: var(--accent); color: #15120a; }
.node-card button.release-btn { color: #c86a4a; }

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

.node-role-row .fleet-role-select {
  margin: 0;
}

.fleet-deploy-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px;
  margin-top: 6px;
}

.fleet-deploy-row button {
  margin-top: 0;
}

.fleet-deploy-row button.active[disabled],
.fleet-deploy-row button.release-btn {
  grid-column: 1 / -1;
}

.ble-scan-results {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 6px;
}

.ble-device {
  border-top: 1px solid var(--border);
  padding-top: 6px;
}

.ble-device-name {
  color: var(--text-primary) !important;
  font-weight: 700;
}

.node-card button.subtle-btn {
  color: var(--text-secondary);
  font-weight: 500;
}

.node-card .node-name {
  color: var(--text-primary);
  font-size: 11px;
  overflow-wrap: anywhere;
}

.radio-room-empty { color: var(--text-dim); font-style: italic; }

.role-line { color: var(--text-secondary); font-size: 11px; margin-top: 2px; }
.role-desc-line { color: var(--text-dim); font-size: 10.5px; line-height: 1.35; margin: 3px 0; }
.pending-role-time { color: var(--text-dim); }
.role-points-line { color: var(--text-secondary); font-size: 11px; margin-top: 2px; }
.asset-status-row {
  display: flex;
  align-items: center;
  margin: 0 0 6px;
}
.asset-status-pill {
  display: block;
  width: 100%;
  min-height: 26px;
  padding: 5px 7px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-elevated);
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  line-height: 1.25;
  text-transform: uppercase;
}
.asset-status-active {
  background: var(--accent-dim);
  border-color: var(--accent);
  color: #fff;
}
.asset-status-deploying {
  border-color: var(--tier-hardened);
  color: var(--tier-hardened);
}
.asset-status-expired {
  border-color: #c86a4a;
  color: #c86a4a;
}
.contested-line { color: #c86a4a; font-size: 11px; font-weight: 700; margin-top: 2px; }
.under-siege-line { color: #e23b2e; font-size: 11px; font-weight: 800; margin-top: 2px; letter-spacing: 0.3px; }
.hex-attack-btn { background: #e23b2e; }
.hex-incur-btn { background: #d98a1f; }
.hex-reinforce-btn { background: #2e6fe2; }
.position-unconfirmed-warning {
  color: #c86a4a;
  font-size: 11px;
  font-weight: 600;
  margin-top: 2px;
}
.best-signal-line, .nearest-rptr-line, .decay-line {
  color: var(--text-secondary);
  font-size: 11px;
  margin-top: 2px;
}
/* The "what advances this hex" hint (#28) — accented so it reads as the action. */
.next-step-line {
  color: var(--accent);
  font-size: 11px;
  margin-top: 4px;
  line-height: 1.35;
}
.network-badge {
  display: inline-block;
  padding: 0 4px;
  border-radius: 3px;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.02em;
}
/* App-wide convention (Filip): MT = green, MC = blue. Must match the coverage
   dot colors in hexLayer.js (COVERAGE_COLORS). These were swapped before S384. */
.network-badge.network-mt { background: #4a6741; color: #e4f2df; }
.network-badge.network-mc { background: #2e6b8a; color: #dff0ff; }

/* Node identity: name prominent, id toned down beneath it (Filip, S384). */
.node-display { display: inline-flex; flex-direction: column; vertical-align: middle; line-height: 1.15; }
.node-display .node-name { font-weight: 600; }
.node-display .node-id-sub { font-size: 0.78em; opacity: 0.6; }

/* Coverage popup leads with the heard node's name as its title (Filip, S384). */
.coverage-popup-title {
  margin-bottom: 8px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border);
  color: var(--accent);
  font-size: 1.05em;
}
.coverage-popup-title .node-name { font-weight: 700; }
.hex-role-marker { pointer-events: auto; }
.hex-role-label {
  background: transparent;
  border: 0;
  box-shadow: none;
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 800;
  text-shadow: 0 1px 2px #000;
}
.hex-role-label-pending { color: var(--text-secondary); }
/* Leaflet gives every directional tooltip a default arrow (border-color
   triangle) tied to its own leaflet-tooltip-{top,bottom,left,right} class,
   completely separate from the .hex-role-label styling above - clearing
   the label's own background/border/shadow never touches it. Left
   unstyled, it renders as a plain white downward triangle sitting right at
   the marker (Filip, S383: "a white down pointing arrow... marking the
   spot where the icon is located"). Hidden outright since the label itself
   has no background to point from in the first place. */
.hex-role-label::before { display: none; }

/* Icon-badge markers (S383) - a role-colored circular badge with a small
   grey glyph inside, used for both hex-role markers (LP/OP/Overwatch/Base,
   hexLayer.js) and Fleet HQ/RPTR position markers (map.js). */
.hex-role-icon { pointer-events: auto; }
.hex-role-icon-badge {
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.55);
}
.hex-role-icon-glyph svg { width: 100%; height: 100%; display: block; }

.deploy-panel {
  position: fixed;
  left: 50%;
  bottom: calc(18px + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  width: min(340px, calc(100vw - 24px));
  /* 2026-08-09 (Filip): in landscape the panel outgrew the viewport and its
     bottom half (behind the header/off-screen) was unreachable — no scroll.
     Cap the height and scroll the body; the header row stays visible. */
  max-height: calc(100vh - 130px - env(safe-area-inset-bottom, 0px));
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.55);
  z-index: 910;
  padding: 12px;
  font-family: var(--font-mono);
}

.deploy-panel[hidden] { display: none; }

.deploy-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
}

.deploy-panel-header h2 {
  color: var(--accent);
  font-size: 13px;
  letter-spacing: 0.06em;
}

.deploy-hex {
  color: var(--text-dim);
  font-size: 10px;
  margin-bottom: 8px;
  overflow-wrap: anywhere;
}

.deploy-options {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
}

.deploy-options button {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-primary);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  text-align: left;
  padding: 7px 10px;
}

.deploy-options button:hover { border-color: var(--accent-dim); }

.deploy-role-label,
.deploy-role-cost,
.deploy-role-desc {
  display: block;
}

.deploy-role-desc {
  color: var(--text-dim);
  font-size: 9px;
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0;
  margin-top: 2px;
  white-space: normal;
}

.deploy-role-cost {
  color: var(--text-dim);
  font-size: 10px;
  font-weight: 600;
  margin-top: 2px;
}

.sentry-nodes-list { max-height: 60vh; overflow-y: auto; }
.sentry-node-row {
  display: flex; justify-content: space-between; align-items: center; gap: 8px;
  padding: 6px 8px; border-bottom: 1px solid var(--border-dim, #222);
}
.sentry-node-name { font-size: 12px; font-weight: 700; }
.sentry-node-id { color: var(--text-dim); font-size: 10px; margin-left: 6px; }
.sentry-node-meta { display: flex; align-items: center; gap: 8px; font-size: 11px; color: var(--text-dim); }
.sentry-net-badge {
  font-size: 9px; font-weight: 700; padding: 1px 4px; border-radius: 3px; letter-spacing: 0.4px;
}
.sentry-net-mt { background: #1e4d2a; color: #9fe8b0; }
.sentry-net-mc { background: #1d3a5e; color: #9cc8f0; }
.sentry-nodes-empty { color: var(--text-dim); font-size: 12px; padding: 12px 8px; }

/* Sentry cooldown — the greyed deploy button (Filip 2026-08-03) */
.deploy-fortify-btn:disabled {
  color: var(--text-dim);
  border-color: var(--border-dim, #333);
  background: var(--bg-elevated, #151515);
  cursor: default;
}

.deploy-sentry-active {
  color: var(--accent);
  font-weight: 700;
  font-size: 12px;
  margin-bottom: 6px;
}

.deploy-note {
  color: var(--text-dim);
  font-size: 10px;
  line-height: 1.35;
  margin-top: 9px;
}

.deploy-current-role {
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 700;
  margin-bottom: 8px;
}

.deploy-cancel-btn {
  background: var(--bg-elevated);
  border: 1px solid #c86a4a;
  border-radius: var(--radius);
  color: #c86a4a;
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  min-height: 44px;
  padding: 7px 6px;
  width: 100%;
}

.deploy-cancel-btn:hover { background: #c86a4a; color: #fff; }

/* Fortify — a tier upgrade tucked into the Deploy panel (S388). Gold-accented
   to match the Fortified crown tier; distinct from the red destructive cancel.
   The #deploy-fortify-section uses the default [hidden] behavior, toggled in JS. */
.deploy-divider {
  height: 1px;
  margin: 12px 0 10px;
  background: var(--border);
}
.deploy-fortify-btn {
  background: var(--bg-elevated);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  color: var(--accent);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
  min-height: 44px;
  padding: 7px 6px;
  width: 100%;
}
.deploy-fortify-btn:not(:disabled):hover { background: var(--accent-dim); border-color: var(--accent); color: #fff; }


.ledger-panel {
  width: min(560px, calc(100vw - 24px));
}

.ledger-list {
  display: grid;
  gap: 8px;
  max-height: min(70vh, 680px);
  overflow: auto;
}

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

.ledger-summary div {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px 10px;
}

.ledger-summary span,
.ledger-row-meta,
.ledger-muted {
  color: var(--text-dim);
}

.ledger-summary span {
  display: block;
  font-family: var(--font-mono);
  font-size: 10px;
  margin-bottom: 3px;
}

.ledger-summary strong {
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: 18px;
}

.ledger-warning {
  background: rgba(200, 106, 74, 0.12);
  border: 1px solid rgba(200, 106, 74, 0.45);
  border-radius: var(--radius);
  color: #d99272;
  font-size: 12px;
  line-height: 1.35;
  padding: 8px 10px;
}

.ledger-day {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.ledger-day-header,
.ledger-group-header {
  align-items: center;
  cursor: pointer;
  display: flex;
  font-family: var(--font-mono);
  gap: 8px;
  justify-content: space-between;
  list-style: none;
  user-select: none;
}

.ledger-day-header::-webkit-details-marker,
.ledger-group-header::-webkit-details-marker {
  display: none;
}

.ledger-day-header::before,
.ledger-group-header::before {
  border-bottom: 4px solid transparent;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  content: "";
  flex: 0 0 auto;
  opacity: 0.72;
  transform: rotate(0deg);
  transition: transform 120ms ease;
}

.ledger-day[open] > .ledger-day-header::before,
.ledger-group[open] > .ledger-group-header::before {
  transform: rotate(90deg);
}

.ledger-day-header span,
.ledger-group-header span {
  flex: 1 1 auto;
}

.ledger-day-header {
  background: var(--bg-elevated);
  color: var(--text-primary);
  font-size: 12px;
  padding: 8px 10px;
  text-transform: uppercase;
}

.ledger-day[open] > .ledger-day-header {
  border-bottom: 1px solid var(--border);
}

.ledger-day-header strong,
.ledger-group-header strong {
  font-size: 11px;
}

.ledger-group {
  display: grid;
  gap: 6px;
  padding: 8px;
}

.ledger-group + .ledger-group {
  border-top: 1px solid var(--border);
}

.ledger-group-header {
  color: var(--text-secondary);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.ledger-row {
  background: rgba(255, 255, 255, 0.025);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px 10px;
}

.ledger-row-main {
  align-items: baseline;
  display: flex;
  gap: 12px;
  justify-content: space-between;
}

/* Public players list (PART A, 2026-08-10) — player-level facts only; the
   backend never sends node data to this list (privacy rule, see the schema). */
.players-callsign {
  align-items: center;
  display: inline-flex;
  font-family: var(--font-mono);
  font-size: 13px;
  gap: 6px;
}

.players-dot {
  border-radius: 50%;
  display: inline-block;
  flex: 0 0 auto;
  height: 9px;
  width: 9px;
}

.faction-chip {
  border: 1px solid color-mix(in srgb, var(--chip, #888) 55%, transparent);
  border-radius: 3px;
  color: var(--chip, #888);
  font-family: var(--font-mono);
  font-size: 10px;
  padding: 1px 5px;
}

.players-activity {
  font-size: 10px;
  margin-left: auto;
}

.players-activity-active_today { color: #9bd08f; }
.players-activity-active_week { color: var(--accent); }
.players-activity-dormant { color: var(--text-dim); }

/* Admin side (B5, 2026-08-10) — read-only views; server enforces access. */
.admin-tabs {
  display: flex;
  gap: 6px;
  margin-bottom: 8px;
}

.admin-tab {
  background: transparent;
  border: 1px solid var(--text-dim);
  border-radius: 3px;
  color: var(--text-dim);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 11px;
  padding: 4px 10px;
}

.admin-tab-active {
  border-color: var(--accent);
  color: var(--accent);
}

.admin-stats-grid {
  display: grid;
  gap: 6px 14px;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  margin: 8px 0;
}

.admin-stat {
  align-items: baseline;
  display: flex;
  flex-direction: column;
}

.admin-stat-label {
  color: var(--text-dim);
  font-size: 10px;
}

.admin-stat-value {
  font-family: var(--font-mono);
  font-size: 16px;
  font-weight: 700;
}

.admin-tier-row,
.admin-subhead {
  font-size: 11px;
}

.admin-list-header {
  color: var(--text-primary);
  font-size: 12px;
  font-weight: 700;
  margin: 8px 0 4px;
}

.admin-player-row {
  background: transparent;
  border: 0;
  color: inherit;
  cursor: pointer;
  padding: 0;
  padding-right: 18px; /* keep the right-aligned status (admin/disabled) clear of the list scrollbar (Filip 2026-08-10) */
  text-align: left;
  width: 100%;
}

/* Thin separators between player rows (Filip 2026-08-10) — border-bottom on
   the row rather than the container so the hover state stays clean. */
.admin-player-row + .admin-player-row {
  border-top: 1px solid rgba(255, 255, 255, 0.07);
  margin-top: 6px;
  padding-top: 6px;
}

.admin-player-row:hover { background: rgba(255, 255, 255, 0.04); }

.admin-section {
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  margin: 10px 0 4px;
}

.admin-pager {
  display: flex;
  gap: 8px;
  margin-top: 10px;
}

.admin-pager-btn,
.admin-back {
  background: transparent;
  border: 1px solid var(--text-dim);
  border-radius: 3px;
  color: var(--text-primary);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 11px;
  padding: 4px 10px;
}

.admin-pager-btn:disabled { opacity: 0.35; }

/* ROW ACTIONS (Filip 2026-09-17: "some buttons are too big/weird"; v225). The tester rows used `admin-pager-btn` —
   the PAGER style — for eight action buttons, which is why they looked wrong beside the note field: a small quiet
   page control and "the action this row is about" are not the same thing. This is compact, the same height as the
   input it sits next to (same padding, same border), secondary by default, and `danger` marks the destructive ones
   (Reject, Ban, Remove) so the row reads in one glance. */
.admin-action-btn {
  background: var(--bg-elevated, #161a16);
  border: 1px solid var(--border);
  border-radius: 3px;
  color: var(--text-primary);
  cursor: pointer;
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1.2;
  padding: 5px 10px;
  white-space: nowrap;
}

.admin-action-btn:hover { border-color: var(--text-dim); }
.admin-action-btn:disabled { opacity: 0.35; }

.admin-action-btn.danger {
  border-color: #7a3b32;
  color: #d98a7a;
}

.admin-action-btn.danger:hover { background: #2a1a17; }

.admin-back { margin-bottom: 8px; }

/* Collapsible admin data sections (Filip 2026-08-10): list sections are
   collapsed by default, unfolded on header click. The header reads like the
   old .admin-section label; the caret shows state. */
.admin-collapse-toggle {
  align-items: center;
  background: transparent;
  border: 0;
  color: var(--text-secondary, #7a8c7a);
  cursor: pointer;
  display: flex;
  font-family: var(--font-mono);
  font-size: 11px;
  justify-content: space-between;
  padding: 10px 2px 4px;
  text-transform: uppercase;
  width: 100%;
}

.admin-collapse-toggle:hover { color: var(--text-primary, #c8d4c8); }

.admin-collapse-caret {
  color: var(--text-dim, #4a5a4a);
  font-size: 12px;
}

.admin-collapse-body { padding-top: 4px; }

/* Wider admin panel (Filip 2026-08-10): the 560px ledger width cropped the
   admin tables; admin gets a near-full-width surface instead. The right edge
   also clears the browser scrollbar — fixed at right:0 it sat "behind" the
   page's vertical scroll slider. */
.ledger-panel.admin-panel {
  right: max(12px, env(safe-area-inset-right, 0px));
  width: min(96vw, 980px);
}

/* Player commands (B3) — reason field + action buttons. */
.admin-commands {
  border: 1px solid var(--border);
  border-radius: 4px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px;
}

.admin-reason,
.admin-amount {
  background: var(--bg-elevated, #161a16);
  border: 1px solid var(--border);
  border-radius: 3px;
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 6px 8px;
}

.admin-reason { width: 100%; }

.admin-command-buttons {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.admin-amount-wrap {
  align-items: center;
  display: inline-flex;
  gap: 6px;
}

.admin-amount { width: 110px; }

.admin-cmd-btn {
  background: transparent;
  border: 1px solid var(--accent-dim, #7a5a08);
  border-radius: 3px;
  color: var(--accent);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 11px;
  padding: 5px 10px;
}

.admin-cmd-btn:hover { background: rgba(255, 255, 255, 0.06); }

.admin-cmd-result { font-size: 11px; }

.admin-cmd-ok { color: #9bd08f; }
.admin-cmd-error { color: #e08f7a; }

/* S411 admin-panel rework (2026-08-27): search/sort/filter rows, the three
   Overview bands, per-action confirm reason, orphan wiring, mobile. */
.admin-filters {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 8px;
}

.admin-filters input[type="text"],
.admin-filters input[type="search"],
.admin-filters select {
  background: var(--bg-elevated, #161a16);
  border: 1px solid var(--border);
  border-radius: 3px;
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: 11px;
  padding: 5px 8px;
  min-width: 0;
}

.admin-filters input[type="search"] { flex: 1 1 160px; }
/* The tester/player row's note field shrinks first; the action buttons keep their size and wrap to the next line
   when there is no room (400 px is the narrow case Filip uses). */
.admin-filters input[type="text"] { flex: 1 1 140px; }
.admin-filters select { flex: 0 0 auto; }

.admin-band { margin-bottom: 14px; }

.admin-band-title {
  color: var(--text-dim, #8a8f96);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  margin-bottom: 6px;
  text-transform: uppercase;
}

.admin-attention-row {
  align-items: center;
  background: rgba(224, 143, 122, 0.08);
  border: 1px solid rgba(224, 143, 122, 0.35);
  border-radius: 4px;
  color: var(--text-primary);
  cursor: pointer;
  display: flex;
  font-family: var(--font-mono);
  font-size: 12px;
  justify-content: space-between;
  margin-bottom: 6px;
  padding: 8px 10px;
  text-align: left;
  width: 100%;
}

.admin-attention-row:hover { background: rgba(224, 143, 122, 0.16); }

.admin-attention-row b { color: #e08f7a; font-size: 14px; }

.admin-attention-static { cursor: default; }
.admin-attention-static:hover { background: rgba(224, 143, 122, 0.08); }

.admin-attention-empty {
  border: 1px dashed var(--border);
  border-radius: 4px;
  color: var(--text-dim, #8a8f96);
  font-size: 12px;
  padding: 10px;
  text-align: center;
}

.admin-recent-row {
  color: var(--text-primary);
  font-size: 12px;
  line-height: 1.7;
}

.admin-recent-row b { color: var(--accent, #d0a536); }

.admin-chip-clear {
  background: transparent;
  border: none;
  color: var(--text-dim, #8a8f96);
  cursor: pointer;
  margin-left: 4px;
  padding: 0 2px;
}

.admin-chip-clear:hover { color: #e08f7a; }

.admin-node-row,
.admin-hex-row {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  justify-content: space-between;
}

.admin-node-remove-btn,
.admin-hex-neutralize-btn { flex: 0 0 auto; }

/* Per-action confirm reason input (S411 §3). */
.confirm-dialog-reason {
  background: var(--bg-elevated, #161a16);
  border: 1px solid var(--border);
  border-radius: 3px;
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: 12px;
  margin-top: 10px;
  padding: 8px 10px;
  width: 100%;
}

.confirm-dialog-reason-error {
  border-color: #e08f7a;
  box-shadow: 0 0 0 1px rgba(224, 143, 122, 0.4);
}

/* Mobile (S411 §5): the panel is used in the field — let the header and
   command rows breathe instead of wrapping into a wall of text. */
@media (max-width: 640px) {
  .admin-player-header { line-height: 1.5; }
  .admin-subhead { line-height: 1.6; }
  .admin-commands { gap: 10px; }
  .admin-amount-wrap { flex-wrap: wrap; width: 100%; }
  .admin-amount { flex: 1 1 100%; width: auto; }
  .admin-pager { padding: 8px 0; }
}

.ledger-label {
  color: var(--text-primary);
  font-size: 13px;
  font-weight: 700;
}

.ledger-amount {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 700;
}

.ledger-amount.earn { color: #8fbf8f; }
.ledger-amount.spend { color: #d99272; }
.ledger-amount.zero { color: var(--text-dim); }

.ledger-row-meta {
  align-items: center;
  display: grid;
  gap: 6px;
  grid-template-columns: minmax(96px, auto) minmax(0, 1fr) auto;
  margin-top: 5px;
  font-family: var(--font-mono);
  font-size: 10px;
}

.ledger-hex {
  appearance: none;
  background: transparent;
  border: 0;
  color: var(--accent);
  cursor: pointer;
  font: inherit;
  overflow-wrap: anywhere;
  padding: 0;
  text-align: left;
}

.ledger-hex:hover { text-decoration: underline; }

@media (max-width: 560px) {
  .ledger-row-meta { grid-template-columns: 1fr; }
}

/* Channel setup status banner (S385) — is the MeshTorry channel already set up? */
.channel-status {
  font-size: 12px;
  padding: 6px 8px;
  border-radius: 4px;
  margin-bottom: 8px;
  border: 1px solid transparent;
}
.channel-status-ok {
  color: #8fbf8f;
  background: rgba(90, 150, 90, 0.12);
  border-color: rgba(90, 150, 90, 0.35);
}
.channel-status-missing {
  color: var(--accent);
  background: rgba(184, 134, 11, 0.1);
  border-color: var(--accent-dim);
}

.confirm-dialog-backdrop {
  position: fixed;
  inset: 0;
  z-index: 5000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(5, 7, 9, 0.72);
  backdrop-filter: blur(2px);
}

.confirm-dialog-backdrop[hidden] { display: none; }

.confirm-dialog {
  width: min(420px, 100%);
  max-height: min(78vh, 520px);
  overflow: auto;
  background: var(--bg-panel);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: 0 18px 55px rgba(0, 0, 0, 0.48);
  color: var(--text-primary);
}

.confirm-dialog-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--border);
}

.confirm-dialog-title {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
}

.confirm-dialog-close {
  width: 28px;
  height: 28px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-elevated);
  color: var(--text-dim);
  font-family: var(--font-mono);
  cursor: pointer;
}

.confirm-dialog-message {
  padding: 14px;
  color: var(--text-secondary);
  font-size: 13px;
  line-height: 1.45;
  white-space: pre-line;
}

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

.confirm-dialog-actions button {
  min-height: 36px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
}

.confirm-dialog-cancel {
  background: var(--bg-elevated);
  color: var(--text-secondary);
}

.confirm-dialog-confirm {
  background: var(--accent-dim);
  border-color: var(--accent);
  color: #fff;
}

.confirm-dialog-confirm.danger {
  background: #7f3327;
  border-color: #c86a4a;
}

.hex-popup-actions {
  display: grid;
  grid-template-columns: 1fr;
  gap: 6px;
  margin-top: 8px;
}

.hex-popup-actions button {
  min-height: 32px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-primary);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  padding: 5px 7px;
  text-align: left;
}

.hex-popup-actions button:hover { border-color: var(--accent-dim); }
.hex-popup-actions button.danger { border-color: #c86a4a; color: #c86a4a; }
.hex-popup-actions button.danger:hover { background: #c86a4a; color: #fff; }

/* S397 (DeepSeek 2026-08-05): the barracks-hex "Buy soldiers" row inside the
   hex popup — input + button side by side, button not full-width. */
.hex-popup-soldiers .soldier-train-row button {
  flex: 1 1 auto;
  text-align: center;
  white-space: nowrap;
}

.fleet-node-popup .hex-popup-actions {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.fleet-node-popup .hex-popup-actions button.danger {
  grid-column: 1 / -1;
}

.hex-popup-node-actions {
  border-top: 1px solid var(--border);
  grid-template-columns: repeat(2, minmax(0, 1fr));
  padding-top: 8px;
}

.hex-popup-node-actions .node-id,
.hex-popup-node-actions .asset-status-row {
  grid-column: 1 / -1;
}

.hex-popup-node-actions .node-title-line {
  color: var(--text-primary);
  font-weight: 700;
}

.hex-popup-node-actions button.danger,
.hex-popup-actions button.active[disabled] {
  grid-column: 1 / -1;
}

.hex-popup-actions button.active,
.fleet-deploy-row button.active {
  background: var(--accent-dim);
  border-color: var(--accent);
  color: #fff;
  cursor: default;
  opacity: 1;
}

/* Notifications bell + badge + panel (S388) — daily OP/LP "who heard you" digest.
   The bell + menu group on the right: the bell takes the auto margin, and
   #menu-btn's own margin-left:auto is overridden to a small fixed gap so the two
   sit together instead of being split apart. */
.notif-btn {
  position: relative;
  margin-left: auto;
  flex: 0 0 auto;
  background: var(--bg-elevated);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 4px 8px;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}
.notif-btn:hover { border-color: var(--accent-dim); }
.notif-btn.has-unread { border-color: var(--accent); }
.notif-btn[aria-expanded="true"] { background: var(--accent-dim); border-color: var(--accent); }
#menu-btn { margin-left: 4px; }

.notif-badge[hidden] { display: none; }
.notif-badge {
  position: absolute;
  top: -5px;
  right: -5px;
  min-width: 16px;
  height: 16px;
  padding: 0 3px;
  border-radius: 8px;
  background: #c86a4a;
  color: #fff;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
}

.notif-list { display: flex; flex-direction: column; gap: 6px; padding: 4px 0; }
.notif-empty { color: var(--text-dim); font-size: 12px; line-height: 1.4; padding: 14px 6px; text-align: center; }
.notif-item {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px 10px;
  background: var(--bg-surface);
}
.notif-item.unread { border-color: var(--accent-dim); background: var(--bg-elevated); }
/* Decay warnings are clickable — jump to the hex (Filip 2026-08-10). */
.notif-item[data-notif-hex] { cursor: pointer; }
.notif-item[data-notif-hex]:hover { background: var(--bg-elevated, #1c201c); }
.notif-msg { color: var(--text-primary); font-size: 12px; line-height: 1.35; }
/* Attack/siege digests stand out (Filip 2026-08-03) — bold. */
.notif-msg.attack-alert { font-weight: 700; }
.notif-time { color: var(--text-dim); font-size: 10px; margin-top: 3px; }

/* Transient toast notices (Filip #20/#26/#37): point-spend failures / blocked
   actions were only shown in the tiny node-info row. Bottom-anchored (zoom-proof)
   and auto-dismissing; click to dismiss early. See showToast() in confirmDialog.js. */
.mt-toast-root {
  position: fixed;
  left: 50%;
  bottom: calc(84px + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 5200;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
  pointer-events: none;
  width: max-content;
  max-width: min(92vw, 420px);
}
.mt-toast {
  pointer-events: auto;
  background: var(--bg-panel);
  border: 1px solid var(--border-strong);
  border-left-width: 4px;
  border-radius: 4px;
  padding: 10px 14px;
  color: var(--text-primary);
  font-size: 13px;
  line-height: 1.35;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.5);
  cursor: pointer;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.mt-toast-visible { opacity: 1; transform: translateY(0); }
.mt-toast-warn { border-left-color: var(--accent); }
.mt-toast-info { border-left-color: var(--accent-dim); }

/* Fleet card: Remove (left) + Connect (right) side by side instead of stacked,
   so they're harder to mis-click (Filip #39). Connect is absent for non-BLE nodes,
   leaving Remove to span the row. */
.fleet-bottom-actions { display: flex; gap: 8px; margin-top: 6px; }
.fleet-bottom-actions > button { flex: 1 1 0; margin-top: 0; }

/* Callsign labels over owned territory regions (faction view, DeepSeek S394). */
.territory-label-wrap { background: transparent; border: none; }
.territory-label {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  white-space: nowrap;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9), 0 0 4px rgba(0, 0, 0, 0.8);
  pointer-events: none;
}

/* Faction management section inside the profile panel (DeepSeek S394). */
.faction-members { display: flex; flex-direction: column; gap: 2px; margin: 4px 0 8px; }
.faction-member-row {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-primary);
}
/* Invite-only membership (S395): pending-invite rows wrap so the buttons
   never poke out of the card border on narrow panels (Filip: join button
   was cutting out of the menu on the right). */
.faction-invite-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 8px;
  margin-bottom: 6px;
}
.faction-invite-row .node-type {
  flex: 1 1 8ch;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.faction-invite-row .node-id {
  flex: 0 0 auto;
}
.faction-invite-row .claim-btn,
.faction-invite-row .release-btn {
  margin-top: 0;
  flex: 0 0 auto;
  white-space: nowrap;
}

/* Leader invite form (callsign input + Invite button, stacked like the
   treasury controls). */
.faction-invite-form {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 6px 0;
}
.faction-invite-form input {
  width: 100%;
  padding: 5px 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-primary);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.faction-invite-form .claim-btn {
  margin-top: 0;
}
.faction-pending-invites .faction-invite-row {
  margin-bottom: 4px;
}
.faction-color-row {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-secondary);
}
.faction-color-row input[type="color"] {
  width: 40px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: transparent;
}

/* Treasury donate / leader payout (S394) — amount fields on their own row,
   buttons below (Filip: fields were squeezed next to the buttons). */
.faction-donate-block,
.faction-payout-block {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 6px 0;
}
.faction-payout-fields {
  display: flex;
  gap: 6px;
}
.faction-payout-fields > * {
  flex: 1 1 0;
  min-width: 0;
}
.faction-donate-block input[type="number"],
.faction-payout-fields input[type="number"] {
  width: 100%;
  padding: 5px 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-primary);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.faction-payout-fields select {
  min-width: 0;
  padding: 5px 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-primary);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.faction-donate-block .claim-btn,
.faction-payout-block .claim-btn {
  margin-top: 0;
  white-space: nowrap;
}

/* Faction colour edit (leader) — picker + Save follow the faction colour. */
.faction-color-edit {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 6px 0;
}
.faction-color-edit .claim-btn {
  margin-top: 0;
}

/* S397 — Soldiers (Phase 0): owner-only map markers + hex-popup sections */
.soldier-train-row {
  display: flex;
  gap: 6px;
  align-items: center;
  margin-bottom: 8px;
}
.soldier-train-row input {
  width: 64px;
  background: var(--bg-elevated, #151515);
  color: var(--text, #ddd);
  border: 1px solid var(--border, #333);
  border-radius: 6px;
  padding: 5px 8px;
  font-size: 13px;
}
/* S397 (DeepSeek 2026-08-05): the per-unit formation input (Phase 3) — a
   compact inline field in the soldiers unit list; the row wraps if tight. */
.soldier-formation {
  flex: 1 1 90px;
  min-width: 70px;
  max-width: 130px;
  background: var(--bg-elevated, #151515);
  color: var(--text, #ddd);
  border: 1px solid var(--border, #333);
  border-radius: 6px;
  padding: 3px 6px;
  font-size: 11px;
}
.soldier-formation::placeholder {
  color: var(--text-dim, #8a8a8a);
}
.soldier-stationed {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 4px 0 6px;
}
.soldier-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  padding: 4px 6px;
  border-radius: 6px;
  background: var(--bg-elevated, #151515);
}
.soldier-size {
  font-weight: 700;
  min-width: 22px;
  text-align: center;
  background: var(--border, #333);
  border-radius: 4px;
  padding: 1px 4px;
}
.soldier-hex {
  font-family: var(--mono, monospace);
  color: var(--text-dim, #8a8a8a);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.soldier-state {
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim, #8a8a8a);
}
.soldier-state.deployed { color: #d0a536; }
.soldier-act {
  margin-left: auto;
  font-size: 11px;
  padding: 2px 8px;
  border-radius: 6px;
  border: 1px solid var(--border, #333);
  background: var(--bg, #101010);
  color: var(--text, #ddd);
  cursor: pointer;
}
.soldier-here { margin-left: auto; color: #d0a536; font-size: 11px; }
.soldier-empty { font-size: 12px; color: var(--text-dim, #8a8a8a); }

/* S397 commands (Filip 2026-08-05): hex-popup soldier command section +
   the "from where / how many teams" picker. */
.hex-popup-soldier-commands {
  display: flex;
  flex-direction: column;
  gap: 6px;
  /* Thin line separating the soldiers section from the rest of the popup —
     Filip 2026-08-05: the section must be clearly visible. */
  border-top: 1px solid var(--border, #333);
  margin-top: 6px;
  padding-top: 6px;
}
.soldier-section-line {
  font-size: 12px;
  color: var(--text-secondary, #aaa);
  line-height: 1.4;
}
.soldier-upkeep { color: #d0a536; }
.soldier-inbound { color: #7fb069; }
.soldier-cmd-row { display: flex; gap: 6px; flex-wrap: wrap; }
.soldier-cmd-row .soldier-act { margin-left: 0; padding: 4px 10px; }
.soldier-picker { border-top: 1px dashed var(--border, #333); padding-top: 6px; }
.soldier-picker-body { display: flex; flex-direction: column; gap: 6px; }
.soldier-picker-row {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-primary, #ddd);
}
.soldier-picker-hex { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.soldier-step {
  width: 24px;
  height: 24px;
  border: 1px solid var(--border, #333);
  border-radius: 6px;
  background: var(--bg-elevated, #1a1a1a);
  color: var(--text-primary, #ddd);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}
.soldier-picker-count { min-width: 18px; text-align: center; font-family: var(--font-mono, monospace); color: #d0a536; }
.soldier-picker-actions { display: flex; gap: 6px; justify-content: flex-end; }
.soldier-act-primary { background: var(--accent-dim, #7a5a08); border-color: var(--accent, #b8860b); color: #fff; }

/* owner-only soldier markers */
.soldier-marker {
  background: none;
  border: none;
}
.soldier-badge {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1.5px solid rgba(200, 212, 200, 0.7);
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.6);
}
.soldier-glyph {
  position: relative;
  width: 62%;
  height: 62%;
}
.soldier-count {
  position: absolute;
  right: -6px;
  bottom: -6px;
  font-size: 8px;
  font-weight: 700;
  background: #101010;
  color: #c8d4c8;
  border: 1px solid rgba(200, 212, 200, 0.6);
  border-radius: 4px;
  padding: 0 3px;
  line-height: 1.4;
}

/* S397 — barracks coexists with the deployed asset: greyed deploy buttons */
.deploy-options button:disabled {
  color: var(--text-dim, #8a8a8a);
  border-color: var(--border-dim, #333);
  background: var(--bg-elevated, #151515);
  cursor: default;
  opacity: 0.55;
}

/* Heard nodes in the hex popup (Filip 2026-08-07, Part C) */
.heard-nodes-block {
  margin-top: 6px;
  padding-top: 4px;
  border-top: 1px solid var(--border, #333);
}
.heard-nodes-title {
  color: var(--text-dim);
  font-size: 10px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  /* The title is a toggle button (Filip 2026-08-07 — the list is collapsed
     by default; some hexes list 100+ contacts). */
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  padding: 0;
  margin-bottom: 3px;
  background: none;
  border: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.heard-caret {
  /* 2026-08-09 (Filip): the ▸ was 10px dim-on-dark — near-invisible, and it's
     the ONLY affordance that the (often 100+) node list is expandable. Bigger
     + brighter so it reads as a control; hexLayer.js flips it ▸/▾. */
  color: var(--text-primary, #ddd);
  font-size: 16px;
  line-height: 1;
  flex: 0 0 auto;
}
.heard-nodes-list {
  margin-top: 3px;
  /* A closing divider when the list is expanded (Filip 2026-08-07) — the
     block already opens with a top border, so the list is framed. */
  padding-bottom: 4px;
  border-bottom: 1px solid var(--border, #333);
}
.heard-node-line {
  color: var(--text-primary);
  font-size: 12px;
  line-height: 1.45;
}
.heard-node-rssi {
  color: var(--text-dim);
  font-size: 11px;
}

/* Coverage strength legend (Filip 2026-08-07, Part B) — 4 bands x 2 networks
   + the striped "heard, strength unmeasured" state. Strong = vivid + solid,
   weak = washed-out + faint (dark basemap). */
.mt-coverage-legend {
  position: absolute;
  right: 12px;
  bottom: calc(74px + env(safe-area-inset-bottom, 0px)); /* v150 nav-bar work */
  z-index: 1000;
  background: var(--bg-elevated, #151515);
  border: 1px solid var(--border, #333);
  border-radius: 8px;
  padding: 6px 10px 8px;
  font-size: 10px;
  color: var(--text-dim);
  width: 148px;
}
/* The legend has interactive content (the dismiss X) — only the body needs
   pointer-events: none. */
.mt-coverage-legend-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 3px;
}
.mt-coverage-legend-close {
  /* Real touch target for a one-handed map UI (Filip 2026-08-07): ~40px, not
     a 12px glyph. The X glyph itself stays small, the hit area is the button. */
  width: 40px;
  height: 40px;
  margin: -8px -10px 0 0;
  flex: 0 0 40px;
  background: transparent;
  border: none;
  color: var(--text-dim);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.mt-coverage-legend-close:active {
  color: var(--text-primary);
}
.mt-coverage-legend-title {
  font-weight: 700;
  color: var(--text-primary);
  font-size: 10px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
.mt-coverage-legend-row {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 3px 0;
  line-height: 1;
}
.mt-coverage-legend-swatch {
  width: 14px;
  height: 10px;
  flex: 0 0 14px;
  border: 1px solid rgba(255, 255, 255, 0.18);
}
.mt-coverage-legend-stripe {
  background: repeating-linear-gradient(
    45deg,
    #5fd768 0 3px,
    transparent 3px 6px
  );
}
