/* 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;

  /* Chart series — identity colour for /stats graphs. Deliberately skips
     the orange / yellow / red / green hues so a series never reads as a
     state. Validated as a set (dataviz validate_palette) per mode. */
  --viz-blue:     #3987e5;
  --viz-magenta:  #d55181;
  --viz-aqua:     #199e70;
  --viz-violet:   #9085e9;

  /* 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;

  /* Spacing scale — single source for gaps that were hand-set at three
     different values (1.25rem / 1rem / 0.9rem). Unitless rems, no color,
     so they need no light-mode override. */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --group-gap: var(--space-5);   /* the one vertical gap between groups */

  --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;

    --viz-blue:     #2a78d6;
    --viz-magenta:  #e87ba4;
    --viz-aqua:     #1baf7a;
    --viz-violet:   #4a3aa7;

    --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);
  /* Paths, JSON samples and hashes are one unbreakable "word"; on a
     phone they ran straight past the card edge. break-word, NOT
     anywhere: `anywhere` also shrinks the min-content width, so auto
     table layout squeezed code cells (/jobs kind + target) to one
     character per line on desktop. Code inside <pre> keeps the pre's
     own wrapping/scrolling. */
  overflow-wrap: break-word;
}
pre code { overflow-wrap: normal; }
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;
}

/* A bright highlighter: 88%-opaque primary (white in dark mode, black in
   light) with the inverse foreground. Derives from --primary/--primary-fg so
   it flips correctly across every theme scope (default :root, prefers-light,
   and the data-theme overrides). Replaces the old --primary-soft (#1a1a1a)
   on #000, which was all but invisible. */
::selection { background: color-mix(in srgb, var(--primary) 88%, transparent); color: var(--primary-fg); }

/* ============================================================
 *  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 count badges (Trash, Background jobs). The shared
   .tab-badge rules only match inside .tab (the detail-page tabs), so
   these had NO box styling — just an inline background colour — and
   rendered as a cramped orange sliver flush against the link edge.
   Give them a proper pill: right-aligned with breathing room, padded,
   centred, min-width for a single digit. Colour stays inline
   (warn for Trash, accent for Jobs). */
.sidebar .nav-link .tab-badge {
  margin-left: auto;
  flex-shrink: 0;
  min-width: 1.1rem;
  padding: 0.1rem 0.4rem;
  border-radius: var(--radius-pill);
  font-size: 0.68rem;
  font-weight: 700;
  line-height: 1;
  text-align: center;
  color: #fff;
}

.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;
}

/* Menu toggle — only exists for the mobile top bar. The checkbox is
   visually hidden but stays focusable, so Tab + Space opens the menu
   without any JS; the label is the visible hamburger. */
.nav-toggle-cb {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.nav-toggle { display: none; }

/* Mobile: collapse sidebar to a top bar with a drop-down menu.
   The old top bar scrolled sideways, which at 375px showed about two
   links with the account controls parked on top of the rest — most of
   the panel was reachable only by a horizontal swipe nobody discovers.
   The bar now holds brand + account + a menu button; the full grouped
   nav (labels and icons included) opens beneath it. */
@media (max-width: 880px) {
  /* minmax(0, …) lets the single column shrink below its content.
     `auto 1fr` rows: the grid has min-height:100dvh, and with implicit
     rows a short page split the spare height between the top bar and
     the content, leaving a tall empty band above the bar. */
  .app {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto 1fr;
  }
  .sidebar {
    position: static;
    height: auto;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    padding: 0.6rem 1rem;
    gap: 0.5rem;
    border-right: 0;
    border-bottom: 1px solid var(--border);
    overflow: visible;
    min-width: 0;
  }
  .sidebar .brand { padding: 0; }
  .sidebar-footer {
    order: 2;
    margin: 0 0 0 auto;
    padding: 0;
    border-top: 0;
    flex: 0 0 auto;
  }
  .sidebar .user-pill { padding: 0.3rem 0.5rem; }
  .sidebar .user-role { display: none; }
  .nav-toggle {
    order: 3;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: var(--radius-xs);
    color: var(--text-dim);
    cursor: pointer;
    flex: 0 0 auto;
  }
  .nav-toggle:hover,
  .nav-toggle-cb:checked + .nav-toggle {
    color: var(--text);
    background: var(--surface-3);
  }
  .nav-toggle-cb:focus-visible + .nav-toggle {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
  }
  .nav-toggle svg { width: 1.3rem; height: 1.3rem; }
  .nav-toggle .icon-close { display: none; }
  .nav-toggle-cb:checked + .nav-toggle .icon-open { display: none; }
  .nav-toggle-cb:checked + .nav-toggle .icon-close { display: block; }
  .sidebar-nav {
    order: 4;
    display: none;
    flex: 1 0 100%;
    max-height: none;
    overflow: visible;
    padding: 0.4rem 0 0.2rem;
  }
  .nav-toggle-cb:checked ~ .sidebar-nav {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
    gap: 0.25rem 0.75rem;
  }
  .sidebar .nav-section { padding: 0; }
  /* The desktop active bar hangs off the sidebar's left edge; in the
     drop-down it would sit on the screen edge. The filled pill is enough. */
  .sidebar a.nav-link.active::before { display: none; }
  .sidebar a.nav-link { padding: 0.55rem 0.7rem; }
  .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) 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;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.2rem 0.45rem;
  color: var(--text-dim);
  font-size: 0.85rem;
  margin-bottom: 0.4rem;
  /* Wrap whole crumbs; only a long domain may break inside itself. On a
     phone the flex items used to squeeze into narrow columns instead. */
  overflow-wrap: anywhere;
}
/* A sticky header pulls itself up by its own padding (-0.85rem) and has
   an opaque background, so it painted over the lower half of a crumb
   line — visible as soon as a crumb wrapped on a phone. */
.breadcrumbs:has(+ .page-header.sticky) { margin-bottom: 1.25rem; }
.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: 1.25rem; }
/* Vertical rhythm inside a hosting-detail tab. The `.card + .card` rule only
   sees two cards that are DIRECT siblings, but a tab mixes plain cards,
   collapsible `details.card`s, and lazy HTMX wrappers — a plain `<div hx-get>`
   with a card inside. Those wrappers are not `.card`, so a wrapper sitting
   between two cards swallowed the 1rem gap on BOTH sides (the card before it
   and the card after it touched their neighbours). Treat a card, an accordion
   or a lazy wrapper following any of the same as one 1rem step, so every
   top-level block in a tab is evenly spaced regardless of its kind. */
.tab-panel > :where(.card, [hx-get]) + :where(.card, [hx-get]) {
  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); }

/* Prose lists. Without this the browser's own 40px indent applies, which is
   nearly twice the gutter everything else in a card uses — one list sat that
   far in while its neighbours (which all carried an inline padding-left) did
   not. Zero-specificity `:where()`, so the list widgets that reset themselves
   by class (.activity-feed, .cmdk-list, .bell-list, .features) still win. */
:where(.card, .panel, .prose, form) ul,
:where(.card, .panel, .prose, form) ol {
  padding-left: 1.2rem;
  margin: 0.4rem 0;
}
:where(.card, .panel, .prose, form) li { margin: 0.15rem 0; }

.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;
}

/* Collapsible card. A `<details class="card">` whose `<summary>` IS the card
   header: the same icon + title as a plain card, an optional right-aligned
   status (`.acc-meta`, e.g. a state pill or "3 kept") and a chevron. It lets a
   set-and-forget or rarely-touched section fold away while its state stays
   readable at a glance — the collapsed summary still answers "is this on?".
   Reuses `.card`, so spacing, border and radius match its non-collapsible
   peers exactly; only the header becomes a disclosure control. */
details.card > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.55rem;
  /* Match `.card h2`, so a bare title in the summary reads as a card header
     whether or not it is wrapped in an inner <h2>. */
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: -0.005em;
}
details.card > summary::-webkit-details-marker { display: none; }
details.card > summary::marker { content: ""; }
details.card > summary > .h-icon {
  width: 18px;
  height: 18px;
  color: var(--text-dim);
  stroke-width: 2;
  flex: none;
}
details.card > summary > h2 {
  margin: 0;
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: -0.005em;
}
details.card > summary .acc-meta {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 400;
  font-size: 0.85rem;
  color: var(--text-dim);
}
details.card > summary .acc-chevron {
  width: 16px;
  height: 16px;
  color: var(--text-dim);
  transition: transform var(--t-fast);
  flex: none;
}
details.card[open] > summary { margin-bottom: 1rem; }
details.card[open] > summary .acc-chevron { transform: rotate(90deg); }

/* ============================================================
 *  Grouped-flat panels
 * ============================================================ */

/* Direction B: related settings share one bordered panel instead of each
   sitting in its own card. A `.group` is the panel, led by an uppercase
   `.ghead` wayfinding label; `.item`s inside are split by hairlines, not
   boxes. Coexists with `.card` above (standalone one-offs keep it) — same
   Vercel-monochrome chrome, just a tighter reading rhythm. */

/* Grouped-flat panel: one theme-section. */
.group {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.group + .group,
.tab-panel > .group + .group { margin-top: var(--group-gap); }
/* On the non-tab pages a group sits next to a standalone card (an exempt table
   or accent card). Keep the same vertical step between the two kinds. */
.card + .group,
.group + .card { margin-top: var(--group-gap); }

/* Section header — the new-user wayfinding label. */
.group > .ghead {
  padding: 0.8rem 1.25rem;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  font-size: 0.73rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--text-dim);
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* A single setting block inside a group. */
.group > .item { padding: 1.25rem 1.25rem; border-bottom: 1px solid var(--border); }
.group > .item:last-child { border-bottom: none; }
.group > .item.muted { background: var(--bg-2); }

/* Item header: same icon+title treatment the old .card h2 had, one notch
   smaller. Descendant (not child) selector on purpose — many titles sit inside
   a status-pill `.row`, so the <h3> is not a direct child of `.item`. */
.item h3 {
  font-size: 0.92rem;
  font-weight: 600;
  margin: 0 0 0.3rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  letter-spacing: -0.005em;
}
.item h3 .h-icon { width: 16px; height: 16px; color: var(--text-dim); stroke-width: 2; }
.item .help, .item .text-soft.small { color: var(--text-soft); line-height: 1.6; }

/* Right-aligned inline save, replaces the lone button at card bottom. */
.item .item-actions { display: flex; justify-content: flex-end; gap: 0.5rem; margin-top: 1rem; }

/* Collapsible item (maps the existing details.card). */
details.item > summary { list-style: none; cursor: pointer; }
details.item > summary::-webkit-details-marker { display: none; }

/* Danger item keeps the existing accent. */
.item.danger h3 { color: var(--danger); }

/* Two-column grids (e.g. the dashboard) lay groups side by side, so the bare
   `.group + .group` top-margin would shove the right column down out of line.
   Zero it when groups are direct grid children; the grid `gap` spaces them. */
.dashboard-grid > .group + .group { margin-top: 0; }

/* Services page: one row per systemd unit inside a .group — label, a single
   status pill, actions. Replaces the old seven-column table. */
.group > .svc-row {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.85rem 1.25rem;
  border-bottom: 1px solid var(--border);
}
.group > .svc-row:last-child { border-bottom: none; }
.svc-row .svc-main { flex: 1; min-width: 0; }
.svc-row .svc-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem; }
.svc-row .svc-title code { font-size: 0.78rem; }
.svc-row .svc-actions { display: flex; gap: 0.3rem; flex: none; min-width: 5.5rem; justify-content: flex-end; }
.svc-row .svc-actions form { display: inline; }
.pill.muted { background: var(--surface-2); color: var(--text-soft); }
.pill.muted::before { background: var(--text-soft); }
.svc-verdict {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: var(--group-gap);
  font-weight: 600;
}
.svc-verdict .dot { width: 9px; height: 9px; border-radius: 50%; flex: none; background: var(--text-soft); }
.svc-verdict.ok .dot   { background: var(--success); }
.svc-verdict.warn .dot { background: var(--warn); }
.svc-verdict.err .dot  { background: var(--danger); }
.svc-verdict.err  { color: var(--danger); }
details.svc-fold > summary { cursor: pointer; list-style: none; }
details.svc-fold > summary::-webkit-details-marker { display: none; }
details.svc-fold > summary::before { content: "▸"; margin-right: 0.2rem; }
details.svc-fold[open] > summary::before { content: "▾"; }
details.svc-fold:not([open]) > summary { border-bottom: none; }
details.svc-fold .svc-row .pill.muted { display: none; }
#svc-health-live > .group + .group { margin-top: var(--group-gap); }
#svc-health-live + .group, #svc-health-live + details.group { margin-top: var(--group-gap); }
@media (max-width: 640px) {
  .group > .svc-row { flex-wrap: wrap; }
  .svc-row .svc-main { flex-basis: 100%; }
}

/* ============================================================
 *  Settings — an overview plus one page per section, with a
 *  sub-navigation beside the content (it becomes a scrolling strip
 *  above the content on narrow screens). Same monochrome tokens as
 *  the main sidebar.
 * ============================================================ */
.settings-shell {
  display: grid;
  grid-template-columns: 12.5rem minmax(0, 1fr);
  gap: 2.25rem;
  align-items: start;
}
.settings-nav {
  position: sticky;
  top: 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 0.875rem;
}
.settings-nav a {
  display: block;
  padding: 0.42rem 0.7rem;
  border-radius: var(--radius-sm);
  color: var(--text-dim);
  text-decoration: none;
  line-height: 1.3;
}
.settings-nav a:hover { background: var(--surface-2); color: var(--text); }
.settings-nav a.active { background: var(--surface-2); color: var(--text); font-weight: 600; }
.settings-nav-label {
  margin: 1.1rem 0 0.3rem;
  padding: 0 0.7rem;
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--text-soft);
}
.settings-main { min-width: 0; max-width: 60rem; }
.settings-main > .flash { margin-bottom: 1rem; }
.item > .flash.in-card { margin: 0 0 1rem; }

/* A panel of owner-only forms is a <fieldset class="group">, so one
   `disabled` makes the whole panel read-only for everybody else. */
fieldset.group { margin-inline: 0; padding: 0; min-inline-size: 0; }

/* Who a setting applies to, on the panel's header. Only rendered when
   worker nodes exist — on one server every setting is "this server". */
.scope-tag {
  margin-left: auto;
  padding: 0.08rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-soft);
  background: var(--surface);
  white-space: nowrap;
}

/* "How it works" — the long explanation, folded under the controls. */
details.howto { margin-top: 0.9rem; font-size: 0.85rem; color: var(--text-soft); }
details.howto > summary { cursor: pointer; color: var(--text-dim); font-weight: 500; list-style: none; }
details.howto > summary::-webkit-details-marker { display: none; }
details.howto > summary::before { content: "▸"; margin-right: 0.35rem; }
details.howto[open] > summary::before { content: "▾"; }
details.howto > :not(summary) { margin: 0.5rem 0 0; line-height: 1.6; }
details.howto ul { padding-left: 1.2rem; }

/* Settings overview: one row per page, its state in a sentence. */
.group > a.set-row {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.85rem 1.25rem;
  border-bottom: 1px solid var(--border);
  color: inherit;
  text-decoration: none;
}
.group > a.set-row:last-child { border-bottom: none; }
.group > a.set-row:hover { background: var(--surface-2); }
.set-row .set-main { flex: 1; min-width: 0; }
.set-row .set-title { font-weight: 600; font-size: 0.92rem; }
.set-row .set-state { color: var(--text-dim); font-size: 0.85rem; margin-top: 0.1rem; overflow-wrap: anywhere; }
.set-row .set-chevron { width: 16px; height: 16px; color: var(--text-soft); flex: none; }

@media (max-width: 880px) {
  .settings-shell { grid-template-columns: minmax(0, 1fr); gap: 1rem; }
  .settings-nav {
    position: static;
    flex-direction: row;
    overflow-x: auto;
    gap: 0.25rem;
    padding-bottom: 0.5rem;
    border-bottom: 1px solid var(--border);
    scrollbar-width: thin;
  }
  .settings-nav-label { display: none; }
  .settings-nav a { white-space: nowrap; }
}

/* Hosting → WordPress tab: one row per check inside a .group — status dot,
   name, a one-line verdict, actions. Problems list underneath; what passes
   folds away. Every row is a lazy partial that re-renders itself by its id,
   so rows of very different length never leave a hole beside each other. */
.wp-checks { --chk-name-w: 11.5rem; --chk-gap: 0.75rem; }
.group > .chk-row { padding: 0.8rem 1.25rem; border-bottom: 1px solid var(--border); }
.group > .chk-row:last-child { border-bottom: none; }
.group > .chk-row.is-err { background: color-mix(in oklab, var(--danger) 5%, var(--surface)); }
.chk-head { display: flex; align-items: center; gap: var(--chk-gap, 0.75rem); min-height: 1.85rem; }
.chk-dot {
  width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: color-mix(in oklab, var(--text-soft) 55%, transparent);
}
.chk-dot.ok   { background: var(--success); }
.chk-dot.warn { background: var(--warn); }
.chk-dot.err  { background: var(--danger); }
.chk-dot.info { background: var(--info); }
.chk-head .chk-name { margin: 0; font-size: 0.9rem; font-weight: 600; flex: none; width: var(--chk-name-w, 11.5rem); }
.chk-sum { flex: 1; min-width: 0; font-size: 0.85rem; color: var(--text-dim); }
.chk-sum .spinner-inline { margin-right: 0.35rem; vertical-align: -2px; }
.chk-actions { display: flex; align-items: center; gap: 0.4rem; flex: none; margin-left: auto; }
.chk-actions form { margin: 0; }
.chk-body { margin: 0.4rem 0 0.15rem calc(8px + var(--chk-gap, 0.75rem)); }
.chk-body > * + * { margin-top: 0.6rem; }
.chk-list { list-style: none; margin: 0; padding: 0; }
.chk-list li {
  display: grid;
  grid-template-columns: 6px calc(var(--chk-name-w, 11.5rem) - 6px - var(--chk-gap, 0.75rem)) minmax(0, 1fr);
  column-gap: var(--chk-gap, 0.75rem);
  align-items: baseline;
  padding: 0.25rem 0;
  font-size: 0.83rem;
}
.chk-list li > .chk-dot { width: 6px; height: 6px; align-self: start; margin-top: 0.45em; }
.chk-list li > strong { font-weight: 500; }
.chk-list.chk-files li { grid-template-columns: 6px minmax(0, max-content) minmax(0, 1fr); }
.chk-list.chk-files code { word-break: break-all; }
.chk-detail { color: var(--text-soft); word-break: break-word; }
.chk-fixes { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
.chk-fixes form { margin: 0; }
.chk-fold > summary { display: inline-block; cursor: pointer; list-style: none; font-size: 0.8rem; color: var(--text-soft); }
.chk-fold > summary::-webkit-details-marker { display: none; }
.chk-fold > summary::before { content: "▸"; margin-right: 0.35rem; }
.chk-fold[open] > summary::before { content: "▾"; }
.chk-fold > summary:hover { color: var(--text-dim); }
.chk-fold[open] > summary { margin-bottom: 0.3rem; }
.chk-fold > .chk-note { margin-top: 0.5rem; }
/* WAF panel: newest refusals only; the rest unfold via the details below the table. */
#waf-activity tr.waf-extra { display: none; }
#waf-activity:has(.waf-more[open]) tr.waf-extra { display: table-row; }
.waf-more { margin-top: 0.5rem; }
.waf-more[open] > summary { margin-bottom: 0; }
.waf-more .waf-more-open, .waf-more[open] .waf-more-closed { display: none; }
.waf-more[open] .waf-more-open { display: inline; }
.chk-note { margin: 0; font-size: 0.8rem; color: var(--text-soft); line-height: 1.55; max-width: 82ch; }
.chk-note.tone-warn { color: var(--warn); }
.chk-note.tone-err  { color: var(--danger); }
.chk-note.tone-ok   { color: var(--success); }

/* Deploy from GitHub — the access picker reveals its own field from the
   checked radio, so the token box shows the moment "Access token" is picked. */
.gs-form .hint { font-size: 0.78rem; font-weight: 400; color: var(--text-dim); line-height: 1.5; }
.gs-auth { border: 0; padding: 0; margin: 0 0 0.8rem; min-width: 0; }
.gs-auth > legend { padding: 0; margin-bottom: 0.4rem; font-size: 0.8rem; font-weight: 500; color: var(--text-dim); }
.gs-auth-opts { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: 0.5rem; }
.gs-auth-opt { display: flex; gap: 0.55rem; align-items: flex-start; padding: 0.6rem 0.7rem; border: 1px solid var(--border); border-radius: var(--radius-sm); cursor: pointer; font-size: 0.85rem; }
.gs-auth-opt:hover { border-color: var(--border-strong); }
.gs-auth-opt:has(input:checked) { border-color: var(--text); background: var(--surface-2); }
.gs-auth-opt input { margin-top: 0.2rem; }
.gs-auth-opt .hint { display: block; margin-top: 0.15rem; }
.gs-auth .gs-when-deploykey, .gs-auth .gs-when-pat { display: none; }
.gs-auth:has(input[value="deploykey"]:checked) .gs-when-deploykey,
.gs-auth:has(input[value="pat"]:checked) .gs-when-pat { display: block; }
.gs-history li { grid-template-columns: 6px 5.5rem minmax(0, 1fr); }
.gs-history .gs-when { color: var(--text-soft); white-space: nowrap; }
.chk-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin: 0; font-size: 0.78rem; color: var(--text-soft); }
.chk-foot strong { color: var(--text-dim); font-weight: 600; }
.chk-link {
  background: none; border: 0; padding: 0; font: inherit; cursor: pointer;
  color: var(--text-dim); text-decoration: underline; text-underline-offset: 2px;
}
.chk-link:hover { color: var(--text); }
.chk-callout { padding: 0.7rem 0.85rem; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg-2); font-size: 0.83rem; }
.chk-callout.err  { border-color: color-mix(in oklab, var(--danger) 45%, var(--border)); }
.chk-callout.warn { border-color: color-mix(in oklab, var(--warn) 45%, var(--border)); }
.chk-callout p { margin: 0 0 0.6rem; line-height: 1.55; }
.chk-callout form { margin: 0; }
.chk-callout > :last-child { margin-bottom: 0; }
.chk-excerpt {
  margin: 0 0 0.6rem; padding: 0.5rem 0.65rem; max-height: 12rem; overflow: auto;
  font-size: 0.72rem; white-space: pre-wrap; word-break: break-all;
  background: var(--surface-2); border-radius: var(--radius-xs);
}
.chk-sub { margin: 0.9rem 0 0.2rem; font-size: 0.82rem; font-weight: 600; }
.chk-switch { display: inline-flex; margin: 0; cursor: pointer; }
@media (max-width: 640px) {
  .chk-head { flex-wrap: wrap; }
  .chk-head .chk-name { width: auto; }
  .chk-sum { order: 3; flex-basis: 100%; padding-left: calc(8px + var(--chk-gap, 0.75rem)); }
  .chk-body { margin-left: 0; }
  .chk-list li, .chk-list.chk-files li { grid-template-columns: 6px minmax(0, 1fr); }
  .chk-list li > :nth-child(3) { grid-column: 2; }
}

/* A group header that carries actions on the right (WordPress plugins and
   themes): the label stays a wayfinding label, the tools read as controls. */
.group > .ghead .ghead-tools {
  margin-left: auto; display: flex; align-items: center; gap: 0.4rem;
  text-transform: none; letter-spacing: 0; font-weight: 400;
}
.group > .ghead .ghead-tools form { margin: 0; }
.group > .ghead:has(.ghead-tools) { padding-top: 0.5rem; padding-bottom: 0.5rem; min-height: 2.9rem; }
/* Its install popovers drop below the header; a clipping group would cut
   them off over a short list. Round the edges instead of clipping. */
.group.has-popover { overflow: visible; }
.group.has-popover > .ghead { border-radius: var(--radius-md) var(--radius-md) 0 0; }
.group.has-popover > :last-child { border-radius: 0 0 var(--radius-md) var(--radius-md); }
.group .wp-table { margin: 0; }
.group .wp-table th:first-child, .group .wp-table td:first-child { padding-left: 1.25rem; }
.group .wp-table th:last-child,  .group .wp-table td:last-child  { padding-right: 1.25rem; }
.wp-table .wp-name { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.wp-table .wp-name .pill { font-size: 0.66rem; padding: 0.1rem 0.5rem; }
.wp-table tr.is-off .wp-name strong { color: var(--text-dim); font-weight: 500; }
.wp-table .row-actions form { display: inline; margin: 0; }
.wp-table .btn.ghost.danger { color: var(--danger); }

/* Tools: a disclosure per tool; the collapsed summary still says "is it on?". */
.group > details.wp-tool { padding: 0.85rem 1.25rem; }
details.wp-tool > summary { display: flex; align-items: center; gap: 0.6rem; min-height: 1.85rem; }
details.wp-tool > summary h3 { margin: 0; }
details.wp-tool > summary .acc-meta { margin-left: auto; display: flex; align-items: center; gap: 0.5rem; font-size: 0.82rem; color: var(--text-dim); }
details.wp-tool > summary .acc-chevron { width: 16px; height: 16px; color: var(--text-dim); transition: transform var(--t-fast); flex: none; }
details.wp-tool[open] > summary { margin-bottom: 0.9rem; }
details.wp-tool[open] > summary .acc-chevron { transform: rotate(90deg); }
details.wp-tool > summary:hover h3 { color: var(--text); }

/* /jobs — one row per job inside a .group: state, what + where, when. The
   whole row is the link to the job page. Running jobs carry a bar inline. */
.group > .job-row {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.75rem 1.25rem;
  border-bottom: 1px solid var(--border);
  color: inherit;
  text-decoration: none;
  transition: background var(--t-fast);
}
.group > .job-row:last-child { border-bottom: none; }
.group > .job-row:hover { background: var(--surface-2); }
.job-row .job-state { flex: none; width: 5.5rem; display: flex; }
.job-row .job-state .spinner-inline { margin-left: 0.35rem; }
.job-row .job-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.2rem; }
.job-row .job-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem; }
.job-row .job-target { font-family: var(--font-mono); font-size: 0.8rem; color: var(--text-dim); overflow-wrap: anywhere; }
.job-row .job-sub { font-size: 0.8rem; color: var(--text-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.job-row .job-sub.job-err { color: var(--danger); }
.job-row .job-progress { display: flex; align-items: center; gap: 0.5rem; max-width: 26rem; }
.job-row .job-progress .progress-bar { height: 6px; }
.job-row .job-pct { font-size: 0.78rem; font-variant-numeric: tabular-nums; color: var(--text-soft); min-width: 2.6rem; text-align: right; }
.job-row .job-meta {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.15rem;
  font-size: 0.78rem;
  color: var(--text-soft);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.job-row .job-meta > :first-child { color: var(--text-dim); }
#jobs-live > .group + .group { margin-top: var(--group-gap); }
/* /packages — one plan per .item: name + price, what it includes as a
   tick list, quiet facts, and the editor folded underneath. The editor is
   split into labelled sections so a long form reads as a few short ones. */
.pkg-head { display: flex; align-items: baseline; gap: 0.75rem; flex-wrap: wrap; }
.pkg-title { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; flex: 1; min-width: 0; }
.pkg-title h3 { margin: 0; font-size: 1rem; }
.pkg-price { font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
.pkg-desc { margin: 0.35rem 0 0; color: var(--text-dim); max-width: 72ch; line-height: 1.55; font-size: 0.88rem; }
.pkg-includes {
  list-style: none;
  margin: 0.85rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(17rem, 100%), 1fr));
  gap: 0.35rem 1.25rem;
  font-size: 0.86rem;
}
.pkg-includes li { display: flex; align-items: baseline; gap: 0.45rem; min-width: 0; }
.pkg-includes svg { width: 13px; height: 13px; flex: none; color: var(--success); transform: translateY(2px); }
.pkg-includes li.off { color: var(--text-soft); }
.pkg-includes li.off svg { color: var(--text-soft); }
.pkg-detail { color: var(--text-soft); }
.pkg-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1rem;
  margin-top: 0.85rem;
  font-size: 0.8rem;
  color: var(--text-soft);
}
.pkg-meta code { font-size: 0.74rem; }
.pkg-edit { margin-top: 0.85rem; }
.pkg-edit > summary { list-style: none; display: inline-flex; align-items: center; gap: 0.35rem; cursor: pointer; }
.pkg-edit > summary::-webkit-details-marker { display: none; }
.pkg-edit[open] > summary { margin-bottom: 0.25rem; }
.pkg-form { margin: 0; }
.pkg-fs { padding: 1rem 0; border-bottom: 1px solid var(--border); }
.pkg-fs:first-of-type { padding-top: 0.6rem; }
.pkg-fs-head {
  font-size: 0.73rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--text-dim);
  margin-bottom: 0.7rem;
}
.pkg-fs-note { text-transform: none; letter-spacing: 0; font-weight: 400; font-size: 0.78rem; color: var(--text-soft); margin-left: 0.35rem; }
.pkg-fs-help { margin: 0 0 0.7rem; font-size: 0.8rem; line-height: 1.55; color: var(--text-soft); max-width: 78ch; }
.pkg-fs .field:last-child, .pkg-fs .field-row:last-child { margin-bottom: 0; }
.pkg-sections { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(14rem, 100%), 1fr)); gap: 0.4rem 1.2rem; margin-top: 0.2rem; }
.pkg-check { display: flex; align-items: center; gap: 0.5rem; font-weight: 400; cursor: pointer; margin: 0; }
.pkg-check input { margin: 0; width: auto; }
.pkg-checks { font-family: var(--font-mono); font-size: 0.78rem; line-height: 1.6; }
.pkg-more > summary { cursor: pointer; font-size: 0.8rem; color: var(--text-dim); }
.pkg-more[open] > summary { margin-bottom: 0.4rem; }
.pkg-form-foot { display: flex; align-items: center; flex-wrap: wrap; gap: 0.6rem 1rem; padding-top: 1rem; }
.pkg-form-foot .pkg-check { max-width: 56ch; font-size: 0.86rem; }
.pkg-delete {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-top: 1.1rem;
  padding: 0.8rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  background: var(--bg-2);
}
.pkg-delete > div { flex: 1; min-width: 0; }
.pkg-delete strong { font-size: 0.86rem; }
.pkg-delete form { margin: 0; }
/* Fields that only apply to some answers: still posted (an edit must never
   silently clear them), just not shown. Custom period only for "custom";
   retention only while the package sets a cadence; report sections only
   while it sends a report. */
.pkg-backup:not(:has(option[value="custom"]:checked)) .pkg-when-custom { display: none; }
/* Hosting Backups card: same rule — the period only for "custom", the time
   only while something is scheduled. Hidden, still posted. */
.backup-sched:not(:has(option[value="custom"]:checked)) .backup-when-custom,
.backup-sched:has(select[name="cadence"] option[value="off"]:checked) .backup-when-on { display: none; }
.pkg-backup:has(select[name="feat_backup_cadence"] option[value="leave"]:checked) .pkg-when-backup,
.pkg-backup:has(select[name="feat_backup_cadence"] option[value="off"]:checked) .pkg-when-backup,
.pkg-report:has(select[name="feat_report_cadence"] option[value="leave"]:checked) .pkg-when-report,
.pkg-report:has(select[name="feat_report_cadence"] option[value="off"]:checked) .pkg-when-report { display: none; }
details.pkg-new > summary.ghead { cursor: pointer; list-style: none; border-bottom: none; }
details.pkg-new > summary.ghead::-webkit-details-marker { display: none; }
details.pkg-new[open] > summary.ghead { border-bottom: 1px solid var(--border); }
.pkg-new-icon { width: 14px; height: 14px; }
details.pkg-new .pkg-fs:first-of-type { padding-top: 0; }

/* Sites on care: one row per site, the whole row links to the site. */
.group > .care-row {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.75rem 1.25rem;
  border-bottom: 1px solid var(--border);
  color: inherit;
  text-decoration: none;
  transition: background var(--t-fast);
}
.group > .care-row:last-child { border-bottom: none; }
.group > .care-row:hover { background: var(--surface-2); }
.care-row .care-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.2rem; }
.care-row .care-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem; }
.care-row .care-title strong { overflow-wrap: anywhere; }
.care-row .care-sub { font-size: 0.8rem; color: var(--text-soft); }
.care-row .care-checks { flex: none; display: flex; gap: 0.3rem; }
.care-row .care-meta {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.15rem;
  min-width: 11rem;
  font-size: 0.78rem;
  color: var(--text-soft);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.care-row .care-meta > :first-child { color: var(--text); font-weight: 500; }
#care-sites > .flash { margin-top: var(--group-gap); }
#care-sites > .group, #care-sites + .group { margin-top: var(--group-gap); }
@media (max-width: 640px) {
  .group > .care-row { flex-wrap: wrap; gap: 0.4rem 0.75rem; padding: 0.75rem 1rem; }
  .care-row .care-main { flex-basis: 100%; }
  .care-row .care-meta { flex-direction: row; flex-wrap: wrap; gap: 0.15rem 0.5rem; flex-basis: 100%; min-width: 0; white-space: normal; }
  .pkg-delete { flex-wrap: wrap; }
}
.jobs-filter { display: flex; gap: 0.5rem; align-items: stretch; }
.jobs-filter .search-input { flex: 1; min-width: 0; }
.jobs-filter select { height: 40px; max-width: 16rem; }
@media (max-width: 640px) {
  .group > .job-row { flex-wrap: wrap; gap: 0.4rem 0.75rem; padding: 0.75rem 1rem; }
  .job-row .job-main { flex-basis: 100%; order: 2; }
  .job-row .job-meta { flex-direction: row; gap: 0.5rem; margin-left: auto; }
  .jobs-filter { flex-wrap: wrap; }
  .jobs-filter select { max-width: none; flex: 1 1 100%; }
}

/* /install (Nodes) — one details row per machine, this server first. The
   summary is the scan line (who answers, sites, pending updates); the body
   holds everything you can do to that node, in sections split by hairlines,
   with Remove folded away at the bottom. Colour means state only. */
.group > .node-row { border-bottom: 1px solid var(--border); }
.group > .node-row:last-child { border-bottom: none; }
.node-row > summary {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.85rem 1.25rem;
  cursor: pointer;
  list-style: none;
  transition: background var(--t-fast);
}
.node-row > summary::-webkit-details-marker { display: none; }
.node-row > summary:hover { background: var(--surface-2); }
.node-row[open] > summary { background: var(--surface-2); border-bottom: 1px solid var(--border); }
.node-row.tone-row-err > summary { box-shadow: inset 2px 0 0 var(--danger); }
.node-row.tone-row-warn > summary { box-shadow: inset 2px 0 0 var(--warn); }
.node-row .node-dot { flex: none; }
.node-row .node-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.2rem; }
.node-row .node-title { display: flex; flex-wrap: wrap; align-items: center; gap: 0.45rem; }
.node-row .node-sub,
.node-row .node-issues {
  display: flex;
  flex-wrap: wrap;
  gap: 0.2rem 0.9rem;
  font-size: 0.78rem;
  color: var(--text-soft);
  font-variant-numeric: tabular-nums;
}
.node-row .node-sub code { font-size: 0.78rem; color: var(--text-dim); }
.node-row .node-facts {
  flex: none;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: 0.35rem 0.8rem;
  max-width: 50%;
}
.node-fact { font-size: 0.8rem; color: var(--text-dim); font-variant-numeric: tabular-nums; white-space: nowrap; }
.node-chevron { width: 16px; height: 16px; flex: none; color: var(--text-soft); transition: transform var(--t-fast); }
.node-row[open] .node-chevron { transform: rotate(90deg); }

.node-body > .node-facts-strip { border-bottom: 1px solid var(--border); }
.node-sec { padding: 1rem 1.25rem; border-bottom: 1px solid var(--border); display: flex; flex-direction: column; gap: 0.6rem; }
.node-sec h4 {
  margin: 0;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.node-sec-row { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem 0.75rem; margin: 0; }
.node-sec-row > .text-soft { flex: 1 1 16rem; }
.node-trust { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.85rem; }
.node-note { margin: 0; }
.node-update-form { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 1.25rem; }
.node-update-form .field-checkbox { margin: 0; }
.node-upd-status:empty { display: none; }
.node-upd-status pre { max-height: 14rem; }
.node-sec-row input.node-rename { flex: 0 1 18rem; width: auto; min-width: 0; margin: 0; }
.node-danger { padding: 0.75rem 1.25rem; background: color-mix(in oklab, var(--danger) 4%, var(--surface)); }
.node-danger > summary { cursor: pointer; font-size: 0.85rem; font-weight: 600; color: var(--danger); }
.node-danger form { margin-top: 0.6rem; }
.node-danger p { margin: 0 0 0.6rem; line-height: 1.55; }
.node-danger .field-checkbox { margin: 0 0 0.7rem; }

/* Fresh invite: the one-time install command. */
.invite-cmd { margin: 0.7rem 0 0; }
.invite-cmd code { white-space: pre-wrap; overflow-wrap: anywhere; }
.invite-cmd-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin-top: 0.5rem; }
.invite-cmd-note { margin-top: 0.5rem; line-height: 1.6; }
.mono-input { font-family: var(--font-mono); }
.field-note { margin: 0.4rem 0 0; }
#add-node .item > h3 + .text-soft { margin: 0 0 0.9rem; }
#add-node details.item > summary h3 { margin: 0; }
#add-node details.item[open] > summary { margin-bottom: 0.8rem; }
#add-node .invites-table { margin-top: 0.5rem; }
.enroll-steps { margin: 0; padding-left: 1.25rem; font-size: 0.88rem; line-height: 1.7; }

@media (max-width: 720px) {
  /* Dot · name · chevron on the first line, the live facts under the name. */
  .node-row > summary {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "dot main chev" ". facts facts";
    gap: 0.4rem 0.75rem;
    padding: 0.8rem 1rem;
  }
  .node-row .node-dot { grid-area: dot; align-self: start; margin-top: 0.45rem; }
  .node-row .node-main { grid-area: main; }
  .node-row .node-chevron { grid-area: chev; align-self: start; margin-top: 0.2rem; }
  .node-row .node-facts { grid-area: facts; max-width: none; justify-content: flex-start; }
  .node-sec { padding: 0.9rem 1rem; }
  .node-danger { padding: 0.75rem 1rem; }
}

/* /audit — one expandable row per entry inside a day .group: what + where,
   a one-line summary of what was recorded, when + who. Opening a row shows
   the entry's facts beside everything recorded with it. */
.audit-day + .audit-day { margin-top: var(--group-gap); }
.group > details.audit-row { border-bottom: 1px solid var(--border); }
.group > details.audit-row:last-child { border-bottom: none; }
.audit-row > summary {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.7rem 1.25rem;
  cursor: pointer;
  list-style: none;
  transition: background var(--t-fast);
}
.audit-row > summary::-webkit-details-marker { display: none; }
.audit-row > summary:hover,
.audit-row[open] > summary { background: var(--surface-2); }
.audit-row[open] > summary { border-bottom: 1px solid var(--border); }
.audit-row .audit-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.2rem; }
.audit-row .audit-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem; }
.audit-row .audit-target { font-family: var(--font-mono); font-size: 0.8rem; color: var(--text-dim); overflow-wrap: anywhere; }
.audit-row .audit-sub { font-size: 0.8rem; color: var(--text-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.audit-row .audit-meta {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.15rem;
  font-size: 0.78rem;
  color: var(--text-soft);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.audit-row .audit-meta > :first-child { color: var(--text-dim); }
.audit-detail {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
}
.audit-detail > * { padding: 0.9rem 1.25rem; min-width: 0; }
.audit-detail > * + * { border-left: 1px solid var(--border); }
.audit-kv {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 0.35rem 1rem;
  margin: 0;
  font-size: 0.84rem;
}
.audit-kv dt { color: var(--text-soft); }
.audit-kv dt::first-letter { text-transform: uppercase; }
.audit-kv dd { margin: 0; overflow-wrap: anywhere; }
.audit-kv code { font-size: 0.78rem; }
.audit-raw {
  margin: 0;
  font-size: 0.78rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.audit-links { display: flex; flex-wrap: wrap; gap: 0.4rem 1rem; margin: 0.8rem 0 0; }
.audit-foot {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-top: 1rem;
}
.audit-foot .grow { flex: 1; }
.audit-verify-wait { display: none; margin-bottom: var(--group-gap); }
.audit-verify-wait.htmx-request { display: block; }
.audit-verify {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 0.8rem 1.25rem;
  margin-bottom: var(--group-gap);
}
.audit-verify.err { border-color: color-mix(in oklab, var(--danger) 55%, var(--border)); }
.audit-verify-head { font-weight: 600; margin-bottom: 0.45rem; }
.audit-verify.err .audit-verify-head { color: var(--danger); }
.audit-verify ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.3rem; font-size: 0.85rem; }
.audit-verify li { display: flex; align-items: center; gap: 0.55rem; flex-wrap: wrap; }
.audit-verify li > span:last-child { color: var(--text-soft); }
.audit-verify li.err > span:last-child { color: var(--danger); }
.audit-verify .dot { width: 8px; height: 8px; border-radius: 50%; flex: none; background: var(--success); }
.audit-verify li.err .dot { background: var(--danger); }
@media (max-width: 640px) {
  .audit-row > summary { flex-wrap: wrap; gap: 0.3rem 0.75rem; padding: 0.7rem 1rem; }
  .audit-row .audit-main { flex-basis: 100%; }
  .audit-row .audit-meta { flex-direction: row; gap: 0.5rem; }
  .audit-detail { grid-template-columns: 1fr; }
  .audit-detail > * { padding: 0.8rem 1rem; }
  .audit-detail > * + * { border-left: none; border-top: 1px solid var(--border); }
}

/* /emails — one folding row per email inside a day .group. The summary is
   the scan line (outcome, subject + type, recipient + site, first line of the
   error or body, when); the fold holds the full error, relay reply and body. */
.group > .mail-row { border-bottom: 1px solid var(--border); }
.group > .mail-row:last-child { border-bottom: none; }
.mail-row > summary {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.75rem 1.25rem;
  list-style: none;
  cursor: pointer;
  transition: background var(--t-fast);
}
.mail-row > summary::-webkit-details-marker { display: none; }
.mail-row > summary:hover,
.mail-row[open] > summary { background: var(--surface-2); }
.mail-row .mail-state { flex: none; width: 4.5rem; display: flex; }
.mail-row .mail-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.2rem; }
.mail-row .mail-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem; overflow-wrap: anywhere; }
.mail-row .mail-sub { display: flex; flex-wrap: wrap; gap: 0.35rem; font-size: 0.8rem; color: var(--text-dim); }
.mail-row .mail-to { font-family: var(--font-mono); font-size: 0.78rem; overflow-wrap: anywhere; }
.mail-row .mail-line { font-size: 0.8rem; color: var(--text-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mail-row .mail-err { color: var(--danger); }
.mail-row .mail-meta {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.15rem;
  font-size: 0.78rem;
  color: var(--text-soft);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.mail-row .mail-meta > :first-child { color: var(--text-dim); }
.mail-detail {
  display: grid;
  grid-template-columns: 7rem 1fr;
  gap: 0.45rem 1rem;
  margin: 0;
  padding: 0.9rem 1.25rem 1rem calc(1.25rem + 4.5rem + 0.9rem);
  font-size: 0.85rem;
  border-top: 1px solid var(--border);
}
.mail-detail dt { color: var(--text-soft); }
.mail-detail dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
.mail-detail code { font-size: 0.8rem; }
.mail-pre {
  margin: 0;
  font: 0.8rem/1.5 var(--font-mono);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.mail-pre.mail-err { color: var(--danger); }
.mail-days > .group + .group { margin-top: var(--group-gap); }

/* /vulns — one folding row per WordPress site inside a bucket .group. The
   summary is the scan line (what it needs, site + node, the component that
   matters, when checked + auto-update); the fold lists every component and
   what happens to it. Same rhythm as .mail-row. */
.group > .upd-row { border-bottom: 1px solid var(--border); }
.group > .upd-row:last-child { border-bottom: none; }
.upd-row > summary {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.75rem 1.25rem;
  list-style: none;
  cursor: pointer;
  transition: background var(--t-fast);
}
.upd-row > summary::-webkit-details-marker { display: none; }
.upd-row > summary:hover,
.upd-row[open] > summary { background: var(--surface-2); }
.upd-row .upd-state { flex: none; width: 8rem; display: flex; }
.upd-row .upd-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.2rem; }
.upd-row .upd-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem; overflow-wrap: anywhere; }
.upd-row .upd-line { font-size: 0.8rem; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.upd-row .upd-meta {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.15rem;
  font-size: 0.78rem;
  color: var(--text-soft);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.upd-row .upd-meta > :first-child { color: var(--text-dim); }
.upd-detail {
  padding: 0.75rem 1.25rem 0.9rem calc(1.25rem + 8rem + 0.9rem);
  font-size: 0.85rem;
  border-top: 1px solid var(--border);
}
.upd-problem { margin: 0 0 0.6rem; color: var(--warn); overflow-wrap: anywhere; }
.upd-none { margin: 0 0 0.6rem; }
.upd-comps { list-style: none; margin: 0 0 0.6rem; padding: 0; display: flex; flex-direction: column; gap: 0.45rem; }
.upd-comps li {
  display: grid;
  grid-template-columns: 8px minmax(10rem, 1.2fr) minmax(9rem, 1fr) 2fr;
  align-items: baseline;
  gap: 0.75rem;
}
.upd-comps .chk-dot { align-self: start; margin-top: 0.4em; }
.upd-comp-name, .upd-comp-ver, .upd-comp-what { min-width: 0; overflow-wrap: anywhere; }
.upd-comp-ver code { font-size: 0.78rem; }
.upd-comp-what { color: var(--text-dim); }
.upd-comp-err { display: block; margin-top: 0.15rem; font: 0.76rem/1.4 var(--font-mono); color: var(--danger); }
.upd-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 1.1rem;
  font-size: 0.8rem;
  color: var(--text-soft);
}
.upd-foot .upd-open { margin-left: auto; }
.upd-groups > .group + .group { margin-top: var(--group-gap); }
@media (max-width: 640px) {
  .upd-row > summary { flex-wrap: wrap; gap: 0.4rem 0.75rem; padding: 0.75rem 1rem; }
  .upd-row .upd-state { width: auto; }
  .upd-row .upd-main { flex-basis: 100%; order: 2; }
  .upd-row .upd-meta { flex-direction: row; gap: 0.5rem; margin-left: auto; }
  .upd-row .upd-line { white-space: normal; }
  .upd-detail { padding: 0.75rem 1rem; }
  .upd-comps li { grid-template-columns: 8px 1fr; gap: 0.1rem 0.6rem; }
  .upd-comps li > :nth-child(n+3) { grid-column: 2; }
  .upd-foot .upd-open { margin-left: 0; }
}

/* /notifications — one row per notification inside a day .group: severity,
   title + topic (+ node), message, when + mark read. Unread rows carry a
   primary edge so they read as new without colouring the whole row. */
.group > .notif-row { border-bottom: 1px solid var(--border); }
.group > .notif-row:last-child { border-bottom: none; }
.notif-row {
  display: flex;
  align-items: flex-start;
  gap: 0.9rem;
  padding: 0.75rem 1.25rem;
}
.notif-row.unread { box-shadow: inset 3px 0 0 var(--primary); background: color-mix(in oklab, var(--primary-soft) 25%, transparent); }
.notif-row .notif-sev {
  flex: none;
  width: 5.25rem;
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.8rem;
  color: var(--text-soft);
  padding-top: 0.15rem;
}
.notif-row .notif-sev.err { color: var(--danger); }
.notif-row .notif-sev.warn { color: var(--warn); }
.seg-dot.info { background: var(--text-soft); }
.notif-row .notif-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.2rem; }
.notif-row .notif-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem; overflow-wrap: anywhere; }
.notif-row .notif-title a { color: inherit; }
.notif-row .notif-title a:hover { color: var(--primary-h); }
.notif-row:not(.unread) .notif-title strong { font-weight: 500; }
.notif-row .notif-body { font-size: 0.85rem; color: var(--text-dim); overflow-wrap: anywhere; }
.notif-row .notif-meta {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.2rem;
  font-size: 0.78rem;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.notif-row .notif-meta form { margin: 0; }
.linklike {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--primary-h);
  cursor: pointer;
}
.linklike:hover { text-decoration: underline; }
.notif-pager { display: flex; justify-content: space-between; gap: 1rem; margin-top: var(--group-gap); font-size: 0.9rem; }
.notif-pager .notif-older { margin-left: auto; }
@media (max-width: 640px) {
  .notif-row { flex-wrap: wrap; gap: 0.35rem 0.75rem; padding: 0.75rem 1rem; }
  .notif-row .notif-main { flex-basis: 100%; order: 2; }
  .notif-row .notif-meta { flex-direction: row; gap: 0.75rem; margin-left: auto; }
}
.mail-scope { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin: -0.25rem 0 1rem; font-size: 0.85rem; }
@media (max-width: 640px) {
  .mail-row > summary { flex-wrap: wrap; gap: 0.4rem 0.75rem; padding: 0.75rem 1rem; }
  .mail-row .mail-main { flex-basis: 100%; order: 2; }
  .mail-row .mail-meta { flex-direction: row; gap: 0.5rem; margin-left: auto; }
  .mail-detail { grid-template-columns: 1fr; gap: 0.15rem; padding: 0.75rem 1rem; }
  .mail-detail dd + dt { margin-top: 0.4rem; }
}

/* /admin/users — one grouped panel; each user is a details row whose summary
   is the scan line and whose body holds role / sign-in / password side by
   side, with delete split off at the bottom. */
.users-list .user-cols,
.users-list .user-row > summary {
  display: grid;
  grid-template-columns: minmax(14rem, 2.2fr) 1.3fr 1fr 0.8fr 1fr 5.5rem;
  gap: 1rem;
  align-items: center;
}
.users-list .user-cols {
  padding: 0.55rem 1.25rem;
  border-bottom: 1px solid var(--border);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--text-dim);
}
.users-list .user-row { padding: 0; }
.users-list .user-row > summary { padding: 0.85rem 1.25rem; }
.users-list .user-row > summary:hover { background: var(--surface-2); }
.users-list .user-row[open] > summary { background: var(--surface-2); border-bottom: 1px solid var(--border); }
.users-list .user-row[open] .user-toggle .btn { color: var(--text); border-color: var(--text-dim); }
.users-list .user-id { display: flex; align-items: center; gap: 0.7rem; min-width: 0; }
.users-list .user-name { display: flex; flex-direction: column; min-width: 0; }
.users-list .user-name > * { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.users-list .user-ava {
  width: 30px; height: 30px; border-radius: 50%; flex: none;
  display: grid; place-items: center;
  background: var(--surface-2); border: 1px solid var(--border);
  font-weight: 600; font-size: 0.8rem; color: var(--text-dim);
}
.users-list .user-role { font-size: 0.86rem; }
.users-list .user-toggle { justify-self: end; }
.users-list .user-panel {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.users-list .user-sec {
  padding: 1.1rem 1.25rem;
  display: flex; flex-direction: column; gap: 0.5rem;
}
.users-list .user-sec + .user-sec { border-left: 1px solid var(--border); }
.users-list .user-sec h3 { margin: 0; }
.users-list .user-sec .help { margin: 0 0 0.2rem; font-size: 0.8rem; }
.users-list .user-sec input:not([type=hidden]),
.users-list .user-sec select { width: 100%; margin: 0; }
.users-list .user-sec .item-actions { margin-top: auto; padding-top: 0.4rem; }
.users-list .user-danger {
  display: flex; justify-content: space-between; align-items: center; gap: 1rem;
  padding: 0.75rem 1.25rem;
  border-top: 1px solid var(--border);
  background: color-mix(in oklab, var(--danger) 5%, var(--surface));
}
.users-add > summary.ghead { cursor: pointer; list-style: none; border-bottom: none; }
.users-add > summary::-webkit-details-marker { display: none; }
.users-add[open] > summary.ghead { border-bottom: 1px solid var(--border); }
.users-add-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.75rem; }
.users-add .role-legend {
  display: grid; grid-template-columns: max-content 1fr; gap: 0.25rem 1rem;
  margin: 1rem 0 0; font-size: 0.8rem; color: var(--text-soft);
}
.users-add .role-legend dt { font-weight: 600; color: var(--text-dim); }
.users-add .role-legend dd { margin: 0; }
@media (max-width: 900px) {
  .users-list .user-cols { display: none; }
  .users-list .user-row > summary { display: flex; flex-wrap: wrap; align-items: center; gap: 0.45rem 0.75rem; }
  .users-list .user-row > summary > .user-id { flex: 1 1 0; order: 0; }
  .users-list .user-row > summary > .user-toggle { order: 1; }
  .users-list .user-row > summary > :not(.user-id):not(.user-toggle) { order: 2; }
  .users-list .user-row > summary > .user-role { flex-basis: 100%; padding-left: calc(30px + 0.7rem); margin-bottom: -0.2rem; }
  .users-list .user-row > summary > :nth-child(3) { margin-left: calc(30px + 0.7rem); }
  .users-list .user-panel { grid-template-columns: 1fr; }
  .users-list .user-sec + .user-sec { border-left: none; border-top: 1px solid var(--border); }
  .users-add-grid { grid-template-columns: 1fr 1fr; }
}

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

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

/* Online/offline dot beside a node name (/stats Nodes table). */
.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(min(13rem, 100%), 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(min(18rem, 100%), 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;
}

/* ── /stats, grouped-flat ───────────────────────────────────────
   Figures sit in hairline-split cells inside a .group, each with its own
   history underneath. Text stays monochrome; graphs (sparklines + meters)
   draw in their metric's series colour (--viz, set by a .viz-* class on
   the cell). A cell crossing a threshold (tone-warn / tone-err, set in
   NodeView::from_stats) repaints its graph in the state colour — state
   beats identity. Network in/out differ by hue AND stroke (solid/dashed),
   so the pair never relies on colour alone. */
.tone-warn { color: var(--warn); }
.tone-err  { color: var(--danger); }

.stats-controls { gap: var(--space-4); }
.stats-controls .m-wrap { display: flex; align-items: center; gap: var(--space-2); margin: 0; }
.stats-controls label { margin: 0; }
.stats-controls select { width: auto; min-width: 0; padding: 0.3rem 0.55rem; font-size: 0.85rem; }
.stats-controls #node-switcher { min-width: 12rem; }

.stats-paused-note { display: none; color: var(--text-soft); font-size: 0.8rem; font-weight: 400; }
#stats-live[data-paused="1"] .stats-paused-note { display: inline; }

.group > .ghead .ghead-note {
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
  font-size: 0.78rem;
  color: var(--text-soft);
  font-variant-numeric: tabular-nums;
}
#stats-live > .group + .group { margin-top: var(--group-gap); }

/* Cells: border-right + border-bottom on every cell, and the grid pulled
   1px past the group's right and bottom edge so the outermost lines fall
   under the group's own border (it clips with overflow:hidden). Any
   column count the auto-fit lands on stays a clean lattice. */
.stat-cells {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
  margin: 0 -1px -1px 0;
}
.stat {
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: 1.1rem 1.25rem 1.15rem;
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  color: var(--text-dim);   /* charts and meters draw in currentColor */
}
.stat.tone-warn { color: var(--warn); }
.stat.tone-err  { color: var(--danger); }
.viz-blue    { --viz: var(--viz-blue); }
.viz-magenta { --viz: var(--viz-magenta); }
.viz-aqua    { --viz: var(--viz-aqua); }
.viz-violet  { --viz: var(--viz-violet); }
.viz-net     { --viz: var(--viz-aqua); --viz-2: var(--viz-violet); }
.stat.tone-warn { --viz: var(--warn);   --viz-2: var(--warn); }
.stat.tone-err  { --viz: var(--danger); --viz-2: var(--danger); }
.stat-label {
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--text-soft);
}
.stat-value {
  margin-top: 0.3rem;
  font-size: 1.6rem;
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.stat.tone-warn .stat-value { color: var(--warn); }
.stat.tone-err  .stat-value { color: var(--danger); }
.stat-unit { font-size: 0.85rem; font-weight: 500; letter-spacing: 0; color: var(--text-soft); }
.stat-meta {
  margin-top: 0.3rem;
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
/* Chart / meter hug the bottom of the cell so a row of cells with
   different amounts of text still lines their graphics up. */
.stat > .stat-meta:not(:last-child) { margin-bottom: 0.8rem; }
.stat-chart { margin-top: auto; position: relative; color: var(--viz, currentColor); }
.stat-chart .spark-svg { height: 44px; overflow: visible; }
.stat-chart .spark-out { stroke: var(--viz-2, var(--text-soft)); }
/* Legend key for the two-series network chart: a short stroke in the
   series' own colour + dash, beside the text that names it. */
.viz-key {
  display: inline-block;
  width: 0.9em;
  height: 0;
  margin-right: 0.3em;
  vertical-align: middle;
  border-top: 2px solid var(--viz, currentColor);
}
.viz-key.out { border-top: 2px dashed var(--viz-2, var(--text-soft)); }
.stat-chart.empty {
  display: flex;
  align-items: flex-end;
  min-height: 44px;
  font-size: 0.75rem;
  color: var(--text-soft);
}
.stat-meter {
  margin-top: auto;
  height: 4px;
  border-radius: 2px;
  background: var(--surface-3);
  overflow: hidden;
}
.stat-meter > span {
  display: block;
  height: 100%;
  max-width: 100%;
  background: var(--viz, currentColor);
  border-radius: inherit;
}

/* Phone: two cells a row rather than one tall column of five. */
@media (max-width: 640px) {
  .stat-cells { grid-template-columns: 1fr 1fr; }
  .stat:last-child:nth-child(odd) { grid-column: 1 / -1; }
  .stat { padding: 0.9rem 1rem 1rem; }
  .stat-value { font-size: 1.3rem; }
  .stat-meta { font-size: 0.75rem; }
  .stat-facts { padding: 0.75rem 1rem; gap: var(--space-2) var(--space-4); }
}

/* Quiet facts strip under the health cells. */
.stat-facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-6);
  margin: 0;
  padding: 0.8rem 1.25rem;
  background: var(--bg-2);
  font-size: 0.8rem;
}
.stat-facts > div { display: flex; gap: var(--space-2); align-items: baseline; }
.stat-facts dt { color: var(--text-soft); margin: 0; }
.stat-facts dd { margin: 0; color: var(--text); font-variant-numeric: tabular-nums; }

/* Tables inside a group run edge to edge: the .card-era negative margin
   and 1.5rem gutters are re-set to the group's own 1.25rem rhythm. */
.group > .table-wrap { margin: 0; border-radius: 0; }
.group .stats-table th:first-child, .group .stats-table td:first-child { padding-left: 1.25rem; }
.group .stats-table th:last-child,  .group .stats-table td:last-child  { padding-right: 1.25rem; }
.stats-table th { background: transparent; }
.stats-table td a .node-dot { display: inline-block; vertical-align: middle; margin-right: 0.15rem; }
/* Share-of-total under the sorted column's value. */
.share-bar {
  height: 2px;
  margin-top: 0.3rem;
  margin-left: auto;
  max-width: 6rem;
  background: var(--surface-3);
  border-radius: 1px;
  overflow: hidden;
}
.share-bar > span { display: block; height: 100%; background: var(--viz, var(--text-dim)); }

/* ─────────────────────────────────────────────────────────────
   "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(min(20rem, 100%), 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 minmax(0, 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);
}
/* A little breathing room after the last table row — with 0 the row sat flush
   against the card's bottom border, so stacked tables read as one block with
   no gap. */
.card.no-pad-table { padding-bottom: 0.5rem; }
.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; }
/* Totals row. Without a surface of its own it inherits the page behind the
   table — `tbody tr` sets one and `tfoot tr` did not — so the Σ row read as
   floating BELOW the table rather than closing it. The heavier top rule is
   what separates a summary from the last data row. */
tfoot tr {
  background: var(--surface-2);
}
tfoot td {
  border-top: 2px solid var(--border);
  border-bottom: none;
  font-variant-numeric: tabular-nums;
}
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; }

/* Sortable column header (per-site table on /stats). The link inherits
   the <th> type styling so the header row still reads as a header rather
   than a row of links; only the active column brightens. Sorting is a
   ?sort= round trip, so these are plain anchors — no JS. */
th a.th-sort {
  color: inherit;
  text-decoration: none;
}
th a.th-sort:hover { color: var(--text); text-decoration: underline; }
th a.th-sort.active { color: var(--text); }

/* Thin share-of-total bar drawn under the value in whichever numeric
   column is currently sorted. Same idiom as the job .progress-bar, just
   quieter and sized to sit inside a table cell without adding a row of
   height. */
.progress-bar.share {
  height: 3px;
  margin-top: 0.35rem;
}
.progress-bar.share .progress-bar-fill { opacity: 0.55; }

/* Per-site usage table (/stats). Eight columns of byte figures in a
   column that is already narrowed by the sidebar: without this a value
   like "512.0 MiB" breaks across two lines and the rows go ragged.
   Scoped to this table so the .table-wrap scrolls sideways instead —
   which is the behaviour every other wide table here already has. */
table.site-usage th,
table.site-usage td.numeric { white-space: nowrap; }

.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);
}

.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;
}
/* A read-only block of forms (a node whose settings follow the master):
   the values stay readable, the Save buttons have nothing to do. */
fieldset:disabled button[type="submit"] {
  display: 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; }
/* Collapse the 3-up row on phones. Must live AFTER the base .field-row.three
   rule (later + equal specificity wins) or the three columns stay and squish
   the inputs below readability. */
@media (max-width: 640px) {
  .field-row.three { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 480px) {
  .field-row.three { grid-template-columns: 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); }

/* Lightweight text banner (no icon/body structure — that's .flash).
   Variants accept both the -ok/-err and -success/-error spellings. */
.alert {
  padding: 0.8rem 1rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  margin-bottom: 1rem;
  font-size: 0.9rem;
  background: var(--surface);
}
.alert strong { color: var(--text); }
.alert-ok, .alert-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);
}
.alert-err, .alert-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);
}
.alert-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);
}
.alert-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 .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);
}
.login-foot strong { color: var(--text-dim); font-weight: 600; }

/* ---- Wordmark (was inline styles, copied into both pages) ---- */
.login-wordmark {
  font-family: var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.login-wordmark .hy {
  color: var(--brand-accent);
  text-shadow: 0 0 18px color-mix(in oklab, var(--brand-accent) 60%, transparent);
}
.login-wordmark .dot { color: var(--text-soft); font-weight: 500; }
.login-card .login-brand.login-wordmark { gap: 0; }
.login-wordmark-aside {
  font-weight: 800;
  font-size: 1.4rem;
  margin-bottom: 2.5rem;
}
.login-wordmark-aside .hy {
  color: #34d399;
  text-shadow: 0 0 22px rgba(52, 211, 153, 0.6);
}
.login-wordmark-aside .dot { color: inherit; opacity: 0.6; }
.login-aside .brand-block h1.login-aside-h1-step { margin-top: 2rem; }

/* ---- Password field: show/hide toggle sits inside the input ---- */
.login-pw { position: relative; }
.login-pw input { padding-right: 4.2rem; }
.login-pw-toggle {
  position: absolute;
  top: 50%;
  right: 0.35rem;
  transform: translateY(-50%);
  background: transparent;
  border: 0;
  border-radius: var(--radius-xs);
  padding: 0.3rem 0.55rem;
  color: var(--text-dim);
  font: 500 0.78rem/1 var(--font-ui);
  cursor: pointer;
}
.login-pw-toggle:hover { color: var(--text); background: var(--surface-2); }
.login-pw-toggle:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
.login-hint {
  font-size: 0.78rem;
  color: var(--warn);
  font-weight: 500;
}

/* "Afterwards you'll go back to /hostings/…" — the deep link survives. */
.login-return {
  margin: -0.2rem 0 1rem;
  font-size: 0.82rem;
  color: var(--text-dim);
  display: flex;
  gap: 0.35rem;
  align-items: baseline;
  min-width: 0;
}
.login-return code {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.login-check {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin: 0 0 0.9rem;
  font-size: 0.85rem;
  color: var(--text-dim);
  cursor: pointer;
}
.login-card .login-submit { margin-top: 0.9rem; }
.login-backup-input {
  font-family: var(--font-mono) !important;
  font-size: 1rem !important;
  letter-spacing: 0.1em;
  text-align: center;
  text-transform: uppercase;
}
/* The aside's step trail is hidden on narrow screens; this line stands in. */
.login-step-inline {
  display: none;
  margin: 0 0 0.35rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--success);
}

@media (max-width: 880px) {
  .login-shell { grid-template-columns: 1fr; }
  .login-aside { display: none; }
  .login-card .login-brand { display: flex; }
  .login-step-inline { display: block; }
  .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(min(18rem, 100%), 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;
  --viz-blue:     #2a78d6;
  --viz-magenta:  #e87ba4;
  --viz-aqua:     #1baf7a;
  --viz-violet:   #4a3aa7;
}
: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;
  --viz-blue:     #3987e5;
  --viz-magenta:  #d55181;
  --viz-aqua:     #199e70;
  --viz-violet:   #9085e9;
}

/* ============================================================
 *  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);
}

/* Import page: the three sources as one switcher under the title. It stands
   alone (no toolbar around it), so the segments need their own height, and it
   wraps on a phone instead of scrolling the page sideways. */
.import-sources { display: flex; flex-wrap: wrap; width: fit-content; max-width: 100%; margin-bottom: var(--group-gap); }
.import-sources .seg { min-height: 2.1rem; }
.import-mint { display: flex; align-items: flex-end; gap: 0.75rem; flex-wrap: wrap; }
.import-mint select { min-width: 12rem; }
.import-oneliner { white-space: pre-wrap; word-break: break-all; user-select: all; margin: 0.5rem 0 0; }
.import-steps { margin: 1rem 0 0; padding-left: 1.25rem; font-size: 0.9rem; line-height: 1.7; }
.import-steps li + li { margin-top: 0.15rem; }
.import-php { padding: 0.15rem 0.35rem; font-size: 0.82rem; }
.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); }

/* ============================================================
 *  Hostings list (/hostings)
 * ============================================================ */

.bulk-bar {
  padding: 0.85rem 1.5rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
  min-height: 3.25rem;
}
.bulk-count { font-size: 0.88rem; font-weight: 500; }
.bulk-hint { font-size: 0.85rem; color: var(--text-soft); }
.bulk-actions { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.bulk-menu { position: relative; }
.bulk-menu > summary { list-style: none; cursor: pointer; }
.bulk-menu > summary::-webkit-details-marker { display: none; }
.bulk-popover {
  position: absolute;
  right: 0;
  z-index: 10;
  margin-top: 0.4rem;
  padding: 0.7rem;
  width: min(24rem, calc(100vw - 1.5rem));
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
}
.bulk-popover .field { margin: 0; }
.bulk-popover select { margin: 0; }
.bulk-popover .field-checkbox { margin: 0.6rem 0 0; font-size: 0.85rem; }
.bulk-popover-link { color: var(--text-soft); font-weight: 400; font-size: 0.78rem; margin-left: 0.4rem; }
.bulk-popover-actions { display: flex; justify-content: flex-end; gap: 0.4rem; margin-top: 0.6rem; }
.bulk-popover-note { margin: 0.5rem 0 0; font-size: 0.78rem; color: var(--text-soft); }

.hostings-table .col-check { width: 1px; padding-right: 0; }
.site-name { font-weight: 600; }
.site-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  margin-top: 0.15rem;
  font-size: 0.78rem;
  color: var(--text-soft);
}
.site-attention { margin-top: 0.15rem; font-size: 0.78rem; color: var(--danger); }
.hostings-table tr.row-attention > td:first-child { box-shadow: inset 2px 0 0 var(--danger); }

/* Small uppercase status flags under the domain (TEST / NODE OFFLINE /
   MAINTENANCE) — formerly inline-styled tags in the Node column. */
.flag {
  display: inline-block;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 0.1rem 0.35rem;
  border-radius: var(--radius-xs);
  color: white;
}
.flag.warn   { background: var(--warn); }
.flag.err    { background: var(--danger); }
.flag.info   { background: var(--info); }

.cert-cell { font-size: 0.85rem; white-space: nowrap; }
.cert-cell.ok   { color: var(--text-dim); }
.cert-cell.warn { color: var(--warn); font-weight: 500; }
.cert-cell.err  { color: var(--danger); font-weight: 600; }
.node-name { font-family: var(--font-mono); font-size: 0.8rem; color: var(--text-dim); }
.inline-form { display: inline; }
.hostings-table td.nowrap { white-space: nowrap; }
/* Created is the least-used column; give its width back to the site
   facts before the table has to scroll sideways. */
@media (max-width: 1200px) {
  .hostings-table .hide-below-wide { display: none; }
}

@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; }
  /* Tables bleed to the card edge with a negative margin equal to the
     card padding; the ≤760 rule set -1rem, so at this tighter padding
     every table overshot its card by a sliver and scrolled sideways. */
  .table-wrap { margin: 0 -0.85rem -0.85rem; }
  .bulk-bar { margin: 0 -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; }

  /* Row actions (View / Delete …) stack in their cell: side by side
     they made the last column ~110px wide, pushing it past the edge
     of the /hostings table so Delete needed a sideways scroll. */
  td .row-actions { flex-direction: column; align-items: stretch; gap: 0.3rem; }
  td .row-actions form { display: block !important; }
  td .row-actions .btn { width: 100%; justify-content: center; }
  /* Long domains break inside the cell instead of widening the table. */
  td a:not(.btn) { overflow-wrap: anywhere; }
  .bulk-bar { padding: 0.7rem 0.85rem; }

  /* 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. */
  /* Bare links/buttons only: a `.btn` already has its own padding,
     and the negative margin shoved stacked row-action buttons
     sideways out of line with each other. */
  td a:not(.btn), td button:not(.btn) { 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-2);
    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(min(180px, 100%), 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;
}

/* ───────────────────────────────────────────────────────────────
 *  Responsive audit fixes (phone/tablet)
 *  See docs: grids with a FIXED minmax minimum are min()-wrapped at
 *  their source so a lone auto-fit column can shrink below the min.
 *  Below we collapse the remaining FIXED N-column inline grids (which
 *  can't be reached by a class rule since inline styles win) via
 *  attribute selectors + !important, matching the existing pattern.
 * ─────────────────────────────────────────────────────────────── */

/* Belt-and-braces: never let a stray wide child scroll the whole page
 * sideways. The mobile top-bar keeps its own overflow-x:auto. */
html, body { overflow-x: clip; }

@media (max-width: 700px) {
  /* Fixed multi-column inline grids → single column on phones. */
  [style*="grid-template-columns:1fr 1fr"],
  [style*="grid-template-columns:1fr 1fr;"],
  [style*="grid-template-columns:1fr 1fr auto"],
  [style*="grid-template-columns:1fr 1fr 2fr auto"],
  [style*="grid-template-columns:1fr 1fr 1fr 1fr auto"],
  [style*="minmax(0,2fr) minmax(0,1fr)"],
  [style*="minmax(0, 2fr) minmax(0, 1fr)"] {
    grid-template-columns: 1fr !important;
    max-width: 100% !important;
  }
  .node-card__inline-form { grid-template-columns: 1fr !important; }
}

/* Touch: bump the small controls to a comfortable 40px tap target. */
@media (pointer: coarse) {
  .search-clear, .node-tab, .chip, .creds-copy, .row-menu-toggle {
    min-height: 40px;
    min-width: 40px;
  }
}

/* Horizontal-scroll wrapper for wide tables the page would otherwise
   clip: body has `overflow-x: clip`, so an unwrapped wide table loses its
   right columns with no scrollbar. This gives the table its own reachable
   scroll axis. */
.scroll-x{overflow-x:auto;-webkit-overflow-scrolling:touch;max-width:100%;}


/* Sidebar status dots. A bare dot when all is well; a small count pill when
   something needs attention — same element either way, so the row does not
   reflow as the state changes. */
.nav-status-dot {
  margin-left: auto;
  min-width: 0.5rem;
  height: 0.5rem;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}
.nav-status-dot.good { background: var(--ok); }
.nav-status-dot.bad {
  background: var(--err);
  color: #fff;
  height: auto;
  min-width: 1.15rem;
  padding: 0.05rem 0.35rem;
  font-size: 0.66rem;
  font-weight: 700;
  line-height: 1.5;
}

/* A <summary> used as a button still draws the default disclosure triangle
   in WebKit, which makes it read as a caption rather than a control. */
summary.btn::-webkit-details-marker { display: none; }
summary.btn::marker { content: ""; }

/* Click-to-copy field. Reads as a value you can take, not a paragraph you
   have to select by hand. */
.copy-field {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.7rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  cursor: pointer;
  transition: border-color var(--t-fast), background var(--t-fast);
  min-width: 0;
}
.copy-field:hover { border-color: var(--primary); }
.copy-field.copied { border-color: var(--ok); }
.copy-field.copy-failed { border-color: var(--err); }
.copy-field .copy-value {
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 0.86rem;
  overflow-x: auto;
  white-space: nowrap;
  flex: 1;
  min-width: 0;
}
.copy-field .copy-label {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-dim);
  flex: none;
  min-width: 4.5rem;
}
.copy-field .copy-hint {
  font-size: 0.68rem;
  color: var(--text-dim);
  flex: none;
  white-space: nowrap;
}
.copy-field.copied .copy-hint { color: var(--ok); }
.copy-field.copy-failed .copy-hint { color: var(--err); }
.copy-grid { display: grid; gap: 0.5rem; margin: 0.6rem 0; }

/* Traffic cartogram: tiles sized by share of requests. Deliberately not a
   geographic map — see the template comment. */
.country-cartogram {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 0.7rem;
}
.country-tile {
  flex-basis: 0;
  min-width: 4.5rem;
  padding: 0.7rem 0.5rem;
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  border: 1px solid var(--border);
  text-align: center;
  line-height: 1.25;
}
.country-tile-flag { display: block; font-size: 1.5rem; }
.country-tile-code {
  display: block;
  font-size: 0.7rem;
  letter-spacing: 0.05em;
  color: var(--text-dim);
}
.country-tile-pct { display: block; font-weight: 700; font-size: 0.9rem; }

/* ============================================================
 *  Hosting detail — the "site page"
 *  ----------------------------------------------------------
 *  One hosting, a dozen sections. The page is built from:
 *    .site-bar     a compact sticky identity bar (domain, state,
 *                  the two or three actions an operator reaches
 *                  for from anywhere on the page)
 *    .site-meta    a row of fact chips under it (kind, PHP, node,
 *                  system user, created…)
 *    .site-shell   a two-column shell: a sticky vertical section
 *                  nav on the left, the active section on the right.
 *                  Below 1100px the nav folds into a horizontal strip.
 *    .site-main    the section area; a CSS container, so the grids
 *                  inside it respond to ITS width, not the viewport
 *                  (the app sidebar and the section nav both eat
 *                  width before the content gets any).
 *
 *  The section nav reuses the `.tabs .tab[data-tab]` contract the
 *  switcher script and the template lints already know; only the
 *  presentation is different, and every rule here is scoped to
 *  `.site-nav` so the Settings page strip is untouched.
 * ============================================================ */

:root { --site-bar-h: 3.5rem; }

/* ── Sticky identity bar ──────────────────────────────────────── */
.site-bar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: 0.75rem;
  min-height: var(--site-bar-h);
  margin: -0.5rem calc(-1 * clamp(1.5rem, 3vw, 3rem)) 0;
  padding: 0.5rem clamp(1.5rem, 3vw, 3rem);
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: saturate(140%) blur(12px);
  -webkit-backdrop-filter: saturate(140%) blur(12px);
  border-bottom: 1px solid var(--border);
}
.site-bar-id {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-width: 0;
  flex: 1 1 auto;
}
.site-bar-back {
  display: inline-grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border-radius: var(--radius-sm);
  color: var(--text-dim);
  flex: none;
  transition: background var(--t-fast), color var(--t-fast);
}
.site-bar-back:hover { background: var(--surface-2); color: var(--text); }
.site-bar-back svg { width: 16px; height: 16px; }
.site-bar h1 {
  margin: 0;
  font-size: 1.2rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.site-bar .pill { flex: none; }
.site-bar .pill.maint {
  background: var(--accent, var(--warn));
  color: #fff;
  border-color: transparent;
  letter-spacing: 0.05em;
  font-size: 0.66rem;
}
.site-bar .pill.maint::before { display: none; }
.site-bar-actions {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  flex: none;
  margin-left: auto;
}
.site-bar-actions form { margin: 0; display: inline-flex; }

/* ── Meta chips under the bar ─────────────────────────────────── */
.site-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem 0.5rem;
  margin: 0.9rem 0 0;
  font-size: 0.8rem;
}
.meta-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.22rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text-dim);
  white-space: nowrap;
  max-width: 100%;
  min-width: 0;
}
.meta-chip svg { width: 13px; height: 13px; flex: none; color: var(--text-soft); }
.meta-chip strong { color: var(--text); font-weight: 600; }
.meta-chip code {
  background: transparent;
  border: 0;
  padding: 0;
  font-size: 0.78rem;
  color: inherit;
  overflow: hidden;
  text-overflow: ellipsis;
}
.meta-chip.copy-field {
  cursor: pointer;
  padding: 0.22rem 0.6rem;
  background: var(--surface);
  border-radius: var(--radius-pill);
}
.meta-chip.copy-field .copy-value { font-size: 0.74rem; overflow: hidden; text-overflow: ellipsis; max-width: 11rem; }
.meta-chip.copy-field .copy-hint { font-size: 0.64rem; }
.meta-chip.kind strong { letter-spacing: 0.01em; }
.site-meta .meta-sep { color: var(--text-soft); opacity: 0.6; }

/* ── Shell: nav + section ─────────────────────────────────────── */
.site-shell {
  display: grid;
  grid-template-columns: 14.5rem minmax(0, 1fr);
  gap: 1.75rem;
  align-items: start;
  margin-top: 1.25rem;
}
.site-main {
  min-width: 0;
  container-type: inline-size;
  container-name: sitemain;
}

/* The vertical section nav. */
.site-nav {
  position: sticky;
  top: calc(var(--site-bar-h) + 1rem);
  max-height: calc(100dvh - var(--site-bar-h) - 2rem);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 0;
  margin: 0;
  background: transparent;
  border: 0;
  border-radius: 0;
  scrollbar-width: thin;
}
.site-nav .nav-group {
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-soft);
  padding: 1.1rem 0.75rem 0.35rem;
}
.site-nav .nav-group:first-child { padding-top: 0.2rem; }
.site-nav .tab {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.75rem;
  font-size: 0.88rem;
  font-weight: 500;
  color: var(--text-dim);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  box-shadow: none;
}
.site-nav .tab:hover { background: var(--surface-2); color: var(--text); }
.site-nav .tab.active {
  background: var(--surface-2);
  color: var(--text);
  border-color: var(--border);
  box-shadow: none;
}
.site-nav .tab .tab-icon { width: 16px; height: 16px; flex: none; }
.site-nav .tab .tab-badge { margin-left: auto; }
.site-nav .tab .tab-ext { margin-left: auto; width: 12px; height: 12px; opacity: 0.55; flex: none; }
.site-nav .tab[data-tone="danger"] { color: var(--danger); }
.site-nav .tab[data-tone="danger"]:hover { background: color-mix(in oklab, var(--danger) 8%, var(--surface)); }
.site-nav .tab[data-tone="danger"].active {
  background: color-mix(in oklab, var(--danger) 10%, var(--surface));
  border-color: color-mix(in oklab, var(--danger) 35%, var(--border));
}
/* A small status dot inside a nav badge (monitor: healthy / alerting). */
.tab-badge.dot-ok   { background: var(--success); color: #fff; }
.tab-badge.dot-bad  { background: var(--danger);  color: #fff; }
.tab-badge.dot-warn { background: var(--warn);    color: #fff; }
.tab.active .tab-badge.dot-ok   { background: var(--success); color: #fff; }
.tab.active .tab-badge.dot-bad  { background: var(--danger);  color: #fff; }
.tab.active .tab-badge.dot-warn { background: var(--warn);    color: #fff; }

/* Fold the nav into a scrollable strip once the two columns stop fitting. */
@media (max-width: 1100px) {
  .site-shell { grid-template-columns: minmax(0, 1fr); gap: 1rem; }
  .site-nav {
    position: static;
    max-height: none;
    flex-direction: row;
    align-items: stretch;
    gap: 0.15rem;
    padding: 0.35rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow-x: auto;
    overflow-y: hidden;
  }
  .site-nav .nav-group { display: none; }
  .site-nav .tab { flex: none; padding: 0.5rem 0.8rem; }
  .site-nav .tab .tab-badge,
  .site-nav .tab .tab-ext { margin-left: 0; }
}
@media (max-width: 880px) {
  .site-bar {
    position: static;
    flex-wrap: wrap;
    min-height: 0;
    margin: 0 0 0.25rem;
    padding: 0 0 0.85rem;
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .site-bar-actions { flex: 1 1 100%; margin-left: 0; }
  .site-bar-actions form { flex: 1; }
  .site-bar-actions .btn { flex: 1; }
  .site-bar h1 { white-space: normal; font-size: 1.1rem; }
  .site-meta { margin-top: 0.5rem; }
}

/* ── Section header inside a panel ────────────────────────────── */
/* A grid, not a wrapping flex row: a flex item's wrap decision uses its
   max-width-clamped size, so a 78ch description happily sat BESIDE a short
   title instead of under it. */
.panel-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: 0.45rem 1.25rem;
  margin: 0 0 1rem;
}
.panel-head h2 {
  grid-column: 1;
  margin: 0;
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: -0.015em;
}
.panel-head p {
  grid-column: 1 / -1;
  margin: 0;
  font-size: 0.86rem;
  color: var(--text-dim);
  max-width: 78ch;
  line-height: 1.5;
}
.panel-head .panel-links {
  grid-column: 2;
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  justify-content: flex-end;
  font-size: 0.82rem;
}
.panel-head .panel-links a { color: var(--text-dim); }
.panel-head .panel-links a:hover { color: var(--text); }
@media (max-width: 640px) {
  .panel-head { grid-template-columns: minmax(0, 1fr); }
  .panel-head .panel-links { grid-column: 1; justify-self: start; justify-content: flex-start; }
}
.panel-head + .panel-head { margin-top: 1.75rem; }
/* The same thing one level down: a group label between cards. */
.panel-group {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  margin: 1.6rem 0 0.6rem;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.panel-group:first-child { margin-top: 0; }
.panel-group span.note { text-transform: none; letter-spacing: 0; font-weight: 400; font-size: 0.78rem; }

/* ── Card header component ────────────────────────────────────── */
.card-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin: 0 0 0.9rem;
}
.card-head h2 { margin: 0; }
.card-head .card-tools {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
}
.card-head .card-tools form { margin: 0; }
.card-head .pill { flex: none; }
.card-desc {
  margin: -0.45rem 0 0.9rem;
  font-size: 0.84rem;
  color: var(--text-dim);
  line-height: 1.55;
  max-width: 78ch;
}
.card-desc:last-child { margin-bottom: 0; }
.card-head + .card-desc { margin-top: -0.4rem; }
.card.compact { padding: 1rem 1.15rem; }
.card.compact .card-head { margin-bottom: 0.6rem; }
.card h2 .pill { font-size: 0.62rem; margin-left: 0.15rem; }

/* Cards laid out by the page, not by `.card + .card`. */
.site-col,
.card-stack {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  min-width: 0;
}
.site-col > .card + .card,
.card-stack > .card + .card,
.card-grid > .card + .card { margin-top: 0; }
/* Lazy panels render their own card with an inline bottom margin that was
   tuned for a stacked tab; in a gap-driven column or grid it double-spaces. */
.site-col > .card,
.site-col > * > .card:last-child,
.card-stack > .card,
.card-stack > * > .card:last-child,
.card-grid > .card,
.card-grid > * > .card:last-child { margin-bottom: 0 !important; }

/* Two or three cards side by side when the section is wide enough. */
.card-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1rem;
  align-items: start;
}
@container sitemain (min-width: 46rem) {
  .card-grid.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@container sitemain (min-width: 64rem) {
  .card-grid.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@container sitemain (min-width: 46rem) and (max-width: 63.98rem) {
  .card-grid.cols-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.card-grid > .span-2 { grid-column: 1 / -1; }

/* ── Overview: KPI strip + two columns ────────────────────────── */
.site-kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
  gap: 0.75rem;
  margin: 0 0 1rem;
}
.site-kpi {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
  padding: 0.8rem 0.95rem 0.75rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: inherit;
  text-decoration: none;
  transition: border-color var(--t-fast), background var(--t-fast);
}
a.site-kpi:hover { border-color: var(--border-strong); background: var(--surface-2); color: inherit; }
.site-kpi .k-label {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.site-kpi .k-label svg { width: 12px; height: 12px; }
.site-kpi .k-value {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  font-size: 1.3rem;
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
  min-width: 0;
}
.site-kpi .k-value > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.site-kpi .k-value small { font-size: 0.76rem; font-weight: 500; color: var(--text-dim); letter-spacing: 0; }
.site-kpi .k-meta {
  font-size: 0.75rem;
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.site-kpi .k-spark { margin-top: 0.3rem; color: var(--text-dim); }
.site-kpi .k-spark svg { height: 26px !important; }
.site-kpi[data-tone="good"] .k-value { color: var(--success); }
.site-kpi[data-tone="warn"] .k-value { color: var(--warn); }
.site-kpi[data-tone="bad"]  .k-value { color: var(--danger); }
.site-kpi[data-tone="good"]::before,
.site-kpi[data-tone="warn"]::before,
.site-kpi[data-tone="bad"]::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  border-radius: var(--radius) 0 0 var(--radius);
}
.site-kpi[data-tone="good"]::before { background: var(--success); }
.site-kpi[data-tone="warn"]::before { background: var(--warn); }
.site-kpi[data-tone="bad"]::before  { background: var(--danger); }

.site-overview {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1rem;
  align-items: start;
}
@container sitemain (min-width: 58rem) {
  .site-overview { grid-template-columns: minmax(0, 1fr) 21rem; gap: 1.25rem; }
  .site-overview .site-col.aside { position: sticky; top: calc(var(--site-bar-h) + 1rem); }
}
@media (max-width: 880px) {
  .site-overview .site-col.aside { position: static; }
}

/* Health grade square. */
.grade {
  display: inline-grid;
  place-items: center;
  width: 2.7rem;
  height: 2.7rem;
  border-radius: var(--radius-sm);
  font-size: 1.35rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text);
  flex: none;
}
.grade[data-grade="A"] { color: var(--success); border-color: color-mix(in oklab, var(--success) 40%, transparent); background: color-mix(in oklab, var(--success) 10%, var(--surface)); }
.grade[data-grade="B"] { color: #84cc16; border-color: color-mix(in oklab, #84cc16 40%, transparent); background: color-mix(in oklab, #84cc16 10%, var(--surface)); }
.grade[data-grade="C"] { color: var(--warn); border-color: color-mix(in oklab, var(--warn) 40%, transparent); background: color-mix(in oklab, var(--warn) 10%, var(--surface)); }
.grade[data-grade="D"],
.grade[data-grade="F"] { color: var(--danger); border-color: color-mix(in oklab, var(--danger) 40%, transparent); background: color-mix(in oklab, var(--danger) 10%, var(--surface)); }

/* Check list with a coloured dot per row (health, to-do). */
.checklist {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.45rem;
}
.checklist li {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  font-size: 0.88rem;
  line-height: 1.45;
}
.checklist .dot {
  flex: none;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  margin-top: 0.42rem;
  background: var(--text-soft);
}
.checklist .dot.good { background: var(--success); }
.checklist .dot.warn { background: var(--warn); }
.checklist .dot.bad  { background: var(--danger); }
.checklist .dot.info { background: var(--text-soft); }
.checklist li .text-soft { color: var(--text-dim); }

/* Fact list for the overview aside: label over value. */
.facts {
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
  margin: 0;
}
.facts .fact { min-width: 0; }
.facts dt {
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-soft);
  margin: 0 0 0.2rem;
}
.facts dd {
  margin: 0;
  font-size: 0.88rem;
  color: var(--text);
  min-width: 0;
  word-break: break-word;
  line-height: 1.45;
}
.facts dd code { font-size: 0.8rem; }
.facts .fact-sub {
  display: block;
  font-size: 0.76rem;
  color: var(--text-soft);
  margin-top: 0.2rem;
  line-height: 1.4;
}
.facts .fact-row {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  flex-wrap: wrap;
}
.facts form.inline-switch {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  margin: 0;
}
.facts form.inline-switch select {
  width: auto;
  padding: 0.3rem 1.9rem 0.3rem 0.55rem;
  font-size: 0.82rem;
  background-position: right 0.55rem center;
}
.facts form.inline-switch input[type="url"] {
  padding: 0.35rem 0.6rem;
  font-size: 0.84rem;
}

/* Security tiles on the overview. */
.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
  gap: 0.7rem;
}
.tile {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.75rem 0.85rem;
  background: var(--bg-2);
  min-width: 0;
}
.tile .tile-title {
  font-size: 0.78rem;
  color: var(--text-dim);
  margin-bottom: 0.4rem;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
}
.tile .tile-body { font-size: 0.82rem; color: var(--text-dim); margin-bottom: 0.45rem; line-height: 1.45; }
.tile .tile-links { display: flex; gap: 0.35rem 0.8rem; flex-wrap: wrap; font-size: 0.78rem; }
.tile .tile-links a { color: var(--text-dim); }
.tile .tile-links a:hover { color: var(--text); }
/* The brute-force tile is a lazy partial that paints its own box. */
.tiles > [hx-get] > div,
.tiles > #bf-scan-tile { background: var(--bg-2); }

/* Link list: "across the cluster". */
.link-list {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.link-list a {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.55rem;
  margin: 0 -0.55rem;
  border-radius: var(--radius-sm);
  color: var(--text-dim);
  font-size: 0.86rem;
}
.link-list a:hover { background: var(--surface-2); color: var(--text); }
.link-list a svg { width: 14px; height: 14px; color: var(--text-soft); flex: none; }
.link-list a .arr { margin-left: auto; opacity: 0.5; }

/* Usage sparklines grid (48 h). */
.usage-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(8rem, 100%), 1fr));
  gap: 0.9rem 1.25rem;
}
.usage-grid > div { min-width: 0; }
.usage-grid .u-label { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-soft); font-weight: 600; }
.usage-grid .u-value { font-size: 1.25rem; font-weight: 700; margin: 0.1rem 0 0.25rem; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.usage-grid .u-spark svg { height: 32px !important; }

/* ── Settings: rows + switches ───────────────────────────────── */
.setting-list { display: flex; flex-direction: column; }
.setting-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 1.25rem;
  row-gap: 0.2rem;
  padding: 0.9rem 0;
  border-top: 1px solid var(--border);
  align-items: start;
}
.setting-list > .setting-row:first-child,
.setting-row.first { border-top: 0; padding-top: 0.25rem; }
.setting-row > .s-label {
  grid-column: 1;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--text);
}
.setting-row > .s-label .hint { font-weight: 400; color: var(--text-dim); font-size: 0.78rem; }
/* Help text flows in column 1, one paragraph per row — an element per
   paragraph, never two in one named area, which is how they overlapped. */
.setting-row > .s-help {
  grid-column: 1;
  font-size: 0.82rem;
  color: var(--text-dim);
  line-height: 1.5;
  max-width: 70ch;
  margin: 0;
}
.setting-row > .s-help + .s-help { margin-top: 0.3rem; }
.setting-row > .s-control {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.setting-row > .s-field {
  grid-column: 1 / -1;
  margin-top: 0.55rem;
  min-width: 0;
}
.setting-row > .s-field .field { margin-bottom: 0; }
.setting-row > .s-field .field + .field { margin-top: 0.75rem; }
.setting-row > .s-field .field-row { margin-top: 0; }
.setting-row > .s-field .field-row .field { margin-bottom: 0; }
/* Side-by-side fields share a top edge: the stacking gap above is for
   fields one under another, not for the second column of a row. */
.setting-row > .s-field .field-row .field + .field { margin-top: 0; }
.setting-row > .s-field .help,
.setting-row > .s-field .muted.small,
.setting-row > .s-field p.small { font-size: 0.78rem; color: var(--text-dim); margin: 0.35rem 0 0; line-height: 1.5; }
.setting-row.stacked { grid-template-columns: minmax(0, 1fr); }
.setting-row.stacked > .s-control { grid-column: 1; grid-row: auto; margin-top: 0.5rem; }
.setting-row > .s-label label { cursor: pointer; }
/* Group title inside a long form. */
.setting-group {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-soft);
  margin: 1.6rem 0 0.2rem;
  padding-bottom: 0.4rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.setting-group:first-child { margin-top: 0.2rem; }
.setting-group svg { width: 13px; height: 13px; }
.setting-group + .setting-list > .setting-row:first-child { border-top: 0; padding-top: 0.2rem; }

/* A checkbox that reads as a switch. Same input, no JS. */
input[type="checkbox"].switch {
  appearance: none;
  -webkit-appearance: none;
  width: 2.45rem;
  height: 1.4rem;
  margin: 0;
  flex: none;
  border-radius: var(--radius-pill);
  background: var(--surface-3);
  border: 1px solid var(--border-strong);
  position: relative;
  cursor: pointer;
  vertical-align: middle;
  transition: background var(--t-fast), border-color var(--t-fast);
}
input[type="checkbox"].switch::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: calc(1.4rem - 6px);
  height: calc(1.4rem - 6px);
  border-radius: 50%;
  background: var(--text-dim);
  transition: transform var(--t-fast), background var(--t-fast);
}
input[type="checkbox"].switch:checked {
  background: var(--primary);
  border-color: var(--primary);
}
input[type="checkbox"].switch:checked::after {
  background: var(--primary-fg);
  transform: translateX(1.05rem);
}
input[type="checkbox"].switch:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--primary) 30%, transparent);
}
input[type="checkbox"].switch:disabled { opacity: 0.45; cursor: not-allowed; }
/* A row that IS a switch: label on the left, switch on the right. */
label.switch-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  cursor: pointer;
  font-size: 0.9rem;
  color: var(--text);
  padding: 0.5rem 0;
}
label.switch-row .sw-text { flex: 1; min-width: 0; }
label.switch-row .sw-text .hint { display: block; color: var(--text-dim); font-size: 0.8rem; font-weight: 400; margin-top: 0.1rem; }
label.switch-row strong { font-weight: 600; }

/* Form footer: primary action right, note left. */
.form-footer {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 1.1rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--border);
}
.form-footer .note { font-size: 0.78rem; color: var(--text-soft); }
.form-footer .btn.primary { margin-left: auto; }
.form-footer.start .btn.primary { margin-left: 0; }

/* Inline actions row (used under headings, inside cards). */
.actions-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.actions-row form { margin: 0; display: inline-flex; }

/* Popover under a <summary class="btn"> (install plugin / theme). */
details.inline-action { position: relative; display: inline-block; }
details.inline-action > summary { list-style: none; cursor: pointer; }
details.inline-action > summary::-webkit-details-marker { display: none; }
details.inline-action > summary::marker { content: ""; }
details.inline-action > .popover {
  position: absolute;
  z-index: 20;
  top: calc(100% + 0.4rem);
  left: 0;
  width: min(24rem, calc(100vw - 1.5rem));
  padding: 0.85rem;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
}
details.inline-action > .popover.right { left: auto; right: 0; }
details.inline-action > .popover .field { margin-bottom: 0; }
details.inline-action > .popover .popover-foot { display: flex; justify-content: flex-end; gap: 0.4rem; margin-top: 0.6rem; }

/* ── Log console ─────────────────────────────────────────────── */
.log-toolbar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin: 0 0 0.6rem;
}
.log-toolbar .log-filter { flex: 1 1 14rem; min-width: 0; }
.log-toolbar .log-filter input { padding: 0.45rem 0.7rem; font-size: 0.85rem; }
.log-toolbar form { margin: 0; }
.log-toolbar .seg-btns { display: inline-flex; gap: 0; border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; }
.log-toolbar .seg-btns form + form { border-left: 1px solid var(--border); }
.log-toolbar .seg-btns .btn { border: 0; border-radius: 0; }
.log-console {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.75rem 0.9rem;
  min-height: 9rem;
  font-size: 0.82rem;
}
.log-console pre {
  margin: 0;
  background: transparent;
  border: 0;
  padding: 0;
}
.log-console .muted { color: var(--text-soft); }

/* ── Table modifiers used on the site page ───────────────────── */
.table-wrap.flush { margin: 0.25rem -1.5rem 0; border-radius: 0; }
.table-wrap.flush-b { margin: 0.25rem -1.5rem -1.4rem; }
.table-wrap.inset {
  margin: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.table-wrap.inset th:first-child, .table-wrap.inset td:first-child { padding-left: 0.85rem; }
.table-wrap.inset th:last-child,  .table-wrap.inset td:last-child  { padding-right: 0.85rem; }
@media (max-width: 760px) {
  .table-wrap.flush { margin: 0.25rem -1rem 0; }
  .table-wrap.flush-b { margin: 0.25rem -1rem -1rem; }
}
@media (max-width: 480px) {
  /* Match the ≤480 card padding (0.85rem), see .table-wrap there. */
  .table-wrap.flush { margin: 0.25rem -0.85rem 0; }
  .table-wrap.flush-b { margin: 0.25rem -0.85rem -0.85rem; }
}
td .cell-sub { display: block; font-size: 0.76rem; color: var(--text-soft); margin-top: 0.1rem; }
td .cell-sub code { font-size: 0.74rem; }

/* ── Small helpers used throughout the site page ─────────────── */
.note {
  font-size: 0.8rem;
  color: var(--text-dim);
  line-height: 1.55;
  margin: 0;
}
.note + .note { margin-top: 0.4rem; }
.note.boxed {
  padding: 0.6rem 0.8rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.stack-sm { display: flex; flex-direction: column; gap: 0.5rem; }
.stack-md { display: flex; flex-direction: column; gap: 0.9rem; }
.cluster { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.cluster form { margin: 0; }
.mt-0 { margin-top: 0 !important; }
.mb-0 { margin-bottom: 0 !important; }
.w-full { width: 100%; }
.mono { font-family: var(--font-mono); }
.text-danger { color: var(--danger); }
.text-warn { color: var(--warn); }
.text-success { color: var(--success); }
.pill.neutral { background: var(--surface-2); color: var(--text-soft); }
.pill.accent { background: var(--primary-soft); color: var(--primary-h); border-color: transparent; }
.pill.accent::before { display: none; }
.tag.accent { background: var(--primary-soft); color: var(--primary-h); border-color: transparent; }
.tag.warn { background: var(--warn-soft); color: var(--warn); border-color: color-mix(in oklab, var(--warn) 30%, transparent); }
.tag.success { background: var(--success-soft); color: var(--success); border-color: color-mix(in oklab, var(--success) 30%, transparent); }
.tag.info { background: var(--info-soft); color: var(--info); border-color: color-mix(in oklab, var(--info) 30%, transparent); }
.tag.muted { opacity: 0.75; }

/* Danger zone layout. */
.danger-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.5rem 1.5rem;
  align-items: center;
  padding: 1rem 0;
  border-top: 1px solid var(--border);
}
.danger-row:first-of-type { border-top: 0; padding-top: 0.25rem; }
.danger-row .d-title { font-weight: 600; font-size: 0.92rem; }
.danger-row .d-help { font-size: 0.82rem; color: var(--text-dim); line-height: 1.5; max-width: 70ch; }
.danger-row .d-opts { display: flex; gap: 0.5rem 1.25rem; flex-wrap: wrap; margin-top: 0.45rem; }
.danger-row .d-opts .field-checkbox { margin: 0; }
@media (max-width: 640px) {
  .danger-row { grid-template-columns: minmax(0, 1fr); }
  .setting-row { grid-template-columns: minmax(0, 1fr) auto; }
}

/* Monitor probe bars. */
.probe-bars {
  display: flex;
  gap: 2px;
  height: 40px;
  align-items: flex-end;
  padding: 0.4rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
}
.probe-bars span {
  display: inline-block;
  flex: 1 1 0;
  min-width: 2px;
  max-width: 6px;
  border-radius: 1px;
  background: var(--success);
  min-height: 6px;
}
.probe-bars span.fail { background: var(--danger); height: 100%; }

/* Credentials panel sits above the site bar on a fresh create; give it
   the page gutter back since the bar bleeds past it. */
.creds-panel { margin-top: 0.75rem; }

/* Site page — the small pieces the sections above lean on. */
.site-alerts {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin-top: 1rem;
}
.site-alerts .flash { margin: 0 !important; }
.site-alerts:not(:has(.flash)) { display: none; }
.site-alerts > [hx-get]:empty { display: none; }

/* A plain text disclosure inside a card: "Reset the password…". */
details.disclosure > summary {
  list-style: none;
  cursor: pointer;
  font-size: 0.84rem;
  color: var(--text-dim);
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}
details.disclosure > summary::-webkit-details-marker { display: none; }
details.disclosure > summary::marker { content: ""; }
details.disclosure > summary::before {
  content: "";
  width: 0.4rem;
  height: 0.4rem;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg);
  transition: transform var(--t-fast);
  flex: none;
  margin-right: 0.15rem;
}
details.disclosure[open] > summary::before { transform: rotate(45deg); }
details.disclosure > summary:hover { color: var(--text); }
details.disclosure.danger > summary { color: var(--danger); font-weight: 600; }

/* The "Resources" and "How this site is backed up" fact rows run inline. */
.facts.inline { flex-direction: row; flex-wrap: wrap; gap: 0.75rem 2rem; }
.help { font-size: 0.78rem; color: var(--text-dim); line-height: 1.5; margin-top: 0.2rem; display: block; }

/* Phone-width escape hatch for inline-styled rows. Many filter bars and
   inline forms set their grid/flex layout in a style="" attribute (one-off
   layouts not worth a class each), so a media query can only reach them
   with !important. `.m-stack` folds a multi-column grid into one column;
   `.m-wrap` lets a flex row wrap instead of pushing its last control
   past the right edge (the /jobs "Clear filters" button sat at 405px on
   a 375px screen). */
@media (max-width: 640px) {
  .m-stack { grid-template-columns: minmax(0, 1fr) !important; }
  .m-wrap { flex-wrap: wrap !important; }
  .m-stack > *, .m-wrap > * { min-width: 0; }
}

/* Page-header children may shrink to the row: a title with a long domain
   or a "Back to <domain>" button otherwise kept its max-content width
   and ran past the right edge at phone width. Buttons there may wrap. */
.page-header > * { min-width: 0; max-width: 100%; }
.page-header .actions .btn,
.page-header .m-wrap .btn { white-space: normal; overflow-wrap: anywhere; text-align: center; }

/* A native file input has a fixed intrinsic width (~230px) that ignores
   its container; next to the avatar on /profile it poked out of the card. */
input[type="file"] { max-width: 100%; min-width: 0; color: var(--text-dim); font-size: 0.85rem; }
/* The browser's grey "Choose File" chip looked like a stray widget; match .btn.small. */
input[type="file"]::file-selector-button {
  margin-right: 0.6rem;
  padding: 0.3rem 0.7rem;
  font: inherit;
  font-size: 0.78rem;
  line-height: 1.2;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  cursor: pointer;
}
input[type="file"]::file-selector-button:hover { background: var(--surface-2); border-color: var(--border-strong); }

/* ============================================================
 * Profiles & limits — /profiles, /profiles/new, /profiles/<id>/edit
 * ============================================================ */
.group .profiles-table th:first-child, .group .profiles-table td:first-child { padding-left: 1.25rem; }
.group .profiles-table th:last-child,  .group .profiles-table td:last-child  { padding-right: 1.25rem; }
.profiles-table th { background: transparent; }
.profiles-table td { vertical-align: top; font-variant-numeric: tabular-nums; }
.profiles-table td:first-child { min-width: 12rem; }
@media (max-width: 640px) { .profiles-table td:first-child { min-width: 9rem; } }
.profiles-table .profile-name { font-weight: 600; color: var(--text); }
.profiles-table .cell-sub { font-size: 0.76rem; color: var(--text-soft); margin-top: 2px; line-height: 1.45; }
.profiles-table .cell-sub.tone-warn { color: var(--warn); }
.empty .empty-actions { display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap; }

/* The profile form: one group per topic, then a save bar that stays in
   reach on a form this long. Buttons on the left: the running-jobs toast
   sits over the bottom-right corner. */
.profile-form { margin-top: var(--group-gap); }
.profile-form .field-row + .field-row { margin-top: 0.25rem; }
.profile-form .item .field-row:last-child .field,
.profile-form .item > .field:last-child { margin-bottom: 0; }
.profile-form textarea.mono { font-family: var(--font-mono); font-size: 0.85rem; width: 100%; }
.profile-form input.upper { text-transform: uppercase; }
.profile-form .profile-wp-help { margin: 0 0 0.9rem; }
/* The group notes are asides; on a phone they only crowd the heading. */
@media (max-width: 640px) { .profile-form .ghead-note { display: none; } }
.form-bar {
  position: sticky;
  bottom: 0;
  z-index: 5;
  display: flex;
  gap: 0.5rem;
  margin-top: var(--group-gap);
  padding: 0.75rem 1.25rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}

/* Websites on a profile (edit page). */
.profile-sites-head { display: flex; align-items: flex-start; gap: 1rem; justify-content: space-between; }
.profile-sites-head .help { margin: 0; max-width: 46rem; }
.profile-sites-head form { flex: none; }
@media (max-width: 640px) {
  .profile-sites-head { flex-direction: column; }
}
.profile-sites {
  list-style: none;
  margin: 0.9rem 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.25rem;
  font-size: 0.85rem;
}
.profile-sites a { color: var(--text); }
.profile-sites a:hover { color: var(--primary-h); }
.profile-sites + .help { margin-top: 0.6rem; }

/* WordPress asset library picker under each plugins/themes textarea — tick
   uploaded ZIPs by name instead of looking up `@asset:N` ids by hand. */
details.asset-picker {
  margin-top: 0.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
details.asset-picker > summary {
  cursor: pointer;
  padding: 0.5rem 0.75rem;
  font-size: 0.82rem;
  font-weight: 600;
  list-style: none;
  user-select: none;
}
details.asset-picker > summary::-webkit-details-marker { display: none; }
details.asset-picker > summary::after {
  content: "▾";
  float: right;
  color: var(--text-soft);
  transition: transform 0.15s ease;
}
details.asset-picker[open] > summary::after { transform: rotate(180deg); }
.asset-picker-body { max-height: 14rem; overflow-y: auto; padding: 0.3rem 0.4rem 0.5rem; }
.asset-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.35rem 0.55rem;
  border-radius: var(--radius-xs);
  cursor: pointer;
  font-size: 0.85rem;
}
.asset-row:hover { background: var(--surface-2); }
.asset-row input { margin: 0; flex: none; }
.asset-name { flex: 1; min-width: 0; overflow-wrap: anywhere; font-weight: 500; }

/* Profile (/profile): identity strip, inline form rows, 2FA enrolment. */
.me-cols { display: grid; grid-template-columns: 1fr; gap: var(--group-gap); align-items: start; }
.me-col { min-width: 0; }
@media (min-width: 1200px) {
  .me-cols { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}
.me-id { display: flex; gap: 1.25rem; align-items: center; flex-wrap: wrap; }
.me-avatar {
  position: relative;
  display: grid;
  place-items: center;
  width: 72px;
  height: 72px;
  flex: none;
  border-radius: 50%;
  overflow: hidden;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text-dim);
  font-size: 1.6rem;
  font-weight: 600;
}
.me-avatar img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.me-facts {
  flex: 1;
  min-width: 14rem;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 0.8rem 1.25rem;
}
.me-head { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.5rem; }
.me-head h3 { margin: 0; }
.me-warn { color: var(--warn); }
.me-inline { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0.5rem; margin: 0; }
.me-form { max-width: 44rem; }
.me-form .field-row .field { margin-bottom: 0.75rem; }
/* Wraps by available width, not viewport: a half-width column at 1200px is
   too narrow for three fields side by side. */
.me-form .field-row { grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: 0 1rem; align-items: end; }
/* 2FA enrolment is the only group outside .me-cols; keep it readable. */
.me-page > .group { max-width: 56rem; }
.me-page input.me-otp {
  width: 8.5rem;
  font-size: 1.05rem;
  letter-spacing: 0.2em;
  font-variant-numeric: tabular-nums;
}
.me-file { font-size: 0.85rem; max-width: 100%; }
.me-fold { margin-top: 1rem; }
.me-fold > summary { cursor: pointer; font-size: 0.85rem; color: var(--text-dim); margin-bottom: 0.75rem; }
.me-ended > summary { cursor: pointer; }
.me-ended-list { list-style: none; margin: 0.75rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.me-ended-list li { display: flex; flex-direction: column; gap: 0.1rem; font-size: 0.88rem; }
.me-steps { margin: 0 0 1rem; padding-left: 1.2rem; line-height: 1.7; font-size: 0.92rem; }
.me-enrol { display: grid; grid-template-columns: auto 1fr; gap: 1.5rem; align-items: start; }
.me-qr { background: #fff; border-radius: var(--radius-md); padding: 0.4rem; line-height: 0; }
.me-secret {
  display: block;
  margin-top: 0.2rem;
  padding: 0.4rem 0.6rem;
  font-size: 0.95rem;
  word-break: break-all;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
}
.me-codes { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.3rem; margin-top: 0.3rem; }
.me-codes code {
  padding: 0.25rem 0.5rem;
  text-align: center;
  font-size: 0.95rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
}
@media (max-width: 640px) {
  .me-enrol { grid-template-columns: 1fr; }
}

/* A form whose POST is in flight (hosting detail in-place saves). */
form[aria-busy="true"] { opacity: 0.6; cursor: progress; }
form[aria-busy="true"] * { pointer-events: none; }

/* ============================================================
 *  First-run setup wizard (/setup/*)
 *  Its own frame — no sidebar, nothing to navigate to until it is done —
 *  built from the same tokens, groups and pills as every other page.
 * ============================================================ */
.setup-body { background: var(--bg); min-height: 100vh; }
.setup-shell {
  display: grid;
  grid-template-columns: 15rem minmax(0, 1fr);
  min-height: 100vh;
}
.setup-rail {
  border-right: 1px solid var(--border);
  background: var(--bg-2);
  padding: 1.5rem 0.9rem;
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}
.setup-brand {
  font: 800 0.95rem/1 var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0 0.5rem;
}
.setup-brand .hy { color: var(--brand-accent); }
.setup-brand .dot { color: var(--text-soft); font-weight: 500; }
.setup-brand small {
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--text-soft);
  margin-left: 0.35rem;
  text-transform: lowercase;
}
.setup-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.setup-steps li > a,
.setup-steps li > span {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.45rem 0.5rem;
  border-radius: var(--radius-sm);
  color: var(--text-dim);
  font-size: 0.88rem;
  text-decoration: none;
}
.setup-steps li > a:hover { background: var(--surface-3); color: var(--text); text-decoration: none; }
.setup-steps li.is-current > a,
.setup-steps li.is-current > span { background: var(--surface-3); color: var(--text); font-weight: 500; }
.setup-dot {
  width: 1.2rem;
  height: 1.2rem;
  flex: none;
  border-radius: 50%;
  border: 1px solid var(--border-strong);
  display: grid;
  place-items: center;
  font-size: 0.68rem;
  font-variant-numeric: tabular-nums;
  color: var(--text-soft);
}
.setup-steps li.is-done .setup-dot { background: var(--success); border-color: var(--success); color: var(--primary-fg); }
.setup-steps li.is-skipped .setup-dot { background: var(--surface-3); border-color: var(--border-strong); color: var(--text-soft); }
.setup-step-name { min-width: 0; }
.setup-opt { margin-left: auto; font-size: 0.68rem; color: var(--text-soft); }
.setup-foot { margin: auto 0 0; padding: 0 0.5rem; font-size: 0.78rem; color: var(--text-soft); line-height: 1.5; }

.setup-pane {
  padding: 2.25rem 2.5rem 3rem;
  max-width: 52rem;
  width: 100%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--group-gap);
}
.setup-pane > .group + .group { margin-top: 0; }
.setup-head h1 { font-size: 1.45rem; font-weight: 600; letter-spacing: -0.01em; margin: 0.2rem 0 0; text-wrap: balance; }
.setup-eyebrow {
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-soft);
  font-weight: 600;
}
.setup-lead { color: var(--text-dim); margin: 0.4rem 0 0; max-width: 62ch; line-height: 1.55; }
.setup-pane > .flash { margin: 0; }

.setup-actions { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.setup-actions form { margin: 0; }
.setup-spacer { flex: 1; }
.setup-skip { margin: -0.5rem 0 0; display: flex; justify-content: flex-end; }
.setup-note { margin: -0.5rem 0 0; }
.setup-wrap { overflow-wrap: anywhere; min-width: 0; }

.setup-code { font-size: 1.15rem; letter-spacing: 0.12em; text-transform: uppercase; max-width: 22rem; }
.setup-otp { font-size: 1.1rem; letter-spacing: 0.2em; max-width: 10rem; }

/* Picker rows: one checkbox + title + one line of help. */
.group > .setup-choice {
  display: flex;
  gap: 0.85rem;
  align-items: flex-start;
  padding: 0.9rem 1.25rem;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
}
.setup-choice input[type="checkbox"] { width: auto; margin-top: 0.2rem; accent-color: var(--primary); }
.setup-choice-body { min-width: 0; display: flex; flex-direction: column; gap: 0.2rem; }
.setup-choice-title { font-weight: 500; display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.setup-inline-select { width: auto; padding: 0.2rem 2rem 0.2rem 0.6rem; font-size: 0.85rem; }
.setup-inline-field { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.35rem; font-size: 0.82rem; color: var(--text-dim); }
.setup-inline-field input { width: 6rem; padding: 0.3rem 0.5rem; }

/* One line per component / check: name, then its pill. */
.group > .setup-prog {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.8rem 1.25rem;
  border-bottom: 1px solid var(--border);
}
.group > .setup-prog:last-child { border-bottom: none; }
.setup-prog-name { flex: 1; min-width: 0; }
.group > .setup-prog.is-err { background: color-mix(in oklab, var(--danger) 5%, var(--surface)); }
.group > .setup-prog.is-failed { background: color-mix(in oklab, var(--danger) 5%, var(--surface)); }
.group > details.setup-log { padding: 0.7rem 1.25rem; border-bottom: 1px solid var(--border); }
.setup-log summary { cursor: pointer; font-size: 0.82rem; color: var(--text-dim); }
.setup-log pre {
  margin: 0.6rem 0 0;
  max-height: 16rem;
  overflow: auto;
  font-size: 0.75rem;
  line-height: 1.5;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.75rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Two-factor: QR beside the steps, backup codes in a grid. */
.setup-qr { display: flex; gap: 1.5rem; align-items: flex-start; flex-wrap: wrap; }
.setup-qr-img svg { display: block; width: 11rem; height: 11rem; border-radius: var(--radius-sm); border: 1px solid var(--border); background: #fff; }
.setup-qr-side { flex: 1 1 16rem; min-width: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.setup-qr-side h3 { margin-top: 0.4rem; }
.setup-secret { overflow-wrap: anywhere; font-size: 0.85rem; }
.setup-codes {
  list-style: none;
  margin: 0.75rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
  gap: 0.4rem;
}
.setup-codes code { display: block; text-align: center; padding: 0.35rem; font-size: 0.85rem; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

@media (max-width: 760px) {
  .setup-shell { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto 1fr; }
  .setup-rail { border-right: 0; border-bottom: 1px solid var(--border); padding: 1rem; }
  .setup-steps { flex-direction: row; overflow-x: auto; gap: 0.25rem; }
  .setup-steps .setup-step-name, .setup-steps .setup-opt { display: none; }
  .setup-steps li.is-current .setup-step-name { display: inline; }
  .setup-foot { display: none; }
  .setup-pane { padding: 1.25rem 1rem 2rem; }
}

/* /protection — refusals per hour, stacked: the part that counts toward a
   ban (magenta) under the part that is only refused (blue). Same series
   hues as /stats; labels under the bars at each UTC midnight. */
.prot-chart { padding-bottom: 1.6rem; position: relative; }
.prot-chart svg { display: block; width: 100%; height: 96px; }
.prot-chart .prot-ban   { fill: var(--viz-magenta); }
.prot-chart .prot-other { fill: var(--viz-blue); opacity: 0.55; }
.prot-chart g:hover rect { opacity: 1; filter: brightness(1.15); }
.prot-days { position: relative; height: 1rem; margin-top: 0.35rem; font-size: 0.7rem; color: var(--text-soft); }
.prot-days > span { position: absolute; top: 0; transform: translateX(-50%); white-space: nowrap; }
.prot-key-ban   { --viz: var(--viz-magenta); }
.prot-key-other { --viz: var(--viz-blue); }
.prot-table th { white-space: nowrap; }
.prot-req { word-break: break-all; max-width: 28rem; }
@media (max-width: 640px) {
  .prot-days > span:nth-child(even) { display: none; }
}
