/* NE20 control panel (server-rendered). Light + dark themes on CSS vars.
   Default follows the system (prefers-color-scheme); an explicit choice is
   applied via data-theme on <html> (set pre-paint + by the topbar toggle).
   Served at GET /admin/static/app.css. */

/* Theme-independent brand tokens. */
:root {
  --accent: #3b82f6;
  --accent-dim: rgba(59,130,246,.14);
  --amber: #f5a623;
  --green: #22c55e;
  --red: #ef4444;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
}

/* LIGHT neutrals (default; also when forced via data-theme="light"). */
:root:not([data-theme="dark"]) {
  --bg: #ffffff;
  --side: #f7f8fa;
  --card: #f7f8fa;
  --card2: #eceef2;
  --border: #e3e5ea;
  --border2: #d6d9e0;
  --text: #0a0a0c;
  --muted: #5b6270;
  --dim: #8a8f99;
  --accent-hover: #2563eb;
  --hover: #eef0f3;
  --topbar-bg: rgba(255,255,255,.8);
}

/* DARK neutrals — auto (system dark, no explicit choice). */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0a0a0c;
    --side: #0c0d10;
    --card: #131419;
    --card2: #1a1c22;
    --border: #23252c;
    --border2: #2c2e36;
    --text: #f3f4f6;
    --muted: #8b909b;
    --dim: #62666f;
    --accent-hover: #5b97f7;
    --hover: #16171c;
    --topbar-bg: rgba(10,10,12,.85);
  }
}

/* DARK neutrals — forced via data-theme="dark". */
[data-theme="dark"] {
  --bg: #0a0a0c;
  --side: #0c0d10;
  --card: #131419;
  --card2: #1a1c22;
  --border: #23252c;
  --border2: #2c2e36;
  --text: #f3f4f6;
  --muted: #8b909b;
  --dim: #62666f;
  --accent-hover: #5b97f7;
  --hover: #16171c;
  --topbar-bg: rgba(10,10,12,.85);
}

/* Theme bridge: map Pico v2's vars onto the NE20 vars. The NE20 vars carry the
   actual light/dark values, so the mapping itself is theme-independent. The two
   selectors are exhaustive (dark vs not-dark) and mirror Pico's specificity, so
   our values win in every theme context (app.css loads after pico.css). */
:root:not([data-theme="dark"]),
[data-theme="dark"] {
  --pico-background-color: var(--bg);
  --pico-color: var(--text);
  --pico-h1-color: var(--text);
  --pico-h2-color: var(--text);
  --pico-h3-color: var(--text);
  --pico-h4-color: var(--text);
  --pico-h5-color: var(--text);
  --pico-h6-color: var(--text);
  --pico-muted-color: var(--muted);
  --pico-muted-border-color: var(--border);
  --pico-border-color: var(--border);
  --pico-primary: var(--accent);
  --pico-primary-background: var(--accent);
  --pico-primary-border: var(--accent);
  --pico-primary-hover: var(--accent-hover);
  --pico-primary-hover-background: var(--accent-hover);
  --pico-primary-hover-border: var(--accent-hover);
  --pico-primary-inverse: #fff;
  --pico-primary-focus: var(--accent-dim);
  --pico-card-background-color: var(--card);
  --pico-card-border-color: var(--border);
  --pico-card-sectioning-background-color: var(--card2);
  --pico-code-background-color: var(--card2);
  --pico-code-color: var(--muted);
  --pico-table-border-color: var(--border);
  --pico-table-row-stripped-background-color: rgba(111, 120, 135, 0.04);
  --pico-form-element-background-color: var(--card2);
  --pico-form-element-selected-background-color: var(--border2);
  --pico-form-element-border-color: var(--border2);
  --pico-form-element-color: var(--text);
  --pico-form-element-placeholder-color: var(--dim);
  --pico-form-element-active-background-color: var(--card2);
  --pico-form-element-active-border-color: var(--accent);
  --pico-form-element-focus-color: var(--accent);
  --pico-form-element-invalid-border-color: rgba(239, 68, 68, .5);
  --pico-form-element-invalid-active-border-color: var(--red);
  --pico-form-element-valid-border-color: rgba(34, 197, 94, .5);
  --pico-form-element-valid-active-border-color: var(--green);
  --pico-switch-checked-background-color: var(--accent);
  --pico-dropdown-background-color: var(--card2);
  --pico-dropdown-border-color: var(--border2);
  --pico-dropdown-hover-background-color: var(--card);
  --pico-progress-color: var(--accent);
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0; background: var(--bg); color: var(--text);
  font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
a { color: inherit; }
.app { display: flex; min-height: 100vh; }

/* ---- Sidebar ---- */
.sidebar {
  width: 232px; flex: 0 0 232px; background: var(--side);
  border-right: 1px solid var(--border); padding: 16px 12px; position: sticky; top: 0; height: 100vh;
}
.brand { display: flex; align-items: center; gap: 11px; padding: 6px 8px 18px; text-decoration: none; }
.brand .logo {
  width: 38px; height: 38px; border-radius: 10px; object-fit: cover; display: block; flex: none;
  box-shadow: 0 0 0 1px rgba(59,130,246,.25);
}
.brand .name { font-weight: 700; font-size: 16px; line-height: 1.1; color: var(--text); }
.brand .sub { color: var(--muted); font-size: 12px; }
.nav-label { color: var(--dim); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; padding: 12px 10px 6px; }
/* Pico styles <nav> as a horizontal flex row; the sidebar nav must stay vertical. */
.sidebar nav { display: block; }
nav a {
  display: flex; align-items: center; gap: 11px; padding: 8px 10px; margin: 1px 0;
  border-radius: 8px; color: var(--muted); text-decoration: none; font-size: 14px; cursor: pointer; user-select: none;
}
nav a:hover { background: var(--hover); color: var(--text); }
nav a.active { background: var(--card2); color: var(--text); }

/* Collapsible sidebar (toggled via the topbar hamburger; persisted). The class
   sits on <html> so a head script can apply it before paint (no flash). */
html.nav-collapsed .sidebar { display: none; }
.navtoggle, .theme-toggle {
  background: var(--card2); border: 1px solid var(--border); color: var(--muted);
  border-radius: 8px; width: 34px; height: 34px; flex: none; display: inline-flex;
  align-items: center; justify-content: center; cursor: pointer; font-size: 16px; line-height: 1;
  /* Pico adds margin-bottom to [type=button]; it shifts these up in the flex topbar. */
  margin-bottom: 0;
}
.navtoggle:hover, .theme-toggle:hover { color: var(--text); }
/* Optical nudge: the ☰/☀/☾ glyphs' ink sits ~2px below their line-box center in
   the system font, so they render low even with the box centered. Bottom padding
   lifts just the glyph; the button box stays put. This also overrides Pico's
   default button padding (15px 20px), which otherwise leaks in and stretches the
   theme toggle into a 42x34 rectangle instead of a 34x34 square. */
.navtoggle, .theme-toggle { padding: 0 0 4px 0; }

/* ---- Content ---- */
.content { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.topbar {
  display: flex; align-items: center; gap: 14px; height: 58px; padding: 0 22px;
  border-bottom: 1px solid var(--border); position: sticky; top: 0; background: var(--topbar-bg);
  backdrop-filter: blur(8px); z-index: 20;
}
.tb-name { font-weight: 600; }
.tb-ver { margin-left: 8px; padding: 2px 7px; border-radius: 999px; font-size: 11px; font-weight: 600;
  color: var(--muted); background: var(--card2); border: 1px solid var(--border); }
.spacer { flex: 1; }
.user { display: flex; align-items: center; gap: 10px; }
.avatar {
  width: 32px; height: 32px; border-radius: 8px; background: var(--accent-dim); border: 1px solid rgba(59,130,246,.35);
  color: var(--accent); display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600;
}
.user .who b { display: block; font-size: 13px; font-weight: 600; line-height: 1.15; }
.user .who span { color: var(--muted); font-size: 12px; }

main { padding: 26px 28px 60px; max-width: 1400px; width: 100%; }
h1 { font-size: 24px; font-weight: 700; margin: 0 0 4px; }
p.sub { color: var(--muted); margin: 0 0 24px; font-size: 14px; }
h2 { font-size: 14px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin: 26px 0 12px; }

.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
@media (max-width: 1080px) { .grid2 { grid-template-columns: 1fr; } }

.card { background: var(--card); border: 1px solid var(--border); border-radius: 14px; padding: 20px 22px; margin-bottom: 18px; }
.divider { height: 1px; background: var(--border); margin: 18px 0; }
.chips { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 12px; }
.chip { padding: 4px 11px; border-radius: 7px; background: var(--card2); color: var(--text); font-size: 12.5px; }

button.btn, .btn {
  font: inherit; font-size: 13.5px; cursor: pointer; border-radius: 8px; padding: 8px 14px;
  border: 1px solid var(--border2); background: var(--card2); color: var(--text);
  display: inline-flex; align-items: center; gap: 7px; text-decoration: none;
  /* Pico forces button[type=submit] to width:100% + margin-bottom; keep ours compact. */
  width: auto; margin-bottom: 0;
}
button.btn:hover:not(:disabled), .btn:hover { border-color: var(--dim); }
button.btn.primary, .btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
button.btn.primary:hover:not(:disabled) { filter: brightness(1.08); }
button.btn.danger, .btn.danger { color: var(--red); border-color: rgba(239,68,68,.4); background: transparent; }
/* Filled red for the no-undo action (immediate delete), heavier than .danger's outline. */
button.btn.danger-solid, .btn.danger-solid { background: var(--red); border-color: var(--red); color: #fff; }
button.btn.danger-solid:hover:not(:disabled), .btn.danger-solid:hover { filter: brightness(1.08); border-color: var(--red); }
button.btn:disabled { opacity: .5; cursor: not-allowed; }
/* In-progress button (e.g. Test DNS while it runs): spinner before the label. */
.btn.loading { opacity: .85; pointer-events: none; }
.btn.loading::before {
  content: ""; width: 12px; height: 12px; flex: 0 0 auto;
  border: 2px solid currentColor; border-top-color: transparent;
  border-radius: 50%; animation: spin .6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Click-to-copy chips for short values the customer pastes elsewhere
   (nameservers, CNAME targets). Each sizes to its content, not full width. */
.copylist { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; margin: 12px 0; }
.copyfield {
  font: inherit; cursor: pointer; display: inline-flex; align-items: center; gap: 9px;
  padding: 8px 13px; border-radius: 8px; border: 1px solid var(--border);
  background: var(--bg); color: var(--text); width: auto; margin-bottom: 0;
}
.copyfield:hover { border-color: var(--dim); }
.copyfield .ic { flex: 0 0 auto; color: var(--dim); }
.copyfield:hover .ic-copy { color: var(--text); }
[data-copy] .ic-check { display: none; color: var(--green); }
[data-copy].copied .ic-copy { display: none; }
[data-copy].copied .ic-check { display: inline-block; }

/* Visually hidden but exposed to screen readers (e.g. the copy-success live region). */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* Segmented radio toggle (e.g. staging | production). Both options visible;
   the checked one is highlighted. Native radios drive state (no JS). */
.segmented {
  display: inline-flex; border: 1px solid var(--border2); border-radius: 8px;
  overflow: hidden; background: var(--card2); vertical-align: middle;
}
.segmented .seg { display: inline-flex; cursor: pointer; }
.segmented .seg input { position: absolute; opacity: 0; pointer-events: none; }
.segmented .seg span {
  padding: 7px 13px; font-size: 13px; color: var(--muted); user-select: none;
  border-right: 1px solid var(--border2); transition: background .12s, color .12s;
}
.segmented .seg:last-child span { border-right: 0; }
.segmented .seg input:checked + span { background: var(--accent); color: #fff; }
.segmented .seg:hover input:not(:checked) + span { color: var(--text); }

/* Pico owns table structure (full width, cell padding, row borders, striping)
   via the theme bridge. We keep only the NE20 header flavor. */
th { color: var(--muted); font-weight: 500; font-size: 12px; text-transform: uppercase; letter-spacing: .05em; }
/* Wide tables (e.g. the fleet grid) can exceed their card; let the card scroll
   them horizontally instead of letting the table break out to the right. Scoped
   to table cards so non-table cards keep overflow:visible (popovers, tooltips).
   The filtered tables (.hfilter in grid/logs) are taller than their popovers, so
   the forced overflow-y clip doesn't cut them off. */
.card:has(table) { overflow-x: auto; }
.pill { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 12px; border: 1px solid var(--border2); color: var(--muted); white-space: nowrap; }
/* Borderless status indicator for dense table cells: solid colored dot + label.
   Never wraps (so it can't balloon into a blob), and collapses to just the dot
   when the column compresses. */
.status { display: inline-flex; align-items: center; gap: 7px; font-size: 12px; white-space: nowrap; color: var(--muted); }
.status::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex: 0 0 auto; }
.status.green { color: var(--green); }
.status.amber { color: var(--amber); }
.status.red { color: var(--red); }
@media (max-width: 760px) { .status .label { display: none; } }
/* CPU/MEM usage gauges: a labelled bar (fill = usage %) with the capacity total
   pinned to the right end. Single line, never collapses to a vertical stack. */
.gauge { display: flex; align-items: center; gap: 8px; white-space: nowrap; line-height: 1.85; }
.gauge-k { flex: 0 0 auto; width: 30px; color: var(--muted); font-size: 10px; letter-spacing: .04em; }
.gauge .bar { position: relative; flex: 1 1 auto; min-width: 46px; height: 6px; background: var(--border2); border-radius: 999px; overflow: hidden; }
.gauge .bar-fill { position: absolute; inset: 0 auto 0 0; border-radius: 999px; background: var(--accent); }
.gauge .bar-fill.warm { background: var(--amber); }
.gauge .bar-fill.hot { background: var(--red); }
.gauge-v { flex: 0 0 auto; min-width: 46px; text-align: right; font-size: 12px; color: var(--text); font-variant-numeric: tabular-nums; }
.pill.green { color: var(--green); border-color: rgba(34,197,94,.4); }
.pill.red { color: var(--red); border-color: rgba(239,68,68,.4); }
.pill.amber { color: var(--amber); border-color: rgba(245,166,35,.4); }
.pill.accent { color: var(--accent); border-color: rgba(59,130,246,.4); }
.pill.dot::before { content: ""; display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: currentColor; margin-right: 6px; vertical-align: middle; }
td .addr { font-family: var(--mono); font-size: 12px; color: var(--muted); display: block; }
/* Column-header filter: a caret trigger (native <details>) that opens a small
   popover of option links. Looks like a plain heading until clicked. */
/* margin-bottom:0 — Pico gives <details> a bottom margin (misaligns the filter
   headers vs plain <th> text) and adds one to an OPEN summary (shifts the table
   on click). Reset both so the header row is stable and aligned. */
.hfilter { position: relative; display: inline-block; margin-bottom: 0; }
.hfilter[open] > summary { margin-bottom: 0; }
.hfilter > summary {
  list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 5px;
  color: var(--muted); user-select: none;
}
.hfilter > summary::-webkit-details-marker { display: none; }
/* Pico adds a chevron ::after to <summary>; we use our own .caret instead. */
.hfilter > summary::after { display: none; }
.hfilter > summary:hover { color: var(--text); }
.hfilter .hfval { text-transform: none; letter-spacing: normal; color: var(--accent); font-size: 11px; }
.hfilter .caret { font-size: 8px; opacity: .7; }
.hfilter[open] .caret { transform: rotate(180deg); }
.hfilter .hfmenu {
  position: absolute; top: 100%; left: 0; margin-top: 6px; z-index: 30;
  background: var(--card2); border: 1px solid var(--border2); border-radius: 8px;
  padding: 4px; min-width: 130px; max-height: 280px; overflow-y: auto;
  box-shadow: 0 10px 28px rgba(0,0,0,.45); display: flex; flex-direction: column; gap: 1px;
}
.hfilter .hfopt {
  display: block; padding: 6px 10px; border-radius: 6px; font-size: 12px;
  text-transform: none; letter-spacing: normal; color: var(--muted);
  text-decoration: none; white-space: nowrap;
}
.hfilter .hfopt:hover { background: var(--card); color: var(--text); }
.hfilter .hfopt.active { color: var(--accent); background: var(--accent-dim); }
/* TLS padlock in the domains list (left of the name), colored by cert state. */
.namecell { display: inline-flex; align-items: center; gap: 8px; }
.lock { display: inline-flex; flex: 0 0 auto; }
.lock-grey { color: var(--dim); }
.lock-red { color: var(--red); }
.lock-yellow { color: var(--amber); }
.lock-green { color: var(--green); }
.lock-blue { color: var(--accent); }
/* Toast: transient action feedback, fixed top-right, auto-fades (pure CSS). */
.toast {
  position: fixed; top: 70px; right: 22px; z-index: 50; max-width: 380px;
  display: flex; align-items: flex-start; gap: 10px;
  background: var(--card2); border: 1px solid var(--border2); border-radius: 10px;
  padding: 12px 14px; font-size: 13px; line-height: 1.45; color: var(--text);
  box-shadow: 0 10px 28px rgba(0,0,0,.45);
  animation: toast-in .18s ease-out, toast-out .4s ease 7s forwards;
}
@keyframes toast-in { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }
@keyframes toast-out { to { opacity: 0; transform: translateY(-8px); visibility: hidden; } }
.rolecount { display: flex; gap: 22px; flex-wrap: wrap; }
.rolecount .rc { display: flex; flex-direction: column; }
.rolecount .rc .n { font-size: 22px; font-weight: 700; }
.rolecount .rc .l { color: var(--muted); font-size: 13px; text-transform: capitalize; }
code, .mono { font-family: var(--mono); font-size: 13px; }
.muted { color: var(--muted); }

/* margin-bottom:0 — Pico adds bottom margin to <label>, which misaligns the field
   against buttons in flex-end .formrow layouts (the field rides high). */
label.fld { display: flex; flex-direction: column; gap: 5px; font-size: 12px; color: var(--muted); margin-bottom: 0; }
/* Pico owns native input/select styling (palette via the theme bridge: focus
   rings, validation states, switches). Our dense forms lay out with flex `gap`,
   so zero out Pico's per-element bottom margin to keep them tight. */
label.fld input, label.fld select,
.formrow input, .formrow select,
.login-card form input { margin-bottom: 0; }
/* Pico forces form controls to width:100%; the panel's dense flex/inline layouts
   size controls themselves (column-flex labels still stretch; row-flex toolbars
   size to content). Selector mirrors Pico's specificity so the override wins. */
input:not([type=checkbox],[type=radio]), select, textarea { width: auto; }
.formrow { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
/* Match the submit button to the form-control height (Pico's own formula) so it
   sits flush beside the fields; bottom-aligned + equal height => centers line up. */
.formrow button[type="submit"] {
  height: calc(1rem * var(--pico-line-height) + var(--pico-form-element-spacing-vertical) * 2 + var(--pico-border-width) * 2);
}
/* The logs filter is a compact toolbar, not a field form: keep the search input
   and Filter button at the role-chip (.btn) height so the row is uniform and
   bottom-aligned, instead of being towered over by Pico's tall default input.
   margin-bottom separates the toolbar from the table that follows it in the card. */
.formrow.filters { margin-bottom: 14px; }
.formrow.filters form.inline-form { display: inline-flex; align-items: center; gap: 8px; }
.formrow.filters input[type="text"] { height: auto; font-size: 13.5px; padding: 8px 14px; }
.formrow.filters button[type="submit"] { height: auto; }
.checks { display: flex; gap: 16px; flex-wrap: wrap; }
.checks label { display: inline-flex; align-items: center; gap: 6px; color: var(--text); font-size: 13px; }
.empty { color: var(--muted); padding: 40px; text-align: center; }
.kpis { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-bottom: 18px; }
@media (max-width: 900px) { .kpis { grid-template-columns: 1fr 1fr; } }
.kpi { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 16px 18px; }
.kpi .v { font-size: 26px; font-weight: 700; }
.kpi .k { color: var(--muted); font-size: 13px; margin-top: 2px; }
a.kpi { display: block; color: inherit; text-decoration: none; transition: border-color .12s ease; }
a.kpi:hover { border-color: var(--dim); }
.inline-form { display: inline; }
pre.cmd {
  background: var(--card2); border: 1px solid var(--border2); border-radius: 8px;
  padding: 12px 14px; font-family: var(--mono); font-size: 12.5px; overflow-x: auto; color: var(--text);
}

/* ---- Node-location map ---- */
/* Fit the map in the viewport: map.js MEASURES the space below the map's top edge
   and sets .nodemap's exact pixel width/height to the live viewBox aspect (so the
   box matches the map -> no letterbox, % tooltip hotspots stay aligned). CSS only
   provides a no-JS fallback height and makes the SVG + tooltip overlay fill the box.
   Measuring beats CSS viewport math here: vh/dvh can't see the dynamic chrome above
   the map, so any computed reserve is a brittle guess that underflows on short
   windows. The [data-tooltip] hotspots need an HTML host (SVG shapes don't paint
   CSS pseudo-elements); sharing the wrapper box keeps them aligned. */
.nodemap {
  position: relative;
  height: 58vh;            /* no-JS fallback; map.js overrides with an exact px fit */
  margin-inline: auto;     /* center when the JS width is narrower than the card */
  overflow: hidden;
}
/* !important so this wins even over a stale cached page whose <svg> still carries
   the old inline width:100%;height:auto. */
.nodemap > svg {
  position: absolute !important; inset: 0 !important;
  width: 100% !important; height: 100% !important; display: block !important;
}
.maptips { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.nodemap .maptip {
  position: absolute; width: 22px; height: 22px; border-radius: 50%;
  transform: translate(-50%, -50%); pointer-events: auto; cursor: help;
  /* Pico gives non-interactive [data-tooltip] a dotted underline; the hotspot is
     a transparent disc over a pin, so drop it. */
  border-bottom: 0;
}

/* ---- Login (standalone, no sidebar) ---- */
.login-wrap { display: flex; min-height: 100vh; align-items: center; justify-content: center; padding: 24px; }
.login-card { width: 360px; max-width: 100%; background: var(--card); border: 1px solid var(--border); border-radius: 16px; padding: 30px 28px; }
.login-card h1 { font-size: 20px; margin-bottom: 6px; }
.login-card form { display: flex; flex-direction: column; gap: 14px; margin-top: 18px; }
.login-card .err { color: var(--red); font-size: 13px; margin-top: 6px; }
