/* hyperion — admin UI styles
 * Modern design system. Dark by default, respects prefers-color-scheme.
 * No build step. Pure CSS, hand-tuned for an ops dashboard.
 */

/* ============================================================
 *  Design tokens
 * ============================================================ */

:root {
  color-scheme: light dark;

  /* ============================================================
   *  Hyperion design system — Vercel-inspired monochrome.
   *  Pure black/white base with grayscale steps. Accent colors
   *  reserved for state (success/warn/error) and never for chrome.
   *  Buttons are high-contrast (white-on-black or inverse), not
   *  blue. Borders are barely-there (#1f1f1f) so the eye reads
   *  layout from spacing, not lines.
   * ============================================================ */

  /* Dark = default. Light overrides come further down. */
  --bg:           #000000;
  --bg-2:         #0a0a0a;
  --surface:      #0a0a0a;
  --surface-2:    #111111;
  --surface-3:    #1a1a1a;
  --border:       #1f1f1f;
  --border-strong:#2e2e2e;
  --text:         #fafafa;
  --text-dim:     #a1a1aa;
  --text-soft:    #71717a;

  /* "Primary" = white. Vercel buttons are white-on-black, full stop. */
  --primary:      #ffffff;
  --primary-h:    #ededed;
  --primary-soft: #1a1a1a;
  --primary-fg:   #000000;

  /* Brand accent — kept subtle. Used ONLY on the wordmark + active
     sidebar indicator. NOT on buttons. */
  --brand-accent: #ffffff;

  /* State colors — semantic only. Used sparingly. */
  --success:      #00d97e;
  --success-soft: #052e1a;
  --warn:         #f5a524;
  --warn-soft:    #2d1f08;
  --danger:       #ff4d4f;
  --danger-soft:  #2d0e10;
  --info:         #3b82f6;
  --info-soft:    #0f1a2e;

  /* Vercel cards have NO shadow — just borders. Keep these for
     elevated overlays (cmdk, toasts) only. */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);
  --shadow:    0 8px 24px rgba(0, 0, 0, 0.5), 0 1px 3px rgba(0, 0, 0, 0.3);
  --shadow-lg: 0 24px 48px rgba(0, 0, 0, 0.6);

  /* Tighter radii — Vercel uses 6–8px almost everywhere. */
  --radius:     8px;
  --radius-md:  10px;   /* used by KPI/spark/creds cards (was undefined → fell back to 0) */
  --radius-sm:  6px;
  --radius-xs:  4px;
  --radius-pill:9999px;

  --t-fast: 100ms cubic-bezier(0.4, 0, 0.2, 1);
  --t-med:  200ms cubic-bezier(0.4, 0, 0.2, 1);

  /* Geist falls back gracefully to Inter then system. */
  --font-ui:   "Geist", "Inter", -apple-system, BlinkMacSystemFont,
               "Segoe UI", system-ui, "Helvetica Neue", sans-serif;
  --font-mono: "Geist Mono", "JetBrains Mono", ui-monospace, "SF Mono",
               "Cascadia Mono", Menlo, Consolas, monospace;

  --sidebar-w: 240px;
}

@media (prefers-color-scheme: light) {
  :root {
    --bg:           #ffffff;
    --bg-2:         #fafafa;
    --surface:      #ffffff;
    --surface-2:    #fafafa;
    --surface-3:    #f4f4f5;
    --border:       #eaeaea;
    --border-strong:#d4d4d8;
    --text:         #000000;
    --text-dim:     #52525b;
    --text-soft:    #71717a;

    --primary:      #000000;
    --primary-h:    #1f1f1f;
    --primary-soft: #f4f4f5;
    --primary-fg:   #ffffff;

    --brand-accent: #000000;

    --success:      #16a34a;
    --success-soft: #dcfce7;
    --warn:         #d97706;
    --warn-soft:    #fef3c7;
    --danger:       #dc2626;
    --danger-soft:  #fee2e2;
    --info:         #0284c7;
    --info-soft:    #e0f2fe;

    --shadow-sm: 0 1px 2px rgba(20, 21, 26, 0.05);
    --shadow:    0 4px 12px rgba(20, 21, 26, 0.05), 0 1px 3px rgba(20, 21, 26, 0.04);
    --shadow-lg: 0 16px 40px rgba(20, 21, 26, 0.1);
  }
}

/* ============================================================
 *  Reset + globals
 * ============================================================ */

*,
*::before,
*::after { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font: 14px/1.55 var(--font-ui);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

body {
  min-height: 100dvh;
}

a {
  color: var(--primary-h);
  text-decoration: none;
  transition: color var(--t-fast);
}
a:hover { color: var(--primary); }

code, pre, kbd {
  font: 12.5px/1.55 var(--font-mono);
}
code {
  background: var(--surface-2);
  border-radius: var(--radius-xs);
  padding: 0.12em 0.45em;
  border: 1px solid var(--border);
}
pre {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 1rem 1.1rem;
  overflow-x: auto;
  font-size: 12.5px;
  line-height: 1.55;
}
pre code {
  background: transparent;
  border: 0;
  padding: 0;
}

::selection { background: var(--primary-soft); color: var(--text); }

/* ============================================================
 *  App shell — sidebar + content
 * ============================================================ */

.app {
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  min-height: 100dvh;
}

.sidebar {
  background: linear-gradient(180deg, var(--bg-2) 0%, var(--bg) 100%);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100dvh;
}

.sidebar .brand {
  padding: 1.5rem 1.25rem 1.1rem;
  display: block;
  font-weight: 800;
  font-size: 1.15rem;
  letter-spacing: 0.04em;
  color: var(--text);
  font-family: var(--font-mono);
  text-transform: uppercase;
}
.sidebar .brand .accent {
  color: var(--brand-accent);
  text-shadow: 0 0 18px color-mix(in oklab, var(--brand-accent) 60%, transparent);
}
.sidebar .brand .sep {
  color: var(--text-soft);
  margin: 0 0.05em;
  font-weight: 500;
}
.sidebar .brand:hover {
  text-decoration: none;
  color: var(--text);
}

/* Scroll region between the pinned brand and the pinned user-pill
   footer. flex:1 + min-height:0 lets it shrink below its content
   height so overflow-y:auto actually engages — without min-height:0
   a flex child refuses to shrink past its content and the overflow
   spills past 100dvh with no scrollbar (the bug that buried Trash
   and everything below it on short viewports). */
.sidebar-nav {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Slim, unobtrusive scrollbar — only noticeable when it's needed. */
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}
.sidebar-nav::-webkit-scrollbar { width: 8px; }
.sidebar-nav::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 4px;
}

.sidebar .nav-section {
  padding: 0.75rem 0.75rem 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.sidebar .nav-label {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-soft);
  font-weight: 600;
  padding: 0.75rem 0.6rem 0.4rem;
}
.sidebar a.nav-link {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.55rem 0.75rem;
  border-radius: var(--radius-sm);
  color: var(--text-dim);
  font-weight: 500;
  font-size: 0.92rem;
  transition: background var(--t-fast), color var(--t-fast);
  position: relative;
}
.sidebar a.nav-link:hover {
  background: var(--surface-2);
  color: var(--text);
}
.sidebar a.nav-link.active {
  background: var(--surface-2);
  color: var(--text);
}
.sidebar a.nav-link.active::before {
  content: '';
  position: absolute;
  left: -0.75rem;
  top: 25%;
  bottom: 25%;
  width: 3px;
  background: var(--primary);
  border-radius: 0 3px 3px 0;
}
.sidebar .nav-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  stroke-width: 1.75;
}

.sidebar-footer {
  margin-top: auto;
  padding: 0.75rem;
  border-top: 1px solid var(--border);
}
.sidebar .user-pill {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.55rem 0.65rem;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.sidebar .user-avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--primary), var(--info));
  display: grid;
  place-items: center;
  color: white;
  font-weight: 700;
  font-size: 0.85rem;
  flex-shrink: 0;
  position: relative;
  overflow: hidden;
}
/* Avatar image overlays the initial when loaded. On 404 the img
   removes itself (onerror in HTML), revealing the initial below. */
.sidebar .user-avatar .user-avatar-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.sidebar .user-info {
  flex: 1;
  min-width: 0;
}
.sidebar .user-name {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sidebar .user-role {
  font-size: 0.72rem;
  color: var(--text-dim);
}
.sidebar .signout {
  background: transparent;
  border: 0;
  cursor: pointer;
  color: var(--text-dim);
  padding: 0.4rem;
  border-radius: var(--radius-xs);
  transition: color var(--t-fast), background var(--t-fast);
}
.sidebar .signout:hover {
  color: var(--text);
  background: var(--surface-3);
}

.content {
  /* Page gutter — generous on wide monitors, comfortable on narrow.
     Outer cap at 1800px keeps line lengths readable on ultrawides
     without leaving the page hugging the sidebar. The original tight
     2.5rem cap is the reason the page felt "stacked" on big screens —
     widening the gutter is more impactful than removing the max-width. */
  padding: 2rem clamp(1.5rem, 3vw, 3rem) 4rem;
  max-width: 1800px;
  margin: 0 auto;
  width: 100%;
  min-width: 0;
}

/* Long-form text blocks (settings descriptions, profile help text)
   stay capped so lines don't run 200ch wide on a 4K monitor. Apply
   this class to any paragraph that's "body copy", NOT to dashboards
   or tables. */
.prose {
  max-width: 70ch;
}

/* Mobile: collapse sidebar to a top bar */
@media (max-width: 880px) {
  .app { grid-template-columns: 1fr; }
  .sidebar {
    position: static;
    height: auto;
    flex-direction: row;
    align-items: center;
    padding: 0.6rem 1rem;
    gap: 0.5rem;
    border-right: 0;
    border-bottom: 1px solid var(--border);
  }
  /* Dissolve the desktop scroll wrapper so the two nav-sections
     flow directly in the horizontal top-bar exactly as before —
     the vertical-overflow fix only matters on the tall desktop
     sidebar. */
  .sidebar-nav {
    display: contents;
  }
  .sidebar .brand { padding: 0; }
  .sidebar .nav-section {
    flex-direction: row;
    padding: 0;
    overflow-x: auto;
  }
  .sidebar .nav-label { display: none; }
  .sidebar a.nav-link {
    padding: 0.4rem 0.7rem;
    font-size: 0.85rem;
  }
  .sidebar a.nav-link.active::before { display: none; }
  .sidebar a.nav-link .nav-icon { display: none; }
  .sidebar-footer {
    margin-top: 0;
    margin-left: auto;
    padding: 0;
    border-top: 0;
  }
  .sidebar .user-pill { padding: 0.3rem 0.5rem; }
  .sidebar .user-role { display: none; }
  .content { padding: 1.25rem 1rem 3rem; }
}

/* ============================================================
 *  Page header
 * ============================================================ */

.page-header {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: 1.5rem;
  flex-wrap: wrap;
}
.page-header h1 {
  font-size: 1.55rem;
  margin: 0;
  font-weight: 700;
  letter-spacing: -0.02em;
}
.page-header .subtitle {
  color: var(--text-dim);
  font-size: 0.95rem;
}
.page-header .grow { flex: 1; }
.page-header .actions { display: flex; gap: 0.55rem; flex-wrap: wrap; }

/* Sticky page-header — opt-in via `class="page-header sticky"`. The
   hosting detail page is tall (often 2000+px with all tabs rendered
   inline) so without sticking the action bar the operator has to
   scroll all the way back up to hit Suspend / Resume / Files. The
   background uses surface-0 + a soft blur so the table rows beneath
   stay legible as they scroll under it. */
.page-header.sticky {
  position: sticky;
  top: 0;
  z-index: 30;
  background: color-mix(in srgb, var(--surface-0) 92%, transparent);
  backdrop-filter: saturate(140%) blur(10px);
  -webkit-backdrop-filter: saturate(140%) blur(10px);
  padding: 0.85rem 0.25rem;
  margin: -0.85rem -0.25rem 1.5rem;
  border-bottom: 1px solid var(--border);
}

/* Universal `[hidden]` rescuer. The HTML5 `hidden` attribute is
   spec'd as `display: none`, but any explicit `display: flex/grid/
   block` rule on the same element overrides it (specificity wins
   over the UA stylesheet). Any popup / dropdown we toggle via the
   `hidden` attribute therefore stays VISIBLE despite the JS doing
   the right thing — the file-manager 3-dot menu was a notable
   victim (every row's menu showed at once). Add `!important` so
   author rules can't accidentally reintroduce the bug. */
[hidden] { display: none !important; }

.breadcrumbs {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  color: var(--text-dim);
  font-size: 0.85rem;
  margin-bottom: 0.4rem;
}
.breadcrumbs a { color: var(--text-dim); }
.breadcrumbs a:hover { color: var(--text); }
.breadcrumbs .sep { color: var(--text-soft); }

/* ============================================================
 *  Cards
 * ============================================================ */

/* Vercel cards: border-only, no shadow. The reading hierarchy comes
   from typography weight + the bg ↔ surface contrast, not from
   elevation tricks. */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.4rem 1.5rem;
  box-shadow: none;
  transition: border-color var(--t-fast), background var(--t-fast);
}
.card.hover:hover {
  border-color: var(--border-strong);
  background: var(--surface-2);
}
.card + .card { margin-top: 1rem; }
.card h2 {
  font-size: 1rem;
  margin: 0 0 1rem;
  font-weight: 600;
  letter-spacing: -0.005em;
  display: flex;
  align-items: center;
  gap: 0.55rem;
}
.card h2 .h-icon {
  width: 18px;
  height: 18px;
  color: var(--text-dim);
  stroke-width: 2;
}
.card .muted { color: var(--text-dim); }

.card.danger {
  border-color: color-mix(in oklab, var(--danger) 30%, var(--border));
}
.card.danger h2 { color: var(--danger); }

.card-section {
  border-top: 1px solid var(--border);
  padding-top: 1.25rem;
  margin-top: 1.25rem;
}

/* Grid of cards (e.g. dashboard stats) */
.grid {
  display: grid;
  gap: 1rem;
}
.grid.cols-2 { grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); }
.grid.cols-3 { grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
.grid.cols-4 { grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); }

/* ─────────────────────────────────────────────────────────────
   Stats page — node switcher, KPI strip, sparklines, kv-grid
   ───────────────────────────────────────────────────────────── */

/* Node switcher tabs at the top of /stats. Pill-style row of buttons. */
.node-switcher {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 1rem;
  padding: 0.35rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.node-tab {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.45rem 0.85rem;
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--text-dim);
  text-decoration: none;
  border-radius: var(--radius-xs);
  border: 1px solid transparent;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.node-tab:hover {
  background: var(--surface);
  color: var(--text);
}
.node-tab.active {
  background: var(--surface);
  color: var(--text);
  border-color: var(--border);
  font-weight: 600;
  box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
.node-tab-meta {
  font-size: 0.72rem;
  color: var(--text-soft);
  font-weight: 400;
  margin-left: 0.15rem;
}
.node-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-soft);
  box-shadow: 0 0 0 2px color-mix(in oklab, currentColor 12%, transparent);
}
.node-dot.on  { background: var(--success); }
.node-dot.off { background: var(--danger); }

/* KPI strip — full-width responsive grid of summary tiles. */
.kpi-grid {
  display: grid;
  /* Bumped from 11rem so tiles never look cramped on wide monitors
     and still wrap cleanly at laptop widths. */
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 0.85rem;
  margin-bottom: 1rem;
}
.kpi-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 1rem 1.15rem;
  position: relative;
  overflow: hidden;
}
.kpi-card::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: var(--text-soft);
}
.kpi-card.hostings::before { background: var(--primary-h, var(--primary)); }
.kpi-card.disk::before     { background: var(--info, var(--primary)); }
.kpi-card.bw::before       { background: var(--success); }
.kpi-card.reqs::before     { background: var(--warn); }
.kpi-card.load::before     { background: #a78bfa; }
.kpi-card.mem::before      { background: #fb7185; }
/* State-driven accent: green when healthy, orange when warned,
   red when critical. Used by widgets whose colour signals state
   (Services, Cert health, etc.) instead of a fixed metric. */
.kpi-card.kpi-ok::before   { background: var(--success); }
.kpi-card.kpi-warn::before { background: var(--warn); }
.kpi-card.kpi-bad::before  { background: var(--danger); }
.kpi-label {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-soft);
  font-weight: 600;
}
.kpi-value {
  font-size: 1.7rem;
  font-weight: 700;
  margin-top: 0.3rem;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}
.kpi-meta {
  font-size: 0.78rem;
  color: var(--text-dim);
  margin-top: 0.25rem;
}
.kpi-meta .dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  vertical-align: middle;
  margin-right: 0.15rem;
}
.kpi-meta .dot.ok   { background: var(--success); }
.kpi-meta .dot.warn { background: var(--warn); }
.kpi-meta .dot.err  { background: var(--danger); }

/* Sparkline mini-charts grid. */
.spark-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  gap: 0.85rem;
}
.spark-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 0.95rem 1.05rem;
}
.spark-load { color: #a78bfa; }
.spark-mem  { color: #fb7185; }
.spark-bw   { color: #34d399; }
.spark-reqs { color: #fbbf24; }
.spark-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 0.4rem;
}
.spark-title {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-soft);
  font-weight: 600;
}
.spark-now {
  font-size: 1.1rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.spark-svg {
  display: block;
  width: 100%;
  height: 60px;
}
.spark-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 60px;
  font-size: 0.78rem;
  color: var(--text-soft);
  font-style: italic;
  background: linear-gradient(90deg, transparent 0, transparent 8px, var(--surface-2) 8px, var(--surface-2) 16px) 0 0 / 16px 1px repeat-x;
  border-bottom: 1px dashed var(--border);
  margin-bottom: 0.4rem;
}
.spark-foot {
  display: flex;
  justify-content: space-between;
  font-size: 0.74rem;
  color: var(--text-dim);
  margin-top: 0.45rem;
  font-variant-numeric: tabular-nums;
}
.spark-foot-label {
  color: var(--text-soft);
  text-transform: uppercase;
  font-size: 0.66rem;
  letter-spacing: 0.04em;
  margin-right: 0.2rem;
}

/* Generic key-value grid used in node detail card. */
.kv-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0.75rem 1.5rem;
}
.kv-grid > div {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}
.kv-grid .text-soft {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.kv-grid strong {
  font-size: 0.95rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* ─────────────────────────────────────────────────────────────
   "Ready to copy" credentials panel after hosting creation
   ───────────────────────────────────────────────────────────── */

.creds-panel {
  background: linear-gradient(180deg,
    color-mix(in oklab, var(--success) 8%, var(--surface)) 0%,
    var(--surface) 35%);
  border: 1px solid color-mix(in oklab, var(--success) 45%, var(--border));
  border-radius: var(--radius-md);
  padding: 1.25rem 1.4rem 1rem;
  margin-bottom: 1.25rem;
  box-shadow: 0 1px 3px rgba(0,0,0,0.04), 0 0 0 4px color-mix(in oklab, var(--success) 8%, transparent);
}
.creds-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--success);
  font-weight: 700;
}
.creds-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  gap: 1rem 1.4rem;
  margin-top: 1rem;
}
.creds-section {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.creds-section-title {
  font-size: 0.74rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-soft);
  font-weight: 700;
  margin-bottom: 0.15rem;
}
.creds-row {
  display: grid;
  grid-template-columns: 7rem 1fr auto;
  gap: 0.5rem;
  align-items: center;
}
.creds-row-secret {
  background: color-mix(in oklab, var(--warn) 8%, transparent);
  border-radius: var(--radius-xs);
  padding: 0.25rem 0.4rem;
  margin-left: -0.4rem;
}
.creds-label {
  font-size: 0.78rem;
  color: var(--text-soft);
}
.creds-value {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.82rem;
  background: var(--surface-2);
  padding: 0.28rem 0.55rem;
  border-radius: var(--radius-xs);
  word-break: break-all;
  user-select: all;
  border: 1px solid var(--border);
}
.creds-value.secret {
  font-weight: 600;
  color: var(--warn);
  background: color-mix(in oklab, var(--warn) 12%, var(--surface));
  border-color: color-mix(in oklab, var(--warn) 30%, transparent);
}
.creds-copy {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  padding: 0.32rem 0.7rem;
  font-size: 0.74rem;
  font-weight: 600;
  color: var(--text-dim);
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.creds-copy:hover {
  background: var(--surface-2);
  color: var(--text);
  border-color: var(--border-strong, var(--border));
}
.creds-copy.ok {
  background: color-mix(in oklab, var(--success) 22%, var(--surface));
  color: var(--success);
  border-color: color-mix(in oklab, var(--success) 50%, transparent);
}
.creds-footnote {
  margin-top: 1rem;
  padding-top: 0.8rem;
  border-top: 1px dashed color-mix(in oklab, var(--success) 35%, transparent);
  font-size: 0.78rem;
  color: var(--text-dim);
}
.creds-footnote kbd {
  display: inline-block;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-bottom-width: 2px;
  border-radius: var(--radius-xs);
  padding: 0.05rem 0.4rem;
  font-size: 0.72rem;
  font-weight: 600;
  margin-right: 0.4rem;
}
.pill.small {
  padding: 0.1rem 0.45rem;
  font-size: 0.7rem;
}

/* Stat card (dashboard) */
.stat-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.25rem 1.4rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  position: relative;
  overflow: hidden;
  transition: border-color var(--t-med), transform var(--t-med);
}
.stat-card:hover { border-color: var(--border-strong); }
.stat-card .stat-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.stat-card .stat-label {
  color: var(--text-dim);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
}
.stat-card .stat-icon {
  width: 32px;
  height: 32px;
  border-radius: 8px;
  display: grid;
  place-items: center;
  background: var(--surface-2);
  color: var(--text-dim);
}
.stat-card .stat-icon.primary { background: var(--primary-soft); color: var(--primary-h); }
.stat-card .stat-icon.success { background: var(--success-soft); color: var(--success); }
.stat-card .stat-icon.warn    { background: var(--warn-soft);    color: var(--warn); }
.stat-card .stat-icon.info    { background: var(--info-soft);    color: var(--info); }
.stat-card .stat-icon svg { width: 18px; height: 18px; }
.stat-card .stat-value {
  font-size: 1.85rem;
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 1.1;
  margin-top: 0.4rem;
}
.stat-card .stat-meta {
  color: var(--text-dim);
  font-size: 0.82rem;
}

/* ============================================================
 *  Tables
 * ============================================================ */

table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}
.table-wrap {
  overflow-x: auto;
  margin: 0 -1.5rem -1.4rem;
  border-radius: 0 0 var(--radius) var(--radius);
}
.card.no-pad-table { padding-bottom: 0; }
.card.no-pad-table table { margin-top: 0.25rem; }
th, td {
  text-align: left;
  padding: 0.6rem 0.85rem;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
  line-height: 1.45;
}
th {
  padding-top: 0.7rem;
  padding-bottom: 0.7rem;
}
th {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  background: var(--surface-2);
  font-weight: 600;
}
th:first-child, td:first-child { padding-left: 1.5rem; }
th:last-child, td:last-child { padding-right: 1.5rem; }
tbody tr {
  transition: background var(--t-fast);
  background: var(--surface);  /* baseline so non-hover rows have a defined surface */
}
tbody tr:nth-child(even) {
  background: color-mix(in oklab, var(--surface-2) 40%, var(--surface));
}
tbody tr:hover {
  background: var(--surface-2);
}
tr:last-child td { border-bottom: none; }
td .row-actions {
  display: flex;
  gap: 0.35rem;
  justify-content: flex-end;
  align-items: center;
}
td a:not(.btn) {
  color: var(--text);
  font-weight: 500;
}
td a:not(.btn):hover { color: var(--primary-h); }
td.numeric { font-variant-numeric: tabular-nums; }
.table-empty {
  text-align: center;
  padding: 3rem 1rem;
  color: var(--text-dim);
}

/* ============================================================
 *  Buttons
 * ============================================================ */

.btn,
button.btn,
a.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  padding: 0.55rem 1rem;
  font: 500 0.88rem/1 var(--font-ui);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  text-decoration: none;
  transition: all var(--t-fast);
  position: relative;
  white-space: nowrap;
}
.btn:hover {
  background: var(--surface-2);
  border-color: var(--border-strong);
  text-decoration: none;
  color: var(--text);
}
.btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--primary) 35%, transparent);
}
.btn:active { transform: translateY(1px); }

.btn .btn-icon { width: 15px; height: 15px; stroke-width: 2; }

/* Vercel-style primary: solid inverse, no gradient, no shadow. The
   crisp white-on-black (or black-on-white in light mode) is the whole
   visual language — buttons are the brightest thing on the page. */
.btn.primary {
  background: var(--primary);
  color: var(--primary-fg);
  border-color: var(--primary);
  box-shadow: none;
}
.btn.primary:hover {
  background: var(--primary-h);
  color: var(--primary-fg);
  border-color: var(--primary-h);
}

.btn.danger {
  color: var(--danger);
  border-color: var(--border);
  background: var(--surface);
}
.btn.danger:hover {
  background: color-mix(in oklab, var(--danger) 12%, var(--surface));
  border-color: var(--danger);
  color: var(--danger);
}
.btn.danger.solid {
  background: var(--danger);
  color: white;
  border-color: var(--danger);
}
.btn.danger.solid:hover {
  background: color-mix(in oklab, var(--danger) 88%, black);
  color: white;
}

.btn.ghost {
  background: transparent;
  border-color: transparent;
  color: var(--text-dim);
}
.btn.ghost:hover {
  background: var(--surface-2);
  color: var(--text);
  border-color: transparent;
}

.btn.small {
  padding: 0.3rem 0.7rem;
  font-size: 0.78rem;
  line-height: 1.2;
  border-radius: var(--radius-xs);
}

/* Filter chips — clickable links that act like compact toggle buttons.
   Used in audit search (time-range row + quick-filter row). */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.22rem 0.65rem;
  font-size: 0.76rem;
  line-height: 1.2;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text-dim);
  text-decoration: none;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.chip:hover {
  background: var(--surface-3, var(--surface-2));
  color: var(--text);
  border-color: color-mix(in oklab, var(--text-dim) 30%, transparent);
}
.chip.chip-on,
a.chip.chip-on {
  background: color-mix(in oklab, var(--primary) 18%, var(--surface-2));
  color: var(--text);
  border-color: color-mix(in oklab, var(--primary) 55%, transparent);
  font-weight: 600;
}
.text-soft.small,
.small {
  font-size: 0.78rem;
}
.btn.small .btn-icon { width: 13px; height: 13px; }

/* When a small button sits inside a table row, the row should still
   measure to the height of plain text cells in the same row — tighten
   the vertical rhythm so the actions cell doesn't push every row tall. */
td .btn.small { padding: 0.25rem 0.6rem; }

.btn.lg {
  padding: 0.75rem 1.4rem;
  font-size: 0.95rem;
}

.btn[disabled],
.btn.loading {
  opacity: 0.65;
  cursor: not-allowed;
  pointer-events: none;
}
.btn.loading::after {
  content: '';
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-right-color: transparent;
  animation: spin 0.7s linear infinite;
  margin-left: 0.3rem;
}
@keyframes spin { to { transform: rotate(360deg); } }

.btn-group {
  display: inline-flex;
  gap: 0.4rem;
  flex-wrap: wrap;
}

/* ============================================================
 *  Forms
 * ============================================================ */

form { display: block; }
.field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin-bottom: 1rem;
}
.field > label {
  font-size: 0.82rem;
  color: var(--text);
  font-weight: 500;
}
.field > label .hint {
  color: var(--text-dim);
  font-weight: 400;
  margin-left: 0.4rem;
  font-size: 0.78rem;
}
.field .help {
  font-size: 0.78rem;
  color: var(--text-dim);
  margin-top: 0.15rem;
}

input[type="text"],
input[type="password"],
input[type="email"],
input[type="number"],
input[type="url"],
input[type="date"],
input[type="datetime-local"],
select,
textarea {
  width: 100%;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.8rem;
  color: var(--text);
  font: 14px/1.45 var(--font-ui);
  transition: border-color var(--t-fast), box-shadow var(--t-fast), background var(--t-fast);
}
input::placeholder, textarea::placeholder {
  color: var(--text-soft);
}
input:hover, select:hover, textarea:hover {
  border-color: var(--border-strong);
}
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--primary);
  background: var(--surface);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--primary) 25%, transparent);
}
/* Red border for invalid inputs — but ONLY after the user has
   interacted (typed something) AND moved focus away. The original
   rule was `:invalid:not(:placeholder-shown)` which fired on EMPTY
   required fields with no placeholder set (`:placeholder-shown` is
   false when there's no placeholder), making a fresh password
   field look like a validation error before the user even clicked
   into it. Now we also exempt `:focus` and `:not(.touched)` —
   styling only kicks in after blur on a field marked touched by
   the small shim below. */
input:invalid:not(:focus).touched {
  border-color: color-mix(in oklab, var(--danger) 60%, var(--border));
}

select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%239aa0ab' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.8rem center;
  padding-right: 2.2rem;
}

.field-row {
  display: grid;
  gap: 1rem;
  grid-template-columns: 1fr 1fr;
}
@media (max-width: 540px) {
  .field-row { grid-template-columns: 1fr; }
}
.field-row.three { grid-template-columns: 1fr 1fr 1fr; }
.field-checkbox {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin: 0.4rem 0;
  font-size: 0.9rem;
  color: var(--text-dim);
  cursor: pointer;
}
.field-checkbox input { width: auto; }

/* ============================================================
 *  Pills, badges, status
 * ============================================================ */

.pill {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.74rem;
  font-weight: 600;
  padding: 0.2rem 0.65rem;
  border-radius: var(--radius-pill);
  letter-spacing: 0.02em;
  background: var(--surface-2);
  color: var(--text-dim);
  border: 1px solid var(--border);
  white-space: nowrap;
}
.pill::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}
.pill.ok {
  background: var(--success-soft);
  color: var(--success);
  border-color: color-mix(in oklab, var(--success) 30%, transparent);
}
.pill.warn {
  background: var(--warn-soft);
  color: var(--warn);
  border-color: color-mix(in oklab, var(--warn) 30%, transparent);
}
.pill.err {
  background: var(--danger-soft);
  color: var(--danger);
  border-color: color-mix(in oklab, var(--danger) 30%, transparent);
}
.pill.info {
  background: var(--info-soft);
  color: var(--info);
  border-color: color-mix(in oklab, var(--info) 30%, transparent);
}

/* Status pill with optional pulse for "in progress" */
.pill.pulse::before {
  animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.5; transform: scale(1.5); }
}

.tag {
  display: inline-block;
  font-size: 0.72rem;
  font-weight: 500;
  padding: 0.15rem 0.45rem;
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  color: var(--text-dim);
  border: 1px solid var(--border);
}

/* ============================================================
 *  Flash / toast messages
 * ============================================================ */

.flash {
  padding: 0.9rem 1.1rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  margin-bottom: 1rem;
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  font-size: 0.92rem;
  background: var(--surface);
  animation: flashIn var(--t-med) both;
}
@keyframes flashIn {
  from { opacity: 0; transform: translateY(-6px); }
}
.flash .flash-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  margin-top: 1px;
}
.flash.success {
  background: color-mix(in oklab, var(--success) 8%, var(--surface));
  border-color: color-mix(in oklab, var(--success) 35%, var(--border));
  color: var(--success);
}
.flash.success .flash-icon { color: var(--success); }
.flash.error {
  background: color-mix(in oklab, var(--danger) 8%, var(--surface));
  border-color: color-mix(in oklab, var(--danger) 35%, var(--border));
  color: var(--danger);
}
.flash.error .flash-icon { color: var(--danger); }
.flash.warn {
  background: color-mix(in oklab, var(--warn) 8%, var(--surface));
  border-color: color-mix(in oklab, var(--warn) 35%, var(--border));
  color: var(--warn);
}
.flash.warn .flash-icon { color: var(--warn); }
.flash.info {
  background: color-mix(in oklab, var(--info) 8%, var(--surface));
  border-color: color-mix(in oklab, var(--info) 35%, var(--border));
  color: var(--info);
}
.flash.info .flash-icon { color: var(--info); }
.flash .flash-body { flex: 1; min-width: 0; }
.flash .flash-body strong { color: var(--text); }
.flash .secret {
  font-family: var(--font-mono);
  background: var(--surface);
  padding: 0.15rem 0.5rem;
  border-radius: var(--radius-xs);
  font-size: 0.85rem;
  border: 1px solid var(--border);
}

/* ============================================================
 *  Key-value list
 * ============================================================ */

.kv {
  display: grid;
  grid-template-columns: minmax(8rem, 14rem) 1fr;
  gap: 0.55rem 1.25rem;
  margin: 0;
}
.kv dt {
  color: var(--text-dim);
  font-size: 0.84rem;
  font-weight: 500;
}
.kv dd {
  margin: 0;
  font-size: 0.92rem;
  word-break: break-word;
  color: var(--text);
}
.kv dd code {
  font-size: 0.82rem;
}

/* ============================================================
 *  Empty states
 * ============================================================ */

.empty {
  padding: 2.5rem 1rem;
  text-align: center;
  color: var(--text-dim);
}
.empty .empty-illustration {
  width: 64px;
  height: 64px;
  border-radius: 20px;
  background: var(--surface-2);
  display: grid;
  place-items: center;
  margin: 0 auto 1rem;
  color: var(--text-soft);
}
.empty .empty-illustration svg { width: 32px; height: 32px; }
.empty h3 {
  margin: 0 0 0.4rem;
  color: var(--text);
  font-size: 1.05rem;
  font-weight: 600;
}
.empty p {
  margin: 0 0 1rem;
  font-size: 0.92rem;
}

/* ============================================================
 *  Login page
 * ============================================================ */

.login-shell {
  min-height: 100dvh;
  display: grid;
  /* Aside is roomy enough for the brand block + step trail, form
     column is sized for comfortable reading (28rem) plus padding
     for the icon puck. On ultrawides the form stays centered with
     a fixed cap instead of stretching wide. */
  grid-template-columns: minmax(0, 1.2fr) minmax(20rem, 32rem);
  background: var(--bg);
}
.login-aside {
  /* DARK on the left (where the headline + lede sit) → fades to a
     softer indigo toward the panel column. The previous gradient
     was 135deg with the light primary at TOP-LEFT — exactly where
     the white text sat — so the text washed out. Inverting the
     stops puts the darkest shade behind the copy and reserves the
     coloured part for the empty negative space. */
  background:
    radial-gradient(circle at 88% 88%, rgba(99, 102, 241, 0.45), transparent 55%),
    linear-gradient(135deg, #06070a 0%, #0f1019 32%, #1c1a3a 62%, #2a2660 100%);
  padding: 3rem;
  color: white;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  position: relative;
  overflow: hidden;
}
.login-aside::before {
  /* Subtle texture overlay — one soft glow far from the text +
     a very faint diagonal sheen. Previously two radial gradients
     bloomed near the headline (20% 30% and 80% 70%) and made the
     contrast worse; both removed. */
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle at 75% 25%, rgba(52, 211, 153, 0.07), transparent 40%),
    linear-gradient(160deg, transparent 0%, rgba(255, 255, 255, 0.03) 100%);
  pointer-events: none;
}
.login-aside .brand-block { position: relative; }
.login-aside .brand-block h1 {
  font-size: 2.5rem;
  margin: 0 0 0.9rem;
  letter-spacing: -0.03em;
  font-weight: 700;
  /* subtle shadow lifts the headline off the gradient so it
     stays readable across the brightest middle of the diagonal */
  text-shadow: 0 2px 14px rgba(0, 0, 0, 0.35);
}
/* "Lede" paragraph (formerly the washed-out subtitle). Was
   opacity:0.85 — barely visible against the gradient. Raise the
   solid weight + drop the opacity trick in favour of a colour
   that already has the soft-white feel without sacrificing
   contrast. */
.login-aside .brand-block .lede {
  font-size: 1.05rem;
  color: rgba(255, 255, 255, 0.95);
  font-weight: 400;
  max-width: 30rem;
  line-height: 1.55;
  margin: 0 0 1.4rem;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.25);
}
/* Crisp three-bullet feature list under the lede. Bullet glyph is
   a faux-checkmark coloured with the brand accent so the eye
   reads them as "what you get". */
.login-aside .brand-block .features {
  list-style: none;
  padding: 0;
  margin: 0 0 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  max-width: 30rem;
}
.login-aside .brand-block .features li {
  color: rgba(255, 255, 255, 0.92);
  font-size: 0.95rem;
  line-height: 1.4;
  padding-left: 1.6rem;
  position: relative;
}
.login-aside .brand-block .features li::before {
  content: '✓';
  position: absolute;
  left: 0;
  top: 0;
  color: #34d399;
  font-weight: 700;
  text-shadow: 0 0 12px rgba(52, 211, 153, 0.5);
}
.login-aside .footer {
  font-size: 0.85rem;
  color: rgba(255, 255, 255, 0.7);
  position: relative;
}
.login-form {
  display: grid;
  place-items: center;
  padding: 3rem 2rem;
}
.login-card {
  width: min(24rem, 100%);
}
.login-card .login-brand {
  display: none;
  align-items: center;
  gap: 0.7rem;
  margin-bottom: 2rem;
  font-weight: 700;
  font-size: 1.3rem;
}
.login-card h2 {
  font-size: 1.6rem;
  margin: 0 0 0.4rem;
  letter-spacing: -0.02em;
  font-weight: 700;
}
.login-card .subtitle {
  color: var(--text-dim);
  margin: 0 0 2rem;
  font-size: 0.95rem;
}
.login-card .btn.primary { width: 100%; padding: 0.7rem 1rem; }

/* ---- Step trail on the aside (2FA "step 2 of 2" indicator) ---- */
.login-step-trail {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin-bottom: 0.5rem;
  position: relative;
}
.login-step {
  display: grid;
  grid-template-columns: 28px 1fr;
  align-items: center;
  gap: 0.85rem;
  position: relative;
}
.login-step + .login-step::before {
  content: '';
  position: absolute;
  left: 13px;
  top: -0.85rem;
  width: 2px;
  height: 0.85rem;
  background: rgba(255, 255, 255, 0.18);
}
.login-step.done + .login-step::before {
  background: rgba(255, 255, 255, 0.55);
}
.login-step-dot {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 0.82rem;
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: white;
}
.login-step.done .login-step-dot {
  background: rgba(255, 255, 255, 0.95);
  color: var(--primary);
  border-color: rgba(255, 255, 255, 0.95);
}
.login-step.current .login-step-dot {
  background: rgba(255, 255, 255, 0.18);
  border: 1px solid rgba(255, 255, 255, 0.45);
  box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.08);
}
.login-step-label {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  opacity: 0.65;
  font-weight: 600;
}
.login-step-detail {
  font-size: 0.95rem;
  font-weight: 500;
  opacity: 0.95;
}

/* ---- 6-box OTP input ---- */
.otp-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr) auto repeat(3, 1fr);
  gap: 0.4rem;
  margin: 0.4rem 0 0.2rem;
}
.otp-box {
  height: 3.2rem;
  width: 100%;
  text-align: center;
  font: 600 1.55rem/1 var(--font-mono);
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  color: var(--text);
  caret-color: var(--primary);
  outline: none;
  padding: 0;
  transition: border-color var(--t-fast), background var(--t-fast),
    box-shadow var(--t-fast);
}
.otp-box:hover { border-color: var(--text-soft); }
.otp-box:focus {
  border-color: var(--primary);
  background: var(--surface);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--primary) 28%, transparent);
}
.otp-box.otp-error {
  border-color: var(--danger);
  animation: otpShake 0.45s ease-out;
}
.otp-sep {
  align-self: center;
  color: var(--text-soft);
  font-weight: 700;
  font-size: 1.4rem;
  user-select: none;
  text-align: center;
}
@keyframes otpShake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-4px); }
  40% { transform: translateX(4px); }
  60% { transform: translateX(-3px); }
  80% { transform: translateX(3px); }
}

/* ---- Icon puck above the page title (gives the card a focal point) ---- */
.login-icon-puck {
  width: 56px;
  height: 56px;
  border-radius: 14px;
  background: linear-gradient(135deg,
    color-mix(in oklab, var(--primary) 22%, transparent),
    color-mix(in oklab, var(--primary) 8%, transparent));
  border: 1px solid color-mix(in oklab, var(--primary) 30%, var(--border));
  color: var(--primary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 1rem;
}
.login-icon-puck svg { width: 28px; height: 28px; }

/* ---- Footer actions: backup-code toggle + "back to sign in" ---- */
.login-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  margin-top: 1.1rem;
  flex-wrap: wrap;
}
.login-link {
  background: transparent;
  border: 0;
  color: var(--primary);
  font-size: 0.85rem;
  font-weight: 500;
  cursor: pointer;
  padding: 0.3rem 0;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-family: inherit;
}
.login-link:hover { text-decoration: underline; }
.login-link-muted { color: var(--text-dim); }
.login-link-muted:hover { color: var(--text); }

.login-field-label {
  display: block;
  margin-bottom: 0.4rem;
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--text-dim);
}

.login-foot {
  margin-top: 1.6rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--border);
  color: var(--text-soft);
  font-size: 0.78rem;
  line-height: 1.55;
}
.login-foot code {
  font-family: var(--font-mono);
  background: var(--surface-2);
  border: 1px solid var(--border);
  padding: 0.05rem 0.35rem;
  border-radius: var(--radius-xs);
}

@media (max-width: 880px) {
  .login-shell { grid-template-columns: 1fr; }
  .login-aside { display: none; }
  .login-card .login-brand { display: flex; }
  .otp-box { height: 2.9rem; font-size: 1.35rem; }
}
@media (max-width: 420px) {
  .otp-row {
    grid-template-columns: repeat(6, 1fr);
    gap: 0.3rem;
  }
  .otp-sep { display: none; }
  .otp-box { height: 2.6rem; font-size: 1.2rem; }
}

/* ============================================================
 *  Misc
 * ============================================================ */

.row { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
.row.gap-md { gap: 1rem; }
.spread { display: flex; justify-content: space-between; align-items: center; gap: 1rem; }
.muted { color: var(--text-dim); }
.text-dim { color: var(--text-dim); }
.text-soft { color: var(--text-soft); }
.mono { font-family: var(--font-mono); font-size: 0.9em; }
.numeric { font-variant-numeric: tabular-nums; }
.center { text-align: center; }
.right { text-align: right; }
.nowrap { white-space: nowrap; }
.hidden { display: none !important; }
.grow { flex: 1; }
.section-title {
  font-size: 0.74rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-soft);
  margin: 1.5rem 0 0.6rem;
  font-weight: 600;
}

hr.divider {
  border: 0;
  border-top: 1px solid var(--border);
  margin: 1.5rem 0;
}

/* Copyable command block (install script one-liners) */
.code-block {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.85rem 1rem;
  overflow-x: auto;
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--text);
  line-height: 1.6;
}

/* Page enter animation */
.content > * {
  animation: pageIn 280ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes pageIn {
  from { opacity: 0; transform: translateY(4px); }
}

/* ============================================================
 *  Tabs (hosting detail and similar multi-section pages)
 * ============================================================ */

.tabs {
  display: flex;
  gap: 0.15rem;
  padding: 0.35rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow-x: auto;
  scrollbar-width: thin;
  margin-bottom: 1rem;
  /* Previously sticky — collided with sticky sidebar on tablet
     widths and the blur muddied underlying text. The page is short
     enough that scrolling back up to switch tabs is fine. */
}
.tab {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.55rem 0.85rem;
  font-size: 0.88rem;
  font-weight: 500;
  color: var(--text-dim);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  white-space: nowrap;
  transition: all var(--t-fast);
  text-decoration: none;
}
.tab:hover { color: var(--text); background: var(--surface-2); text-decoration: none; }
.tab.active {
  color: var(--text);
  background: var(--surface-2);
  border-color: var(--border);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
}
.tab .tab-icon { width: 15px; height: 15px; stroke-width: 2; flex-shrink: 0; }
.tab .tab-badge {
  background: var(--primary-soft);
  color: var(--primary-h);
  border-radius: var(--radius-pill);
  padding: 0.05rem 0.45rem;
  font-size: 0.7rem;
  font-weight: 600;
}
.tab.active .tab-badge { background: var(--primary); color: var(--primary-fg); }

.tab-panels > .tab-panel { display: none; }
.tab-panels > .tab-panel.active {
  display: block;
  animation: tabIn 200ms cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes tabIn {
  from { opacity: 0; transform: translateY(2px); }
}

/* ============================================================
 *  Dashboard layout
 * ============================================================ */

.dashboard-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(18rem, 1fr);
  gap: 1rem;
  margin-top: 1.5rem;
  align-items: start;
}
/* Stack only when the sidebar collapses too (was 1100px which broke
   1366×768 + 1440×900 laptops unnecessarily). */
@media (max-width: 880px) {
  .dashboard-grid { grid-template-columns: 1fr; }
}

/* ============================================================
 *  Activity feed (dashboard right column)
 * ============================================================ */

.activity-feed {
  list-style: none;
  padding: 0;
  margin: 0;
}

.activity-item {
  display: grid;
  grid-template-columns: 14px 1fr;
  column-gap: 0.6rem;
  align-items: start;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--border);
}
.activity-item:first-child { padding-top: 0.2rem; }
.activity-item:last-child  { border-bottom: 0; padding-bottom: 0.2rem; }

.activity-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  margin-top: 0.45rem;
  background: var(--success);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--success) 18%, transparent);
  flex-shrink: 0;
}
.activity-item.err .activity-dot {
  background: var(--danger);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 18%, transparent);
}

.activity-body {
  min-width: 0; /* allow inner ellipsis to work */
}

.activity-title {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  flex-wrap: wrap;
  font-size: 0.88rem;
  line-height: 1.4;
}
.activity-action {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  padding: 0.05rem 0.4rem;
  font-size: 0.76rem;
  white-space: nowrap;
  color: var(--text);
}
/* Humanized action label — no chip, just a clean weight-600 word.
   The chip-style .activity-action above was visually noisy when
   every row is a chip-id-meta sandwich. */
.activity-action-name {
  font-weight: 600;
  font-size: 0.92rem;
  color: var(--text);
}
.activity-target {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--text-dim);
  text-decoration: none;
  border-bottom: 1px dotted color-mix(in oklab, var(--text-dim) 50%, transparent);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 12rem;
}
.activity-target:hover {
  color: var(--text);
  border-bottom-color: var(--primary-h);
}

.activity-meta {
  color: var(--text-soft);
  font-size: 0.76rem;
  margin-top: 0.2rem;
  font-variant-numeric: tabular-nums;
}

/* On narrow viewports the dashboard stacks; let activity items breathe. */
@media (max-width: 980px) {
  .activity-target { max-width: 18rem; }
}

/* HTMX progressive indicator — fades in while a request is in flight */
.htmx-indicator {
  opacity: 0;
  transition: opacity var(--t-med);
}
.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator { opacity: 1; }

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

/* ============================================================
 *  Command palette (Cmd+K / Ctrl+K)
 *  Modal backdrop + centered card with live-filterable list.
 * ============================================================ */

.cmdk-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: rgba(8, 9, 12, 0.6);
  backdrop-filter: blur(6px);
  display: none;
  align-items: flex-start;
  justify-content: center;
  padding-top: 12vh;
  animation: cmdkFade var(--t-fast) ease-out;
}
.cmdk-backdrop.open { display: flex; }
@keyframes cmdkFade {
  from { opacity: 0; }
  to { opacity: 1; }
}
.cmdk-panel {
  width: min(640px, 92vw);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  animation: cmdkRise var(--t-med);
}
@keyframes cmdkRise {
  from { transform: translateY(-12px); opacity: 0; }
  to   { transform: translateY(0);     opacity: 1; }
}
.cmdk-input-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.85rem 1rem;
  border-bottom: 1px solid var(--border);
}
.cmdk-input-row svg { color: var(--text-dim); flex-shrink: 0; }
.cmdk-input {
  flex: 1;
  border: 0;
  background: transparent;
  color: var(--text);
  font-size: 1rem;
  outline: none;
  font-family: inherit;
}
.cmdk-input::placeholder { color: var(--text-soft); }
.cmdk-hint {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--text-soft);
  background: var(--surface-2);
  padding: 0.15rem 0.4rem;
  border-radius: var(--radius-xs);
  border: 1px solid var(--border);
}
.cmdk-list {
  list-style: none;
  margin: 0;
  padding: 0.4rem 0;
  max-height: 50vh;
  overflow-y: auto;
}
.cmdk-group {
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-soft);
  padding: 0.5rem 1rem 0.25rem;
  font-weight: 600;
}
.cmdk-item {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.6rem 1rem;
  cursor: pointer;
  font-size: 0.92rem;
  color: var(--text);
  text-decoration: none;
  border: 0;
  background: transparent;
  width: 100%;
  text-align: left;
  font-family: inherit;
}
.cmdk-item .cmdk-meta {
  color: var(--text-soft);
  font-size: 0.78rem;
  margin-left: auto;
  font-family: var(--font-mono);
}
.cmdk-item.active,
.cmdk-item:hover {
  background: var(--surface-2);
}
.cmdk-item.active::before {
  content: '';
  width: 3px;
  align-self: stretch;
  background: var(--primary);
  margin-right: -0.4rem;
  margin-left: -1rem;
}
.cmdk-empty {
  padding: 1.5rem 1rem;
  text-align: center;
  color: var(--text-soft);
  font-size: 0.88rem;
}

/* Hint widget in the page-header that says "press Cmd+K" */
.cmdk-trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.35rem 0.6rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text-dim);
  font-size: 0.82rem;
  cursor: pointer;
  transition: border-color var(--t-fast), color var(--t-fast);
  font-family: inherit;
}
.cmdk-trigger:hover {
  border-color: var(--border-strong);
  color: var(--text);
}
.cmdk-trigger kbd {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  background: var(--surface-3);
  padding: 0.1rem 0.35rem;
  border-radius: var(--radius-xs);
  border: 1px solid var(--border);
}

/* ============================================================
 *  Toast notifications (HTMX-aware)
 * ============================================================ */

/* Toast container — pinned to TOP-RIGHT so important messages
 * (config-save confirms, error banners, MTA test results) land
 * inside the operator's natural eye path. Previously bottom-
 * right which got hidden by short viewports / chat bubbles. */
.toast-container {
  position: fixed;
  top: 1rem;
  right: 1rem;
  z-index: 1100;          /* above the cmdk + bell dropdown */
  display: flex;
  flex-direction: column; /* newest on top */
  gap: 0.6rem;
  pointer-events: none;
  max-width: 26rem;
}
/* Phones — span the top edge instead of pinning right so a long
 * message doesn't get clipped to a narrow column. */
@media (max-width: 520px) {
  .toast-container {
    top: 0.5rem;
    left: 0.5rem;
    right: 0.5rem;
    max-width: none;
  }
}
.toast {
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-left: 3px solid var(--primary);
  border-radius: var(--radius-sm);
  padding: 0.7rem 0.9rem;
  box-shadow: var(--shadow);
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  font-size: 0.88rem;
  pointer-events: auto;
  animation: toastSlide 220ms cubic-bezier(0.16, 1, 0.3, 1);
}
.toast.success { border-left-color: var(--success); }
.toast.warn    { border-left-color: var(--warn); }
.toast.error   { border-left-color: var(--danger); }
.toast.info    { border-left-color: var(--info); }
.toast .toast-msg { flex: 1; color: var(--text); }
.toast button.toast-close {
  background: transparent;
  border: 0;
  color: var(--text-soft);
  cursor: pointer;
  font-size: 1.1rem;
  line-height: 1;
  padding: 0;
}
.toast button.toast-close:hover { color: var(--text); }
.toast.leaving { animation: toastExit 180ms ease-in forwards; }
@keyframes toastSlide {
  from { transform: translateX(110%); opacity: 0; }
  to   { transform: translateX(0);    opacity: 1; }
}
@keyframes toastExit {
  to { transform: translateX(110%); opacity: 0; }
}

/* ============================================================
 *  Theme toggle button in sidebar footer
 * ============================================================ */
.theme-toggle {
  background: transparent;
  border: 0;
  color: var(--text-dim);
  cursor: pointer;
  padding: 0.4rem;
  border-radius: var(--radius-xs);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--t-fast), color var(--t-fast);
}
.theme-toggle:hover {
  background: var(--surface-3);
  color: var(--text);
}
/* When user explicitly picks a theme, the chosen icon is highlighted */
:root[data-theme="dark"] .theme-toggle .theme-dark-icon,
:root[data-theme="light"] .theme-toggle .theme-light-icon {
  color: var(--primary-h);
}

/* Explicit theme override (set via theme-toggle button). Mirrors the
   :root + prefers-color-scheme blocks above so the operator-chosen
   palette wins regardless of the OS setting. */
:root[data-theme="light"] {
  --bg:           #ffffff;
  --bg-2:         #fafafa;
  --surface:      #ffffff;
  --surface-2:    #fafafa;
  --surface-3:    #f4f4f5;
  --border:       #eaeaea;
  --border-strong:#d4d4d8;
  --text:         #000000;
  --text-dim:     #52525b;
  --text-soft:    #71717a;
  --primary:      #000000;
  --primary-h:    #1f1f1f;
  --primary-soft: #f4f4f5;
  --primary-fg:   #ffffff;
  --brand-accent: #000000;
  --success:      #16a34a;
  --success-soft: #dcfce7;
  --warn:         #d97706;
  --warn-soft:    #fef3c7;
  --danger:       #dc2626;
  --danger-soft:  #fee2e2;
  --info:         #0284c7;
  --info-soft:    #e0f2fe;
}
:root[data-theme="dark"] {
  --bg:           #000000;
  --bg-2:         #0a0a0a;
  --surface:      #0a0a0a;
  --surface-2:    #111111;
  --surface-3:    #1a1a1a;
  --border:       #1f1f1f;
  --border-strong:#2e2e2e;
  --text:         #fafafa;
  --text-dim:     #a1a1aa;
  --text-soft:    #71717a;
  --primary:      #ffffff;
  --primary-h:    #ededed;
  --primary-soft: #1a1a1a;
  --primary-fg:   #000000;
  --brand-accent: #ffffff;
  --success:      #00d97e;
  --success-soft: #052e1a;
  --warn:         #f5a524;
  --warn-soft:    #2d1f08;
  --danger:       #ff4d4f;
  --danger-soft:  #2d0e10;
  --info:         #3b82f6;
  --info-soft:    #0f1a2e;
}

/* ============================================================
 *  Keyboard shortcuts overlay (press ?)
 * ============================================================ */
.shortcuts-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1001;
  background: rgba(8, 9, 12, 0.65);
  backdrop-filter: blur(6px);
  display: none;
  align-items: center;
  justify-content: center;
  animation: cmdkFade var(--t-fast);
}
.shortcuts-backdrop.open { display: flex; }
.shortcuts-panel {
  width: min(560px, 92vw);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 1.4rem 1.6rem;
  box-shadow: var(--shadow-lg);
  animation: cmdkRise var(--t-med);
}
.shortcuts-panel h2 {
  margin: 0 0 1rem;
  font-size: 1.1rem;
}
.shortcuts-grid {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.55rem 1rem;
  font-size: 0.9rem;
}
.shortcuts-grid kbd {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  background: var(--surface-2);
  padding: 0.18rem 0.45rem;
  border-radius: var(--radius-xs);
  border: 1px solid var(--border);
  color: var(--text);
  white-space: nowrap;
}
.shortcuts-grid .desc { color: var(--text-dim); align-self: center; }
.shortcuts-panel .close-hint {
  margin-top: 1.2rem;
  color: var(--text-soft);
  font-size: 0.78rem;
  text-align: center;
}

/* ============================================================
 *  Interactive sparkline tooltips + crosshair
 * ============================================================ */
.spark-card { position: relative; }
.spark-svg {
  cursor: crosshair;
  /* Ensure the SVG element itself captures mousemove events regardless
     of whether the cursor is over a painted path or in transparent
     space. Without this, the gradient-with-0%-opacity at the bottom
     of the area path swallows events in some browsers and the
     tooltip never fires. The inner paths get pointer-events: none
     below so the SVG root is the only hit target — simpler hit-test
     than per-element. */
  pointer-events: all;
}
.spark-svg .spark-area,
.spark-svg .spark-line {
  pointer-events: none;
}
.spark-svg .spark-hitbox {
  /* Transparent rect covering the full viewBox — a guaranteed
     opaque-to-the-event-loop hit target even if the area gradient
     is fully transparent at this point along the X axis. */
  fill: transparent;
  pointer-events: all;
}
.spark-tip {
  position: absolute;
  background: var(--surface-3);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-xs);
  padding: 0.32rem 0.55rem;
  font: 600 0.78rem var(--font-mono);
  white-space: nowrap;
  z-index: 50;
  pointer-events: none;
  box-shadow: var(--shadow);
  letter-spacing: 0.01em;
}
.spark-tip-t {
  color: var(--text-dim);
  font-weight: 400;
  margin-left: 0.5rem;
}
.spark-dot {
  fill: currentColor;
  stroke: var(--bg);
  stroke-width: 1.2;
  pointer-events: none;
}
.spark-vline {
  stroke: currentColor;
  stroke-opacity: 0.4;
  stroke-width: 0.8;
  stroke-dasharray: 2 2;
  pointer-events: none;
}

/* ============================================================
 *  Node chip — small badge on hosting list/detail showing which
 *  Hyperion node a site lives on. Mono font + subtle border so it
 *  reads as a stable identifier, not a state label.
 * ============================================================ */
.node-chip {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.01em;
  background: var(--surface-2);
  border-color: var(--border-strong);
  color: var(--text-dim);
  padding: 0.18rem 0.45rem;
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  max-width: 22rem;
  overflow: hidden;
  text-overflow: ellipsis;
}
.node-chip code {
  background: transparent;
  border: 0;
  padding: 0;
  color: inherit;
  font-family: inherit;
  font-size: inherit;
}
.node-chip svg { color: var(--primary); flex-shrink: 0; }

/* ============================================================
 *  Copy-row — readonly input + Copy button used on the migration
 *  export result page (and any other "operator pastes this on
 *  another machine" UI).
 * ============================================================ */
.copy-row + .copy-row { margin-top: 0.65rem; }
.copy-label {
  display: block;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-soft);
  margin-bottom: 0.3rem;
}
.copy-input {
  display: flex;
  align-items: stretch;
  gap: 0.4rem;
}
.copy-input input {
  flex: 1;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  padding: 0.5rem 0.7rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
}

/* ============================================================
 *  Search toolbar — modern integrated search input + segmented
 *  filter control (replaces the cramped row of small buttons that
 *  the hostings list used to ship). Reusable for any list page.
 * ============================================================ */
.search-toolbar {
  display: flex;
  align-items: stretch;
  gap: 0.75rem;
  margin-bottom: 1rem;
  flex-wrap: wrap;
}
.search-form {
  flex: 1 1 24rem;
  min-width: 18rem;
  margin: 0;
}
.search-input {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0 0.85rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  height: 40px;
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.search-input:focus-within {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--primary) 18%, transparent);
}
.search-input svg { color: var(--text-soft); flex-shrink: 0; }
.search-input input[type="search"] {
  flex: 1;
  border: 0;
  background: transparent;
  color: var(--text);
  font: 500 0.92rem var(--font-ui);
  padding: 0;
  outline: none;
  /* Strip the native search-input UA appearance so the field aligns
     with our other inputs. */
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  min-width: 0;
}
.search-input input[type="search"]::placeholder { color: var(--text-soft); }
.search-input input[type="search"]::-webkit-search-cancel-button { display: none; }
.search-clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--text-soft);
  text-decoration: none;
  transition: background var(--t-fast), color var(--t-fast);
  flex-shrink: 0;
}
.search-clear:hover { background: var(--surface-3); color: var(--text); }
.search-hint {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  padding: 0.05rem 0.4rem;
  color: var(--text-soft);
  flex-shrink: 0;
}

.search-segments {
  display: inline-flex;
  align-items: stretch;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 4px;
  gap: 2px;
  flex-shrink: 0;
}
.search-segments .seg {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0 0.85rem;
  font: 500 0.85rem var(--font-ui);
  color: var(--text-dim);
  text-decoration: none;
  border-radius: calc(var(--radius) - 4px);
  transition: background var(--t-fast), color var(--t-fast);
  white-space: nowrap;
}
.search-segments .seg:hover {
  color: var(--text);
  background: var(--surface-2);
}
.search-segments .seg.active {
  background: var(--primary);
  color: var(--primary-fg);
  font-weight: 600;
}
.search-segments .seg.active .seg-dot { box-shadow: 0 0 0 1px var(--primary-fg); }
.search-segments .seg-count {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  padding: 0.05rem 0.4rem;
  background: var(--surface-2);
  border-radius: var(--radius-xs);
  color: inherit;
  opacity: 0.85;
}
.search-segments .seg.active .seg-count {
  background: color-mix(in oklab, var(--primary-fg) 18%, transparent);
}
.seg-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  display: inline-block;
  flex-shrink: 0;
}
.seg-dot.ok   { background: var(--success); }
.seg-dot.warn { background: var(--warn); }
.seg-dot.err  { background: var(--danger); }

@media (max-width: 720px) {
  .search-segments {
    width: 100%;
    overflow-x: auto;
  }
  .search-segments .seg { padding: 0 0.65rem; }
  .search-hint { display: none; }
}

/* ============================================================
 *  Sidebar live status pulse dot
 * ============================================================ */
.live-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--success);
  display: inline-block;
  flex-shrink: 0;
  box-shadow: 0 0 0 0 color-mix(in oklab, var(--success) 80%, transparent);
  animation: livePulse 1.8s ease-out infinite;
  margin-left: auto;
}
.live-dot.warn  { background: var(--warn); }
.live-dot.error { background: var(--danger); animation-duration: 0.9s; }
@keyframes livePulse {
  0%   { box-shadow: 0 0 0 0   color-mix(in oklab, currentColor 0%, transparent); }
  60%  { box-shadow: 0 0 0 8px color-mix(in oklab, currentColor 0%, transparent); }
  100% { box-shadow: 0 0 0 0   color-mix(in oklab, currentColor 0%, transparent); }
}

/* ============================================================
 *  MOBILE / TABLET BREAKPOINTS
 *  ----------------------------------------------------------
 *  The desktop layout was built around 1280–1800px monitors. Below
 *  ~880px the sidebar already collapses to a top bar (see line ~342)
 *  but a lot of internals (tables, cards, page-headers, tabs, forms)
 *  needed dedicated mobile rules. This block adds them — phone-first
 *  at 480px, then a tablet/landscape-phone tier at 760px.
 *
 *  Anything above 880px keeps the existing desktop layout untouched.
 *
 *  Test on:
 *    - iPhone 13  (390 × 844)
 *    - iPhone SE  (375 × 667)
 *    - Pixel 7    (412 × 915)
 *    - iPad mini  (768 × 1024 portrait)
 *    - Landscape phone (~896 × 414)
 * ============================================================ */

/* ── 760px: tablet / landscape phone ──────────────────────────── */
@media (max-width: 760px) {
  .content { padding: 1rem 0.85rem 3rem; }

  /* Smaller heading; subtitle drops to a new line. */
  .page-header h1 { font-size: 1.3rem; }
  .page-header .subtitle { font-size: 0.85rem; flex-basis: 100%; }
  .page-header .actions {
    /* Full-width action row — buttons fill the line instead of
       hugging the right edge where the user can't reach them. */
    flex-basis: 100%;
    margin-top: 0.3rem;
  }
  .page-header .actions .btn { flex: 1 0 auto; }

  /* Cards: trim padding + margin so they don't eat half the screen. */
  .card { padding: 1rem 1rem; border-radius: var(--radius-sm); }
  .card h2 { font-size: 1rem; }
  .card + .card { margin-top: 0.75rem; }

  /* Tables: card padding compensation needs to match the card's
     reduced 1rem horizontal pad — otherwise table-wrap's negative
     margin overshoots and the scroll-edge clips behind the border. */
  .table-wrap { margin: 0 -1rem -1rem; }
  th:first-child, td:first-child { padding-left: 1rem; }
  th:last-child,  td:last-child  { padding-right: 1rem; }
  th, td {
    padding: 0.5rem 0.6rem;
    font-size: 0.85rem;
  }

  /* Tabs: stay scrollable but tighten so more fit in one swipe. */
  .tab {
    padding: 0.45rem 0.65rem;
    font-size: 0.82rem;
    gap: 0.35rem;
  }
  .tabs { padding: 0.25rem; gap: 0.1rem; }

  /* Field rows that were two-column on desktop become single-column
     here too (was already 540px-only — tablet portrait benefits). */
  .field-row { grid-template-columns: 1fr; }
  .field-row.three { grid-template-columns: 1fr; }

  /* Buttons: bigger touch target. iOS Human Interface Guidelines
     recommend 44px minimum; we go 40px to keep visual density. */
  .btn { min-height: 40px; padding-inline: 0.85rem; }

  /* Modals / dialogs: full-bleed on phones. */
  .modal { width: calc(100vw - 1.5rem); max-width: none; }

  /* Stats cards on dashboard: 2-up instead of 4-up. */
  .stat-grid { grid-template-columns: 1fr 1fr; }
}

/* ── 480px: phone portrait ─────────────────────────────────────── */
@media (max-width: 480px) {
  /* Sidebar already collapsed; drop more aggressively so the nav
     row doesn't eat 3 lines on a small screen. */
  .sidebar { padding: 0.5rem 0.75rem; }
  .sidebar .brand { font-size: 0.95rem; }
  .sidebar a.nav-link {
    padding: 0.35rem 0.55rem;
    font-size: 0.8rem;
  }

  /* Stats cards: single column — 2-up at 360px feels cramped. */
  .stat-grid { grid-template-columns: 1fr; }

  /* Page header: action buttons stack. */
  .page-header { gap: 0.5rem; }
  .page-header .actions { flex-direction: column; }
  .page-header .actions .btn { width: 100%; }

  /* Cards: tighter still. */
  .card { padding: 0.85rem 0.85rem; }

  /* Forms inside cards: pull-in the per-field padding. */
  .field input,
  .field select,
  .field textarea {
    font-size: 16px; /* prevents iOS Safari auto-zoom on focus */
  }

  /* Tables on very small screens: hide secondary columns. We use
     a `.hide-on-phone` class that templates can opt into for the
     less-essential columns (created_at, node, etc.). The first
     and last columns (domain + actions) always stay visible. */
  .hide-on-phone { display: none !important; }

  /* Activity feed: simpler layout on phones. */
  .activity-target { max-width: 100%; white-space: normal; }
  .activity-meta { font-size: 0.72rem; }

  /* Tabs: even tighter, hide icons (titles are clear enough). */
  .tab .tab-icon { display: none; }
  .tab { padding: 0.4rem 0.55rem; font-size: 0.78rem; }

  /* Modal padding shrinks too. */
  .modal .modal-body { padding: 0.85rem; }
  .modal .modal-actions { flex-direction: column-reverse; gap: 0.4rem; }
  .modal .modal-actions .btn { width: 100%; }
}

/* ── Tap-target accessibility ──────────────────────────────────── */
@media (pointer: coarse) {
  /* Anyone on a touch device — including iPads in desktop view —
     gets larger hit areas on inline links inside tables. */
  td a, td button { padding: 0.25rem 0.4rem; margin: -0.25rem -0.4rem; }
}

/* ────────────────────────────────────────────────────────────────
 *  ADDITIONAL MOBILE POLISH (≤ 760 / ≤ 480)
 *  Targets specific pain points reported on phones:
 *  - KPI tiles wrapping awkwardly
 *  - cols-2 / cols-3 grids overflowing
 *  - Tabs bar squeezing icons + labels off-screen
 *  - Page-header actions running off the edge
 *  - "spread" rows wrapping but not stacking cleanly
 *  - Filter chips on list pages stretching off-screen
 * ──────────────────────────────────────────────────────────────── */
@media (max-width: 760px) {
  .grid.cols-2,
  .grid.cols-3 { grid-template-columns: 1fr; }

  /* KPI tiles: 2-up on tablets, 1-up on phones (handled below). */
  .kpi-grid { grid-template-columns: 1fr 1fr; gap: 0.6rem; }
  .kpi-card { padding: 0.7rem 0.85rem; }
  .kpi-value { font-size: 1.3rem !important; }
  .kpi-label { font-size: 0.72rem; }

  /* "spread" rows (title + secondary control) wrap; align both
     halves at start so the secondary doesn't jam against the title. */
  .spread { flex-wrap: wrap; gap: 0.4rem; align-items: flex-start; }

  /* Filter chips on /hostings list — let them wrap + shrink padding. */
  .filter-chips { flex-wrap: wrap; gap: 0.3rem; }
  .filter-chips .chip { font-size: 0.78rem; padding: 0.25rem 0.55rem; }

  /* dl.kv: stack key over value instead of side-by-side which
     starts wrapping into single chars at narrow widths. */
  dl.kv {
    grid-template-columns: 1fr;
    gap: 0.3rem 0;
  }
  dl.kv dt { padding-top: 0.3rem; opacity: 0.7; }
}

@media (max-width: 480px) {
  .kpi-grid { grid-template-columns: 1fr; gap: 0.5rem; }
  .kpi-card { padding: 0.6rem 0.75rem; }
  .kpi-value { font-size: 1.15rem !important; }

  /* Tables in "card" mode: opt-in via `.data-table--cards` on the
     table. Each row collapses into a stacked card with its first
     cell as title and the rest stacked underneath. Used on phones
     for /hostings, /trash, /jobs lists where the squished-table
     view loses too much info. Templates don't need any per-td
     changes — the CSS just unfolds the tr into a flex column. */
  .data-table--cards { display: block; }
  .data-table--cards thead { display: none; }
  .data-table--cards tbody { display: block; }
  .data-table--cards tr {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 0.6rem;
    padding: 0.75rem 0.85rem;
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    margin-bottom: 0.55rem;
  }
  .data-table--cards td {
    border: none;
    padding: 0;
    flex: 0 1 auto;
  }
  /* First TD (often a checkbox or icon) and second TD (the domain
     / name) anchor the card visually at top-left. */
  .data-table--cards td:first-child { order: -1; }
  /* Actions cell drops to the bottom and stretches full-width. */
  .data-table--cards td.right,
  .data-table--cards td:last-child {
    flex: 1 0 100%;
    text-align: left;
    display: flex;
    gap: 0.4rem;
    flex-wrap: wrap;
  }
  .data-table--cards td:last-child .btn { flex: 1 1 auto; min-width: 7rem; }

  /* Page header h1 + subtitle stack tighter so the actions row
     doesn't push them off-screen at 360 px. */
  .page-header h1 { font-size: 1.15rem; }
  .page-header .subtitle { font-size: 0.78rem; line-height: 1.35; }

  /* Tabs: keep them swipeable horizontally but tighter. */
  .tabs { padding: 0.2rem; gap: 0; margin-bottom: 0.6rem; }
  .tab { padding: 0.4rem 0.5rem; font-size: 0.78rem; gap: 0.25rem; }
  .tab .tab-badge { font-size: 0.62rem; padding: 0.05rem 0.35rem; }

  /* Activity feed: drop the dot column on the narrowest viewport. */
  .activity-item { grid-template-columns: 1fr; column-gap: 0; }
  .activity-item .activity-dot { display: none; }

  /* Modal: cap height + scroll body so it never blocks the buttons. */
  .modal, #app-modal { max-height: 90vh; overflow: hidden; }
  #app-modal-body { max-height: 50vh; overflow-y: auto; }

  /* Login card: dial back padding so it fits 360 × 640 without
     forcing a viewport scroll. */
  .login-card { padding: 1.2rem 1rem; }
  .login-card h1 { font-size: 1.2rem; }

  /* Forms inside cards: tighter row-gap. */
  .field + .field { margin-top: 0.6rem; }

  /* Stats card sparkline 4-up → 2-up on phones (saves vertical space). */
  .card [style*="grid-template-columns:repeat(auto-fit,minmax(180px,1fr))"] {
    grid-template-columns: 1fr 1fr !important;
  }
}

/* ============================================================
 *  CUSTOMER ROLE — slim tenant chrome
 *  Body gets `role-customer` class set by the nav-shim script
 *  when the session role is "customer". CSS hides everything
 *  marked .admin-only across the app — admins/operators still
 *  see those controls.
 * ============================================================ */
body.role-customer .admin-only { display: none !important; }
/* The /hostings/new "Add hosting" header button has class
   .header-action-new in the list template — customers can't
   create, so it disappears entirely. */
body.role-customer .header-action-new { display: none !important; }

/* ============================================================
 *  NOTIFICATION BELL (sidebar + dropdown portal)
 * ============================================================ */
.bell-wrap {
  position: relative;
  display: inline-flex;
  align-items: center;
}
.bell-btn {
  position: relative;
  background: transparent;
  border: 0;
  padding: 0.4rem 0.45rem;
  margin: 0 0.15rem;
  color: var(--text-dim);
  cursor: pointer;
  border-radius: var(--radius-sm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color var(--t-fast), background var(--t-fast);
}
.bell-btn:hover { color: var(--text); background: var(--surface-2); }
.bell-badge {
  position: absolute;
  top: 0.15rem;
  right: 0.15rem;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--danger);
  color: white;
  font-size: 0.65rem;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
  border: 2px solid var(--surface);
  box-sizing: content-box;
}

.bell-dropdown {
  position: fixed;
  width: 22rem;
  max-width: calc(100vw - 1rem);
  max-height: 70vh;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.35);
  z-index: 8500;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.bell-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.7rem 1rem;
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
}
.bell-mark-all {
  background: none;
  border: 0;
  color: var(--primary-h);
  font-size: 0.8rem;
  cursor: pointer;
  padding: 0.15rem 0.4rem;
  border-radius: var(--radius-xs);
}
.bell-mark-all:hover { background: var(--surface); }
.bell-list {
  list-style: none;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  flex: 1;
}
.bell-empty {
  padding: 1.4rem 1rem;
  color: var(--text-soft);
  text-align: center;
  font-size: 0.88rem;
}
.bell-item {
  border-bottom: 1px solid var(--border);
}
.bell-item:last-child { border-bottom: 0; }
.bell-link {
  display: grid;
  grid-template-columns: 12px 1fr;
  gap: 0.6rem;
  padding: 0.7rem 1rem;
  color: var(--text);
  text-decoration: none;
  align-items: start;
}
.bell-link:hover { background: var(--surface-2); }
.bell-item.unread .bell-link { background: color-mix(in oklab, var(--primary-soft) 35%, transparent); }
.bell-item.unread .bell-link:hover { background: color-mix(in oklab, var(--primary-soft) 55%, transparent); }
.bell-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-top: 0.4rem;
  background: var(--text-soft);
}
.bell-item.unread .bell-dot { background: var(--primary); }
.bell-item.sev-warn .bell-dot { background: var(--warn); }
.bell-item.sev-error .bell-dot { background: var(--danger); }
.bell-text { font-size: 0.86rem; line-height: 1.4; min-width: 0; }
.bell-text strong { font-weight: 600; }
.bell-body {
  display: block;
  color: var(--text-dim);
  font-size: 0.8rem;
  margin-top: 0.15rem;
}
.bell-meta {
  display: block;
  color: var(--text-soft);
  font-size: 0.72rem;
  margin-top: 0.2rem;
  font-variant-numeric: tabular-nums;
}
.bell-footer {
  padding: 0.6rem 1rem;
  border-top: 1px solid var(--border);
  background: var(--surface-2);
  text-align: center;
}
.bell-footer a { font-size: 0.83rem; color: var(--text-dim); }
.bell-footer a:hover { color: var(--text); }

@media (max-width: 480px) {
  .bell-dropdown {
    /* Full-width portal on phones — easier to read + tap. */
    right: 0.5rem !important;
    left: 0.5rem !important;
    width: auto;
    max-width: none;
  }
}

/* ============================================================
   Background jobs — progress bar + live-polling spinner.

   Used by /jobs (list), /jobs/<id> (detail) and any inline
   migration/install/backup/clone progress panel embedded in
   other pages. Stays consistent across all of them so an
   operator who learns the bar once recognises it everywhere.
   ============================================================ */

.progress-bar {
  position: relative;
  height: 8px;
  width: 100%;
  background: var(--surface-2, #1a1a1a);
  border-radius: 4px;
  overflow: hidden;
}

.progress-bar-fill {
  height: 100%;
  background: var(--accent, #4a8aff);
  transition: width 320ms ease-out;
  border-radius: 4px;
}

.progress-bar-fill.done {
  background: var(--success, #34c759);
}

.progress-bar-fill.failed {
  background: var(--danger, #ff453a);
}

/* Tiny animated dot used in the job-detail header next to
   the step label while a job is running. Pure CSS — no JS,
   no image, CSP-safe. */
.spinner-inline {
  display: inline-block;
  width: 0.85em;
  height: 0.85em;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: spin 0.85s linear infinite;
  opacity: 0.7;
  vertical-align: -0.1em;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

/* Monospaced log tail block — shows the bounded ~16 KiB
   from the agent without forcing the whole card to grow.
   Falls back to the system mono stack on every platform. */
.log-tail {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.78rem;
  line-height: 1.35;
  background: var(--surface-2, #111);
  color: var(--text, #ddd);
  padding: 0.7rem 0.85rem;
  border-radius: 6px;
  white-space: pre-wrap;
  word-break: break-word;
}

/* Inline error pill inside the progress card. Picks up the
   same red the rest of the panel uses for failure states. */
.job-error {
  background: rgba(255, 69, 58, 0.12);
  border: 1px solid rgba(255, 69, 58, 0.35);
  color: var(--danger, #ff453a);
  padding: 0.6rem 0.8rem;
  border-radius: 6px;
  font-size: 0.9rem;
}

/* Sortable table-header links — used on /hostings, future
   /notifications, etc. Inactive columns stay subtle; the active
   one carries a small chevron + brand-tinted weight so the
   operator can tell at a glance which axis the table is sorted on
   without re-reading the URL. */
.sort-link {
  color: inherit;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-weight: inherit;
}
.sort-link:hover { text-decoration: underline; }
.sort-link.active {
  color: var(--accent, #4a8aff);
  font-weight: 700;
}
.sort-arrow {
  font-size: 0.8em;
  opacity: 0.85;
}
