/* RocketVPN monitor — dark cosmic theme (palette borrowed from ru.rvpn.space). */

:root {
  --bg: #02031C;
  --bg-2: #050629;
  --card: #15122b;
  --card-2: #1a1736;
  --border: #1C1641;
  --border-2: #2a2154;
  --text: #ffffff;
  --muted: #8b8a9e;
  --accent: #6239EC;
  --accent-hi: #835FFD;
  --accent-dim: #3a226e;
  --ok: #2ecc71;
  --fail: #ff4d6d;
  --warn: #f0a800;
  --mono: "JetBrains Mono", ui-monospace, monospace;

  --shadow-card: 0 2px 8px rgba(0,0,0,0.25), 0 0 0 1px var(--border);
  --shadow-card-hi: 0 4px 16px rgba(98,57,236,0.15), 0 0 0 1px var(--border-2);
  --shadow-stat: 0 0 0 1px var(--border), 0 4px 14px rgba(0,0,0,0.3);
}

* { box-sizing: border-box; }

html, body {
  margin: 0; padding: 0;
  background: linear-gradient(180deg, var(--bg) 0%, var(--bg-2) 100%) fixed;
  color: var(--text);
  font-family: "Inter", system-ui, -apple-system, sans-serif;
  font-size: 14px;
  min-height: 100vh;
  position: relative;
  /* `overflow-x: hidden` would break `position: sticky` on the matrix thead
     (it creates a new scroll context that sticky elements attach to instead
     of the viewport). `clip` clips overflowing content the same way but
     doesn't establish a scroll container, so descendants can still stick
     to the page viewport. Supported in Chrome 90+, Firefox 81+, Safari 16+. */
  overflow-x: clip;
}

/* === Scrollbars — violet, matched to the cosmic theme === */
* { scrollbar-width: thin; scrollbar-color: rgba(131,95,253,0.55) rgba(131,95,253,0.08); }
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: rgba(131,95,253,0.07); border-radius: 8px; }
::-webkit-scrollbar-thumb { background: rgba(131,95,253,0.5); border-radius: 8px;
  border: 2px solid transparent; background-clip: padding-box; }
::-webkit-scrollbar-thumb:hover { background: rgba(131,95,253,0.8);
  border: 2px solid transparent; background-clip: padding-box; }
::-webkit-scrollbar-thumb:active { background: var(--accent-hi, #835FFD);
  border: 2px solid transparent; background-clip: padding-box; }
::-webkit-scrollbar-corner { background: transparent; }

/* === Cosmic background — ported from rocketvpn.net (rvpn-cosmic.css) ===
   body::before = drifting tiny stars; body::after = nebula glow;
   .grid-hud (added in base.html) = subtle HUD grid masked at edges. */
body::before, body::after, .grid-hud {
  content: ""; position: fixed; inset: -10vh 0;
  /* Horizontal inset deliberately 0 (not -10vw): on iOS Safari, fixed
     pseudo-elements extending past viewport-width can rubber-band the
     scroll position even with overflow-x:clip on html/body, making the
     mobile layout feel like it "wanders" on touch. Vertical -10vh kept
     so the cosmic background still covers during over-scroll. */
  pointer-events: none; z-index: 0;
}
body::before {
  background-image:
    radial-gradient(1px 1px at 12% 18%, #ffffff 50%, transparent 51%),
    radial-gradient(1px 1px at 27% 71%, #d6c8ff 50%, transparent 51%),
    radial-gradient(1px 1px at 44% 33%, #ffffff 50%, transparent 51%),
    radial-gradient(1px 1px at 58% 87%, #b8a4ff 50%, transparent 51%),
    radial-gradient(1px 1px at 71% 12%, #ffffff 50%, transparent 51%),
    radial-gradient(1px 1px at 83% 44%, #cabbff 50%, transparent 51%),
    radial-gradient(1px 1px at 92% 79%, #ffffff 50%, transparent 51%),
    radial-gradient(1px 1px at 5% 53%, #ffffff 50%, transparent 51%),
    radial-gradient(1px 1px at 35% 96%, #b8a4ff 50%, transparent 51%),
    radial-gradient(1px 1px at 65% 23%, #ffffff 50%, transparent 51%);
  background-size: 220px 220px;
  animation: stars-drift 90s linear infinite;
  opacity: 0.85;
}
body::after {
  background:
    radial-gradient(ellipse 50% 35% at 18% 22%, rgba(98, 57, 236, 0.28), transparent 65%),
    radial-gradient(ellipse 45% 30% at 82% 65%, rgba(140, 54, 164, 0.24), transparent 65%),
    radial-gradient(ellipse 40% 25% at 50% 100%, rgba(50, 48, 157, 0.22), transparent 70%),
    radial-gradient(ellipse 35% 25% at 90% 10%, rgba(170, 145, 254, 0.18), transparent 65%);
  animation: nebula-shift 30s ease-in-out infinite alternate;
  filter: blur(2px);
}
.grid-hud {
  inset: 0;
  background-image:
    linear-gradient(rgba(131, 95, 253, 0.06) 1px, transparent 1px),
    linear-gradient(90deg, rgba(131, 95, 253, 0.06) 1px, transparent 1px);
  background-size: 80px 80px;
  mask-image: radial-gradient(ellipse 80% 60% at 50% 50%, black, transparent 80%);
  -webkit-mask-image: radial-gradient(ellipse 80% 60% at 50% 50%, black, transparent 80%);
}
@keyframes stars-drift {
  0%   { transform: translate3d(0, 0, 0); }
  100% { transform: translate3d(-220px, -220px, 0); }
}
@keyframes nebula-shift {
  0%   { transform: translate(0, 0) scale(1);    opacity: 0.85; }
  50%  { transform: translate(2vw, -1vh) scale(1.05); opacity: 1;    }
  100% { transform: translate(-1vw, 1vh) scale(0.97); opacity: 0.7;  }
}
/* All content sits above the cosmos layer. */
.topbar, main, footer { position: relative; z-index: 1; }

.topbar {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 28px;
  border-bottom: 1px solid var(--border);
  background: rgba(2,3,28,0.85);
  backdrop-filter: blur(12px);
  position: sticky; top: 0; z-index: 50;
}
.topbar .logo a { color: var(--text); text-decoration: none; font-weight: 600; font-size: 15px; }
.topbar nav a { color: var(--muted); margin-left: 22px; text-decoration: none; font-size: 13px; font-weight: 500; }
.topbar nav a:hover { color: var(--accent-hi); }

main { max-width: 1400px; margin: 0 auto; padding: 28px 24px 60px; }
footer { padding: 22px 28px; text-align: center; color: var(--muted); font-size: 12px; }
.probe-state { color: var(--accent-hi); font-family: var(--mono); }

/* ---------- HERO ---------- */
.hero {
  display: flex; justify-content: space-between; align-items: flex-end;
  gap: 24px; flex-wrap: wrap;
  margin-bottom: 22px;
}
.hero-left { flex: 1 1 280px; min-width: 0; }
.hero h1 { margin: 0 0 4px 0; font-size: 28px; font-weight: 700; letter-spacing: -0.02em; }
.hero h1 .muted { font-weight: 400; }
.hero h1 small { color: var(--muted); font-weight: 400; font-size: 14px; margin-left: 10px; font-family: var(--mono); }
.hero .meta { color: var(--muted); margin: 0; font-size: 13px; }
.hero .actions { margin-top: 14px; }

.hero-detail h1 .hero-flag { font-size: 32px; margin-right: 6px; vertical-align: middle; filter: drop-shadow(0 0 4px rgba(0,0,0,0.4)); }

.stat-cards {
  display: flex; gap: 12px; flex-wrap: wrap;
  min-width: 0;
}
.stat {
  background: linear-gradient(155deg, var(--card) 0%, var(--card-2) 100%);
  border-radius: 12px;
  padding: 14px 18px;
  min-width: 92px;
  box-shadow: var(--shadow-stat);
  transition: transform 0.15s, box-shadow 0.15s;
}
.stat:hover { transform: translateY(-2px); box-shadow: var(--shadow-card-hi); }
.stat-value { font-size: 22px; font-weight: 700; line-height: 1.1; }
.stat-value small { color: var(--muted); font-weight: 400; font-size: 12px; }
.stat-label { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; margin-top: 2px; }

/* ---------- FILTERS / PILLS ---------- */
.filters {
  display: flex; gap: 12px; align-items: center; flex-wrap: wrap;
  margin-bottom: 16px;
}
.pills-strip {
  display: flex; gap: 8px; flex-wrap: wrap; flex: 1 1 auto;
}
.proto-pill {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 6px 14px 6px 10px;
  font-size: 12px;
  color: var(--muted);
  text-decoration: none;
  display: inline-flex; align-items: center; gap: 8px;
  transition: all 0.12s;
}
.proto-pill:hover {
  border-color: var(--accent);
  color: var(--text);
}
.proto-pill b {
  background: var(--accent-dim);
  color: var(--text);
  border-radius: 999px;
  padding: 2px 10px;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  min-width: 28px;
  text-align: center;
}
.proto-pill.active {
  background: var(--accent);
  border-color: var(--accent-hi);
  color: white;
}
.proto-pill.active b {
  background: rgba(255,255,255,0.22);
  color: white;
}

.search-form { flex: 0 0 260px; }
.search-form input[type="search"] {
  width: 100%;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 12px;
  color: var(--text);
  font: inherit;
  outline: none;
  transition: border-color 0.15s;
}
.search-form input[type="search"]:focus { border-color: var(--accent-hi); }
.search-form input[type="search"]::placeholder { color: var(--muted); }

.active-filters {
  display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
  margin-bottom: 14px;
}
.chip {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 10px;
  font-size: 12px;
  color: var(--text);
  display: inline-flex; align-items: center; gap: 6px;
}
.chip a { color: var(--fail); text-decoration: none; font-weight: 600; }
.chip a:hover { color: var(--text); }

/* ---------- CARDS ---------- */
.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 18px 20px;
  margin-bottom: 18px;
  box-shadow: var(--shadow-card);
}
.card h2 { font-size: 14px; margin: 0 0 14px 0; font-weight: 600; letter-spacing: 0.3px; }
/* matrix-card: no overflow:hidden — it would break sticky thead inside.
   Border-radius is applied directly to first/last thead cells instead. */
.card.matrix-card { padding: 0; }

.two-col { display: grid; gap: 18px; grid-template-columns: 1fr; }
@media (min-width: 920px) { .two-col { grid-template-columns: 1fr 1fr; } }

/* ---------- MATRIX TABLE ---------- */
table { width: 100%; border-collapse: collapse; }
.matrix thead th {
  text-align: left; font-weight: 500; color: var(--muted); font-size: 11px;
  padding: 14px 12px;
  border-bottom: 1px solid var(--border-2);
  text-transform: uppercase; letter-spacing: 0.6px;
  position: sticky;
  /* Topbar is sticky `top: 0; z-index: 50`. Thead must sit BELOW it, not
     at the same top:0 (otherwise they overlap and topbar — with higher
     z-index — hides the thead). 56px = topbar padding 14+14 + content
     ~27 + 1px border. If you tweak topbar padding, update this too. */
  top: 56px;
  background: rgba(5, 6, 41, 0.88);
  backdrop-filter: saturate(150%) blur(10px);
  -webkit-backdrop-filter: saturate(150%) blur(10px);
  z-index: 20;
  box-shadow: 0 4px 8px -4px rgba(0,0,0,0.5);
}
.matrix thead tr:first-child th:first-child { border-top-left-radius: 12px; }
.matrix thead tr:first-child th:last-child { border-top-right-radius: 12px; }
.matrix thead .th-link { color: var(--muted); text-decoration: none; }
.matrix thead .th-link:hover { color: var(--accent-hi); }
.matrix thead .th-link.active { color: var(--accent-hi); }
.matrix td {
  padding: 8px 12px;
  border-bottom: 1px solid rgba(28,22,65,0.5);
  font-size: 13px;
}
.matrix tbody tr { transition: background 0.1s; }
.matrix tbody tr:hover { background: rgba(98,57,236,0.06); }
.matrix tbody tr.disabled { opacity: 0.4; }

/* Health row badges (computed in routers/servers.py from last-24h probes) */
.matrix tr.health-down  td.code { color: var(--fail); }
.matrix tr.health-down  { background: rgba(255, 77, 109, 0.06); }
.matrix tr.health-down:hover { background: rgba(255, 77, 109, 0.12); }
.health-dot {
  display: inline-block; width: 6px; height: 6px;
  background: var(--fail); border-radius: 50%;
  /* Placed AFTER the server-code link; small left margin separates them. */
  margin-left: 6px; vertical-align: middle;
  box-shadow: 0 0 6px var(--fail);
  animation: alert-pulse 1.6s ease-in-out infinite;
}
.health-dot.ok {
  background: var(--ok); box-shadow: 0 0 6px var(--ok);
  animation: none;
}
.health-dot.warn { background: var(--warn); box-shadow: 0 0 6px var(--warn); animation: none; }
@keyframes alert-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.5; transform: scale(1.25); }
}

.matrix .th-flag { width: 34px; }
.matrix .cell-flag {
  font-size: 18px; text-align: center; width: 34px;
  filter: drop-shadow(0 0 2px rgba(0,0,0,0.3));
}
.matrix .th-code { width: 110px; }
.matrix .code { font-family: var(--mono); font-size: 12px; }
.matrix .code a { color: var(--text); text-decoration: none; }
.matrix .code a:hover { color: var(--accent-hi); }

/* Stat-card link variant — same look as .stat but is an <a>. */
.stat.stat-link { text-decoration: none; cursor: pointer; transition: transform 0.12s, border-color 0.15s; }
.stat.stat-link:hover { transform: translateY(-1px); border-color: var(--accent); }

/* ─── Traffic dashboard ─────────────────────────────────────────── */
.range-buttons { display: flex; gap: 8px; }
.range-btn {
  background: rgba(28,22,65,0.4);
  border: 1px solid rgba(131,95,253,0.25);
  color: var(--text-dim, #a8a3d4);
  padding: 8px 16px;
  border-radius: 8px;
  font-family: var(--mono); font-size: 12px;
  cursor: pointer;
  transition: all 0.15s;
}
.range-btn:hover { border-color: var(--accent); color: var(--text); }
.range-btn.active {
  background: var(--accent);
  border-color: var(--accent);
  color: white;
}

.traffic-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 14px;
  margin-bottom: 22px;
}
.tcard {
  background: rgba(28,22,65,0.45);
  border: 1px solid rgba(131,95,253,0.2);
  border-radius: 12px;
  padding: 16px 18px;
}
.tcard-label {
  color: var(--text-dim, #a8a3d4);
  font-size: 11px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  margin-bottom: 6px;
}
.tcard-value {
  font-size: 22px; font-weight: 600;
  font-family: var(--mono);
  color: var(--text, #ece9ff);
}
.tcard-value.accent { color: #fbbf24; }
.tcard-awg-dn { border-left: 3px solid #3b82f6; }
.tcard-awg-up { border-left: 3px solid #06b6d4; }
.tcard-wg     { border-left: 3px solid #10b981; }
.tcard-proxies { border-left: 3px solid #f59e0b; }
.tcard-grand  { border-left: 3px solid #fbbf24; }

.chart-wrap { height: 420px; padding: 12px 6px 0; }

/* Total online connections — own column between Code and Country.
   Yellow/amber to set it apart from the green per-protocol counts.
   Right-aligned + fixed width so the digits stack neatly. */
.matrix .th-online-total {
  width: 50px;
  text-align: right;
  color: rgba(251, 191, 36, 0.7);
  font-size: 12px;
}
.matrix .online-total-cell {
  width: 50px;
  text-align: right;
  padding-right: 12px;
  font-family: var(--mono);
}
.matrix .online-total {
  display: inline-block;
  min-width: 22px;
  font-size: 11px;
  font-weight: 600;
  color: #fbbf24;
  background: rgba(251, 191, 36, 0.10);
  border: 1px solid rgba(251, 191, 36, 0.3);
  border-radius: 6px;
  padding: 1px 6px;
  line-height: 14px;
}

.matrix .th-loc { width: 220px; }
.matrix .loc a.country-link { color: var(--text); text-decoration: none; }
.matrix .loc a.country-link:hover { color: var(--accent-hi); }

.matrix td.cell {
  font-family: var(--mono);
  font-size: 11px;
  min-width: 96px;
  /* Padding kept on td; grid inside .cell-grid handles internal alignment. */
}
.matrix td.cell.has-cfg { color: var(--text); }
.matrix .dash { color: rgba(255,255,255,0.12); font-size: 16px; }
.matrix .cell small { color: var(--muted); margin-left: 1px; font-size: 9px; }
.matrix .empty { text-align: center; padding: 30px; }

/* Per-cell grid: [count] [dot] [speed]. Fixed widths on the first two
   columns mean dots line up vertically across all rows AND across all
   protocol columns — no more jitter from missing badges shifting the
   dot left/right. */
.matrix .cell-grid {
  display: grid;
  grid-template-columns: 24px 12px 1fr;
  align-items: center;
  column-gap: 5px;
}
.matrix .cell-grid .cell-count {
  font-size: 10px;
  font-weight: 600;
  text-align: right;
  color: #4ade80;
  background: rgba(74,222,128,0.12);
  border: 1px solid rgba(74,222,128,0.3);
  border-radius: 6px;
  padding: 0 4px;
  line-height: 14px;
  /* When empty (no badge), the box collapses but the grid slot stays. */
}
.matrix .cell-grid .cell-count:empty {
  background: transparent;
  border-color: transparent;
}
.matrix .cell-grid .cell-speed {
  text-align: left;
  white-space: nowrap;
}
/* `dash` is the placeholder for cells without a config — span all 3
   tracks and keep it visually centred where the dot would have been. */
.matrix .cell-grid .dash {
  grid-column: 2 / 3;
  text-align: center;
}

.dot {
  display: inline-block;
  width: 8px; height: 8px; border-radius: 50%;
  margin-right: 4px; vertical-align: middle;
}
.dot.ok { background: var(--ok); box-shadow: 0 0 8px var(--ok); }
.dot.fail { background: var(--fail); box-shadow: 0 0 8px var(--fail); }
.dot.none { background: var(--muted); opacity: 0.6; }

/* ---------- BUTTONS / FORMS ---------- */
button {
  background: var(--accent);
  color: white; border: 0;
  padding: 9px 18px;
  border-radius: 8px;
  font: inherit; font-weight: 500;
  cursor: pointer;
  transition: all 0.15s;
}
button:hover { background: var(--accent-hi); box-shadow: 0 4px 12px rgba(98,57,236,0.3); }
button.ghost {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text);
  box-shadow: none;
}
button.ghost:hover { border-color: var(--accent-hi); color: var(--accent-hi); background: rgba(98,57,236,0.05); }
button.ghost.danger { color: var(--fail); border-color: rgba(255,77,109,0.3); }
button.ghost.danger:hover { color: var(--fail); border-color: var(--fail); }
button.small { padding: 4px 10px; font-size: 12px; }

form label { display: block; margin-bottom: 12px; font-size: 12px; color: var(--muted); }
form input, form textarea, form select {
  display: block;
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 8px;
  padding: 10px 12px;
  font: inherit;
  margin-top: 4px;
  transition: border-color 0.15s, background 0.15s;
}
form input:focus, form textarea:focus, form select:focus {
  outline: none;
  border-color: var(--accent-hi);
  background: var(--card);
}
form input[type="file"] {
  padding: 6px;
  cursor: pointer;
}
form input[type="file"]::file-selector-button {
  background: var(--accent-dim);
  color: white;
  border: 0;
  padding: 6px 12px;
  margin-right: 10px;
  border-radius: 6px;
  cursor: pointer;
  font: inherit;
}
form input[type="file"]::file-selector-button:hover { background: var(--accent); }

.add-server-form { display: grid; grid-template-columns: 1fr 1fr; gap: 0 18px; margin-top: 10px; }
.add-server-form label.full { grid-column: 1 / -1; }

.upload-form .row { display: grid; grid-template-columns: 1fr 1fr; gap: 0 14px; }
.upload-form label { margin-bottom: 14px; }
.upload-form small { color: var(--muted); font-weight: 400; }

details > summary { cursor: pointer; user-select: none; color: var(--accent-hi); font-weight: 500; }
details[open] > summary { margin-bottom: 10px; }

/* ---------- MISC ---------- */
.crumbs { margin-bottom: 12px; }
.crumbs a { color: var(--accent-hi); text-decoration: none; font-size: 13px; }
.crumbs a:hover { text-decoration: underline; }

.muted { color: var(--muted); }
.small { font-size: 12px; }
.mono { font-family: var(--mono); }
.pill {
  padding: 2px 10px; border-radius: 12px; font-size: 11px;
  vertical-align: middle; margin-left: 8px;
}
.pill.warn { background: rgba(240,168,0,0.15); color: var(--warn); border: 1px solid rgba(240,168,0,0.5); }

.configs td.mono { font-size: 11px; color: var(--muted); }
.legend { color: var(--muted); font-size: 12px; margin-top: 14px; text-align: center; }
.legend .dot { margin-right: 2px; }

code { font-family: var(--mono); font-size: 0.9em; background: rgba(98,57,236,0.1); padding: 1px 5px; border-radius: 4px; color: var(--accent-hi); }

/* ---------- PROBE HISTORY CHART ---------- */
.chart-card { padding: 18px 22px; }
.chart-header { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 14px; margin-bottom: 10px; }
.chart-header h2 { margin: 0; }
.range-controls { display: flex; gap: 14px; align-items: center; flex-wrap: wrap; }
.range-presets { display: inline-flex; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 3px; }
.range-btn {
  background: transparent;
  color: var(--muted);
  border: 0; padding: 5px 12px;
  font: inherit; font-size: 12px; font-weight: 500;
  cursor: pointer; border-radius: 6px;
  transition: all 0.12s;
}
.range-btn:hover { color: var(--text); background: rgba(98,57,236,0.1); }
.range-btn.active { background: var(--accent); color: white; }
.range-btn.active:hover { background: var(--accent-hi); }

.range-dates {
  display: inline-flex; gap: 6px; align-items: center;
}
.range-dates input[type="date"] {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 6px;
  padding: 5px 8px;
  font: inherit; font-size: 12px;
  margin: 0;
  color-scheme: dark;
  width: 130px;
}
.range-dates input[type="date"]:focus { border-color: var(--accent-hi); outline: none; }

.chart-stats { font-size: 12px; color: var(--text); margin-bottom: 14px; }
.chart-wrap {
  position: relative;
  height: 360px;
  background: var(--bg);
  border-radius: 8px;
  padding: 12px;
  border: 1px solid var(--border);
}

.chart-legend {
  display: flex; flex-wrap: wrap; gap: 14px;
  margin-top: 12px; padding-top: 10px;
  border-top: 1px solid var(--border);
  font-family: var(--mono);
}
.legend-item {
  display: inline-flex; align-items: center; gap: 5px;
  cursor: pointer; user-select: none;
  padding: 2px 8px; border-radius: 12px;
  transition: opacity 0.12s, background 0.12s;
}
.legend-item:hover { background: rgba(98,57,236,0.1); }
.legend-item.muted-out { opacity: 0.35; }
.legend-swatch {
  display: inline-block;
  width: 10px; height: 10px; border-radius: 2px;
}

/* ─── Status column (ICMP ping over last 30 min) ──────────────────── */
.th-status { width: 60px; text-align: center; }
.status-cell { text-align: center; padding: 4px 0; }
.ping-dot {
  display: inline-block;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #555;
  vertical-align: middle;

  border: 1px solid rgba(0, 0, 0, 0.2);
}
.ping-dot.green  { background: #2ecc71; box-shadow: 0 0 6px rgba(46, 204, 113, 0.7); }
.ping-dot.yellow { background: #f1c40f; box-shadow: 0 0 6px rgba(241, 196, 15, 0.7); }
.ping-dot.orange { background: #e67e22; box-shadow: 0 0 6px rgba(230, 126, 34, 0.7); }
.ping-dot.red    { background: #e74c3c; box-shadow: 0 0 8px rgba(231, 76, 60, 0.9); animation: pulse-red 1.4s ease-in-out infinite; }
.ping-dot.none   { background: transparent; border-color: #888; }
@keyframes pulse-red {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}

/* Live-session override: probe failed/missing but the panel shows active
   sessions → service is provably up, the probe just couldn't replicate the
   node's config. Teal, distinct from green (probed-ok) and red (fail). */
.dot.live { background: #18c0c4; box-shadow: 0 0 8px #18c0c4; }


/* ═══════════════════════════════════════════════════════════════════
   MOBILE POLISH — added 2026-05-30 (RADAR mostly used from phones).
   Two breakpoints: ≤768px (most phones in portrait) and ≤480px (narrow).
   ═══════════════════════════════════════════════════════════════════ */

@media (max-width: 768px) {
  /* Topbar — compact, wrap nicely, no left-margin on nav links */
  .topbar { padding: 10px 12px; flex-wrap: wrap; gap: 6px; row-gap: 4px; }
  .topbar .logo a { font-size: 14px; }
  .topbar nav { gap: 10px; flex-wrap: wrap; row-gap: 6px; }
  .topbar nav a { margin-left: 0; font-size: 13px; padding: 3px 0; }
  .topbar nav .nav-user, .topbar nav .nav-logout, .topbar nav .nav-login { padding: 6px 0; }
  .nav-flags { margin-inline-start: 0; }
  .nav-flag { font-size: 17px; padding: 2px; }

  /* Main + footer padding */
  main { padding: 0 10px; }
  footer { padding: 12px; font-size: 12px; }

  /* Hero — stack vertically */
  .hero { padding: 16px 14px; gap: 12px; flex-direction: column; align-items: stretch; }
  .hero h1 { font-size: 22px; margin-bottom: 4px; }
  .hero h1 small { font-size: 12px; margin-left: 6px; }
  .hero .meta { font-size: 12px; }
  .hero-left { flex: none; }

  /* Stat cards — 2 per row */
  .stat-cards { gap: 6px; flex-wrap: nowrap; }
  .stat {
    padding: 8px 6px;
    min-width: 0;
    flex: 1 1 0;
    text-align: center;
    overflow: hidden;
  }
  .stat-value { font-size: 16px; line-height: 1.05; }
  .stat-value small { font-size: 10px; }
  .stat-label {
    font-size: 9px; letter-spacing: 0.3px;
    margin-top: 3px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }

  /* Filters bar — stack */
  .filters { flex-direction: column; gap: 10px; padding: 12px; }
  .pills-strip {
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
    padding-bottom: 4px;
    margin: 0 -2px;
  }
  .pills-strip::-webkit-scrollbar { height: 4px; }
  .pills-strip::-webkit-scrollbar-thumb { background: rgba(131,95,253,0.3); border-radius: 2px; }
  .proto-pill { white-space: nowrap; flex-shrink: 0; font-size: 13px; padding: 6px 11px; min-height: 32px; }
  .search-form { width: 100%; flex: 0 0 auto; }
  .search-form input { width: 100%; box-sizing: border-box; padding: 6px 12px; font-size: 16px; min-height: 34px; }

  /* Active-filter chips */
  .active-filters { gap: 6px; padding: 10px 12px; flex-wrap: wrap; }
  .active-filters .chip { font-size: 12px; padding: 4px 8px; }

  /* ─── MATRIX: horizontal scroller with a frozen identity column ─── */
  /* Approach ported from monitor.rvpn.online — proven on real phones.
     Instead of fighting to fit all columns into 360px (which mangles labels
     character-by-character), let the table be its NATURAL width, scroll
     horizontally, and freeze flag+code on the left so the user always
     knows which row they're looking at. */
  .card.matrix-card {
    padding: 0;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    border-radius: 12px;
    /* right-edge gradient hint: visual cue that there's more to scroll */
    background-image: linear-gradient(to left, rgba(98,57,236,0.10), transparent 28px);
    background-attachment: local;
  }
  /* Drop the VERTICAL sticky thead on phones — a horizontal scroller can't
     simultaneously vertically-stick to the viewport. The frozen left
     column (flag+code) matters more on a small screen than a pinned header. */
  .matrix thead th {
    position: static; top: auto;
    backdrop-filter: none; -webkit-backdrop-filter: none;
    box-shadow: none;
    padding: 11px 8px; font-size: 10px; letter-spacing: 0.4px;
  }
  .matrix td { padding: 8px 8px; font-size: 12px; }

  /* Freeze flag (left:0) + code (left:34px) so row identity is always visible
     while horizontally scrolling through protocol columns. */
  .matrix .th-flag, .matrix .cell-flag {
    position: sticky; left: 0; z-index: 6;
    background: #0a0b28;
  }
  .matrix .th-code, .matrix .code {
    position: sticky; left: 34px; z-index: 6;
    background: #0a0b28;
    box-shadow: 8px 0 8px -6px rgba(0,0,0,0.55);   /* seam shadow */
  }
  .matrix thead .th-flag, .matrix thead .th-code { z-index: 8; background: #0a0b28; }
  /* Keep frozen cells opaque over row-hover and health-down tints */
  .matrix tbody tr:hover .cell-flag,
  .matrix tbody tr:hover .code { background: #12132f; }
  .matrix tr.health-down .cell-flag,
  .matrix tr.health-down .code { background: #1a0c18; }

  /* Reclaim width: auto-size country/city, hide the city sub-label,
     tighten code column. */
  .matrix .th-loc { width: auto; }
  .matrix .loc .muted.small { display: none; }
  .matrix .th-code { width: 88px; }
  .matrix td.cell { min-width: 58px; font-size: 10px; }
  .matrix .cell-grid { grid-template-columns: 17px 10px 1fr; column-gap: 3px; }
  .matrix .cell-grid .cell-count { font-size: 9px; }
  .matrix .cell-speed small { font-size: 8px; }
  .matrix .th-online-total, .matrix .online-total-cell { width: 40px; }

  /* Generic cards */
  .card { padding: 14px; border-radius: 12px; }
  .card h2 { font-size: 15px; }

  /* Server-detail hero */
  .hero-detail h1 .hero-flag { font-size: 22px; }

  /* Tap targets — main CTAs at least 44px tall */
  button, .btn, input[type="submit"], .login-btn { min-height: 44px; }
}

@media (max-width: 480px) {
  .topbar { padding: 8px 10px; }
  .topbar .logo a { font-size: 13px; }
  .topbar nav { gap: 8px; }
  .topbar nav a { font-size: 12px; }
  .nav-flag { font-size: 15px; }

  .hero h1 { font-size: 19px; }
  .hero .meta { font-size: 11px; }

  /* Stat cards — 1 per row on very narrow */
  .stat { flex: 1 1 100%; }
  .stat-value { font-size: 20px; }

  .pills-strip { gap: 6px; }
  .proto-pill { font-size: 12px; padding: 5px 9px; }

  table.matrix { font-size: 11px; }
  .matrix td.cell { min-width: 52px; }
  .matrix .cell-grid { grid-template-columns: 15px 9px 1fr; }

  .card { padding: 12px; }
  .card h2 { font-size: 14px; }
}

/* Touch-friendly any device with no hover (phones/tablets) */
@media (hover: none) {
  .proto-pill:hover { background: inherit; }
  .stat:hover { transform: none; box-shadow: var(--shadow-card); }
}


/* ─── Zebra striping for all tables + recommendation rows ──────────── */
.matrix tbody tr:nth-child(odd)  { background: rgba(131, 95, 253, 0.07); }
.matrix tbody tr:nth-child(even) { background: rgba(131, 95, 253, 0.02); }
/* Hover wins over zebra (same specificity → !important keeps it crisp). */
.matrix tbody tr:hover           { background: rgba(98, 57, 236, 0.18) !important; }

/* Same zebra for the recommendation top-10 list rows (used on the
   dashboard's saved/done block and inside /me). */
.rec-top-row:nth-child(odd)  { background: rgba(131, 95, 253, 0.07); }
.rec-top-row:nth-child(even) { background: rgba(131, 95, 253, 0.02); }
.rec-top-row { border-radius: 6px; }
.rec-top-row + .rec-top-row { margin-top: 2px; }

/* Collapsible recommendations card: click the header to fold the table. */
.rec-rich-head { cursor: pointer; user-select: none; }
.rec-chevron { display: inline-block; margin-inline-end: 7px; font-size: 0.8em;
  color: var(--accent-hi); transition: transform .15s; }
.rec-rich.rec-collapsed .rec-chevron { transform: rotate(-90deg); }
.rec-rich.rec-collapsed .rec-saved-list,
.rec-rich.rec-collapsed .rec-saved-meta,
.rec-rich.rec-collapsed > p { display: none !important; }

/* ─── Mobile viewport containment (anti-wobble safety net) ──────────────
   Ported from monitor.rvpn.online — proven on iOS Safari.
   The matrix block above drops `position: sticky` on thead at <=768px,
   so a hard `overflow-x: hidden` here is safe (no sticky to break). */
@media (max-width: 880px) {
  /* Hard viewport lock — html+body cannot move horizontally. */
  html {
    width: 100%;
    max-width: 100vw;
    overflow-x: clip;
  }
  /* iOS Safari: overflow:hidden on html/body makes body a scroll
     container and breaks position:fixed (the AI widget button then
     scrolls with the page). Containment stays via overflow-x:clip
     (no scroll box) + the frozen cosmic layers below. */
  body {
    width: 100%;
    max-width: 100vw;
    overflow-x: clip;
    overscroll-behavior: contain;
    overscroll-behavior-x: none;
  }
  /* Cosmic decoration layers may extend ±10vw via inset on desktop; on
     phones keep them strictly inside the viewport. */
  body::before, body::after, .grid-hud {
    inset: 0;
    max-width: 100vw;
    max-height: 100vh;
    pointer-events: none;
    /* Freeze drift/scale on phones: the fixed pseudo-elements moving
       past the viewport were the real horizontal-wander source on iOS
       Safari (clip can't contain a position:fixed layer). */
    animation: none !important;
    transform: none !important;
  }
  /* Any rogue child that escapes its container cannot bleed sideways. */
  main, .matrix-card, .card { max-width: 100%; }
  main > *, .card > * { max-width: 100%; box-sizing: border-box; }

  /* Floating AI widget (bottom-right, ~64px) covers final rows on Safari.
     Give the page room to scroll past it. */
  main { padding-bottom: 120px; }

  /* Kill vertical pockets from hidden/empty sections that still reserve
     space due to CSS overrides. */
  [hidden] { display: none !important; }
  .rec-progress-wrap:empty,
  .rec-progress-wrap > [hidden] { display: none !important; }

  /* Tighten the seam between sections — cut card+filters gap ~3×.
     Default .card margin-bottom = 18px → 6px on phones. */
  .card { margin-bottom: 3px; }
  .filters { margin-bottom: 2px; padding: 2px 10px; }
  .card.matrix-card { margin-top: 0; }

  /* Force the filters bar into a vertical stack up to 880px (was 768px),
     so iPad-portrait + phablets also get pills-row → search-row → matrix,
     with the search input full-width left-aligned directly above the table. */
  .filters { flex-direction: column; align-items: stretch; gap: 2px; }
  .pills-strip { flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden;
                 -webkit-overflow-scrolling: touch; padding-bottom: 2px; }
  .search-form { width: 100%; order: 99; flex: 0 0 auto; }
  .search-form input { width: 100%; box-sizing: border-box; text-align: left; }
  /* 16px = iOS Safari no-zoom threshold: stop the page zooming when the
     search field is focused on phones (was 14px -> auto-zoom). */
  .search-form input[type="search"] { font-size: 16px; }

  /* Prose wrap on narrow viewports.
     ⚠️ Do NOT include `.card` here — word-break and overflow-wrap are
     INHERITED, so .card.matrix-card (a card wrapping the table) would
     cascade aggressive breaking into every <td>/<th>, mangling "Canada"
     into a vertical letter stack. Limit to prose-only containers. */
  .hero > *, .rec-rich p, p, li {
    overflow-wrap: anywhere; word-break: break-word;
  }
  /* Hard reset inside the matrix: explicit normal in case some ancestor
     still cascades break-word into table cells. */
  .matrix, .matrix th, .matrix td {
    word-break: normal; overflow-wrap: normal;
  }
  img, video, iframe { max-width: 100%; height: auto; }
}
