:root {
  --bg: #0b0f14;
  --panel: #141b24;
  --panel-2: #1b2431;
  --line: #263140;
  --text: #e6edf5;
  --muted: #8b9bb0;
  --accent: #38bdf8;

  --c-bidir: #22c55e;
  --c-disc: #06b6d4;
  --c-tx: #f97316;
  --c-rx: #a855f7;
  --c-dead: #94a3b8;
  --c-drop: #ef4444;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font: 13px/1.45 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

/* A compact menu floating over the map, on the same line as the sidebar's
   title. Deliberately not a full-width bar: the map keeps its full height, and
   the menu reads as a menu rather than as a second header. */
#topbar {
  position: absolute;
  /* Clear of the sidebar: 12 px margin + 268 px width + 12 px gap, and pulled
     down so the pill's centre sits on the centre of the sidebar's title line. */
  top: 20px;
  left: 292px;
  z-index: 6;
  display: flex;
  gap: 8px;
}

#map { position: absolute; inset: 0; }

/* Shown in place of the canvas when WebGL is unavailable. Centred and muted:
   it explains a blank map instead of leaving the user to guess. */
.map-unavailable {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  padding: 24px;
  text-align: center;
  color: var(--muted);
  font: 13px/1.5 ui-sans-serif, system-ui, sans-serif;
  background: var(--bg, #0b0f14);
}

#sidebar {
  position: absolute;
  top: 12px;
  left: 12px;
  bottom: 12px;
  width: 268px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
  overflow-y: auto;
  background: color-mix(in srgb, var(--panel) 92%, transparent);
  backdrop-filter: blur(12px);
  border: 1px solid var(--line);
  border-radius: 10px;
  z-index: 5;
}

#sidebar header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
}

#sidebar h1 {
  /* The name and its version stack: "MapMesher v1.5.0" does not fit beside the
     two status pills in a 268 px panel, and letting it wrap mid-word made the
     pills themselves wrap too. Narrowing the title keeps one line each. */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  margin: 0;
  font-size: 16px;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

/* The build marker next to the name. Quiet and small: it answers "which build
   is this?" without competing with the title. */
.version {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

#sidebar h2 {
  margin: 0 0 6px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}

/* The observer list link, floating over the map next to the sidebar title. It
   used to sit inside the sidebar, where it read as one more map control. */
.top-link {
  display: inline-flex;
  align-items: baseline;
  gap: 9px;
  margin: 0;
  padding: 6px 14px;
  color: var(--text);
  background: color-mix(in srgb, var(--panel) 88%, transparent);
  backdrop-filter: blur(12px);
  border: 1px solid var(--line);
  border-radius: 999px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.top-link:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.top-link-hint {
  font-size: 10px;
  font-weight: 400;
  color: var(--muted);
}

.top-link:hover .top-link-hint { color: inherit; }

.panel {
  padding: 9px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 8px;
}

/* The heading of a panel, now a button so it can be folded.
   It keeps the look of the `#sidebar h2` it replaces — small, uppercase,
   muted — because folding must not change how a section reads. */
.panel-toggle {
  display: flex;
  align-items: center;
  gap: 5px;
  width: 100%;
  margin: 0 0 6px;
  padding: 0;
  background: none;
  border: 0;
  color: var(--muted);
  font: inherit;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  text-align: left;
  cursor: pointer;
}

.panel-toggle:hover { color: var(--text); }

.panel-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 3px;
}

/* The triangle says which way the panel is folded: down means there is more to
   open, right means it is already folded. A CSS rotation, not two glyphs, so the
   two states cannot drift apart. */
.panel-toggle .chevron {
  flex: 0 0 auto;
  width: 9px;
  font-size: 10px;
  line-height: 1;
  text-align: center;
  transition: transform 120ms ease;
}

.panel.collapsed > .panel-toggle { margin-bottom: 0; }
.panel.collapsed > .panel-toggle .chevron { transform: rotate(-90deg); }

/* Everything except the heading is hidden while folded. `display: none` rather
   than a class per child: a panel can grow a control without the folding code
   needing to know about it. */
.panel.collapsed > :not(.panel-toggle) { display: none; }

/* A hidden panel stays hidden even when folded — `display: none` on the parent
   would reveal it otherwise, since the two rules differ in specificity. */
.panel[hidden] { display: none; }

label {
  display: block;
  margin: 6px 0 2px;
  font-size: 11px;
  color: var(--muted);
}

label.check {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 5px 0;
  font-size: 12px;
  color: var(--text);
  cursor: pointer;
}

select {
  width: 100%;
  padding: 5px 7px;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 6px;
  font: inherit;
}

button {
  width: 100%;
  margin-top: 8px;
  padding: 6px 8px;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 6px;
  font: inherit;
  cursor: pointer;
}

button:hover { border-color: var(--accent); }

/* A secondary action next to a primary one: same hit area, less weight, so
   Reset view does not read as the thing to press. */
button.ghost {
  margin-top: 6px;
  color: var(--muted);
  background: transparent;
}

button.ghost:hover { color: var(--text); }

#repeater-filter .filter-note {
  margin: 0;
  font-size: 11px;
  line-height: 1.45;
  color: var(--muted);
}

#repeater-filter .empty-note {
  margin-top: 6px;
  padding-left: 8px;
  border-left: 2px solid var(--line);
}

#repeater-filter.loading .filter-note { opacity: 0.6; }

#repeater-filter strong { color: var(--text); }

.to-client {
  display: block;
  margin-top: 8px;
  padding: 6px 8px;
  text-align: center;
  color: var(--text);
  text-decoration: none;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel);
}

.to-client:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* The admin link sits under the client one and is deliberately quieter: it is
   used by the operator, not by every visitor of the map. */
.to-admin {
  margin-top: 6px;
  color: var(--muted);
  font-size: 11px;
}

.pill {
  padding: 2px 7px;
  border-radius: 999px;
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  background: #16351f;
  color: #6ee7a0;
  border: 1px solid #245c34;
}

.pill.bad {
  background: #3a1a1a;
  color: #fca5a5;
  border-color: #622727;
}

/* Neutral by default: a slot count is information, not an alarm. Only a full
   zone turns it red, because that is when the next node cannot transmit. */
.pill.slots {
  background: #1a2233;
  color: #a8c0e8;
  border-color: #2b3d5c;
  font-variant-numeric: tabular-nums;
  /* The TX pill is a count, not a sentence: it must not break into two lines
     when the header is tight. */
  white-space: nowrap;
}

.pill.slots.full {
  background: #3a1a1a;
  color: #fca5a5;
  border-color: #622727;
}

.types {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
}

.types .check { margin: 2px 0; font-size: 11px; }

/* Легенда тепловой карты: та же рампа, что и в слое, поэтому подпись не может
   разойтись с цветами на экране. Пять цветов здесь — единственная копия,
   которую нельзя импортировать из `signal-layer.js`; при правке рампы меняйте
   оба файла. */
.signal-legend {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 6px 0 0;
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
}

.signal-legend[hidden] { display: none; }

.signal-legend .ramp {
  flex: 1 1 auto;
  height: 8px;
  border-radius: 4px;
  background: linear-gradient(to right, #d7191c, #f46d43, #fdae61, #a6d96a, #1a9641);
}

.signal-hint {
  margin: 5px 0 0;
  font-size: 10px;
  line-height: 1.4;
  color: var(--muted);
}

.signal-hint[hidden] { display: none; }

.swatch {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  flex: 0 0 auto;
  border: 1px solid rgba(255, 255, 255, 0.25);
}

.stats { margin: 0; }
.stats div {
  display: flex;
  justify-content: space-between;
  padding: 2px 0;
  border-bottom: 1px dashed var(--line);
}
.stats div:last-child { border-bottom: 0; }
.stats dt { color: var(--muted); font-size: 11px; }
.stats dd { margin: 0; font-variant-numeric: tabular-nums; }

#sidebar footer {
  margin-top: auto;
  color: var(--muted);
  font-size: 10px;
  line-height: 1.4;
}

.detail {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 300px;
  max-height: calc(100% - 24px);
  overflow-y: auto;
  padding: 12px;
  background: color-mix(in srgb, var(--panel) 96%, transparent);
  border: 1px solid var(--line);
  border-radius: 10px;
  z-index: 6;
}

.detail.hidden { display: none; }

.detail h2 {
  margin: 0 0 8px;
  font-size: 13px;
}

#detail-close {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 24px;
  height: 24px;
  margin: 0;
  padding: 0;
  line-height: 1;
  font-size: 16px;
}

.detail table {
  width: 100%;
  border-collapse: collapse;
  font-size: 11px;
}

.detail td {
  padding: 2px 0;
  vertical-align: top;
}

.detail td:first-child { color: var(--muted); padding-right: 8px; }

.detail h3 {
  margin: 12px 0 4px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.maplibregl-popup-content {
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 8px;
  font: 12px/1.4 ui-sans-serif, system-ui, sans-serif;
}

.maplibregl-popup-tip { border-top-color: var(--panel) !important; }

/* --- observer dialog ------------------------------------------------------ */

.observers-dialog {
  width: min(420px, calc(100vw - 32px));
  max-height: min(70vh, 560px);
  padding: 14px;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  font: 13px/1.45 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

.observers-dialog::backdrop { background: rgba(3, 7, 12, 0.6); }

.observers-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.observers-head h2 {
  margin: 0;
  font-size: 14px;
}

#observers-close,
#top-close {
  width: 26px;
  height: 26px;
  margin: 0;
  padding: 0;
  flex: 0 0 auto;
  line-height: 1;
  font-size: 17px;
}

.observers-note {
  margin: 8px 0 10px;
  font-size: 11px;
  color: var(--muted);
}

.observers-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
  overflow-y: auto;
}

.observers-list li {
  padding: 8px 10px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 8px;
}

.obs-row-head {
  display: flex;
  align-items: center;
  gap: 7px;
}

.obs-row-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}

.obs-row-meta {
  display: block;
  margin-top: 4px;
  font-size: 11px;
  color: var(--muted);
}

.obs-badge {
  flex: 0 0 auto;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: 9.5px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border: 1px solid var(--line);
}

.obs-badge.online { color: #86efac; border-color: #245c34; background: #11251a; }
.obs-badge.stale { color: #fcd34d; border-color: #6b4e13; background: #2a2110; }
.obs-badge.offline { color: #fca5a5; border-color: #622727; background: #2a1414; }
.obs-badge.never { color: #fca5a5; border-color: #622727; background: #2a1414; }

.observers-empty,
.observers-error {
  margin: 10px 0 0;
  font-size: 11px;
  color: var(--muted);
}

.observers-error { color: #fca5a5; }

/* --- leaderboard dialog --------------------------------------------------- */

.top-dialog {
  width: min(560px, calc(100vw - 32px));
  max-height: min(82vh, 760px);
  overflow-y: auto;
}

.top-totals {
  margin: 0 0 10px;
  padding: 7px 9px;
  font-size: 11px;
  color: var(--muted);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 7px;
}

.top-section { margin-top: 14px; }

.top-section h3 {
  margin: 0;
  font-size: 12.5px;
  color: var(--text);
}

.top-hint {
  margin: 3px 0 7px;
  font-size: 11px;
  line-height: 1.4;
  color: var(--muted);
}

.top-empty {
  margin: 0;
  font-size: 11px;
  color: var(--muted);
}

.top-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}

.top-table th {
  padding: 4px 6px;
  color: var(--muted);
  font-size: 10px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  text-align: left;
  border-bottom: 1px solid var(--line);
}

.top-table td {
  padding: 5px 6px;
  border-bottom: 1px solid color-mix(in srgb, var(--line) 55%, transparent);
  vertical-align: top;
}

.top-table tr:last-child td { border-bottom: 0; }

/* The rank and the number are scanned down the column, so they stay tight and
   numeric; the name column takes whatever is left. */
.top-rank {
  width: 26px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.top-value {
  width: 92px;
  text-align: right;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.top-who {
  min-width: 0;
  overflow: hidden;
}

.top-link-name {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}

.top-sub {
  display: block;
  margin-top: 1px;
  font-size: 10.5px;
  color: var(--muted);
}


@media (max-width: 720px) {
  #sidebar {
    top: auto;
    left: 8px;
    right: 8px;
    bottom: 8px;
    width: auto;
    max-height: 46%;
  }
  .detail { top: 8px; left: 8px; right: 8px; width: auto; }
  /* The sidebar is a bottom sheet on a phone, so the menu starts at the left
     edge instead of reserving room for a panel that is no longer up there. */
  #topbar { top: 8px; left: 8px; }
}
