/* ==========================================================================
   Server Dashboard — Liquid Glass Design System
   --------------------------------------------------------------------------
   One definition per visual role. A component is a thin extension of a shared
   base, never a second copy of it; a variant is a modifier class, never a
   sibling with the same declarations under a different name. Spacing between
   blocks comes from the small utility set in §5, not from per-component
   "-spaced" classes.

   Reading order: tokens → base → layout → surfaces → text → spacing → tone →
   components → overlays → decoration.
   ========================================================================== */

/* ==========================================================================
   0. Tokens
   ========================================================================== */

:root {
  /* base */
  --bg-base: #0b0d17;
  --bg-radial: #12142a;

  /* glass surface */
  --glass-bg: rgba(255,255,255,0.12);
  --glass-bg-strong: rgba(255,255,255,0.18);
  --glass-bg-light: rgba(255,255,255,0.08);
  --glass-border: rgba(255,255,255,0.35);
  --glass-highlight: rgba(255,255,255,0.6);

  /* the blur every glass surface uses, in three strengths */
  --blur: blur(24px) saturate(160%);
  --blur-soft: blur(12px) saturate(160%);
  --blur-strong: blur(32px) saturate(160%);

  /* inset panels — a box sitting *on* a card */
  --inset-bg: rgba(255,255,255,0.08);
  --inset-border: rgba(255,255,255,0.15);

  /* form fields */
  --field-bg: rgba(255,255,255,0.08);
  --field-bg-active: rgba(255,255,255,0.14);
  --field-border: rgba(255,255,255,0.25);
  --field-border-hover: rgba(255,255,255,0.45);
  --field-border-focus: rgba(255,255,255,0.6);
  --focus-ring: 0 0 0 3px rgba(255,255,255,0.08);
  --placeholder: rgba(255,255,255,0.4);

  /* text */
  --text: #ffffff;
  --text-dim: rgba(255,255,255,0.65);
  --text-faint: rgba(255,255,255,0.35);

  /* radii */
  --radius-sm: 12px;
  --radius-md: 16px;
  --radius-lg: 28px;

  /* spacing scale — the only values the spacing utilities use */
  --space-sm: 10px;
  --space-md: 16px;
  --space-lg: 20px;

  /* blob palette */
  --blob-violet-a: #7c5cff;
  --blob-violet-b: #3a2ba3;
  --blob-pink-a: #ff6fa5;
  --blob-pink-b: #a5227e;
  --blob-cyan-a: #4fd7ff;
  --blob-cyan-b: #1f6fbf;
  --blob-amber-a: #ffd76f;
  --blob-amber-b: #d98a2b;

  /* tones — the four state tints, each a fill, a border and a text colour.
     Pulled from the blob palette so the whole app reads as one material;
     never introduce a new hue outside this set. */
  --online: var(--blob-cyan-a);
  --online-bg: rgba(79, 215, 255, 0.16);
  --online-border: rgba(79, 215, 255, 0.4);
  --online-text: #b9ecff;

  --warn: var(--blob-amber-a);
  --warn-bg: rgba(255, 215, 111, 0.16);
  --warn-border: rgba(255, 215, 111, 0.4);
  --warn-text: #fff0c2;

  --offline: var(--blob-pink-a);
  --offline-bg: rgba(255, 111, 165, 0.16);
  --offline-border: rgba(255, 111, 165, 0.4);
  --offline-text: #ffd0e2;

  --lock: var(--blob-violet-a);
  --lock-bg: rgba(124, 92, 255, 0.18);
  --lock-border: rgba(124, 92, 255, 0.4);
  --lock-text: #d9ccff;

  --accent: var(--blob-cyan-a);

  /* chart series. Both the legend swatches and the canvas read these, so a
     swatch and its line cannot drift apart. */
  --chart-load: var(--blob-violet-a);
  --chart-read: var(--blob-cyan-a);
  --chart-write: var(--blob-amber-a);
  --chart-mem: var(--blob-pink-a);
  --chart-kernel: var(--blob-amber-a);

  --font-display: -apple-system, "SF Pro Display", "Segoe UI", Inter, system-ui, sans-serif;
  --font-body: -apple-system, "SF Pro Display", "Segoe UI", Inter, system-ui, sans-serif;
  --font-mono: "SFMono-Regular", Consolas, monospace;
}

/* ==========================================================================
   1. Base
   ========================================================================== */

* { box-sizing: border-box; }
html { color-scheme: dark; }

body {
  margin: 0;
  color: var(--text);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  min-height: 100vh;
}

h1, h2, h3 { font-family: var(--font-display); font-weight: 600; margin: 0 0 0.4em; letter-spacing: -0.01em; color: var(--text); }
h1 { font-size: 1.6rem; }
h2 { font-size: 1.15rem; }
h3 { font-size: 0.95rem; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.06em; }

p { color: var(--text-dim); font-weight: 400; }

a { color: var(--text); text-decoration: none; }
a:hover { text-decoration: underline; }

:focus-visible {
  outline: 2px solid rgba(255,255,255,0.6);
  outline-offset: 2px;
}

/* The one rule for the hidden attribute. Components that set their own
   display would otherwise beat the browser's default, and did — this used to
   be repeated per class. */
[hidden] { display: none !important; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition-duration: 0.001ms !important; }
}

/* ==========================================================================
   2. Layout
   ========================================================================== */

.shell { position: relative; z-index: 2; max-width: 1080px; margin: 0 auto; padding: 0 24px 64px; }

.center-shell {
  position: relative;
  z-index: 2;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: stretch;
  gap: var(--space-lg);
}
/* Equal heights across a row. Spacing *between* cards is the grid gap here and
   .mt-lg elsewhere — there is deliberately no .card + .card rule, which used to
   tie with this one on specificity and push every right-hand card down 20px. */
.grid-2 .card { height: 100%; }
@media (max-width: 760px) { .grid-2 { grid-template-columns: 1fr; } }

/* A row of controls that wraps. */
.btn-row { display: flex; gap: var(--space-sm); flex-wrap: wrap; }

/* Keeps the logout form from breaking the navbar's flex row. */
.inline-form { display: inline-flex; margin: 0; }

/* The panel page is half again as wide as the rest, navbar included — leaving
   the nav at 1080px would put its links visibly inboard of the card below. */
body.panel-wide .shell,
body.panel-wide .navbar-inner { max-width: 1620px; }

/* ==========================================================================
   3. Surfaces
   --------------------------------------------------------------------------
   Three depths. glass: a card, the navbar, a pill. inset: a box sitting on a
   card. popover: a floating panel above everything — dropdown lists, tooltips.
   ========================================================================== */

.glass, .card, .navbar, .status-pill, .badge {
  position: relative;
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  box-shadow:
    0 8px 32px rgba(0,0,0,0.35),
    inset 0 1px 0 rgba(255,255,255,0.5),
    inset 0 0 0 1px rgba(255,255,255,0.04);
}

.card {
  border-radius: var(--radius-lg);
  padding: 22px;
  overflow: hidden;
}
.card::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 50%;
  background: linear-gradient(180deg, rgba(255,255,255,0.35), rgba(255,255,255,0) 100%);
  pointer-events: none;
  opacity: 0.5;
}

.card-header {
  display: flex;
  flex-wrap: wrap;               /* the tools drop to a second line before they overflow */
  align-items: center;
  justify-content: space-between;
  gap: 6px 12px;
  margin-bottom: var(--space-md);
}
.card-header > h2 { margin-bottom: 0; }
/* The right-hand side of a header holding more than one thing. */
.card-header-tools { display: inline-flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--space-sm); min-width: 0; }

/* An inset: the stat boxes and the service rows are the same box. */
.inset, .stat-box, .service-row {
  background: var(--inset-bg);
  border: 1px solid var(--inset-border);
  border-radius: var(--radius-md);
  padding: 12px 14px;
}
.service-row { padding: 14px 16px; }

/* A popover: the dropdown list and the tooltip bubble are the same panel. */
.popover, .gs-panel, .info-tip::after {
  background: var(--glass-bg-strong);
  backdrop-filter: var(--blur-strong);
  -webkit-backdrop-filter: var(--blur-strong);
  border: 1px solid var(--glass-border);
  border-radius: 18px;
  box-shadow:
    0 16px 44px rgba(0,0,0,0.45),
    inset 0 1px 0 rgba(255,255,255,0.4);
}

/* ==========================================================================
   4. Text
   ========================================================================== */

/* eyebrow: the small uppercase label over a value. Stat-box labels, table
   headers, the metric labels in a service row and the row headings between
   cards are all this one style. */
.eyebrow, .stat-box .label, .service-metric-label, th, .section-heading {
  font-size: 0.72rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-faint);
}
.stat-box .label { margin-bottom: 4px; }
.section-heading { font-family: var(--font-display); margin: 28px 0 12px; }

/* caption: small monospace, faint. The note in a card header, the detail line
   under a service name, the tag beside the brand, the username in the nav. */
.caption, .service-meta, .brand-tag, .nav-user, .public-eyebrow {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--text-faint);
}

/* hint: a short explanatory line in the body font. Under a form field, under
   a chart, in an empty list. */
.hint, .chart-caption, .gs-empty, .panel-state {
  font-size: 0.82rem;
  color: var(--text-faint);
}
.hint { margin: 0 0 14px; }
.chart-caption { margin-top: 8px; }
.chart-caption:empty { display: none; }

/* ==========================================================================
   5. Empty-state spacing
   ========================================================================== */

/* A block that should vanish entirely, spacing included, when it has nothing
   in it — message areas that are empty most of the time. */
.services-message:not(:empty),
.services-interlock:not(:empty) { margin-bottom: 14px; }

/* ==========================================================================
   6. Tone
   --------------------------------------------------------------------------
   The four state tints, as one class each, applied to whatever needs them: a
   banner, a badge, a status pill, a lock notice. The element keeps its own
   shape; the tone supplies fill and border.
   ========================================================================== */

.tone-online  { background: var(--online-bg);  border-color: var(--online-border); }
.tone-warn    { background: var(--warn-bg);    border-color: var(--warn-border); }
.tone-offline { background: var(--offline-bg); border-color: var(--offline-border); }
.tone-lock    { background: var(--lock-bg);    border-color: var(--lock-border); }

/* Small text on a tint reads better tinted too. Banners keep white text. */
.badge.tone-online  { color: var(--online-text); }
.badge.tone-warn    { color: var(--warn-text); }
.badge.tone-offline { color: var(--offline-text); }
.badge.tone-lock    { color: var(--lock-text); }

/* ==========================================================================
   7. Navbar
   ========================================================================== */

.navbar {
  position: sticky;
  top: 0;
  z-index: 10;
  width: 100%;
  border-radius: 0 0 20px 20px;
  background: var(--glass-bg-strong);
  backdrop-filter: var(--blur-strong);
  -webkit-backdrop-filter: var(--blur-strong);
  border-top: none;
  border-left: none;
  border-right: none;
  box-shadow:
    0 8px 32px rgba(0,0,0,0.35),
    inset 0 1px 0 rgba(255,255,255,0.5);
}

.navbar-inner {
  max-width: 1080px;
  margin: 0 auto;
  padding: 14px 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
}

.brand {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.05rem;
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--text);
}
.brand-tag {
  font-size: 0.7rem;
  border: 1px solid var(--glass-border);
  border-radius: 999px;
  padding: 1px 8px;
}

.nav-links { display: flex; align-items: center; gap: 18px; font-size: 0.9rem; }
.nav-links a { color: var(--text-dim); }
.nav-links a.active { color: var(--text); }
.nav-links a:hover { color: var(--text); text-decoration: none; }

.nav-user { font-size: 0.8rem; display: flex; align-items: center; gap: var(--space-sm); }

/* ==========================================================================
   8. Status pill and badge
   ========================================================================== */

.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  padding: 7px 16px 7px 12px;
  border-radius: 999px;
  backdrop-filter: var(--blur-soft);
  -webkit-backdrop-filter: var(--blur-soft);
  background: var(--glass-bg-light);
  color: var(--text);
}

.status-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--text-faint);
  flex-shrink: 0;
}

/* The dot takes the tone's colour; the two "in progress" tones pulse. */
.status-pill.tone-online  .status-dot { background: var(--online); box-shadow: 0 0 0 3px var(--online-bg); animation: pulse 2.4s ease-in-out infinite; }
.status-pill.tone-offline .status-dot { background: var(--offline); }
.status-pill.tone-warn    .status-dot { background: var(--warn); animation: pulse 1s ease-in-out infinite; }
.status-pill.tone-lock    .status-dot { background: var(--lock); }

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

.badge {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  padding: 2px 9px;
  border-radius: 999px;
  color: var(--text-dim);
}

/* ==========================================================================
   9. Buttons
   ========================================================================== */

.btn {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 0.9rem;
  padding: 10px 18px;
  border-radius: var(--radius-md);
  border: 1px solid var(--glass-border);
  background: rgba(255,255,255,0.1);
  backdrop-filter: var(--blur-soft);
  -webkit-backdrop-filter: var(--blur-soft);
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;   /* a label never breaks across lines */
  transition: transform 150ms ease, box-shadow 150ms ease, background 150ms ease;
}
.btn:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0,0,0,0.3); }
.btn:active:not(:disabled) { transform: scale(0.98); }
.btn:disabled { opacity: 0.35; cursor: not-allowed; }

.btn-primary {
  background: linear-gradient(145deg, rgba(255,255,255,0.55), rgba(255,255,255,0.12));
  border-color: rgba(255,255,255,0.5);
  color: #1a1a2e;
  font-weight: 600;
}
.btn-primary:hover:not(:disabled) { box-shadow: 0 10px 28px rgba(255,255,255,0.15); }

.btn-danger {
  background: rgba(255, 111, 165, 0.12);
  border-color: var(--offline-border);
  color: var(--offline-text);
}
.btn-danger:hover:not(:disabled) { background: rgba(255, 111, 165, 0.2); }

.btn-ghost {
  background: transparent;
  border-color: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  color: var(--text-dim);
  padding: 8px 10px;
  box-shadow: none;
}
.btn-ghost:hover:not(:disabled) { color: var(--text); text-decoration: none; transform: none; box-shadow: none; }

/* Two smaller sizes: sm for a row of actions inside a card, xs for a control
   living in a card header. */
.btn-sm { font-size: 0.84rem; padding: 8px 14px; }
.btn-xs { font-size: 0.76rem; padding: 5px 11px; border-radius: 11px; }
.btn-block { width: 100%; }

/* Sits tight to the credential field it clears. */
.cred-clear { margin-top: 6px; padding-left: 0; font-size: 0.78rem; }

/* ==========================================================================
   10. Banners
   ========================================================================== */

.banner {
  border-radius: var(--radius-md);
  border: 1px solid var(--glass-border);
  backdrop-filter: var(--blur-soft);
  -webkit-backdrop-filter: var(--blur-soft);
  padding: 12px 14px;
  font-size: 0.88rem;
  display: flex;
  gap: var(--space-sm);
  align-items: flex-start;
  color: var(--text);
}
.banner + .banner { margin-top: var(--space-sm); }

/* An interlock is a lock nobody clears, and the solid edge says so. Used with
   .tone-lock. */
.banner-interlock { border-left: 3px solid var(--lock); }

/* ==========================================================================
   11. Forms
   ========================================================================== */

.form-group { margin-bottom: var(--space-md); }
.form-group label {
  display: block;
  font-size: 0.82rem;
  color: var(--text-dim);
  margin-bottom: 6px;
}

/* The field. Text inputs, the closed face of the glass select, the search box
   inside it and the inline password fields in the admin table are all this
   one control at one of two sizes. */
.input, .gs-trigger {
  width: 100%;
  background-color: var(--field-bg);
  border: 1px solid var(--field-border);
  border-radius: 14px;
  padding: 10px 12px;
  color: var(--text);
  font-family: var(--font-body);
  font-size: 0.9rem;
  transition: background-color 150ms ease, border-color 150ms ease, box-shadow 150ms ease;
}
.input::placeholder { color: var(--placeholder); }

.input-sm, .gs-compact .gs-trigger {
  padding: 7px 10px;
  font-size: 0.85rem;
  border-radius: 11px;
}

/* One focus ring. */
.input:focus, .gs-trigger:focus-visible {
  outline: none;
  background-color: var(--field-bg-active);
  border-color: var(--field-border-focus);
  box-shadow: var(--focus-ring);
}

/* Native <select>: custom chevron and a dark option list, so the closed
   control never falls back to plain OS chrome even before glass-select.js
   has replaced it. */
select.input {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  color-scheme: dark;
  padding-right: 34px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none'%3E%3Cpath d='M5 8l5 5 5-5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 13px;
  cursor: pointer;
}
option { background-color: #171b30; color: var(--text); }
option:checked, option:hover { background-color: var(--blob-cyan-b); color: var(--text); }

/* Inline password reset row in the admin users table. */
.reset-cell { background: rgba(255,255,255,0.04); }
.reset-form { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-sm); }
.reset-form .input { flex: 1 1 180px; min-width: 150px; width: auto; }
.reset-label { font-size: 0.82rem; color: var(--text-dim); margin-right: 4px; }

/* ==========================================================================
   12. Tables and stats
   ========================================================================== */

table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
th { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--inset-border); }
td { padding: 10px; border-bottom: 1px solid rgba(255,255,255,0.08); color: var(--text); }
tr:last-child td { border-bottom: none; }

/* Scrolls a wide table inside its own box instead of the whole page. Also a
   size container, so the row-action menu below can react to the room the
   table actually has rather than to the viewport. */
.table-wrap { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; container-type: inline-size; }
.table-wrap table { min-width: 560px; }
.table-wrap .table-fit { min-width: 0; }

/* Actions in a table cell stay on one line; the table scrolls before they wrap. */
td > .btn-row { flex-wrap: nowrap; }

/* --- row actions: inline, or behind a ⋮ when the table is narrow ---------
   One set of buttons either way. Below the threshold the row is hidden and
   the toggle shown; admin.js opens the row as a popover, lifted to <body> so
   the table's overflow clip cannot cut it off. 800px is just above the width
   at which the users table with inline actions starts to overflow. */
.row-actions-toggle { display: none; font-size: 1.1rem; line-height: 1; padding: 4px 8px; }

@container (max-width: 800px) {
  .row-actions-toggle { display: inline-block; }
  .row-actions-menu { display: none; }
}

.row-actions-menu.is-open {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 2px;
  position: fixed;
  z-index: 60;
  min-width: 180px;
  padding: 6px;
}
.row-actions-menu.is-open .btn { text-align: left; border-radius: 11px; }

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 14px;
}
.stat-box .value { font-family: var(--font-mono); font-size: 1.15rem; color: var(--text); }
.stat-box .value-small { font-size: 0.9rem; }

/* A value with an (i) beside it. The ellipsis lives on the inner span, not on
   this box: the tooltip pops up outside it, and overflow:hidden here would
   clip the whole bubble. */
.value-with-tip { display: flex; align-items: center; gap: 6px; min-width: 0; }
.value-with-tip > span:first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ==========================================================================
   13. Meter — a thin horizontal bar
   --------------------------------------------------------------------------
   The load bar under the dashboard readout and the per-core bar in the CPU
   popup. Fills are laid left to right, so a stacked bar is two fills.
   ========================================================================== */

.meter {
  display: flex;
  height: 6px;
  border-radius: 4px;
  background: var(--inset-bg);
  border: 1px solid var(--inset-border);
  overflow: hidden;
}
.meter-fill { height: 100%; transition: width 0.4s ease; }
.meter-fill-load   { background: linear-gradient(90deg, var(--online), var(--warn) 70%, var(--offline) 100%); border-radius: 4px; }
.meter-fill-kernel { background: var(--chart-kernel); }
.meter-fill-user   { background: var(--chart-load); }

/* ==========================================================================
   14. Load readout
   ========================================================================== */

.load-readout {
  font-family: var(--font-mono);
  font-size: 3rem;
  font-weight: 500;
  letter-spacing: -0.02em;
  margin: 10px 0 4px;
  color: var(--text);
}
.load-readout small { font-size: 1.4rem; color: var(--text-faint); font-family: var(--font-body); font-weight: 400; }
.load-readout + .meter { margin-top: 6px; }

/* ==========================================================================
   15. Charts
   ========================================================================== */

.chart-wrap { position: relative; width: 100%; height: 220px; }
.chart-wrap-short { height: 180px; }

.chart-legend { display: flex; gap: 18px; font-size: 0.78rem; color: var(--text-dim); margin-top: var(--space-sm); }
.chart-legend .swatch { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: 6px; }
.swatch-load   { background: var(--chart-load); }
.swatch-read   { background: var(--chart-read); }
.swatch-write  { background: var(--chart-write); }
.swatch-kernel { background: var(--chart-kernel); }

/* ==========================================================================
   16. Services
   --------------------------------------------------------------------------
   Rows rather than a table. Auto-fit so one rule serves both mounts: one
   column in the dashboard's half-width card, two side by side in the
   full-width configuration card.
   ========================================================================== */

.services-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 12px;
  align-items: start;
}

.service-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.service-name { font-family: var(--font-display); font-weight: 600; font-size: 0.98rem; color: var(--text); }
.service-meta { margin-top: 4px; font-size: 0.74rem; overflow-wrap: anywhere; }

/* Used with .tone-lock. */
.service-lock {
  margin-top: var(--space-sm);
  padding: 8px 11px;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  font-size: 0.8rem;
  color: var(--text);
}

.service-actions { margin-top: 12px; }

/* Per-service metrics, filled by metrics.js. */
.service-metrics { margin-top: var(--space-sm); }
.service-metrics-row { display: flex; flex-wrap: wrap; gap: 6px 18px; font-size: 0.8rem; }
.service-metric { display: inline-flex; align-items: baseline; gap: 6px; }
.service-metric-label { font-size: 0.7rem; }
.service-metric-value { color: var(--text-dim); }

/* Sparkline strip. Fixed height so rows with and without one line up. */
.service-spark-wrap {
  position: relative;
  width: 100%;
  height: 34px;
  margin-top: 8px;
  border-radius: 8px;
  background: rgba(255,255,255,0.04);
}

/* A pill above its detail: the health card's pill over its stats, and the
   offline state's pill over one line of text, in the health card and the
   services list alike. The column is align-items:flex-start so the pill hugs
   its text, which means a full-width child must ask for its width back. */
.api-status, .offline-state { display: flex; flex-direction: column; gap: 14px; align-items: flex-start; }
.api-status .stat-grid { width: 100%; }
.offline-state .hint { margin: 0; }

/* ==========================================================================
   17. Panel page
   ========================================================================== */

.panel-state { margin: 0; padding: 28px 0; text-align: center; font-size: 0.9rem; }

.panel-frame {
  display: block;
  width: 100%;
  height: min(72vh, 900px);
  min-height: 420px;
  border: 1px solid var(--glass-border);
  border-radius: 14px;
  /* Opaque, or the card's blur shows through the embedded page's text. */
  background: var(--bg-base);
}

/* ==========================================================================
   18. Login and public cards
   ========================================================================== */

.login-card { width: 100%; max-width: 380px; }
.login-card .card { padding: 32px; }
.login-card h1 { text-align: center; margin-bottom: 22px; }

.public-card { width: 100%; max-width: 420px; text-align: center; }
.public-card .card { padding: 40px 32px; }
.public-eyebrow { font-size: 0.78rem; margin-bottom: 18px; }

/* ==========================================================================
   19. Glass select — the custom dropdown from glass-select.js
   --------------------------------------------------------------------------
   A native <select>'s open list is OS chrome no stylesheet can reach. This
   replaces the list so it is made of the same material as everything else.
   The closed face is a .input (§11); the open list is a .popover (§3).
   ========================================================================== */

.gs { position: relative; display: block; }

.gs-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  text-align: left;
  backdrop-filter: var(--blur-soft);
  -webkit-backdrop-filter: var(--blur-soft);
  cursor: pointer;
}
.gs-trigger:hover { background-color: var(--field-bg-active); border-color: var(--field-border-hover); }
.gs-trigger[aria-expanded="true"] { background-color: rgba(255,255,255,0.16); border-color: rgba(255,255,255,0.55); }
.gs-trigger.is-empty .gs-label { color: var(--text-faint); }

.gs-label, .gs-option-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Chevron from borders, so it scales with the control and needs no image. */
.gs-chevron {
  flex-shrink: 0;
  width: 7px;
  height: 7px;
  margin-top: -3px;
  border-right: 1.5px solid rgba(255,255,255,0.7);
  border-bottom: 1.5px solid rgba(255,255,255,0.7);
  transform: rotate(45deg);
  transition: transform 150ms ease;
}
.gs-trigger[aria-expanded="true"] .gs-chevron { transform: rotate(-135deg); margin-top: 2px; }

.gs-panel {
  position: fixed;
  z-index: 60;
  min-width: 190px;
  max-width: 320px;
  max-height: 300px;
  overflow-y: auto;
  padding: 6px;
  animation: gs-rise 140ms ease both;
}
@keyframes gs-rise {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.gs-search { padding: 4px 4px 6px; }

.gs-options { display: flex; flex-direction: column; gap: 2px; }

.gs-option {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 8px 10px;
  border-radius: 11px;
  font-size: 0.85rem;
  color: var(--text-dim);
  cursor: pointer;
  outline: none;
  transition: background-color 120ms ease, color 120ms ease;
}
.gs-option.is-active { background-color: rgba(255,255,255,0.12); color: var(--text); }
.gs-option.is-selected { color: var(--text); }
.gs-option.is-disabled { opacity: 0.4; cursor: not-allowed; }

/* Single-choice rows mark the current value with a tick. */
.gs-panel:not([aria-multiselectable]) .gs-option.is-selected::after {
  content: "";
  margin-left: auto;
  width: 4px;
  height: 8px;
  border: solid var(--accent);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

/* Multi-select rows carry a checkbox. */
.gs-check {
  flex-shrink: 0;
  position: relative;
  width: 15px;
  height: 15px;
  border: 1px solid var(--glass-border);
  border-radius: 5px;
  background-color: var(--field-bg);
  transition: background-color 150ms ease, border-color 150ms ease;
}
.gs-option.is-selected .gs-check { background-color: var(--lock); border-color: var(--lock); }
.gs-option.is-selected .gs-check::after {
  content: "";
  position: absolute;
  left: 4px;
  top: 1px;
  width: 4px;
  height: 8px;
  border: solid #fff;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

.gs-empty { padding: 12px 10px; text-align: center; }

/* ==========================================================================
   20. Dialogs — the app's own confirm, prompt and info
   ========================================================================== */

.dialog-backdrop {
  position: fixed;
  inset: 0;
  z-index: 100;               /* above .gs-panel at 60 */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  /* Darkened and blurred: the blobs behind are in constant motion. */
  background: rgba(6, 8, 16, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  animation: dialog-fade 160ms ease both;
}
body.dialog-open { overflow: hidden; }

.dialog {
  width: 100%;
  max-width: 440px;
  /* .card sets overflow:hidden, which would clip a long body instead of
     letting it scroll on a short window. */
  max-height: calc(100vh - 48px);
  overflow-y: auto;
  animation: dialog-rise 200ms ease both;
}
.dialog-wide { max-width: 640px; }

.dialog-title { margin: 0 0 10px; font-size: 1.05rem; font-weight: 600; }
.dialog-message { margin: 0 0 18px; font-size: 0.9rem; line-height: 1.5; color: var(--text-dim); }
.dialog-body { margin-bottom: var(--space-md); }
.dialog .form-group { margin-bottom: 18px; }
.dialog-actions { display: flex; justify-content: flex-end; gap: var(--space-sm); }

@keyframes dialog-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes dialog-rise {
  from { opacity: 0; transform: translateY(12px) scale(0.98); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
@media (max-width: 420px) {
  .dialog-actions { flex-direction: column-reverse; }
  .dialog-actions .btn { width: 100%; }
}

/* The CPU-by-core table inside the info dialog. */
.cores-table th:last-child, .cores-table td:last-child { width: 34%; }
.cores-bar-cell { vertical-align: middle; }
.cores-bar-cell .meter { height: 8px; border: none; }

/* ==========================================================================
   21. Info tip — (i) with a popover tooltip
   --------------------------------------------------------------------------
   Keyboard-reachable, shows on focus as well as hover. The bubble anchors to
   the icon's right edge and opens leftward, because the icon sits at the end
   of the widest line in its box and the card clips anything escaping right.
   ========================================================================== */

.info-tip {
  position: relative;
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--glass-border);
  background: rgba(255,255,255,0.1);
  color: var(--text-dim);
  font-family: var(--font-body);
  font-size: 0.68rem;
  font-style: italic;
  font-weight: 600;
  line-height: 14px;
  text-align: center;
  cursor: help;
}
.info-tip:hover, .info-tip:focus-visible { color: var(--text); background: rgba(255,255,255,0.18); outline: none; }

.info-tip::after {
  content: attr(data-tip);
  position: absolute;
  right: -4px;
  bottom: calc(100% + 8px);
  z-index: 5;
  width: max-content;
  max-width: 260px;
  padding: 8px 11px;
  border-radius: var(--radius-sm);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 0.74rem;
  font-style: normal;
  font-weight: 400;
  line-height: 1.35;
  text-align: left;
  white-space: normal;
  opacity: 0;
  transform: translateY(4px);
  pointer-events: none;
  transition: opacity 120ms ease, transform 120ms ease;
}
.info-tip:hover::after, .info-tip:focus-visible::after { opacity: 1; transform: translateY(0); }

/* ==========================================================================
   22. Accessibility
   ========================================================================== */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: 10px 16px;
  border-radius: 0 0 14px 0;
  background: var(--bg-radial);
  color: var(--text);
  border: 1px solid var(--glass-border);
}
.skip-link:focus { left: 0; text-decoration: none; }

/* ==========================================================================
   23. Page entrance
   ========================================================================== */

.shell > h1:first-child,
.center-shell .login-card,
.center-shell .public-card { animation: rise 400ms ease both; }

@keyframes rise {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ==========================================================================
   24. Error page detail block (development only)
   ========================================================================== */

.error-detail {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--text-dim);
  background: rgba(0,0,0,0.25);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: var(--radius-sm);
  padding: 12px;
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-word;
}

/* ==========================================================================
   25. Background layer — persistent across every view
   ========================================================================== */

.bg {
  position: fixed;
  inset: 0;
  z-index: 0;
  background: radial-gradient(circle at 30% 20%, var(--bg-radial) 0%, var(--bg-base) 60%);
  overflow: hidden;
}

.blob {
  position: absolute;
  border-radius: 50%;
  filter: blur(70px);
  opacity: 0.55;
  mix-blend-mode: screen;
  will-change: transform, filter;
}
.blob-1 { width: 40vw; height: 40vw; top: -12vh; left: -10vw;  background: radial-gradient(circle, var(--blob-violet-a), var(--blob-violet-b)); animation: float-1 26s ease-in-out infinite, hue 42s linear infinite; }
.blob-2 { width: 34vw; height: 34vw; top: -8vh;  right: -12vw; background: radial-gradient(circle, var(--blob-pink-a),   var(--blob-pink-b));   animation: float-2 30s ease-in-out infinite, hue 46s linear infinite; }
.blob-3 { width: 44vw; height: 44vw; bottom: -16vh; left: -10vw; background: radial-gradient(circle, var(--blob-cyan-a),  var(--blob-cyan-b));   animation: float-3 24s ease-in-out infinite, hue 38s linear infinite; }
.blob-4 { width: 30vw; height: 30vw; bottom: -10vh; right: -8vw; background: radial-gradient(circle, var(--blob-amber-a), var(--blob-amber-b));  animation: float-4 33s ease-in-out infinite, hue 50s linear infinite; }

@media (max-width: 640px) { .blob-4 { display: none; } }

@keyframes float-1 { 0%, 100% { transform: translate(0, 0) scale(1); } 50% { transform: translate(6vw, 8vh) scale(1.08); } }
@keyframes float-2 { 0%, 100% { transform: translate(0, 0) scale(1); } 50% { transform: translate(-7vw, 6vh) scale(0.94); } }
@keyframes float-3 { 0%, 100% { transform: translate(0, 0) scale(1); } 50% { transform: translate(5vw, -7vh) scale(1.06); } }
@keyframes float-4 { 0%, 100% { transform: translate(0, 0) scale(1); } 50% { transform: translate(-6vw, -5vh) scale(0.96); } }

/* One hue cycle, not four identical copies. */
@keyframes hue { from { filter: blur(70px) hue-rotate(0deg); } to { filter: blur(70px) hue-rotate(360deg); } }

.grain {
  position: absolute;
  inset: 0;
  opacity: 0.03;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ==========================================================================
   26. Small screens
   --------------------------------------------------------------------------
   Three breakpoints, each for one reason:

     900px  the navbar's links no longer fit in a row → menu button
     760px  two cards no longer fit side by side → one column (see .grid-2)
     640px  a table no longer fits → rows become stacked, labelled cards
     480px  phone: tighter padding, two-up stat boxes, full-width buttons

   Touch is detected by pointer, not width — a tablet in landscape is wide and
   still wants 44px targets.
   ========================================================================== */

/* --- navbar: links in a row, or behind a button ------------------------- */

.nav-toggle { display: none; }
.nav-menu { display: contents; }   /* wide: children join the navbar's flex row */

/* Three bars from one span. */
.nav-toggle-bar,
.nav-toggle-bar::before,
.nav-toggle-bar::after {
  display: block;
  width: 18px;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
}
.nav-toggle-bar { position: relative; }
.nav-toggle-bar::before,
.nav-toggle-bar::after { content: ""; position: absolute; left: 0; }
.nav-toggle-bar::before { top: -6px; }
.nav-toggle-bar::after { top: 6px; }

@media (max-width: 900px) {
  .navbar-inner { position: relative; }

  .nav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    color: var(--text);
  }

  .nav-menu { display: none; }

  /*
   * Solid rather than glass, on purpose. backdrop-filter only samples within
   * its nearest filtered ancestor, and the navbar is that ancestor — a menu
   * hanging below it has nothing to blur, and the page shows through crisp.
   * The glass select escapes this by living in <body>; a navigation menu
   * over dense content is better served by simply being opaque.
   */
  .navbar.is-open .nav-menu {
    display: flex;
    flex-direction: column;
    gap: 4px;
    position: absolute;
    top: calc(100% + 8px);
    right: 12px;
    left: 12px;
    padding: 8px;
    background: linear-gradient(180deg, rgb(34, 36, 62), rgb(18, 20, 38));
    border: 1px solid var(--glass-border);
    border-radius: 18px;
    box-shadow: 0 16px 44px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.25);
    animation: gs-rise 140ms ease both;
  }

  .nav-links { flex-direction: column; align-items: stretch; gap: 2px; font-size: 1rem; }
  .nav-links a { padding: 12px 14px; border-radius: var(--radius-sm); }
  .nav-links a.active,
  .nav-links a:hover { background: rgba(255,255,255,0.1); }

  .nav-user {
    justify-content: space-between;
    padding: 10px 6px 4px 14px;
    border-top: 1px solid var(--inset-border);
  }

  /* Below this a half-width card is too narrow for auto-fit to give the stat
     boxes two columns, and one column of six is a tower. Two, always. */
  .stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .stat-box .value { font-size: 1rem; overflow-wrap: anywhere; }
}

/* --- tables: stacked, labelled cards when the row cannot fit ------------- */

@media (max-width: 640px) {
  .table-wrap .table-stack { min-width: 0; }

  .table-stack thead { display: none; }
  .table-stack,
  .table-stack tbody,
  .table-stack tr { display: block; }

  .table-stack tr {
    position: relative;
    padding: 12px 14px;
    padding-right: 48px;              /* room for the ⋮ at the top right */
    margin-bottom: var(--space-sm);
    background: var(--inset-bg);
    border: 1px solid var(--inset-border);
    border-radius: var(--radius-md);
  }

  .table-stack td {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 10px;
    padding: 5px 0;
    border-bottom: none;
  }

  /* The column header, brought along as a label. */
  .table-stack td[data-label]::before {
    content: attr(data-label);
    flex: 0 0 84px;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-faint);
  }

  /* A select or a pill fills what is left of the line. */
  .table-stack td > .gs { flex: 1 1 160px; min-width: 0; }

  /* Actions sit at the card's top right; the ⋮ is already showing here,
     since the table container is well under 800px. */
  .table-stack .row-actions-cell {
    position: absolute;
    top: 6px;
    right: 6px;
    padding: 0;
  }

  /* The inline password-reset row keeps its own layout. */
  .table-stack td.reset-cell { display: block; padding: 10px 0 4px; }
  .table-stack td[colspan] { display: block; }
}

/* --- phone ---------------------------------------------------------------- */

@media (max-width: 480px) {
  .shell { padding: 0 14px 48px; }
  .navbar-inner { padding: 12px 14px; }

  h1 { font-size: 1.4rem; }

  .card { padding: 16px; border-radius: 20px; }
  .card-header { margin-bottom: 12px; }

  .stat-grid { gap: var(--space-sm); }
  .stat-box { padding: 10px 12px; }

  /* A row of actions becomes a grid of full-width halves. */
  .btn-row > .btn { flex: 1 1 calc(50% - var(--space-sm)); }

  .load-readout { font-size: 2.4rem; }
  .chart-wrap { height: 200px; }
  .chart-wrap-short { height: 160px; }

  .dialog-backdrop { padding: 12px; }
  .dialog { padding: 18px; }

  /* iOS zooms into any field smaller than 16px on focus. */
  .input, .gs-trigger { font-size: 16px; }
}

/* --- touch ---------------------------------------------------------------- */

@media (pointer: coarse) {
  .btn:not(.btn-xs) { min-height: 44px; }
  .nav-links a { min-height: 44px; display: flex; align-items: center; }
  .gs-option { min-height: 40px; }
  .info-tip { width: 22px; height: 22px; line-height: 20px; }
}

/* ==========================================================================
   27. Utilities
   --------------------------------------------------------------------------
   Last on purpose. A utility is an explicit instruction from the markup and
   must win a specificity tie with any component default above — .danger-text
   on a .btn-ghost, say. Spacing is between blocks only; inside a component,
   spacing belongs to the component.
   ========================================================================== */

.mono { font-family: var(--font-mono); }
.dim { color: var(--text-dim); }
.faint { color: var(--text-faint); }
.danger-text { color: var(--offline); }

.mt-sm { margin-top: var(--space-sm); }
.mt-md { margin-top: var(--space-md); }
.mt-lg { margin-top: var(--space-lg); }
.mb-md { margin-bottom: var(--space-md); }
.mb-lg { margin-bottom: var(--space-lg); }
