:root {
  color-scheme: light;
  --surface-1:      #fcfcfb;
  --page:           #f9f9f7;
  --text-primary:   #0b0b0b;
  --text-secondary: #52514e;
  --text-muted:     #898781;
  --grid:           #e1e0d9;
  --axis:           #c3c2b7;
  --border:         rgba(11,11,11,0.10);
  --series-1:       #2a78d6; /* blue: reachable */
  --series-2:       #eb6834; /* orange: known */
  --seq-blue-450:   #2a78d6;
  --seq-blue-300:   #6da7ec;
  --seq-orange-450: #eb6834;
  --seq-orange-300: #f0966e;
  --seq-aqua-450:    #1baf7a;
  --seq-yellow-450:  #eda100;
  --seq-magenta-450: #e87ba4;
  --seq-green-450:   #008300;
  --seq-violet-450:  #4a3aa7;
  --seq-red-450:     #e34948;
  --good:           #0ca30c;
}
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) {
    color-scheme: dark;
    --surface-1:      #1a1a19;
    --page:           #0d0d0d;
    --text-primary:   #ffffff;
    --text-secondary: #c3c2b7;
    --text-muted:     #898781;
    --grid:           #2c2c2a;
    --axis:           #383835;
    --border:         rgba(255,255,255,0.10);
    --series-1:       #3987e5;
    --series-2:       #d95926;
    --seq-blue-450:   #3987e5;
    --seq-blue-300:   #5598e7;
    --seq-orange-450: #d95926;
    --seq-orange-300: #e8814f;
    --seq-aqua-450:    #199e70;
    --seq-yellow-450:  #c98500;
    --seq-magenta-450: #d55181;
    --seq-green-450:   #008300;
    --seq-violet-450:  #9085e9;
    --seq-red-450:     #e66767;
    --good:           #0ca30c;
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --surface-1:      #1a1a19;
  --page:           #0d0d0d;
  --text-primary:   #ffffff;
  --text-secondary: #c3c2b7;
  --text-muted:     #898781;
  --grid:           #2c2c2a;
  --axis:           #383835;
  --border:         rgba(255,255,255,0.10);
  --series-1:       #3987e5;
  --series-2:       #d95926;
  --seq-blue-450:   #3987e5;
  --seq-blue-300:   #5598e7;
  --seq-orange-450: #d95926;
  --seq-orange-300: #e8814f;
  --seq-aqua-450:    #199e70;
  --seq-yellow-450:  #c98500;
  --seq-magenta-450: #d55181;
  --seq-green-450:   #008300;
  --seq-violet-450:  #9085e9;
  --seq-red-450:     #e66767;
  --good:           #0ca30c;
}

* { box-sizing: border-box; }

html, body {
  /* safety net: nothing on the page is meant to scroll horizontally except
     .table-scroll and the map/chart zoom-pan wrappers, which manage their
     own overflow already — this just stops any stray sub-pixel overflow
     elsewhere from ever dragging the whole page sideways on mobile */
  overflow-x: hidden;
}
body {
  margin: 0;
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  background: var(--page);
  color: var(--text-primary);
}

.wrap {
  max-width: 1100px;
  margin: 0 auto;
  padding: 32px 20px 80px;
}

header.page-header {
  margin-bottom: 28px;
}
.page-header-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
header.page-header h1 {
  font-size: 1.5rem;
  margin: 0 0 4px;
}
.theme-toggle {
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-1);
  color: var(--text-primary);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
.theme-toggle:hover {
  background: var(--grid);
}
.theme-toggle svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
header.page-header p {
  color: var(--text-secondary);
  margin: 0;
  font-size: 0.95rem;
}
header.page-header a {
  color: var(--seq-blue-450);
}

.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
  margin-bottom: 24px;
}
.stat-tile {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px 16px;
}
.stat-tile .label {
  font-size: 0.78rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.stat-tile .value {
  font-size: 1.7rem;
  font-weight: 600;
  margin-top: 2px;
  font-variant-numeric: proportional-nums;
}
.stat-tile .sub {
  font-size: 0.8rem;
  color: var(--text-secondary);
  margin-top: 2px;
}
.stat-tile.muted {
  background: transparent;
  border-style: dashed;
}
.stat-tile.muted .value {
  font-size: 1.3rem;
  color: var(--text-secondary);
}

.card {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 18px 20px;
  margin-bottom: 20px;
  /* CSS Grid items default to a minimum width based on their content's
     natural (unwrapped) size, not the track's actual size — a single bit of
     content that's a little too wide (varies by font rendering/hinting per
     device) can silently stretch the whole grid track past the viewport.
     min-width:0 removes that floor so the card actually shrinks to fit. */
  min-width: 0;
}
.card h2 {
  font-size: 1rem;
  margin: 0 0 4px;
}

/* Hidden until the answer is known, so it never flashes a wrong verdict. The
   accent only appears on a hit — a miss is information, not a failure. */
.visitor-card .card-sub { margin-bottom: 0; }
.visitor-card.is-node {
  border-color: var(--seq-aqua-450);
  box-shadow: inset 3px 0 0 var(--seq-aqua-450);
}
.visitor-meme {
  display: block;
  max-width: min(320px, 100%);
  height: auto;
  margin-top: 14px;
  border-radius: 8px;
}

/* Sits under the card's intro text, above the layer checkboxes: it changes
   what the whole card counts, so it reads as part of the description rather
   than as one more thing to tick. */
.switch {
  display: inline-flex;
  margin-bottom: 12px;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  user-select: none;
  font-size: 0.8rem;
  color: var(--text-secondary);
}
/* the real checkbox stays in the DOM (keyboard, forms, and the JS reads
   .checked off it) — it is only visually replaced by the track below */
.switch input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}
.switch-track {
  position: relative;
  flex: none;
  width: 34px;
  height: 20px;
  border-radius: 999px;
  background: var(--grid);
  border: 1px solid var(--border);
  transition: background 0.15s;
}
.switch-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--surface-1);
  box-shadow: 0 1px 2px rgba(0,0,0,0.25);
  transition: transform 0.15s;
}
.switch input:checked + .switch-track {
  background: var(--seq-blue-450);
  border-color: var(--seq-blue-450);
}
.switch input:checked + .switch-track .switch-thumb { transform: translateX(14px); }

/* Both sides are named, so which one is live has to be visible without
   reading the thumb position — the side that is counting goes solid, the
   other stays muted. :has() looks backwards past the input, which a sibling
   selector cannot; browsers without it simply show both sides muted. */
.switch-side { color: var(--text-muted); }
.switch:has(input:not(:checked)) .switch-side:first-child,
.switch:has(input:checked) .switch-side:last-child {
  color: var(--text-primary);
  font-weight: 600;
}
.switch input:focus-visible + .switch-track {
  outline: 2px solid var(--seq-blue-450);
  outline-offset: 2px;
}
.card .card-sub {
  font-size: 0.82rem;
  color: var(--text-muted);
  margin: 0 0 14px;
}
.card-subheading {
  font-size: 0.78rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin: 16px 0 8px;
}
.card summary {
  cursor: pointer;
  list-style: none;
}
.card summary::-webkit-details-marker { display: none; }
.card summary h2 {
  display: flex;
  align-items: center;
  gap: 6px;
}
.card summary h2::before {
  content: "▸";
  font-size: 0.8em;
  color: var(--text-muted);
  transition: transform 0.15s;
}
.card details[open] summary h2::before {
  transform: rotate(90deg);
}
.card details .card-sub,
.card details #entity-clusters {
  margin-top: 14px;
}

.cluster-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding-bottom: 12px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--border);
}
.cluster-tag {
  font-size: 0.7rem;
  line-height: 1.4;
  padding: 2px 7px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--grid);
  color: var(--text-secondary);
  white-space: nowrap;
  cursor: default;
}
.cluster-row {
  padding: 8px 6px;
  margin: 0 -6px;
  border-radius: 6px;
  font-size: 0.82rem;
}
.cluster-row.clickable { cursor: pointer; }
.cluster-row.clickable:hover { background: var(--grid); }
.cluster-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-bottom: 3px;
}
.cluster-size {
  font-weight: 600;
  color: var(--text-primary);
  margin-right: 2px;
}
/* the whole point of this block: an IP list is one long unbroken token soup,
   so it must be allowed to wrap onto as many lines as it needs. break-word
   (not anywhere) keeps the break between addresses where there's a space,
   and only splits inside a single address if one alone exceeds the width. */
.cluster-ips {
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  white-space: normal;
  overflow-wrap: break-word;
  word-break: break-word;
  line-height: 1.6;
}

.donate-body {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
}
.donate-qr {
  background: #fff;
  padding: 8px;
  border-radius: 8px;
  border: 1px solid var(--border);
  flex: 0 0 auto;
}
.donate-details {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.donate-details code {
  font-size: 0.85rem;
  color: var(--text-primary);
  background: var(--grid);
  padding: 6px 10px;
  border-radius: 8px;
  word-break: break-all;
}
.donate-address-link { text-decoration: none; }
.donate-address-link:hover code { background: var(--border); }
.donate-copy {
  background: none;
  border: 1px solid var(--border);
  color: var(--text-secondary);
  border-radius: 999px;
  padding: 5px 14px;
  font-size: 0.78rem;
  cursor: pointer;
  white-space: nowrap;
}
.donate-copy:hover { color: var(--text-primary); border-color: var(--text-muted); }
.donate-copy.copied { color: var(--seq-blue-450); border-color: var(--seq-blue-450); }

.charts-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
}
@media (min-width: 760px) {
  .charts-row.two-up { grid-template-columns: 1fr 1fr; }
}

.tabs { display: flex; gap: 4px; margin-bottom: 10px; }
.tabs .tab {
  background: none;
  border: 1px solid var(--border);
  color: var(--text-secondary);
  border-radius: 999px;
  padding: 4px 12px;
  font-size: 0.78rem;
  cursor: pointer;
}
.tabs .tab.active { background: var(--seq-blue-450); color: #fff; border-color: var(--seq-blue-450); }

.legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  font-size: 0.82rem;
  color: var(--text-secondary);
  margin-bottom: 8px;
}
.legend .item { display: flex; align-items: center; gap: 6px; }
.legend .swatch { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }

svg.chart { display: block; width: 100%; height: auto; overflow: visible; }
.gridline { stroke: var(--grid); stroke-width: 1; }
.axis-line { stroke: var(--axis); stroke-width: 1; }
.land-mass { fill: var(--grid); stroke: var(--axis); stroke-width: 0.5; }
.axis-label { fill: var(--text-muted); font-size: 10px; }
.chart-tooltip {
  position: absolute;
  pointer-events: none;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 6px 10px;
  font-size: 0.78rem;
  box-shadow: 0 4px 16px rgba(0,0,0,0.15);
  /* nowrap keeps a "SERVICE: 12.3%" row on one line, but the box as a whole
     must stay inside a phone screen — seven series at full label length is
     wider than 390px, and the overflow ran off the left edge with the labels
     cut in half. Capping the width lets the rows wrap instead. */
  white-space: nowrap;
  max-width: min(300px, 78vw);
  overflow-wrap: anywhere;
  opacity: 0;
  transition: opacity 0.1s;
  z-index: 5;
}
.chart-wrap { position: relative; overflow: hidden; }

/* The version chart used to be half-width next to a "Top countries" card and
   needed a flex stretch to match its height. It is full-width now and sizes
   from its own viewBox, so only the aspect ratio is pinned — without this,
   preserveAspectRatio="none" leaves the height to the browser. */
#version-chart { aspect-ratio: 1000 / 300; height: auto; }

.choropleth-legend {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.78rem;
  color: var(--text-muted);
  margin-bottom: 8px;
}
.choro-scale {
  flex: 1;
  max-width: 220px;
  height: 10px;
  border-radius: 5px;
  border: 1px solid var(--border);
}

.chart-wrap.map-wrap { cursor: grab; touch-action: none; }
.chart-wrap.map-wrap.panning { cursor: grabbing; }
.map-zoom-controls {
  position: absolute;
  top: 8px;
  right: 8px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  z-index: 4;
}
.map-zoom-controls button {
  width: 26px;
  height: 26px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--surface-1);
  color: var(--text-primary);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
.map-zoom-controls button:hover:not(:disabled) { border-color: var(--text-muted); }
.map-zoom-controls button:disabled { opacity: 0.35; cursor: default; }

.range-slider {
  position: relative;
  height: 20px;
  margin: 10px 4px 0;
}
.range-slider-track,
.range-slider-track-fill {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  height: 4px;
  border-radius: 2px;
}
.range-slider-track { left: 0; right: 0; background: var(--grid); }
.range-slider-track-fill { background: var(--seq-blue-450); }
.range-slider input[type="range"] {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 20px;
  margin: 0;
  background: none;
  pointer-events: none;
  -webkit-appearance: none;
  appearance: none;
}
.range-slider input[type="range"]::-webkit-slider-thumb {
  pointer-events: auto;
  -webkit-appearance: none;
  appearance: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--seq-blue-450);
  border: 2px solid var(--surface-1);
  cursor: pointer;
  margin-top: 0;
}
.range-slider input[type="range"]::-moz-range-thumb {
  pointer-events: auto;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--seq-blue-450);
  border: 2px solid var(--surface-1);
  cursor: pointer;
}
.range-slider input[type="range"]::-webkit-slider-runnable-track { background: none; }
.range-slider-labels {
  display: flex;
  justify-content: space-between;
  font-size: 0.75rem;
  color: var(--text-muted);
  margin-top: 4px;
}

/* Label sits on its own full-width line above the bar, instead of sharing a
   row with a fixed-width column — a side-by-side layout has no width left
   over for the label on a phone once a track and value column both claim
   their share, and every user_agent here starts with the same "/LitecoinCore:"
   prefix, so a squeezed label truncates to something identical on every row. */
.bar-row { margin: 10px 0; font-size: 0.82rem; }
.bar-row.clickable { cursor: pointer; border-radius: 6px; padding: 2px 4px; margin: 10px -4px; }
.bar-row.clickable:hover { background: var(--grid); }
.bar-row-top {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  margin-bottom: 4px;
}
.bar-row .bar-label {
  color: var(--text-secondary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.bar-row .bar-value {
  flex: 0 0 auto; white-space: nowrap;
  color: var(--text-primary); font-variant-numeric: tabular-nums;
}
.bar-row .bar-pct { margin-left: 5px; color: var(--text-muted); font-size: 0.9em; }
.bar-row .bar-track { display: block; width: 100%; background: var(--grid); border-radius: 3px; height: 8px; overflow: hidden; }
.bar-row .bar-fill { display: block; height: 100%; border-radius: 3px; }

.controls {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 12px;
}
.controls input[type="search"],
.controls select {
  background: var(--page);
  border: 1px solid var(--border);
  color: var(--text-primary);
  border-radius: 8px;
  padding: 7px 10px;
  font-size: 0.85rem;
}
.controls input[type="search"] { flex: 1; min-width: 180px; }
.controls button.csv-download {
  background: var(--page);
  border: 1px solid var(--border);
  color: var(--text-primary);
  border-radius: 8px;
  padding: 7px 12px;
  font-size: 0.85rem;
  cursor: pointer;
  white-space: nowrap;
}
.controls button.csv-download:hover { border-color: var(--text-muted); }
.cluster-chip {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--seq-blue-450);
  color: #fff;
  border-radius: 999px;
  padding: 6px 8px 6px 14px;
  font-size: 0.8rem;
  white-space: nowrap;
}
.cluster-chip button {
  background: rgba(255,255,255,0.25);
  border: none;
  color: #fff;
  border-radius: 50%;
  width: 20px;
  height: 20px;
  cursor: pointer;
  line-height: 1;
  font-size: 0.9rem;
}
.cluster-chip button:hover { background: rgba(255,255,255,0.4); }
/* the display:flex above outranks the hidden attribute's UA display:none,
   which left an empty blue pill in the filter bar whenever no group was
   selected — i.e. almost always */
.cluster-chip[hidden] { display: none; }

/* anything carrying a real explanation on hover, rather than the plain
   "here is the full text again" title most labels get */
.explained { cursor: help; text-decoration: underline dotted var(--text-muted); text-underline-offset: 3px; }

/* Tap target for that same explanation, because hover does not exist on a
   phone. Deliberately small and quiet next to the label, but with a padded
   hit area so it is still thumb-sized. */
.explain-btn {
  flex: none;
  width: 15px;
  height: 15px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: transparent;
  color: var(--text-muted);
  font: italic 600 10px/13px Georgia, "Times New Roman", serif;
  cursor: pointer;
}
.explain-btn:hover, .explain-btn:focus-visible {
  color: var(--text-primary);
  border-color: var(--text-muted);
}
.explain-pop {
  position: fixed;
  z-index: 60;
  width: max-content;
  max-width: min(320px, calc(100vw - 16px));
  padding: 10px 12px;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.18);
  color: var(--text-secondary);
  font-size: 0.78rem;
  line-height: 1.5;
}
.explain-pop[hidden] { display: none; }
.explain-pop strong { color: var(--text-primary); }
.explain-pop p { margin: 5px 0 0; }

table.nodes-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.82rem;
}
table.nodes-table th, table.nodes-table td {
  text-align: left;
  padding: 7px 10px;
  border-bottom: 1px solid var(--grid);
  white-space: nowrap;
}
table.nodes-table th {
  color: var(--text-muted);
  font-weight: 500;
  cursor: pointer;
  user-select: none;
  position: sticky;
  top: 0;
  background: var(--surface-1);
}
table.nodes-table th:hover { color: var(--text-primary); }
table.nodes-table td.mono { font-variant-numeric: tabular-nums; }
.status-dot {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 50%;
  margin-right: 6px;
}
.status-dot.up { background: var(--good); }
.status-dot.down { background: var(--text-muted); }

.table-scroll { overflow-x: auto; }
.pager {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 10px;
  font-size: 0.82rem;
  color: var(--text-secondary);
}
.pager button {
  background: var(--page);
  border: 1px solid var(--border);
  color: var(--text-primary);
  border-radius: 6px;
  padding: 5px 10px;
  cursor: pointer;
  font-size: 0.82rem;
}
.pager button:disabled { opacity: 0.4; cursor: default; }

/* Reads as body text rather than as a stat card: it is the one block on the
   page meant to be read in sentences. */
.privacy-card p {
  font-size: 0.85rem;
  color: var(--text-secondary);
  line-height: 1.55;
  max-width: 70ch;
  margin: 0 0 10px;
}
.privacy-card p:last-child { margin-bottom: 0; }
.privacy-card a { color: var(--seq-blue-450); }

footer.page-footer {
  margin-top: 28px;
  font-size: 0.78rem;
  color: var(--text-muted);
}

.series-toggles {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  margin-bottom: 10px;
  font-size: 0.78rem;
  color: var(--text-secondary);
}
.series-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  user-select: none;
}
.series-toggle input {
  margin: 0;
  cursor: pointer;
  accent-color: var(--seq-blue-450);
}
.series-toggle .swatch {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  display: inline-block;
}

/* private cluster-criteria view only */
.cluster-count {
  font-size: 0.7rem;
  color: var(--text-muted);
  margin: 0 10px 0 -2px;
  font-variant-numeric: tabular-nums;
}

/* SVG strokes scale with the viewBox, so zooming a map thickened every
   coastline and border along with it — the geometry was always vector, the
   line weight just followed the zoom. non-scaling-stroke pins stroke width to
   screen pixels instead. Covers land outlines, country borders and the node
   dots' halos in one rule. */
/* every stroked shape, not just paths: the world map draws its meridians as
   <line> and its frame as <rect>, so a path-only rule fixed the choropleths
   while the big map kept thickening. vector-effect is inert on unstroked
   elements, so casting the net wide costs nothing. */
.map-wrap svg * {
  vector-effect: non-scaling-stroke;
}

/* Tor easter egg: only ever inserted on click, so it adds nothing to a
   normal page load. */
.tor-egg {
  position: relative;
  padding-top: 56.25%;
  border-radius: 8px;
  overflow: hidden;
}
.tor-egg video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  background: #000;
}

.page-footer a { color: var(--seq-blue-450); }

/* Privacy note: a footer link that pops a small panel, rather than a card
   that pretends this is a legal document. */
.link-button {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: var(--seq-blue-450);
  cursor: pointer;
  text-decoration: underline;
}
.page-footer { position: relative; }
.privacy-pop {
  position: absolute;
  bottom: 100%;
  left: 0;
  z-index: 20;
  width: min(420px, calc(100vw - 40px));
  margin-bottom: 8px;
  padding: 14px 16px;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.18);
  color: var(--text-secondary);
  font-size: 0.82rem;
  line-height: 1.5;
}
.privacy-pop strong { color: var(--text-primary); }
.privacy-pop p { margin: 8px 0 0; }
.privacy-pop code {
  font-size: 0.95em;
  background: var(--grid);
  padding: 1px 5px;
  border-radius: 4px;
}
.privacy-close {
  position: absolute;
  top: 6px;
  right: 8px;
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}
.privacy-close:hover { color: var(--text-primary); }

/* Map time scrubber: one handle, so it reads as "a moment" rather than as
   the two-handle crop the charts use for a range. */
.map-time {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 2px 0 10px;
}
.map-time input[type="range"] {
  flex: 1;
  max-width: 420px;
  accent-color: var(--seq-blue-450);
}
.map-time-label {
  font-size: 0.8rem;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.map-time .link-button { font-size: 0.8rem; }
.map-time-hint { color: var(--text-muted); }

/* the hover surface must take pointer events even though it is transparent,
   and must not swallow the page's own scrolling on touch */
.hover-surface { pointer-events: all; touch-action: pan-y; }
