:root {
  --bg: #0a0e14;
  --bg-1: #11161e;
  --bg-2: #161c26;
  --line: #1f2632;
  --line-2: #2a3342;
  --text: #d6dde8;
  --muted: #7a8699;
  --dim: #4e5867;
  --accent: #00d9c0;
  --accent-2: #6b8aff;
  --ok: #41d99b;
  --warn: #ffb74d;
  --down: #ff5e7a;
  --mono: ui-monospace, SFMono-Regular, "JetBrains Mono", Menlo, Consolas, monospace;
  /* type scale — fixed steps; consolidates the old ad-hoc px sizes so IPs/labels match everywhere */
  --fs-2xs: 9px; --fs-xs: 10px; --fs-sm: 11px; --fs-base: 12px; --fs-md: 13px; --fs-lg: 14px; --fs-xl: 16px;
}

* { box-sizing: border-box; }

/* ---------- themed scrollbars (webkit + firefox) ---------- */
* { scrollbar-width: thin; scrollbar-color: var(--line-2) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: linear-gradient(180deg, var(--line-2), #2f3a4d);
  border-radius: 6px;
  border: 2px solid var(--bg);
}
::-webkit-scrollbar-thumb:hover {
  background: linear-gradient(180deg, var(--accent), var(--accent-2));
}
::-webkit-scrollbar-corner { background: transparent; }

/* Flag glyphs use system color-emoji fonts. Without this fallback, regional-
   indicator unicode shows as letter pairs on some platforms (older Windows
   especially). */
.flag-icon, .flag-cell {
  font-family: "Twemoji Country Flags", "Apple Color Emoji", "Segoe UI Emoji",
               "Noto Color Emoji", "Segoe UI Symbol", emoji, sans-serif;
}
html, body {
  margin: 0; padding: 0;
  background: var(--bg);
  color: var(--text);
  font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, sans-serif;
  min-height: 100vh;
}
body {
  background:
    radial-gradient(900px 600px at 0% -10%, rgba(0,217,192,0.08), transparent 70%),
    radial-gradient(800px 500px at 100% 10%, rgba(107,138,255,0.07), transparent 60%),
    var(--bg);
}
a { color: var(--accent); text-decoration: none; }
a:hover { color: #5cf2dd; }
.mono { font-family: var(--mono); font-size: var(--fs-base); }
.muted { color: var(--muted); }
.small { font-size: var(--fs-base); }

/* ---------- nav ---------- */
.topnav {
  display: flex;
  align-items: center;
  gap: 32px;
  padding: 14px 28px;
  border-bottom: 1px solid var(--line);
  background: rgba(10,14,20,0.85);
  backdrop-filter: blur(8px);
  position: sticky; top: 0; z-index: 10;
}
.brand {
  display: flex; align-items: center; gap: 10px;
  color: var(--text);
}
.brand-dot {
  width: 10px; height: 10px; border-radius: 50%;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  box-shadow: 0 0 14px rgba(0,217,192,0.5);
}
.brand-name { font-weight: 700; letter-spacing: 0.18em; font-size: var(--fs-lg); }
.brand-sub  { color: var(--muted); font-family: var(--mono); font-size: var(--fs-sm); }

.nav-links { display: flex; gap: 4px; flex: 1; }
.nav-links a {
  color: var(--muted);
  padding: 6px 12px;
  border-radius: 6px;
  font-size: var(--fs-md);
}
.nav-links a:hover { color: var(--text); background: var(--bg-1); }
.nav-links a.active { color: var(--accent); background: rgba(0,217,192,0.08); }

.nav-auth { display: flex; align-items: center; gap: 8px; }
.auth-user {
  font-family: var(--mono);
  font-size: var(--fs-base);
  color: var(--accent);
  padding: 4px 10px;
  border: 1px solid rgba(0,217,192,0.3);
  border-radius: 14px;
  white-space: nowrap;
}
.inline-form { display: inline; margin: 0; }

/* Nav responsive: compact / wrap at narrow widths so iPhone landscape
   (~932 px) and below doesn't squish the page content beneath. */
@media (max-width: 1000px) {
  .topnav { gap: 12px; padding: 10px 14px; }
  .brand-sub { display: none; }
  .nav-links a { padding: 5px 8px; font-size: var(--fs-base); }
  .auth-user { font-size: var(--fs-sm); padding: 3px 7px; }
  .btn-ghost { padding: 4px 9px; font-size: var(--fs-sm); }
}
@media (max-width: 720px) {
  .topnav { flex-wrap: wrap; gap: 8px; }
  .nav-links { order: 3; width: 100%; gap: 2px; overflow-x: auto; }
  .nav-auth { margin-left: auto; }
}

/* ---------- buttons ---------- */
.btn-primary, .btn-ghost, .btn-mini {
  font: inherit; cursor: pointer;
  border-radius: 6px;
  border: 1px solid transparent;
  transition: all 0.12s ease;
}
.btn-primary {
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #061018;
  padding: 10px 18px; font-weight: 600;
  border: none;
}
.btn-primary:hover { filter: brightness(1.1); }
.btn-ghost {
  background: transparent;
  color: var(--text);
  border-color: var(--line-2);
  padding: 6px 14px;
  font-size: var(--fs-base);
}
.btn-ghost:hover { background: var(--bg-1); border-color: var(--accent); }
.btn-mini {
  background: var(--bg-1);
  color: var(--text);
  border-color: var(--line-2);
  padding: 3px 9px;
  font-size: var(--fs-sm);
  margin-right: 4px;
}
.btn-mini:hover { background: var(--bg-2); border-color: var(--accent); }

/* ---------- layout ---------- */
.page { padding: 28px; max-width: 1500px; margin: 0 auto; }
.page-head { margin-bottom: 22px; }
.page-head h1 { font-size: 24px; font-weight: 700; margin: 0 0 4px; letter-spacing: -0.01em; }

.grid { display: grid; gap: 16px; }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1100px) { .grid-4 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 700px)  { .grid-4 { grid-template-columns: 1fr; } }
.span-2 { grid-column: span 2; }
@media (max-width: 1100px) { .grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); } .span-2 { grid-column: span 1; } }
@media (max-width: 700px)  { .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; } }

/* ---------- tiles + cards ---------- */
.tile, .card {
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 18px;
  transition: border-color 0.12s;
  position: relative;
}

/* Section accents — colored left bar + emoji-aligned headers per system.
   The accent is applied via a `data-section` attribute on the card. */
.card[data-section] { padding-left: 22px; }
.card[data-section]::before {
  content: "";
  position: absolute; left: 0; top: 14px; bottom: 14px; width: 4px;
  border-radius: 0 4px 4px 0;
  background: var(--section-accent, var(--accent));
}
.card[data-section="weather"]  { --section-accent: #6b8aff; }
.card[data-section="power"]    { --section-accent: #ffb74d; }
.card[data-section="honeypot"] { --section-accent: #ff5e7a; }
.card[data-section="dns"]      { --section-accent: #b785ff; }
.card[data-section="hubitat"]  { --section-accent: #41d99b; }
.card[data-section] .card-head h2 { color: var(--section-accent); }

.section-icon {
  display: inline-block;
  margin-right: 8px;
  font-size: 18px;
  vertical-align: -2px;
  filter: drop-shadow(0 0 4px var(--section-accent, var(--accent)));
}
.tile { display: block; color: var(--text); }
.tile:hover { border-color: var(--line-2); transform: translateY(-1px); }
.tile-offline { opacity: 0.6; }
.tile-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.tile-head h2, .card-head h2 { font-size: var(--fs-lg); font-weight: 600; margin: 0; letter-spacing: 0.03em; text-transform: uppercase; color: var(--muted); }
.card { padding: 20px; }
.card-head { display: flex; flex-wrap: wrap; gap: 12px; justify-content: space-between; align-items: baseline; margin-bottom: 16px; }
.card-head h2 { color: var(--text); font-size: 18px; text-transform: none; letter-spacing: -0.01em; }
.card .meta { display: flex; flex-wrap: wrap; gap: 6px; }
.meta-pill {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  color: var(--muted);
  background: var(--bg-2);
  padding: 3px 8px;
  border-radius: 10px;
}

.tile-stats { display: flex; gap: 18px; margin: 0; flex-wrap: wrap; }
.tile-stats > div { display: flex; flex-direction: column; }
.tile-stats dt { font-size: var(--fs-sm); color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }
.tile-stats dd { margin: 0; font-family: var(--mono); font-size: var(--fs-xl); color: var(--text); }
.big-num { font-family: var(--mono); font-size: 32px; margin: 8px 0 0; color: var(--accent); }

/* ---------- status pills ---------- */
.status {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 3px 8px;
  border-radius: 10px;
  border: 1px solid var(--line-2);
}
.status.ok   { color: var(--ok);   border-color: rgba(65,217,155,0.4); background: rgba(65,217,155,0.08); }
.status.off  { color: var(--dim);  border-color: rgba(78,88,103,0.4); }
.status.down { color: var(--down); border-color: rgba(255,94,122,0.4); background: rgba(255,94,122,0.06); }

.sev { font-family: var(--mono); font-size: var(--fs-sm); text-transform: uppercase; padding: 2px 6px; border-radius: 8px; }
.sev-info     { color: var(--accent-2); background: rgba(107,138,255,0.08); }
.sev-warning  { color: var(--warn);     background: rgba(255,183,77,0.08); }
.sev-error, .sev-critical { color: var(--down); background: rgba(255,94,122,0.08); }

/* ---------- tables ---------- */
.data-table {
  width: 100%;
  border-collapse: collapse;
  margin: 8px 0;
}
/* Mobile: allow data tables to scroll horizontally inside their card so
   the page itself never overflows. Tables with a specific responsive
   strategy (hp-attackers / hp-ports / visits-card) opt out. */
@media (max-width: 700px) {
  .card .data-table:not(.hp-attackers):not(.hp-ports):not(.visits-card) {
    display: block; overflow-x: auto; white-space: nowrap;
  }
  .card .data-table:not(.hp-attackers):not(.hp-ports):not(.visits-card) thead,
  .card .data-table:not(.hp-attackers):not(.hp-ports):not(.visits-card) tbody,
  .card .data-table:not(.hp-attackers):not(.hp-ports):not(.visits-card) tr {
    display: table; width: 100%; table-layout: auto;
  }
}
.data-table th, .data-table td {
  text-align: left;
  padding: 9px 12px;
  border-bottom: 1px solid var(--line);
  font-size: var(--fs-md);
  vertical-align: middle;
}
.data-table th {
  color: var(--muted);
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 500;
}
.data-table.compact th, .data-table.compact td { padding: 6px 10px; font-size: var(--fs-base); }
.data-table tbody tr:hover { background: var(--bg-2); }
.ua { color: var(--muted); }

.data-fold { margin-top: 14px; }
.data-fold summary { cursor: pointer; color: var(--muted); padding: 6px 0; font-size: var(--fs-base); }
.data-fold summary:hover { color: var(--text); }

.json-dump {
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 12px;
  font-family: var(--mono);
  font-size: var(--fs-base);
  color: var(--text);
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-word;
}

.kv { display: grid; grid-template-columns: max-content 1fr; gap: 8px 16px; margin: 0; }
.kv dt { color: var(--muted); font-family: var(--mono); font-size: var(--fs-base); }
.kv dd { margin: 0; word-break: break-word; }

.log-list { columns: 3; column-gap: 16px; font-size: var(--fs-base); color: var(--muted); list-style: none; padding: 0; margin: 0; }
@media (max-width: 900px) { .log-list { columns: 2; } }
@media (max-width: 600px) { .log-list { columns: 1; } }
.log-list li { margin: 2px 0; break-inside: avoid; }

.rank-list { list-style: none; padding: 0; margin: 8px 0 0; }
.rank-list li { display: flex; justify-content: space-between; padding: 4px 0; border-bottom: 1px solid var(--line); }
.rank-list li:last-child { border-bottom: none; }

/* ---------- alerts ---------- */
.alert { padding: 12px 16px; border-radius: 6px; font-size: var(--fs-md); }
.alert.error { background: rgba(255,94,122,0.08); border: 1px solid rgba(255,94,122,0.3); color: #ff97a9; }

/* ---------- login ---------- */
.login-shell { display: flex; align-items: center; justify-content: center; min-height: calc(100vh - 200px); }
.login-card {
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 40px;
  width: 100%; max-width: 380px;
}
.login-card h1 {
  font-size: 28px;
  margin: 0 0 8px;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  letter-spacing: 0.15em;
}
.login-form { display: flex; flex-direction: column; gap: 14px; margin-top: 24px; }
.login-form label { display: flex; flex-direction: column; gap: 5px; font-size: var(--fs-base); color: var(--muted); }
.login-form input {
  background: var(--bg);
  border: 1px solid var(--line-2);
  color: var(--text);
  padding: 10px 12px;
  border-radius: 6px;
  font: inherit;
  outline: none;
  transition: border 0.12s;
}
.login-form input:focus { border-color: var(--accent); }
.form-error { color: #ff97a9; font-size: var(--fs-base); margin: -4px 0 0; }

/* ---------- metric strip (top of overview/mezel) ---------- */
.metric-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 14px;
  margin-bottom: 8px;
}
.metric {
  background: linear-gradient(180deg, var(--bg-1) 0%, var(--bg-2) 100%);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 16px 20px;
  position: relative;
  overflow: hidden;
}
.metric::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: linear-gradient(180deg, var(--accent), var(--accent-2));
}
.metric-label {
  font-size: var(--fs-sm); color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.08em;
  margin-bottom: 6px;
}
.metric-value {
  font-family: var(--mono); font-size: 26px; color: var(--text);
  line-height: 1.1;
}
.metric-unit { font-size: var(--fs-lg); color: var(--muted); margin-left: 2px; }
.metric-sub { font-size: var(--fs-sm); color: var(--muted); margin-top: 4px; }
.metric-sub-f { font-size: var(--fs-base); color: var(--muted); margin-top: 2px; font-weight: 400; }

/* Smaller secondary °F under the big °C in the weather card */
.now-temp-f {
  font-size: 18px;
  color: var(--muted);
  margin-top: 6px;
  font-weight: 400;
  letter-spacing: 0;
}
.fc-temp-f { font-size: var(--fs-xs); color: var(--muted); display: block; margin-top: 2px; }

/* ---------- spacing helpers ---------- */
.mt-m { margin-top: 16px; }
.mt-l { margin-top: 28px; }

/* Hazmat zone — radiation-warning yellow stripes (matches weather dashboard) */
.hazmat-zone {
  margin: 14px -8px 0;
  padding: 10px 14px 14px;
  background: repeating-linear-gradient(135deg,
    rgba(255,180,40,0.05) 0 10px,
    rgba(255,180,40,0.10) 10px 20px);
  border: 1px solid rgba(255,180,40,0.35);
  border-radius: 10px;
  box-shadow: inset 0 0 0 1px rgba(255,180,40,0.04);
}
.hazmat-zone .kv-title { color: #ffd27a !important; letter-spacing: 0.3px; }
.hazmat-zone .kv-row { border-bottom-color: rgba(255,180,40,0.18); }

/* Power grid SVG circuit — animated flow */
/* =========================================================
   Power grid: SCADA-style HUD. The diagram itself is unchanged
   (same SVG, same flow animation) — wrapped in a terminal-feel
   frame: corner brackets, dot-grid background, slow scan line,
   header status bar with live clock + outlet/load telemetry,
   footer carrier line. Pure CSS / one tiny JS clock tick.
   ========================================================= */
.power-hud {
  position: relative;
  margin-bottom: 14px;
  border-radius: 10px;
  background:
    radial-gradient(ellipse at 50% 0%, rgba(65,217,155,0.07), transparent 70%),
    radial-gradient(ellipse at 100% 100%, rgba(107,138,255,0.06), transparent 60%),
    linear-gradient(180deg, #0a1018 0%, #07121a 100%);
  border: 1px solid rgba(65,217,155,0.18);
  box-shadow:
    inset 0 0 60px rgba(65,217,155,0.04),
    0 0 1px rgba(65,217,155,0.35),
    0 18px 40px rgba(0,0,0,0.45);
  font-family: var(--mono);
  overflow: hidden;
}

/* ---- Top HUD header ---- */
.power-hud-head {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 8px 14px;
  border-bottom: 1px solid rgba(65,217,155,0.18);
  background: linear-gradient(90deg,
    rgba(65,217,155,0.10), rgba(65,217,155,0.02) 70%, transparent);
  font-size: var(--fs-sm);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #9aa4ad;
  flex-wrap: wrap;
}
.phud-id {
  color: #41d99b;
  font-weight: 700;
  text-shadow: 0 0 6px rgba(65,217,155,0.5);
}
.phud-status {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 9px; border-radius: 12px;
  background: rgba(65,217,155,0.10);
  border: 1px solid rgba(65,217,155,0.35);
  color: #41d99b;
  letter-spacing: 0.16em;
}
.phud-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: #41d99b; display: inline-block;
  box-shadow: 0 0 8px #41d99b, 0 0 16px rgba(65,217,155,0.6);
  animation: phud-blink 1.4s ease-in-out infinite;
}
@keyframes phud-blink {
  0%, 100% { opacity: 1;   transform: scale(1); }
  50%      { opacity: 0.35; transform: scale(0.85); }
}
.phud-meta {
  margin-left: auto;
  display: inline-flex; gap: 12px; align-items: center; flex-wrap: wrap;
  font-size: var(--fs-xs);
}
.phud-meta-cell { display: inline-flex; gap: 5px; align-items: center; }
.phud-meta-k { color: #4a6071; }
.phud-meta-v { color: #cbd5db; font-weight: 600; letter-spacing: 0.08em; }
.phud-clock {
  color: #41d99b;
  letter-spacing: 0.12em;
  padding-left: 12px;
  border-left: 1px dashed rgba(65,217,155,0.3);
  font-variant-numeric: tabular-nums;
}


/* ---- Power-svg wrap (the existing container that holds the SVG) ---- */
.power-svg-wrap {
  position: relative;
  width: 100%;
  /* pdu-control's reactor container has no scroll viewport and never
     scrolls; mirror that. The old overflow-x:auto was for a retired
     linear diagram and is what kept bringing the mobile scroll back. */
  overflow: visible;
  /* Dot grid: 24px spacing, faint mint dots; plus subtle vignette so
     the corners feel like a CRT viewport. */
  background:
    radial-gradient(ellipse at center, transparent 50%, rgba(0,0,0,0.55) 100%),
    radial-gradient(circle, rgba(65,217,155,0.10) 1px, transparent 1.5px) 0 0 / 24px 24px,
    linear-gradient(180deg, #07121a 0%, #050a10 100%);
  -webkit-overflow-scrolling: touch;
  padding: 14px 14px 22px;
}
@media (min-width: 1100px) {
  .power-svg-wrap { overflow-x: visible; }
  .power-svg { width: 100% !important; height: auto !important; }
}

/* ---- Corner brackets (HUD viewport feel) ---- */
.phud-corner {
  position: absolute;
  width: 14px; height: 14px;
  border-color: #41d99b;
  pointer-events: none;
  z-index: 2;
  filter: drop-shadow(0 0 3px rgba(65,217,155,0.5));
}
.phud-corner-tl { top: 6px;    left: 6px;    border-top: 2px solid; border-left:  2px solid; }
.phud-corner-tr { top: 6px;    right: 6px;   border-top: 2px solid; border-right: 2px solid; }
.phud-corner-bl { bottom: 16px;left: 6px;    border-bottom: 2px solid; border-left:  2px solid; }
.phud-corner-br { bottom: 16px;right: 6px;   border-bottom: 2px solid; border-right: 2px solid; }

/* ---- Scan line ---- */
.phud-scan {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 50%;
  pointer-events: none;
  z-index: 1;
  background: linear-gradient(180deg,
    transparent 0%,
    rgba(65,217,155,0.06) 48%,
    rgba(65,217,155,0.16) 50%,
    rgba(65,217,155,0.06) 52%,
    transparent 100%);
  animation: phud-scan 6s ease-in-out infinite;
  mix-blend-mode: screen;
}
@keyframes phud-scan {
  0%   { transform: translateY(-60%); opacity: 0.15; }
  50%  { transform: translateY(180%); opacity: 0.55; }
  100% { transform: translateY(420%); opacity: 0.15; }
}
.power-svg {
  display: block;
  /* Keep the SVG at its NATURAL pixel size so the container can scroll —
     never shrunk to viewport width (which collapses everything to a
     sliver under the waterfall). */
}

.power-mobile { display: none; }
/* Phones: keep the radial reactor (it already scales to 100% width);
   just tighten the frame padding so it uses the narrow screen. */
@media (max-width: 700px) {
  .power-svg-wrap { padding: 6px 6px 14px; }
}

.pm-lane {
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-left: 3px solid var(--ok);
  border-radius: 10px;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.pm-ups {
  border-bottom: 1px solid var(--line);
  padding-bottom: 8px;
}
.pm-row-head {
  display: flex; justify-content: space-between; align-items: center;
  gap: 8px;
}
.pm-name { font-weight: 600; color: var(--text); font-size: var(--fs-lg); letter-spacing: 0.04em; }
.pm-row-sub { color: var(--muted); margin-top: 2px; }

.pm-groups {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 8px;
}
.pm-group {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-height: 56px;
}
.pm-group.has-pdu { border-color: rgba(65,217,155,0.3); }
.pm-grp-label {
  font-size: var(--fs-xs);
  color: var(--accent);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.pm-grp-empty { color: var(--dim); font-style: italic; }

.pm-pdus { display: flex; flex-direction: column; gap: 8px; }
.pm-pdu {
  background: var(--bg-1);
  border: 1px solid var(--line-2);
  border-radius: 6px;
  padding: 8px 10px;
  cursor: pointer;
}
.pm-pdu:hover { border-color: var(--accent); }
.pm-outlets {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(28px, 1fr));
  gap: 4px;
  margin-top: 6px;
}
.pm-outlet {
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 4px;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--mono);
  font-size: var(--fs-xs);
  border: 1px solid var(--line-2);
  cursor: pointer;
  transition: filter 0.12s, transform 0.12s;
}
.pm-outlet:active { transform: scale(0.94); }
.pm-on  { background: rgba(65,217,155,0.18); border-color: rgba(65,217,155,0.55); color: var(--ok); }
.pm-off { background: var(--bg-2); color: var(--dim); }
.pm-outlet-num { line-height: 1; }
.power-svg .ps-line {
  stroke: rgba(74, 96, 113, 0.55);
  stroke-width: 1.5; fill: none;
  stroke-dasharray: 4 4;
}

/* ============================================================
   Radial reactor styles (the reimagined power grid layout).
   ============================================================ */
/* Exact mirror of pdu-control's .pdu-reactor sizing: a square 1000x1000
   viewBox that always scales to the container width, capped, centred.
   width:100% means it can never overflow, so no scrollbar ever frames
   the core (no fragile :has() needed). */
/* Exact copy of pdu-control's working .pdu-reactor model: plain
   viewBox SVG, width:100%/height:auto, no aspect-ratio, no width/height
   attrs — sized purely by the container, which must NOT be a scroll
   viewport (see .power-svg-wrap overflow:visible below). */
.power-svg.power-svg-radial {
  display: block;
  width: 100%;
  height: auto;
  max-width: 760px;
  margin: 0 auto;
}
.power-svg-radial .ps-beam {
  stroke-linecap: round;
  fill: none;
}
.power-svg-radial .ps-beam-dim {
  stroke: rgba(74, 96, 113, 0.30);
  stroke-width: 1.1;
  stroke-dasharray: 2 5;
}
.power-svg-radial .ps-beam-live {
  stroke: #41d99b;
  stroke-width: 1.7;
  stroke-dasharray: 16 4;
  animation: ps-flow 1.8s linear infinite;
  filter:
    drop-shadow(0 0 3px rgba(65,217,155,0.85))
    drop-shadow(0 0 8px rgba(65,217,155,0.35));
  opacity: 0.95;
}

/* Core (meter) */
.power-svg-radial .ps-core-pulse {
  animation: ps-core-pulse 3.4s ease-in-out infinite;
  transform-origin: center;
  transform-box: fill-box;
}
@keyframes ps-core-pulse {
  0%, 100% { opacity: 0.85; transform: scale(1.00); }
  50%      { opacity: 1.00; transform: scale(1.06); }
}
.power-svg-radial .ps-core-rotate {
  transform-origin: 500px 500px;
  animation: ps-core-rotate 28s linear infinite;
}
@keyframes ps-core-rotate {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
.power-svg-radial .ps-core-label {
  fill: #b785ff; font-size: var(--fs-sm); font-weight: 700;
  letter-spacing: 0.22em; text-anchor: middle;
  filter: drop-shadow(0 0 5px rgba(183,133,255,0.7));
}
/* Core text — mirror pdu-control's .rx-core-* exactly so the readout
   reads the same: big value vertically centred on the core, "WATTS"
   on its own line below it, then V · A. */
.power-svg-radial .ps-core-val {
  fill: #41d99b;
  font-size: 42px; font-weight: 800;
  text-anchor: middle; dominant-baseline: middle;
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
  filter: drop-shadow(0 0 8px rgba(65,217,155,0.7));
}
.power-svg-radial .ps-core-unit {
  fill: rgba(65,217,155,0.65); font-size: var(--fs-xs); font-weight: 600;
  text-anchor: middle; letter-spacing: 0.42em;
}
.power-svg-radial .ps-core-sub  {
  fill: #cbd5db; font-size: var(--fs-base); text-anchor: middle;
  letter-spacing: 0.06em;
}

/* Orbital nodes (UPS + PDU) — share the same chip styling */
.power-svg-radial .ps-orbital {
  fill: rgba(8, 16, 22, 0.92);
  stroke-width: 1.4;
  filter: drop-shadow(0 0 10px rgba(65,217,155,0.18));
}
.power-svg-radial .ps-orb-name {
  fill: #d6e2ea; font-size: var(--fs-sm); font-weight: 700;
  letter-spacing: 0.08em; text-anchor: middle;
}
.power-svg-radial .ps-orb-sub {
  fill: #9aa4ad; font-size: var(--fs-2xs);
  letter-spacing: 0.10em; text-anchor: middle;
}

/* Orbit guide rings (UPS / PDU / Outlet layers) — dashed, glowing, pulsing. */
.power-svg-radial .ps-orbit-ring {
  fill: none;
  stroke: rgba(65,217,155,0.42);
  stroke-width: 1.1;
  stroke-dasharray: 3 5;
  filter: drop-shadow(0 0 3px rgba(65,217,155,0.55)) drop-shadow(0 0 8px rgba(65,217,155,0.25));
  animation: psOrbitPulse 4s ease-in-out infinite;
}
@keyframes psOrbitPulse {
  0%, 100% { stroke-opacity: 0.42; }
  50%      { stroke-opacity: 0.70; }
}

/* Outer-ring outlet dots — small glowing circles, coloured by state,
   clickable to open the outlet detail tooltip. */
.power-svg-radial .ps-outlet {
  stroke-width: 1.2;
  cursor: pointer;
  transition: r 0.15s, filter 0.15s, fill 0.15s, stroke 0.15s;
}
.power-svg-radial .ps-outlet-on {
  fill: #41d99b;
  stroke: #d6e2ea;
  filter: drop-shadow(0 0 5px #41d99b) drop-shadow(0 0 12px rgba(65,217,155,0.4));
}
.power-svg-radial .ps-outlet-off {
  fill: rgba(20, 28, 36, 0.95);
  stroke: rgba(74, 96, 113, 0.55);
  filter: none;
}
.power-svg-radial .ps-outlet:hover { r: 8; }

/* Consumer labels — small tag radiating just outside its outlet dot.
   Dashed tether ties the label visually to the dot so the association
   reads at a glance even when neighbours sit close. */
.power-svg-radial .ps-consumer {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  pointer-events: none;
}
.power-svg-radial .ps-consumer-on {
  fill: #d6ffe6;
  filter: drop-shadow(0 0 3px rgba(65,217,155,0.45));
}
.power-svg-radial .ps-consumer-off {
  fill: #94a3b8;
  opacity: 0.85;
}
.power-svg-radial .ps-consumer-tether {
  stroke: rgba(65, 217, 155, 0.55);
  stroke-width: 0.7;
  stroke-dasharray: 2 2;
  pointer-events: none;
}
.power-svg-radial .ps-consumer-tether.off {
  stroke: rgba(74, 96, 113, 0.35);
}

.power-svg-radial .ps-ring-cap {
  fill: rgba(65, 217, 155, 0.85);
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  filter: drop-shadow(0 0 4px rgba(65, 217, 155, 0.6));
}
.power-svg-radial .ps-ring-cap-bg {
  fill: rgba(8, 16, 22, 0.95);
  stroke: rgba(65, 217, 155, 0.45);
  stroke-width: 1;
}
/* Live wires: brighter neon green pulse + a wider blurred halo trace
   underneath for the bloom effect, plus a moving energy 'packet' that
   reads as a streaming pulse. */
.power-svg .ps-line-live {
  stroke: #41d99b;
  stroke-width: 2.2;
  fill: none;
  stroke-dasharray: 22 6;
  animation: ps-flow 1.6s linear infinite;
  filter:
    drop-shadow(0 0 4px rgba(65,217,155,0.9))
    drop-shadow(0 0 10px rgba(65,217,155,0.45));
}
@keyframes ps-flow {
  from { stroke-dashoffset: 30; }
  to   { stroke-dashoffset: 0;  }
}

/* Text inside SVG is presentation-only; let clicks fall through to the
   rect underneath so the whole node card is a tap target. */
.power-svg text { pointer-events: none; cursor: default; user-select: none; }
.power-svg .ps-node {
  fill: rgba(8, 16, 22, 0.85);
  stroke: rgba(65, 217, 155, 0.35);
  stroke-width: 1.2;
  filter: drop-shadow(0 0 6px rgba(65, 217, 155, 0.18));
}
.power-svg .ps-node-mains  { stroke: #6b8aff; }
.power-svg .ps-node-ups    { stroke: #41d99b; }
.power-svg .ps-node-pdu    { stroke: #ffb74d; }
.power-svg .ps-node-load   { stroke: #41d99b; }
.power-svg .ps-node-meter  { stroke: #b785ff; fill: rgba(183,133,255,0.08); }
.power-svg .ps-node-grp    { stroke: #60a5fa; }
.power-svg .ps-node-dev    { stroke: #60a5fa; fill: rgba(96,165,250,0.07); }
/* Meter core glow — was an inline style="filter:…" which the page CSP
   (style-src 'self') stripped; class form is CSP-safe. */
.power-svg-radial .ps-core-disc {
  filter: drop-shadow(0 0 12px rgba(65,217,155,0.6));
}
.power-svg text {
  fill: #d6e2ea;
  font-family: var(--mono);
  font-size: var(--fs-xs);
  text-anchor: middle;
  dominant-baseline: middle;
  letter-spacing: 0.04em;
}
.power-svg text.ps-label {
  fill: #41d99b;
  font-size: var(--fs-2xs);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  filter: drop-shadow(0 0 2px rgba(65,217,155,0.7));
}
.power-svg .ps-outlet-on  {
  fill: #41d99b;
  filter: drop-shadow(0 0 4px rgba(65,217,155,0.8));
}
.power-svg .ps-outlet-off { fill: rgba(20, 28, 36, 0.9); stroke: rgba(74, 96, 113, 0.4); }
.power-svg .ps-tap { cursor: pointer; }
.power-svg .ps-tap:hover { filter: brightness(1.25); }

.grid-tip {
  /* fixed (not absolute): a tall tooltip placed near the reactor used
     to extend the section's scroll height, bringing back the mobile
     scroll. Fixed floats over the viewport and never grows a
     container. JS positions it in viewport coords. */
  position: fixed;
  z-index: 200;
  background: rgba(15,20,28,0.97);
  border: 1px solid var(--line-2);
  border-radius: 8px;
  padding: 9px 12px;
  font-family: var(--mono);
  font-size: var(--fs-xs);
  color: var(--text);
  min-width: 200px;
  max-width: 240px;
  width: max-content;
  max-height: 70vh;
  overflow-y: auto;
  box-shadow: 0 10px 28px rgba(0,0,0,0.55);
  line-height: 1.55;
  font-weight: 400;
}
.grid-tip .gt-title {
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--accent);
  margin-bottom: 6px;
  padding-bottom: 5px;
  border-bottom: 1px solid var(--line);
  display: flex; justify-content: space-between; align-items: center; gap: 8px;
}
.grid-tip .gt-badge {
  font-size: var(--fs-2xs); letter-spacing: 0.1em; text-transform: uppercase;
  padding: 1px 7px; border-radius: 10px; font-weight: 500;
}
.grid-tip .gt-badge.gt-ok  { background: rgba(65,217,155,0.15); }
.grid-tip .gt-badge.gt-off { background: rgba(255,94,122,0.15); }
.grid-tip .gt-section {
  margin-top: 10px;
  font-size: var(--fs-2xs); letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--muted);
  padding-bottom: 2px;
  border-bottom: 1px dashed var(--line);
  font-weight: 400;
}
.grid-tip .gt-section:first-of-type { margin-top: 2px; }
.grid-tip .gt-row {
  display: flex; justify-content: space-between; gap: 10px;
  padding: 2px 0;
  font-size: var(--fs-xs);
}
.grid-tip .gt-row span { color: var(--muted); white-space: nowrap; font-weight: 400; }
.grid-tip .gt-row b    {
  text-align: right; word-break: break-word;
  font-weight: 400;
  color: var(--text);
}
.grid-tip .gt-ok  { color: var(--ok); }
.grid-tip .gt-off { color: var(--down); }
@media (max-width: 600px) {
  .grid-tip {
    min-width: 0;
    max-width: calc(100vw - 32px);
    width: max-content;
    max-height: 75vh;
  }
}
.power-svg .ps-glow {
  fill: none; stroke: #41d99b; stroke-width: 5;
  filter: blur(4px); opacity: 0.55;
  animation: ps-pulse 2.4s ease-in-out infinite;
}
@keyframes ps-pulse {
  0%, 100% { opacity: 0.3; }
  50%      { opacity: 0.7; }
}

/* Tap-hover on a node: subtle accent flash to confirm registered. */
.power-svg .ps-tap:hover .ps-node {
  stroke: #41d99b;
  filter: drop-shadow(0 0 8px rgba(65,217,155,0.7));
}

/* Waterfall (scintillator spectrogram) canvas */
.waterfall-wrap {
  position: relative;
  width: 100%;
  background: var(--bg-2);
  border: 1px solid rgba(255,180,40,0.35);
  border-radius: 8px;
  padding: 10px;
  margin-top: 12px;
}
.waterfall-wrap > summary {
  cursor: pointer;
  color: #ffd27a;
  font-size: var(--fs-sm);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  list-style: none;
}
.waterfall-wrap > summary::-webkit-details-marker { display: none; }
.waterfall-wrap > summary::before { content: "▶ "; font-size: var(--fs-xs); }
.waterfall-wrap[open] > summary::before { content: "▼ "; }
.waterfall-wrap .wf-title {
  font-size: var(--fs-md);
  color: var(--muted);
  margin-bottom: 8px;
  line-height: 1.45;
  max-width: 78ch;
}

.wf-canvas-wrap { position: relative; }
canvas.waterfall {
  display: block;
  width: 100%;
  height: 300px;
  background: #000;
  border-radius: 4px;
}
@media (max-width: 600px) {
  canvas.waterfall { height: 360px; }
}
#wf-marker {
  position: absolute; pointer-events: none;
  width: 1px; height: 100%; top: 0;
  background: rgba(255,255,255,0.6);
  box-shadow: 0 0 8px rgba(255,255,255,0.6);
  display: none;
}
#wf-tip {
  position: absolute;
  background: rgba(20,24,32,0.97);
  border: 1px solid var(--line-2);
  color: var(--text);
  font-family: var(--mono);
  font-size: var(--fs-sm);
  padding: 7px 10px;
  border-radius: 6px;
  pointer-events: none;
  white-space: normal;
  word-wrap: break-word;
  box-shadow: 0 4px 12px rgba(0,0,0,0.55);
  z-index: 20;
  width: 240px;
  max-width: 70vw;
  line-height: 1.45;
}
#wf-tip .tip-line   { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#wf-tip .tip-iso    { color: #ffd27a; font-weight: 600; margin-top: 4px; }
#wf-tip .tip-iso-info {
  color: var(--muted);
  font-size: var(--fs-xs);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  margin-top: 2px;
  line-height: 1.4;
}

.wf-axis {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 8px;
  font-family: var(--mono); font-size: var(--fs-xs); color: var(--muted);
  margin-top: 8px; padding: 0 2px;
}
.wf-axis-mid {
  flex: 1; text-align: center;
  font-size: var(--fs-2xs);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.wf-axis #wf-xmin, .wf-axis #wf-xmax {
  flex: 0 0 auto; color: #ffd27a;
}
@media (max-width: 600px) {
  .wf-axis-mid { display: none; }
}
.wf-legend {
  display: flex; align-items: center; gap: 8px;
  margin-top: 6px;
  font-size: var(--fs-xs);
}
.wf-gradient {
  flex: 1;
  height: 8px;
  border-radius: 2px;
  background: linear-gradient(90deg,
    #000000 0%, #00083c 10%, #001496 20%, #005ae6 30%,
    #00c8f0 40%, #1ee682 50%, #b4f03c 60%, #ffdc28 70%,
    #ff961e 80%, #ff4614 90%, #ff28c8 100%);
}
.wf-iso-row {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin-top: 10px;
  padding-top: 8px;
  border-top: 1px solid rgba(255,180,40,0.2);
}
.iso-badge {
  display: inline-flex; align-items: baseline; gap: 5px;
  padding: 3px 9px;
  border: 1px solid;
  border-radius: 11px;
  font-size: var(--fs-sm);
  line-height: 1;
  white-space: nowrap;
  cursor: help;
}
.iso-badge.strong { color: #ff5e7a; border-color: rgba(255,94,122,0.45); background: rgba(255,94,122,0.08); }
.iso-badge.likely { color: #ffb74d; border-color: rgba(255,183,77,0.45); background: rgba(255,183,77,0.08); }
.iso-badge.trace  { color: #6b8aff; border-color: rgba(107,138,255,0.4); background: rgba(107,138,255,0.06); }
.iso-badge { position: relative; }
.iso-badge:hover { filter: brightness(1.15); }
.iso-name { font-weight: 700; letter-spacing: 0.3px; }
.iso-conf { font-size: var(--fs-2xs); letter-spacing: 1px; text-transform: uppercase; opacity: 0.7; }

/* Detailed isotope click-tooltip — anchored to the badge inline, like the
   weather dashboard's tap-info. Sits just below the badge, clamps to the
   viewport so it can't slide off-screen. */
.iso-tip {
  position: absolute;
  z-index: 9999;
  top: 100%; left: 0;
  margin-top: 6px;
  min-width: 260px;
  max-width: 340px;
  background: rgba(15,20,28,0.98);
  border: 1px solid var(--line-2);
  border-radius: 8px;
  padding: 12px 14px;
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: var(--fs-base);
  line-height: 1.45;
  box-shadow: 0 8px 24px rgba(0,0,0,0.55);
  text-align: left;
  white-space: normal;
  cursor: default;
}
/* Small close X at the top-right */
.iso-tip-close {
  position: absolute; top: 6px; right: 10px;
  color: var(--muted);
  font-size: 18px; line-height: 1;
  cursor: pointer;
}
.iso-tip-close:hover { color: var(--text); }
.iso-tip-title {
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: var(--fs-lg); font-weight: 700; color: #ffd27a;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 6px;
}
.iso-tip-conf {
  font-size: var(--fs-xs); letter-spacing: 0.1em;
  padding: 2px 8px; border-radius: 10px;
  text-transform: uppercase; font-weight: 600;
}
.iso-tip-conf-strong { color: #ff5e7a; background: rgba(255,94,122,0.15); }
.iso-tip-conf-likely { color: #ffb74d; background: rgba(255,183,77,0.15); }
.iso-tip-conf-trace  { color: #6b8aff; background: rgba(107,138,255,0.15); }
.iso-tip-info { color: var(--muted); margin-bottom: 8px; }
.iso-tip-section { display: flex; flex-direction: column; gap: 2px; margin: 6px 0; }
.iso-tip-row {
  display: flex; justify-content: space-between; gap: 10px;
  font-family: var(--mono); font-size: var(--fs-sm);
}
.iso-tip-row span { color: var(--muted); }
.iso-tip-conf-text {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: var(--fs-sm);
  font-style: italic;
}
/* Make the isotope row a positioning context so the tooltip anchors to it
   (the row spans the full waterfall card width) — gives us natural
   horizontal alignment under the badges. */
.wf-iso-row { position: relative; }
.sub-h { font-size: var(--fs-md); color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; margin: 0 0 10px; font-weight: 500; }

/* ---------- kv blocks ---------- */
.kv-block {
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 14px 16px;
}
.kv-title { font-size: var(--fs-base); color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 8px; }
.kv-row { display: flex; justify-content: space-between; padding: 4px 0; border-bottom: 1px solid var(--line); font-size: var(--fs-base); }
.kv-row:last-child { border-bottom: none; }
.kv-k { color: var(--muted); }
.kv-v { color: var(--text); text-align: right; max-width: 60%; overflow: hidden; text-overflow: ellipsis; }

/* ---------- PDU device + outlet grid ---------- */
.pdu-device { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--line); }
.pdu-device:first-child { border-top: none; padding-top: 0; }
.pdu-dev-head { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-bottom: 12px; }
.pdu-name { font-size: var(--fs-lg); font-weight: 600; color: var(--text); }

.outlet-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 10px;
  margin-top: 10px;
}
.outlet {
  background: var(--bg-2);
  border: 1px solid var(--line-2);
  border-radius: 8px;
  padding: 12px;
  position: relative;
  transition: transform 0.12s, border-color 0.12s;
}
.outlet:hover { transform: translateY(-1px); }
.outlet-on  { border-color: rgba(65,217,155,0.45); box-shadow: inset 0 0 0 1px rgba(65,217,155,0.08); }
.outlet-off { opacity: 0.65; }
.outlet-num { position: absolute; top: 8px; right: 10px; font-family: var(--mono); font-size: var(--fs-xs); color: var(--muted); }
.outlet-name { font-size: var(--fs-base); font-weight: 500; margin-bottom: 4px; }
.outlet-state {
  font-family: var(--mono); font-size: var(--fs-sm);
  text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted);
}
.outlet-on .outlet-state { color: var(--ok); }
.outlet-watts { font-size: var(--fs-sm); color: var(--muted); margin-top: 4px; }

/* ---------- honeypot event feed + port badges ---------- */
.port-badges { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0; }
.port-badge {
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 3px 10px;
  font-size: var(--fs-sm);
  color: var(--text);
}

/* Honeypot trap pills — bigger, info-rich */
.trap-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 10px;
  margin-top: 10px;
}
.trap-pill {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: linear-gradient(90deg, rgba(255,94,122,0.08), rgba(255,94,122,0.02));
  border: 1px solid rgba(255,94,122,0.25);
  border-left: 3px solid var(--down);
  border-radius: 8px;
  font-size: var(--fs-base);
}
.trap-icon { font-size: 18px; line-height: 1; }
.trap-port { color: var(--down); font-weight: 600; font-size: var(--fs-lg); min-width: 50px; }
.trap-proto {
  text-transform: uppercase; letter-spacing: 0.06em;
  font-size: var(--fs-sm); color: var(--text);
  padding: 2px 8px; background: rgba(255,94,122,0.18); border-radius: 10px;
}
.trap-banner { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.event-feed {
  display: flex; flex-direction: column; gap: 4px;
  /* show ~10 rows then scroll; absolute cap via JS to 30 max children */
  max-height: 280px; overflow-y: auto;
  padding-right: 4px;
}
.event-row {
  display: grid;
  grid-template-columns: 70px 1fr 60px auto;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  background: var(--bg-2);
  border-left: 2px solid var(--line-2);
  border-radius: 4px;
  font-size: var(--fs-base);
}
.event-row:hover { border-left-color: var(--accent); }
.event-ts { color: var(--muted); white-space: nowrap; }
.event-ip { color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.event-port { color: var(--muted); text-align: right; }
.event-tags { display: flex; gap: 4px; flex-wrap: wrap; justify-content: flex-end; }
@media (max-width: 500px) {
  .event-row {
    grid-template-columns: 1fr auto;
    grid-template-rows: auto auto auto;
  }
  .event-ts { grid-row: 1; grid-column: 1; font-size: var(--fs-xs); }
  .event-ip { grid-row: 1; grid-column: 1; padding-left: 80px; }
  .event-port { grid-row: 1; grid-column: 2; }
  .event-tags { grid-row: 2; grid-column: 1 / -1; justify-content: flex-start; }
}
.event-tag {
  font-family: var(--mono); font-size: var(--fs-xs);
  padding: 2px 6px; border-radius: 4px;
  text-transform: uppercase; letter-spacing: 0.04em;
}
.tag-bad { background: rgba(255,94,122,0.15); color: var(--down); }
.tag-ok  { background: rgba(65,217,155,0.13); color: var(--accent); }
.tag-info { background: rgba(107,138,255,0.10); color: var(--accent-2); }
.tag-meta { background: var(--bg); color: var(--muted); }

/* Interface-type icons (WiFi / LAN / FW). Use currentColor so they
   pick up whatever text color the row label is using — keeps them
   on-theme automatically. Slightly muted via a softer accent so they
   read as decorative chrome, not as status (the UP/DOWN pill
   carries the actual state). */
.iface-icon {
  display: inline-block;
  vertical-align: -2px;
  margin-right: 5px;
  color: var(--accent-2);
  opacity: 0.85;
}
.kv-row .kv-k, .hub-card-row .hub-k { display: inline-flex; align-items: center; }
/* Hubitat attention feed: 3 cells (ts | level tag | title) — overrides the
   4-column honeypot grid so the title gets the flex room, not the tag. */
.event-row-hub { grid-template-columns: 70px auto 1fr; }
.event-row-hub .event-tag { justify-self: start; }
.event-row-hub .event-title { color: var(--text); white-space: normal; }

/* ---------- Hubitat monitored hub cards ---------- */
.hub-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 10px;
  margin-top: 10px;
}
.hub-card {
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-left: 3px solid var(--ok);
  border-radius: 8px;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.hub-card-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.hub-card-name { font-size: var(--fs-lg); font-weight: 600; color: var(--text); }
.hub-card-ip { color: var(--muted); }
.hub-card-row { display: flex; justify-content: space-between; gap: 10px; }
.hub-card-row > div { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.hub-k { font-size: var(--fs-xs); color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }
.hub-v { font-size: var(--fs-base); color: var(--text); }
.hub-card-msg {
  margin-top: 4px;
  padding-top: 6px;
  border-top: 1px solid var(--line);
  font-style: italic;
}

/* ---------- DNS grid ---------- */
.dns-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 10px;
}
.dns-tile {
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 12px 14px;
  position: relative;
}
.dns-tile:hover { border-color: var(--line-2); }
.dns-entry { font-size: var(--fs-base); color: var(--text); word-break: break-all; }
.dns-updated, .dns-meta { margin-top: 4px; }
.dns-status { position: absolute; top: 10px; right: 10px; }

/* ---------- internal dashboard links ---------- */
.dash-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 10px;
}
.dash-tile {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 12px 14px;
  text-decoration: none;
  color: var(--text);
  transition: border-color .12s ease, transform .12s ease;
}
.dash-tile:hover { border-color: var(--accent); transform: translateY(-1px); }
.dash-icon { font-size: 22px; line-height: 1; flex: 0 0 auto; }
.dash-text { display: flex; flex-direction: column; min-width: 0; gap: 2px; }
.dash-label { font-size: var(--fs-lg); font-weight: 600; color: var(--text); }
.dash-note { line-height: 1.2; }
.dash-url { word-break: break-all; opacity: .75; }

/* ---------- flag grid (mezel) ---------- */
.flag-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: 8px;
}
.flag-tile {
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 12px 8px;
  text-align: center;
  text-decoration: none;
  color: inherit;
  display: block;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s;
}
.flag-tile:hover {
  background: rgba(0,217,192,0.06);
  border-color: var(--line-2);
}
.flag-tile-active {
  background: rgba(0,217,192,0.12);
  border-color: var(--accent);
  box-shadow: inset 3px 0 0 var(--accent);
}
.block-src {
  display: inline-block;
  font-size: var(--fs-xs);
  font-family: var(--mono);
  padding: 1px 6px;
  border-radius: 3px;
  letter-spacing: 0.04em;
}
.block-src-auto   { background: rgba(255, 153, 19, 0.15); color: #ffb74d; border: 1px solid rgba(255, 153, 19, 0.4); }
.block-src-manual { background: rgba(107, 138, 255, 0.10); color: #b4c1ff; border: 1px solid rgba(107, 138, 255, 0.3); }
.row-auto-blocked { background: rgba(255, 153, 19, 0.04); }

/* Blocked IPs table — explicit column widths so the note column wraps
   instead of pushing the unblock button off-screen on narrow viewports. */
.blocked-table th:nth-child(1),
.blocked-table td:nth-child(1) { width: 130px; }
.blocked-table th:nth-child(2),
.blocked-table td:nth-child(2) { width: 80px; }
.blocked-table th:nth-child(3),
.blocked-table td:nth-child(3) { width: 90px; }
.blocked-table th:nth-child(5),
.blocked-table td:nth-child(5) { width: 80px; text-align: right; }
.blocked-table .block-note {
  word-break: break-word;
  overflow-wrap: anywhere;
  max-width: 360px;
}
@media (max-width: 700px) {
  /* The .visits-card class already converts to stacked cards on mobile;
     we just need to undo the desktop column widths and let the note
     reflow across the full card width. */
  .blocked-table th:nth-child(n),
  .blocked-table td:nth-child(n) { width: auto !important; }
  .blocked-table .block-note { max-width: none; }
  .blocked-table td[data-label=""] { text-align: left; }
}
.flag-icon { font-size: 28px; line-height: 1; }
.flag-name {
  font-size: var(--fs-base);
  color: var(--text);
  margin-top: 6px;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 0 4px;
}
.flag-cc { font-size: var(--fs-xs); color: var(--muted); margin-top: 2px; letter-spacing: 0.08em; }
.flag-n { font-size: var(--fs-lg); color: var(--accent); margin-top: 2px; font-weight: 600; }
.flag-cell { font-size: 18px; line-height: 1; text-align: center; min-width: 28px; }

/* ---------- country-ban admin panel ---------- */
.block-form {
  display: flex; gap: 8px; flex-wrap: wrap;
  margin: 8px 0 0;
}
.block-form input[type="text"] {
  background: var(--bg);
  border: 1px solid var(--line-2);
  color: var(--text);
  padding: 8px 10px;
  border-radius: 6px;
  font: inherit;
  outline: none;
}
.block-form input#country-input { width: 110px; text-transform: uppercase; font-family: var(--mono); letter-spacing: 0.1em; }
.block-form input#country-note  { flex: 1; min-width: 140px; }
.block-form input:focus { border-color: var(--accent); }
.block-form button { padding: 8px 16px; }

/* ---------- SSH-security firewall IP field ---------- */
.ip-input {
  width: 100%;
  margin-top: 4px;
  background: var(--bg);
  border: 1px solid var(--line-2);
  color: var(--text);
  padding: 8px 10px;
  border-radius: 6px;
  font-family: var(--mono);
  letter-spacing: 0.08em;
  outline: none;
}
.ip-input::placeholder { color: var(--muted); letter-spacing: 0.04em; }
.ip-input:focus { border-color: var(--accent); }

/* SSH-over-443 access allowlist (admin panel in the SSH security card).
   Inline styles are blocked by the page CSP, so all layout lives here. */
.sal-panel { display: block; }
.sal-toggle {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 8px;
  color: var(--text);
  font-size: var(--fs-md);
  cursor: pointer;
}
.sal-toggle input { flex: 0 0 auto; }
.sal-note { margin: 4px 0 0; }
.sal-add { align-items: flex-end; margin-top: 10px; }
.sal-add-cidr { flex: 2 1 200px; }
.sal-add-label { flex: 1 1 120px; }
.sal-add-btns { flex: 0 0 auto; display: flex; gap: 6px; align-items: center; }
.sal-list { margin-top: 10px; display: flex; flex-direction: column; gap: 4px; }
.sal-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 2px;
  border-bottom: 1px solid var(--line);
}
.sal-row:last-child { border-bottom: none; }
.sal-label-txt { flex: 1 1 auto; color: var(--text); }
.sal-cidr-txt { flex: 0 0 auto; color: var(--muted); }
.sal-added { flex: 0 0 auto; }
.sal-remove { flex: 0 0 auto; }
.sal-empty { margin: 6px 0 0; }

/* ---------- shared scalable IP-list UX (allowlist + blocklist) ---------- */
.iplist-box {
  max-height: 300px;
  overflow-y: auto;
  padding-right: 4px;
}
.iplist-filter {
  width: 100%;
  margin: 10px 0 6px;
  padding: 6px 10px;
  background: var(--bg-2);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 6px;
  font-size: var(--fs-base);
}
.iplist-filter:focus { outline: none; border-color: var(--accent); }
.iplist-count {
  font-family: var(--mono);
  font-size: var(--fs-xs);
  color: var(--muted);
  margin-left: 8px;
}
.iplist-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 5px 2px;
  border-bottom: 1px solid var(--line);
  font-size: var(--fs-base);
}
.iplist-row:last-child { border-bottom: none; }
.iplist-flag { flex: 0 0 auto; }
.iplist-ip { flex: 0 0 auto; color: var(--text); white-space: nowrap; }
.iplist-reason { flex: 1 1 auto; color: var(--muted); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.iplist-meta { flex: 0 0 auto; color: var(--muted); white-space: nowrap; }
.iplist-unblock { flex: 0 0 auto; }

.country-block-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 10px;
}
.country-block-tile {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px;
  background: rgba(255,94,122,0.06);
  border: 1px solid rgba(255,94,122,0.3);
  border-left: 3px solid var(--down);
  border-radius: 8px;
}
.cb-flag  { font-size: 24px; line-height: 1; }
.cb-text  { flex: 1; min-width: 0; }
.cb-cc    { font-size: var(--fs-lg); font-weight: 600; color: var(--down); letter-spacing: 0.05em; }
.cb-note  { color: var(--text); margin-top: 2px; }
.cb-since { margin-top: 2px; }

/* ---------- visits tables: card-stack layout on phones ---------- */
.data-table.visits-card { width: 100%; }

@media (max-width: 700px) {
  /* Higher specificity than the generic mobile-table override above. */
  .card .data-table.visits-card,
  .card .data-table.visits-card thead,
  .card .data-table.visits-card tbody,
  .card .data-table.visits-card tr,
  .card .data-table.visits-card td {
    display: block !important;
    width: 100% !important;
    white-space: normal !important;
  }
  .card .data-table.visits-card thead { display: none !important; }
  .card .data-table.visits-card tr {
    background: linear-gradient(180deg, var(--bg-1), var(--bg-2));
    border: 1px solid var(--line);
    border-radius: 10px;
    padding: 36px 14px 12px 14px;  /* room for flag badge in corner */
    margin-bottom: 10px;
    position: relative;
  }
  .card .data-table.visits-card tr.row-login-fail {
    border-color: rgba(255,94,122,0.45);
    box-shadow: inset 3px 0 0 var(--down);
  }
  .card .data-table.visits-card tr.row-login-ok {
    border-color: rgba(65,217,155,0.45);
    box-shadow: inset 3px 0 0 var(--ok);
  }
  .card .data-table.visits-card td {
    display: grid !important;
    grid-template-columns: 70px minmax(0, 1fr);
    align-items: baseline;
    gap: 10px;
    padding: 3px 0 !important;
    border-bottom: none !important;
    font-size: var(--fs-base);
    text-align: left !important;
    line-height: 1.4;
    /* Force long unbreakable strings (URLs, ASN strings, etc.) to wrap
       inside the cell instead of pushing the row past the viewport. The
       minmax(0,1fr) above is what actually permits the wrap — without
       it, grid tracks default to their content's intrinsic min-content
       width, which for a 35-char no-space URL is the full URL width. */
    overflow-wrap: anywhere;
    word-break: break-word;
    min-width: 0;
  }
  /* Caption rendered from data-label on every <td>. */
  .card .data-table.visits-card td[data-label]:not([data-label=""])::before {
    content: attr(data-label);
    color: var(--muted);
    font-size: var(--fs-xs);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    align-self: center;
  }
  /* Flag becomes a leading badge at the top-left of each card. */
  .card .data-table.visits-card td.flag-cell {
    position: absolute !important; top: 8px; left: 12px;
    font-size: 22px;
    padding: 0 !important;
    width: auto !important;
    display: block !important;
  }
  /* Action button row */
  .card .data-table.visits-card td[data-label=""]:last-child {
    position: absolute; top: 8px; right: 10px;
    padding: 0 !important;
    display: block !important;
    width: auto !important;
    grid-template-columns: none;
  }
  .card .data-table.visits-card td[data-label=""]:last-child::before { content: none; }
  /* Drop the lowest-value columns on phones to keep cards short. */
  .card .data-table.visits-card td[data-label="ISP"],
  .card .data-table.visits-card td[data-label="Host"] { display: none !important; }
}

/* ---------- pager ---------- */
.pager {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  margin: 8px 0 14px;
  flex-wrap: wrap;
}
.pager-left { display: flex; gap: 14px; align-items: center; flex-wrap: wrap; }

.page-range-tabs { margin-bottom: 14px; }
.page-range-tabs .range-span {
  margin-top: 6px;
  font-family: var(--mono);
}
.page-range-tabs .range-span b { color: var(--text); font-weight: 600; }
.page-range-tabs .range-span-len { opacity: 0.7; margin-left: 6px; }
/* Top row contains the seven time-range tabs (grow to fill) + the
   admin ⚙ Settings button on the right. On desktop they sit side by
   side; the mobile breakpoint below stacks Settings beneath the tabs
   so neither feels squeezed. */
.range-tabs-row {
  display: flex;
  align-items: stretch;
  gap: 8px;
}
.range-tabs-settings {
  flex-shrink: 0;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
}
.range-tabs { display: flex; gap: 0; border: 1px solid var(--line-2); border-radius: 6px; overflow: hidden; flex-wrap: wrap; flex: 1; }
@media (max-width: 700px) {
  /* Seven tabs would steamroll a phone width — let them reflow as
     stacked pill rows. Each tab grows to fill 1/3 of the row.
     Settings drops to its own row below so it's full-width and
     unambiguous to tap. */
  .range-tabs { border-radius: 6px; }
  .range-tabs .range-tab { flex: 1 1 30%; text-align: center; border-radius: 0; }
  .range-tabs-row { flex-direction: column; gap: 8px; }
  .range-tabs-settings { width: 100%; justify-content: center; }
}
.range-tab {
  padding: 5px 12px;
  font-size: var(--fs-base);
  color: var(--muted);
  background: var(--bg-2);
  border-right: 1px solid var(--line-2);
  text-decoration: none;
}
.range-tab:last-child { border-right: none; }
.range-tab:hover { color: var(--text); background: var(--bg-1); }
.range-tab.active { color: var(--bg); background: var(--accent); font-weight: 600; }
.pager select {
  background: var(--bg-2);
  border: 1px solid var(--line-2);
  color: var(--text);
  border-radius: 5px;
  padding: 4px 8px;
  font-family: var(--mono);
  font-size: var(--fs-base);
}
.pager-pages { display: flex; gap: 8px; align-items: center; }
.pager-pages .mono { color: var(--muted); }

/* ---------- value-band colorization ---------- */
.v-ok    { color: var(--ok)      !important; }
.v-warn  { color: var(--warn)    !important; }
.v-bad   { color: var(--down)    !important; }
.v-cold  { color: #6bb6ff        !important; }
.v-hot   { color: #ff9966        !important; }

/* ---------- honeypot top-IPs / top-ports tables: fixed columns ---------- */
.hp-attackers, .hp-ports {
  table-layout: fixed;
  width: 100%;
}
.hp-attackers .col-flag    { width: 34px; text-align: center; padding-right: 4px !important; }
.hp-attackers .col-ip      { width: auto; }
.hp-attackers .col-hits    { width: 60px; }
.hp-attackers .col-country { width: 38%; }
.hp-attackers td.col-country .mono { color: var(--text); margin-right: 4px; }
.hp-attackers td.col-ip    { overflow: hidden; text-overflow: ellipsis; }

.hp-ports .col-port { width: 60%; }
.hp-ports .col-hits { width: 40%; }

/* Right-align numeric columns in both tables so hits stack neatly. */
.col-num, th.col-num { text-align: right !important; padding-right: 12px !important; }

/* Mobile: drop the country name (keep code), tighten paddings. */
@media (max-width: 600px) {
  .hp-attackers .col-country { width: 70px; }
  .hp-attackers .col-country .muted { display: none; }
  .data-table.compact th, .data-table.compact td { padding: 5px 6px; }
}

/* ---------- sortable tables ---------- */
.th-sortable { cursor: pointer; user-select: none; position: relative; padding-right: 22px !important; white-space: nowrap; }
.th-sortable:hover { color: var(--accent); }
.th-sortable::after {
  /* U+FE0E (text variation selector) + font-variant-emoji:text stop
     iOS from rendering the arrows as wide emoji glyphs that bled over
     the header label. */
  content: "↕\FE0E";
  font-variant-emoji: text;
  position: absolute; right: 5px;
  top: 50%; transform: translateY(-50%);
  line-height: 1; opacity: 0.35; font-size: var(--fs-xs);
  pointer-events: none;
}
.th-sortable.sort-asc::after  { content: "▲\FE0E"; opacity: 0.9; color: var(--accent); }
.th-sortable.sort-desc::after { content: "▼\FE0E"; opacity: 0.9; color: var(--accent); }

/* ---------- status badges (visitor table) ---------- */
.status-badge {
  display: inline-block;
  padding: 2px 6px;
  border-radius: 4px;
  font-family: var(--mono); font-size: var(--fs-sm);
  background: var(--bg-2);
  color: var(--muted);
}
.s2xx { background: rgba(65,217,155,0.12); color: var(--ok); }
.s3xx { background: rgba(107,138,255,0.12); color: var(--accent-2); }
.s4xx { background: rgba(255,183,77,0.10); color: var(--warn); }
.s5xx { background: rgba(255,94,122,0.12); color: var(--down); }

/* Login-event rows (synthetic entries in /mezel) — call them out so the
   user can quickly spot brute-force activity. */
.row-login-fail { background: rgba(255,94,122,0.08); }
.row-login-fail .mono { color: var(--down); }
.row-login-ok   { background: rgba(65,217,155,0.06); }
.row-login-ok   .mono { color: var(--ok); }

.row-blocked { opacity: 0.55; background: rgba(255,94,122,0.04); }
.row-blocked .mono { text-decoration: line-through; }

/* Top-IP rows: clickable to filter recent visits */
.top-ip-row { cursor: pointer; transition: background 0.12s; }
.top-ip-row:hover { background: rgba(0,217,192,0.06); }
.top-ip-row.row-active-filter {
  background: rgba(0,217,192,0.12);
  box-shadow: inset 3px 0 0 var(--accent);
}
.ip-filter-link {
  color: var(--accent);
  text-decoration: none;
  display: block;
  width: 100%;
}
.ip-filter-link:hover { text-decoration: underline; }

/* Probable-bot badge next to suspicious IPs. Hover for prober reasons. */
.susp-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--warn);
  background: rgba(255,183,77,0.12);
  border: 1px solid rgba(255,183,77,0.4);
  border-radius: 10px;
  cursor: help;
}

/* Active-filter chip in Recent visits header */
.ip-filter-chip {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 4px 10px;
  background: rgba(0,217,192,0.12);
  border: 1px solid rgba(0,217,192,0.4);
  border-radius: 14px;
  font-size: var(--fs-base);
}
.ip-filter-chip .mono { color: var(--accent); }
.ip-filter-clear {
  color: var(--muted);
  text-decoration: none;
  font-size: var(--fs-xl);
  line-height: 1;
  padding: 0 4px;
}
.ip-filter-clear:hover { color: var(--down); }

.btn-danger { color: var(--down); border-color: rgba(255,94,122,0.4); }
.btn-danger:hover { background: rgba(255,94,122,0.08); border-color: var(--down); }

/* ---------- role pill ---------- */
.auth-user.role-admin {
  color: var(--accent);
  border-color: rgba(0,217,192,0.3);
}
.auth-user.role-view {
  color: var(--accent-2);
  border-color: rgba(107,138,255,0.3);
}

/* ---------- C/F unit toggle ---------- */
.page-head-row { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }
body.unit-c .t-f, body.unit-f .t-c { display: none; }

/* ---------- weather widgets ---------- */
.weather-stats-row {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 8px 0 14px;
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}
.weather-stats-row .now-cond {
  font-size: var(--fs-md); color: var(--muted);
  display: flex; align-items: baseline; gap: 8px;
}
.now-cond-label {
  font-size: var(--fs-sm);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.ns-extra {
  font-size: var(--fs-sm);
  color: var(--muted);
  margin-left: 6px;
}
.now-main { display: flex; flex-direction: column; gap: 6px; }
.now-temp {
  font-family: var(--mono);
  font-size: 56px;
  font-weight: 500;
  line-height: 1;
  color: var(--text);
  letter-spacing: -0.02em;
  transition: color 0.3s ease;
}
.now-temp.v-cold { color: #6bb6ff; text-shadow: 0 0 18px rgba(107,182,255,0.35); }
.now-temp.v-ok   { color: #41d99b; text-shadow: 0 0 18px rgba(65,217,155,0.35); }
.now-temp.v-warn { color: #ffb74d; text-shadow: 0 0 18px rgba(255,183,77,0.35); }
.now-temp.v-hot  { color: #ff9966; text-shadow: 0 0 18px rgba(255,153,102,0.45); }
.now-unit { font-size: 22px; color: var(--muted); margin-left: 4px; }
.now-cond { font-size: var(--fs-lg); color: var(--muted); }
.now-icon { font-size: 22px; margin-right: 6px; }

.now-stats {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px 24px;
}
.now-stats > div { display: flex; flex-direction: column; gap: 2px; }
.ns-label { font-size: var(--fs-sm); color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }
.ns-value { font-size: var(--fs-lg); color: var(--text); }

/* ---------- sparkline row ---------- */
.spark-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 12px;
}
@media (max-width: 500px) { .spark-row { grid-template-columns: 1fr; } }
.spark-cell {
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px 12px;
}
.spark-title { font-size: var(--fs-sm); color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 4px; }
svg.spark { display: block; width: 100%; height: 100%; }
/* Explicit height on the wrap so the tile's `top: X%` resolves to a
   deterministic pixel value (otherwise it inherits 0 from a shrink-
   wrapped block and the percentage collapses). */
.spark-wrap { position: relative; height: 90px; }
.spark-axis {
  position: absolute;
  left: 4px;
  font-size: var(--fs-xs);
  color: var(--muted);
  background: rgba(15, 23, 42, 0.6);
  padding: 0 5px;
  border-radius: 3px;
  pointer-events: none;
  z-index: 1;
  line-height: 1.25;
  white-space: nowrap;
}
.spark-axis-hi { top: 2px; }
.spark-axis-lo { bottom: 2px; }
.spark-axis-alt { color: var(--muted); opacity: 0.65; font-size: var(--fs-2xs); }
.spark-now {
  position: absolute;
  /* Latest sample is always at the right edge of the chart, so the tile's
     right edge is pinned 8px from the wrap right edge (clear of the dot
     glyph). top is set inline from the dot's y%, and translateY(-50%)
     vertically centers the tile on the dot — so it literally hugs the
     blob: fixed X right at the dot, Y tracks the data. */
  right: 8px;
  transform: translateY(-50%);
  font-size: var(--fs-2xs);
  font-weight: 600;
  background: rgba(15, 23, 42, 0.7);
  padding: 0 3px;
  border-radius: 2px;
  pointer-events: none;
  z-index: 2;
  white-space: nowrap;
  line-height: 1.2;
}
.spark-tip {
  position: absolute; top: -28px; left: 0;
  background: var(--bg-1);
  border: 1px solid var(--line-2);
  color: var(--text);
  font-family: var(--mono);
  font-size: var(--fs-sm);
  padding: 3px 8px;
  border-radius: 5px;
  pointer-events: none;
  white-space: nowrap;
  box-shadow: 0 4px 10px rgba(0,0,0,0.3);
  z-index: 10;
}
.spark-foot { color: var(--muted); margin-top: 2px; }

/* ---------- forecast strip (12-hour, fits full width) ---------- */
.forecast-strip {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 6px;
}
@media (max-width: 900px) { .forecast-strip { grid-template-columns: repeat(6, minmax(0, 1fr)); } }
@media (max-width: 500px) { .forecast-strip { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.fc-hour {
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px 4px;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.fc-icon { font-size: 24px; line-height: 1.25; }
.fc-temp { font-size: var(--fs-lg); font-weight: 700; }
.fc-temp-c { display: block; }
/* Temperature step bands — match the weather page palette exactly.
   !important so the class color wins over any cascaded text color. */
.tc-arctic { color: #1b4cff !important; }
.tc-frigid { color: #3d8cff !important; }
.tc-cold   { color: #6bb9ff !important; }
.tc-icy    { color: #9cd6ff !important; }
.tc-cool   { color: #63b3ff !important; }
.tc-mild   { color: #46c463 !important; }
.tc-warm   { color: #ff9913 !important; }
.tc-hot    { color: #ff4d4f !important; }
.tc-na     { color: var(--muted) !important; }

/* ---------- Honeypot firewall stats strip ---------- */
.fw-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px 12px;
  align-items: stretch;
}
.fw-stat {
  flex: 1 1 110px;
  min-width: 110px;
  padding: 4px 10px;
  border-right: 1px solid var(--line);
}
.fw-stat:last-of-type { border-right: none; }
.fw-stat-label {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  margin-bottom: 4px;
}
.fw-stat-value { font-size: 22px; font-weight: 700; color: var(--text); }
.fw-stat-sub   { margin-top: 2px; }
.fw-stat-warn  { color: #ff9913; }
.fw-stat-stale .fw-stat-value { color: #ef4444; }
/* rayhunter telemetry value colors */
.fw-stat-value.rh-good { color: var(--ok); }
.fw-stat-value.rh-warn { color: #ff9913; }
.fw-stat-value.rh-bad  { color: var(--down); }
.fw-stat-value.rh-tmo  { color: #e20074; font-weight: 700; }
/* rayhunter card: force meta text small + let telemetry tiles flow without overflow */
[data-section="rayhunter"] .rh-meta { font-size: var(--fs-base); line-height: 1.5; }
[data-section="rayhunter"] .rh-tele .fw-stat { min-width: 150px; flex-basis: 150px; }
[data-section="rayhunter"] .rh-tele .fw-stat-label { white-space: nowrap; }
[data-section="rayhunter"] .rh-tele .fw-stat-value { font-size: var(--fs-lg); line-height: 1.3; white-space: nowrap; }
/* Capture-source colors, matched to the events-per-hour chart legend
   (.lg-hp / .lg-wp) and bar fills: green = honeypot, purple = web-probe.
   Classes (not inline styles) because the page CSP is style-src 'self'. */
.fw-stat-hp { color: #41d99b; }
.fw-stat-wp { color: #b785ff; }
/* Defensive-countermeasures values — red to match the honeypot's own
   panel (.emg-value { color: #ef4444 }). */
.fw-stat-defense { color: #ef4444; }
.fw-stat-foot {
  flex-basis: 100%;
  text-align: right;
  padding-top: 2px;
}
/* Hubitat platform-update pill in the card head */
.platform-update-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(255, 153, 19, 0.12);
  border: 1px solid rgba(255, 153, 19, 0.45);
  color: #ffb74d;
  padding: 4px 10px;
  border-radius: 14px;
  font-size: var(--fs-base);
  font-weight: 600;
  text-decoration: none;
}
.platform-update-pill:hover { background: rgba(255, 153, 19, 0.20); }

/* ---------- Prober settings card ---------- */
#prober-settings .block-form label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: var(--fs-base);
  color: var(--muted);
}
#prober-settings .block-form input[type="number"] {
  padding: 4px 8px;
  background: var(--bg);
  border: 1px solid var(--line);
  color: var(--text);
  border-radius: 4px;
  font-family: var(--mono);
}
#prober-settings .prober-toggle {
  flex-direction: row !important;
  align-items: center;
  gap: 6px;
  color: var(--text);
  font-size: var(--fs-md);
  cursor: pointer;
}
#prober-settings .prober-toggle .muted { margin-left: 22px; }
#prober-status {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  padding: 2px 8px;
  border-radius: 4px;
}
.fc-precip { font-size: var(--fs-sm); color: #6bb9ff; }
.fc-precip.dry { color: var(--muted); }
.fc-time { font-size: var(--fs-sm); color: var(--muted); font-weight: 600; }
.fc-hour-now {
  border-color: var(--accent);
  background: linear-gradient(180deg, rgba(0,217,192,0.10), var(--bg-2));
}
.fc-hour-now .fc-time { color: var(--accent); font-weight: 700; }

/* ---------- click-to-show chart tooltip (weather-style) ---------- */
.chart-tip {
  position: absolute;
  z-index: 30;
  pointer-events: none;
  background: rgba(15, 23, 42, 0.96);
  border: 1px solid var(--line-2);
  border-radius: 6px;
  padding: 8px 10px;
  font-size: var(--fs-sm);
  color: var(--text);
  box-shadow: 0 6px 14px rgba(0,0,0,0.45);
  min-width: 160px;
  max-width: 240px;
}
.chart-tip[hidden] { display: none; }
.chart-tip .ct-title { font-weight: 700; color: var(--accent); margin-bottom: 4px; }
.chart-tip .ct-row   { display: flex; justify-content: space-between; gap: 12px; font-family: var(--mono); }
.chart-tip .ct-row .ct-k { color: var(--muted); }
.chart-tip .ct-note  { color: var(--muted); font-style: italic; margin-top: 4px; }
.bars-wrap { position: relative; }
.bar-rect:hover { opacity: 1 !important; }
.chart-tip .ct-titles { margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--line); }
.chart-tip .ct-line   { font-family: var(--mono); font-size: var(--fs-xs); color: var(--text); line-height: 1.35; }
.chart-tip .ct-more   { color: var(--muted); font-style: italic; }
.chart-tip { max-width: 320px; }

/* ---------- power grid flow ---------- */
.grid-flow {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 0;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--line);
  overflow-x: auto;
}
.flow-node {
  background: var(--bg-2);
  border: 1px solid var(--line-2);
  border-radius: 8px;
  padding: 10px 16px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 120px;
}
.flow-node .node-label {
  font-size: var(--fs-xs); color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.1em;
}
.flow-node .node-stat {
  font-family: var(--mono); font-size: var(--fs-md);
}
.node-mains { border-left: 3px solid var(--accent-2); }
.node-ups   { border-left: 3px solid var(--accent); }
.node-load  { border-left: 3px solid var(--ok); }
.flow-arrow {
  color: var(--accent);
  font-size: 20px;
  opacity: 0.7;
}

/* ---------- bars (honeypot) ---------- */
svg.bars { display: block; width: 100%; height: 80px; background: var(--bg-2); border: 1px solid var(--line); border-radius: 6px; padding: 4px; box-sizing: border-box; }
/* Dual-series hits/hour chart: uniform scaling (no preserveAspectRatio
   "none" stretch) so the count labels render crisp, sized by its
   viewBox aspect. */
svg.bars.bars-2x { height: auto; width: 100%; }
/* pdu-control reactor embedded via the /power proxy (same-origin).
   Square aspect to match the reactor; overview.js isolates .grid-frame
   inside it. */
.reactor-frame {
  display: block; width: 100%;
  /* The reactor now renders as a wide 8:5 graph (pdu-control stretches it
     horizontally on landscape), so let the iframe fill the card width but
     cap it so its height still fits the viewport (no scrolling). The plain
     max-width is a fallback for browsers without min()/dvh. */
  aspect-ratio: 8 / 5;
  max-width: 1200px;
  max-width: min(1700px, calc((100dvh - 150px) * 1.6));
  margin: 6px auto 0;
  border: 0; border-radius: 8px; background: #070d18; color-scheme: dark;
}
/* Phones: pdu-control falls back to the square radial (bigger nodes on a
   narrow screen), so match the iframe shape and use the full width. */
@media (max-width: 700px) {
  .reactor-frame { aspect-ratio: 1 / 1; max-width: 100%; }
}
/* Locked placeholder for unauthed visitors — same shape as the
   real reactor iframe so the layout doesn't reflow when they sign in. */
.reactor-frame-locked {
  display: flex; align-items: center; justify-content: center;
  border: 1px dashed var(--line-2);
}
.reactor-frame-locked .locked-msg { text-align: center; padding: 24px; }
.reactor-frame-locked .locked-msg-h { font-size: var(--fs-xl); color: var(--text); margin-bottom: 6px; }
.reactor-frame-locked .locked-msg-b a { color: var(--accent); }
.bars .bar-count {
  fill: var(--text); font-family: var(--mono);
  font-size: var(--fs-2xs); text-anchor: middle;
}
/* Legend colors live here (page CSP is style-src 'self' — inline
   style="" attributes are blocked, so colors must come from a class). */
.bars-legend { margin-top: 6px; }
.bars-legend .lg-hp  { color: #41d99b; }
.bars-legend .lg-wp  { color: #b785ff; }
.bars-legend .lg-sep { color: var(--dim); }
.bar-axis { display: flex; justify-content: space-between; color: var(--muted); margin-top: 4px; padding: 0 4px; }

/* ---------- footer ---------- */
.footer {
  padding: 18px 28px;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: var(--fs-base);
  display: flex;
  gap: 8px;
  justify-content: center;
}
.footer .sep { opacity: 0.5; }

/* ---------- settings modal ----------
   Scroll lives on .modal (not .modal-overlay) because backdrop-filter
   on the overlay breaks position:sticky for descendants on iOS Safari.
   Moving the scroll inside .modal keeps the blurred backdrop AND lets
   .modal-head stay correctly stuck at the top while the body scrolls. */
.modal-overlay {
  position: fixed; inset: 0; z-index: 9998;
  background: rgba(4, 7, 11, 0.74);
  backdrop-filter: blur(4px);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 48px 16px;
  /* IMPORTANT: no overflow-y here — scroll happens on .modal */
}
.modal-overlay[hidden] { display: none; }
.modal {
  background: var(--bg-1);
  border: 1px solid var(--line-2);
  border-radius: 14px;
  width: 100%; max-width: 880px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
  /* Cap height to the viewport (minus the overlay's vertical padding)
     and scroll inside, so .modal-head's position:sticky has a parent
     scroll container without the backdrop-filter problem above.
     Use 100dvh (dynamic viewport height) so iOS Safari's collapsing
     URL bar doesn't push the bottom of the modal past the visible
     viewport — the static 100vh approach left the "Block country"
     form sometimes hidden under the iOS toolbar. Fallback to 100vh
     for browsers without dvh support. */
  max-height: calc(100vh - 96px);
  max-height: calc(100dvh - 96px);
  overflow-y: auto;
  /* overscroll-behavior: contain stops iOS's rubber-band bounce from
     escaping the modal scroll context — without this, scrolling past
     the bottom of the modal's last form pulls the whole modal off-
     screen visually. */
  overscroll-behavior: contain;
}
.modal-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 16px 22px; border-bottom: 1px solid var(--line);
  position: sticky; top: 0;
  /* Opaque background + z-index above the .modal-body content so the
     header sits cleanly over scrolled rows. */
  background-color: #11161e;
  z-index: 2;
  border-radius: 14px 14px 0 0;
}
.modal-head h2 { margin: 0; font-size: 18px; font-weight: 600; }
.modal-close {
  background: none; border: none; color: var(--muted);
  font-size: 24px; line-height: 1; cursor: pointer;
  padding: 2px 9px; border-radius: 6px;
}
.modal-close:hover { color: var(--text); background: var(--bg-2); }
.modal-body { padding: 22px; }
/* Narrow notice variant — small info / warning popups (LAN-only gate,
   "couldn't reach daemon", etc.) reuse the same .modal-overlay/.modal
   chrome but want a smaller max-width and a single dismiss action. */
.modal.modal-narrow { max-width: 460px; }
.modal-narrow .modal-body p { margin: 0 0 14px 0; line-height: 1.45; }
.modal-narrow .modal-body p:last-child { margin-bottom: 0; }
.modal-narrow .modal-body .muted { color: var(--muted); font-size: var(--fs-md); }
.modal-narrow .modal-foot {
  display: flex; justify-content: flex-end; gap: 8px;
  padding: 14px 22px 18px; border-top: 1px solid var(--line);
}
.modal-body .card {
  padding: 0; background: none; border: none; box-shadow: none;
}
.modal-body .card + .card {
  margin-top: 24px; padding-top: 22px; border-top: 1px solid var(--line);
}

/* ---- hp-attackers Countries popover (hover on desktop, tap on touch) ----
   Each .hpc-row is a clickable/focusable row. .hpc-popover is the
   IP list, hidden by default. CSS :hover shows it on devices with
   real hover (desktop). JS adds .is-open on tap/click so touch users
   (and pinned desktop reads) see it too. Click-outside / Esc dismisses. */
.hpc-row { cursor: pointer; }
.hpc-row:focus-visible { outline: 1px solid var(--accent, #41d99b); outline-offset: -1px; }
.hpc-ips-cell { position: relative; }
.hpc-popover {
  position: absolute;
  top: 100%; right: 0;
  margin-top: 4px;
  min-width: 180px;
  max-width: 320px;
  padding: 8px 10px;
  background: var(--card-bg, #0f1418);
  border: 1px solid var(--line, rgba(255,255,255,0.08));
  border-radius: 6px;
  box-shadow: 0 6px 18px rgba(0,0,0,0.45);
  z-index: 50;
  font-size: var(--fs-base);
  text-align: left;
  white-space: nowrap;
  display: none;
  pointer-events: none;
}
.hpc-popover-title {
  font-weight: 600;
  margin-bottom: 4px;
  color: var(--text, #e6eef5);
}
.hpc-popover-line { color: var(--text, #e6eef5); }
@media (hover: hover) {
  .hpc-row:hover .hpc-popover { display: block; }
}
.hpc-row.is-open .hpc-popover {
  display: block;
  pointer-events: auto;
}

/* ---- /mezel mute cards: gray-out states ---- */
/* When the backend behind a proxied card is unreachable (502 from
   titan's /api/<system>/notifications), the JS adds .card--unavailable
   to the section so the operator can see the card is present but
   intentionally disabled — better UX than a silently broken save row. */
.card.card--unavailable {
  opacity: 0.55;
  position: relative;
}
.card.card--unavailable input,
.card.card--unavailable button[type="submit"] {
  pointer-events: none;
  cursor: not-allowed;
}
.card.card--unavailable .meta-pill.status-unavailable {
  background: rgba(140,140,140,0.18);
  color: #c7cdd3;
  border: 1px solid rgba(140,140,140,0.35);
}
.card.card--unavailable .meta-pill.status-unreachable {
  background: rgba(255,94,122,0.18);
  color: #ffb4b4;
  border: 1px solid rgba(255,94,122,0.35);
}

/* ---------- Firewall badges (FortiGate primary / Check Point backup) ----------
   Active gateway = full opacity + tinted fill; the other stays faded. Styling
   lives here (not inline) because the page CSP is style-src 'self'. */
.fw-badges { display: flex; gap: .5rem; align-items: center; margin-top: .45rem; flex-wrap: wrap; }
.fw-badge {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .2rem .6rem; border-radius: 999px;
  font-size: var(--fs-md); font-weight: 600;
  border: 1px solid currentColor; background: transparent;
  opacity: .32; transition: opacity .2s ease, background .2s ease;
}
.fw-badge__role { font-weight: 400; opacity: .75; }
.fw-badge--fortigate  { color: #ff6b60; }
.fw-badge--checkpoint { color: #7abaff; }
.fw-badge.is-active { opacity: 1; }
.fw-badge--fortigate.is-active  { background: rgba(255, 91, 82, .18); }
.fw-badge--checkpoint.is-active { background: rgba(74, 163, 255, .18); }
.fw-badge--comcast    { color: #6bd48f; }
.fw-badge--tmobile    { color: #e879c7; }
.fw-badge--comcast.is-active  { background: rgba(107, 212, 143, .18); }
.fw-badge--tmobile.is-active  { background: rgba(232, 121, 199, .18); }
.fw-badge--na { color: var(--muted); opacity: .6; }
/* two firewall columns, always side by side (even in portrait) */
.fw-cols { display: grid; grid-template-columns: 1fr 1fr; gap: .75rem; margin-top: .45rem; align-items: start; }
.fw-col { display: flex; flex-direction: column; gap: .35rem; align-items: stretch; min-width: 0; }
.fw-col > .fw-badge { justify-content: space-between; }          /* firewall badge: name left, role right */
.fw-col-uplinks {
  display: flex; flex-direction: column; gap: .3rem;
  margin-left: .4rem; padding-left: .5rem; border-left: 2px solid rgba(255, 255, 255, .1);
}
/* single-line uplink pills: WAN name left, main/failsafe right. Lit = the active
   uplink; faded = standby (the highlight conveys state, no active/standby text). */
.fw-col-uplinks .fw-badge { width: 100%; justify-content: space-between; }
/* global T-Mobile-failover toggle */
.cell-toggle { display: flex; align-items: center; gap: .5rem; margin-top: .6rem; flex-wrap: wrap; }
.cell-toggle-label { font-size: var(--fs-sm); color: var(--muted); text-transform: uppercase; letter-spacing: .05em; }
.cell-toggle-btn {
  font-family: var(--mono); font-size: var(--fs-sm); font-weight: 700; letter-spacing: .05em;
  padding: 2px 14px; border-radius: 999px; cursor: pointer;
  border: 1px solid currentColor; background: transparent; transition: background .15s ease;
}
.cell-toggle-btn.is-on  { color: var(--ok); }
.cell-toggle-btn.is-off { color: var(--muted); }
.cell-toggle-btn.is-on:hover  { background: rgba(65,217,155,0.15); }
.cell-toggle-btn.is-off:hover { background: rgba(255,255,255,0.06); }
/* keep firewall + uplink pills on one line; shrink to fit on phone portrait */
.fw-col > .fw-badge, .fw-col-uplinks .fw-badge { white-space: nowrap; }
@media (max-width: 480px) {
  .fw-cols { gap: .5rem; }
  .fw-col > .fw-badge, .fw-col-uplinks .fw-badge {
    font-size: var(--fs-sm); gap: .2rem; padding-left: .45rem; padding-right: .45rem;
  }
  .fw-col-uplinks { margin-left: .25rem; padding-left: .35rem; }
}
.fw-badge-age { font-size: var(--fs-sm); }

/* ---------- Live attack map ---------- */
.attackmap-card { padding: 0; overflow: hidden; }
.attackmap-card .card-head { padding: 12px 16px 0; }
.attackmap-wrap { position: relative; width: 100%; aspect-ratio: 2 / 1; background: #0a1014; }
.attackmap-bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: fill; opacity: 0.92; }
.attackmap-canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
.attackmap-empty { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; text-align: center; padding: 0 20px; color: #4e5867; font-size: var(--fs-md); }
.attackmap-legend { position: absolute; left: 12px; bottom: 10px; display: flex; gap: 14px; align-items: center; font-size: var(--fs-sm); color: #8a9598; background: rgba(8,14,17,.55); padding: 3px 8px; border-radius: 6px; }
.attackmap-legend .am-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 5px; vertical-align: middle; }
.am-dot--atk { background: #ff5b6b; }
.am-dot--probe { background: #a970ff; }
.am-dot--home { background: #34ff8d; }
.attackmap-bg { transform-origin: 0 0; will-change: transform; }
.attackmap-canvas { touch-action: none; cursor: grab; }
.attackmap-hint { position: absolute; right: 10px; top: 8px; font-size: var(--fs-xs); color: #5a6672; background: rgba(8,14,17,.5); padding: 2px 7px; border-radius: 6px; pointer-events: none; }
/* Firewall HA grids: self-adjust column count to the width (no fixed breakpoints).
   Primary/Backup sit side-by-side once there's room; the 3 node cards fill one
   row on wider/landscape screens and reflow down as it narrows. */
.fwha-2    { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.fwha-nodes { grid-template-columns: repeat(auto-fit, minmax(165px, 1fr)); }
/* Firewall HA status pills are short labels — never wrap them to a second line. */
[data-section="firewall-ha"] .kv-v .status { white-space: nowrap; }

/* center stat-tile content across cards (kv/table cards keep their own alignment) */
.fw-stat { text-align: center; }

/* plain-language tooltips — hover on desktop, tap-to-focus on touch (no JS, CSP-safe) */
[data-tip] { cursor: help; }
[data-tip]:hover, [data-tip]:focus { position: relative; z-index: 40; outline: none; }
[data-tip]:hover::after, [data-tip]:focus::after {
  content: attr(data-tip);
  position: absolute; left: 50%; bottom: calc(100% + 9px); transform: translateX(-50%);
  width: max-content; max-width: min(240px, 74vw);
  white-space: normal; text-align: left;
  text-transform: none; letter-spacing: normal; font-weight: 400; font-style: normal;
  color: var(--text); background: #10151d;
  border: 1px solid var(--line); border-radius: 8px;
  padding: 9px 11px; font-size: var(--fs-base); line-height: 1.45;
  box-shadow: 0 10px 28px rgba(0,0,0,0.5);
  z-index: 60; pointer-events: none;
}
[data-tip]:hover::before, [data-tip]:focus::before {
  content: ""; position: absolute; left: 50%; bottom: calc(100% + 4px); transform: translateX(-50%);
  border: 5px solid transparent; border-top-color: var(--line);
  z-index: 61; pointer-events: none;
}
[data-tip]:focus-visible { box-shadow: 0 0 0 2px var(--section-accent, #41d99b); border-radius: 8px; }
