/* ═══════════════════════════════════════════════════════════════════════════
   QYVERIX DESIGN SYSTEM
   The customer hosting platform at cloud.qyverix.in.

   Not the panel's stylesheet. panel.css is violet, built to hPanel parity for
   an internal tool; this is a product a stranger pays for, and the people who
   arrive here come from qyverix.in and srv.qyverix.in, which are gold on
   near-black. Two palettes across the signup boundary would read as two
   companies.

   Rules this file holds itself to
   -------------------------------
   1. ONE accent, and it is gold. Used for primary action and current state
      only -- never decoration, never a gradient wash. The restraint is what
      makes it read as premium instead of as a theme.
   2. STATUS is a separate axis from accent, so a live site never competes
      visually with a primary button. Status always renders as dot + label,
      never colour alone: it has to survive greyscale and colour blindness.
   3. Every colour pair below was MEASURED with the repo's own contrast.py,
      not eyeballed. The ratios are in the comments. Two of them changed the
      design: dark #7e7e8e fails on the top surface (4.37), so tertiary text
      collapses into secondary in dark; and the light gold button fills with
      #7c5c1e (6.06) rather than #8a7434 (4.46, under AA).
   4. Elevation on dark is a lighter ground plus a hairline, not a shadow.
      Shadows belong only to things that genuinely float.
   5. No loose pixel values in component rules -- space comes from the scale.
   ═══════════════════════════════════════════════════════════════════════════ */

@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');

/* ─────────────────────────────────────────────────────────────── tokens ── */

:root {
  color-scheme: light dark;

  /* Each token is declared twice: a plain dark value first, then the
     light-dark() pair. An engine without light-dark() discards the second
     declaration and renders the brand's dark theme -- a correct page, not an
     unstyled one. That failure mode is the reason for the duplication, and it
     is the same mechanism DESIGN-TOKENS.md already documents. */

  /* ground ------------------------------------------------------------- */
  --bg:          #050508;  --bg:          light-dark(#f7f4ec, #050508);
  --surface-1:   #0b0b11;  --surface-1:   light-dark(#fffdf7, #0b0b11);  /* cards        */
  --surface-2:   #12121a;  --surface-2:   light-dark(#f2eee2, #12121a);  /* inputs, rows */
  --surface-3:   #191922;  --surface-3:   light-dark(#ebe6d8, #191922);  /* hover, menus */

  --border:        rgba(240,236,224,.09);
  --border:        light-dark(rgba(20,20,31,.10), rgba(240,236,224,.09));
  --border-strong: rgba(240,236,224,.17);
  --border-strong: light-dark(rgba(20,20,31,.20), rgba(240,236,224,.17));

  /* type — dark ratios vs --surface-1 #0b0b11, light vs #fffdf7 --------- */
  --text-1: #f0ece0;  --text-1: light-dark(#14141f, #f0ece0);  /* 16.61 / 17.96 AAA */
  --text-2: #9a9aaa;  --text-2: light-dark(#565665, #9a9aaa);  /*  7.08 /  7.08 AAA */
  /* Tertiary is #8a8a9a in dark, NOT something dimmer: #7e7e8e measures
     4.37 on --surface-3 and fails. In light there is headroom for a real
     third step. DESIGN-TOKENS.md bars sub-AA greys from informational text
     and this is that rule, applied. */
  --text-3: #8a8a9a;  --text-3: light-dark(#636374, #8a8a9a);  /*  5.14 /  5.79 AA  */

  /* accent -------------------------------------------------------------- */
  --accent:      #c8a84b;  --accent:      light-dark(#7c5c1e, #c8a84b);  /* 8.88 / 6.06 */
  --accent-hi:   #e6c96e;  --accent-hi:   light-dark(#634913, #e6c96e);
  /* Strokes and indicators only -- WCAG 1.4.11 wants 3.0, this is 4.13.
     It is NOT safe as a button fill in light; see --accent-fill. */
  --accent-ui:   #c8a84b;  --accent-ui:   light-dark(#8a7434, #c8a84b);
  --accent-fill: #c8a84b;  --accent-fill: light-dark(#7c5c1e, #c8a84b);
  --accent-ink:  #050508;  --accent-ink:  light-dark(#fffdf7, #050508);  /* 8.88 / 6.06 */
  --accent-wash: rgba(200,168,75,.10);
  --accent-wash: light-dark(rgba(124,92,30,.09), rgba(200,168,75,.10));
  --accent-line: rgba(200,168,75,.30);
  --accent-line: light-dark(rgba(124,92,30,.32), rgba(200,168,75,.30));

  /* status — a separate axis, never reused for accent -------------------- */
  --live:  #34d399;  --live:  light-dark(#0e7a54, #34d399);  /* 10.21 / 5.25 */
  --build: #fbbf24;  --build: light-dark(#8a6100, #fbbf24);  /* 11.75 / 5.45 */
  --fail:  #f87171;  --fail:  light-dark(#b3261e, #f87171);  /*  7.09 / 6.43 */
  --info:  #1ecbc8;  --info:  light-dark(#0f7d7b, #1ecbc8);  /*  9.75 / 4.87 */
  --idle:  #8a8a9a;  --idle:  light-dark(#636374, #8a8a9a);

  --live-wash:  rgba(52,211,153,.12);  --live-wash:  light-dark(rgba(14,122,84,.10), rgba(52,211,153,.12));
  --build-wash: rgba(251,191,36,.12);  --build-wash: light-dark(rgba(138,97,0,.10),  rgba(251,191,36,.12));
  --fail-wash:  rgba(248,113,113,.12); --fail-wash:  light-dark(rgba(179,38,30,.09), rgba(248,113,113,.12));
  --info-wash:  rgba(30,203,200,.12);  --info-wash:  light-dark(rgba(15,125,123,.10),rgba(30,203,200,.12));

  /* space — 8px system. 4px survives as --s1 for the few places a hairline
     gap is genuinely right (icon to label), but everything structural steps
     in 8s, which is what stops a layout drifting into arbitrary values. */
  --s1: 4px;  --s2: 8px;   --s3: 12px;  --s4: 16px;  --s5: 20px;
  --s6: 24px; --s7: 32px;  --s8: 40px;  --s9: 48px;  --s10: 64px;

  /* radius. 12px on the containers that are genuinely panels; the small
     steps stay small because a 12px radius on a 22px status pill or a 26px
     input looks inflated rather than soft. */
  --r1: 4px; --r2: 6px; --r3: 8px; --r4: 12px; --rp: 999px;

  /* type scale — DENSE. Body is 13px and secondary 12px, which is the
     register Linear and Cloudflare operate in: someone who lives in this
     product all day wants more on screen, not larger text. 14px survives for
     the few places that need emphasis inside dense copy. */
  --t-11: 11px; --t-12: 12px; --t-13: 13px; --t-14: 14px;
  --t-16: 16px; --t-20: 20px; --t-24: 24px; --t-30: 30px;
  /* Retained so nothing referencing them breaks while the views are
     rebuilt; both now resolve one step down. */
  --t-26: var(--t-24); --t-34: var(--t-30);

  --sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;

  /* elevation — only for things that float ------------------------------ */
  --shadow-1: 0 1px 2px rgba(0,0,0,.30);
  --shadow-1: light-dark(0 1px 2px rgba(20,20,31,.06), 0 1px 2px rgba(0,0,0,.30));
  --shadow-2: 0 4px 12px rgba(0,0,0,.45), 0 1px 2px rgba(0,0,0,.30);
  --shadow-2: light-dark(0 4px 12px rgba(20,20,31,.10), 0 4px 12px rgba(0,0,0,.45));
  --shadow-3: 0 16px 48px rgba(0,0,0,.60), 0 2px 8px rgba(0,0,0,.40);
  --shadow-3: light-dark(0 16px 48px rgba(20,20,31,.16), 0 16px 48px rgba(0,0,0,.60));

  /* motion — communicates state change, nothing else -------------------- */
  --fast: 120ms; --base: 180ms; --slow: 240ms;
  --ease: cubic-bezier(.2, 0, 0, 1);

  /* layout -------------------------------------------------------------- */
  --rail-w: 56px;
  --side: 248px;
  --content-max: 1280px;
  /* Roomy where the eye lands, dense where the data is. */
  --page-head-y: var(--s8);     /* 40px around a page title */
  --section-gap: var(--s7);     /* 32px between sections    */
  --row-h: 40px;                /* table row                */

  --z-sticky: 10; --z-drawer: 40; --z-modal: 50; --z-toast: 60; --z-palette: 70;
}

/* An explicit choice wins in both directions; "Auto" sets no attribute and
   lets prefers-color-scheme decide. Three states, one mechanism. */
:root[data-theme="dark"]  { color-scheme: dark; }
:root[data-theme="light"] { color-scheme: light; }

/* ───────────────────────────────────────────────────────────────── base ── */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text-1);
  font: 400 var(--t-13)/1.55 var(--sans);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Quota counters, prices and metrics must not jitter as they update. */
  font-variant-numeric: tabular-nums;
}

h1, h2, h3, h4, p, figure, blockquote, dl, dd { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 2px; }

img, svg { display: block; max-width: 100%; }

code, kbd, samp, pre, .mono { font-family: var(--mono); font-size: .95em; }

hr { border: 0; border-top: 1px solid var(--border); margin: var(--s6) 0; }

::selection { background: var(--accent-line); color: var(--text-1); }

/* Focus is keyboard-only and unmissable. Full keyboard operation is a
   requirement here, not a nicety, so the ring is never removed -- only
   scoped to :focus-visible so a mouse click does not paint it. */
:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--accent-ui);
  outline-offset: 2px;
  border-radius: var(--r1);
}

/* Skip link — the first thing a keyboard or screen-reader user meets. */
.skip {
  position: fixed; top: var(--s2); left: var(--s2); z-index: var(--z-palette);
  padding: var(--s2) var(--s4); background: var(--surface-1);
  border: 1px solid var(--accent-ui); border-radius: var(--r2);
  transform: translateY(-200%); transition: transform var(--fast) var(--ease);
}
.skip:focus-visible { transform: none; text-decoration: none; }

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

/* ─────────────────────────────────────────────────────────── typography ── */

.h1 { font-size: var(--t-24); line-height: 1.2;  font-weight: 600; letter-spacing: -.02em; }
.h2 { font-size: var(--t-20); line-height: 1.3;  font-weight: 600; letter-spacing: -.01em; }
.h3 { font-size: var(--t-16); line-height: 1.4;  font-weight: 600; }
.h4 { font-size: var(--t-14); line-height: 1.45; font-weight: 600; }
.display { font-size: var(--t-30); line-height: 1.15; font-weight: 600; letter-spacing: -.025em; }

.body   { font-size: var(--t-13); line-height: 1.6; }
.small  { font-size: var(--t-12); line-height: 1.5; }
.micro  { font-size: var(--t-12); line-height: 1.45; }

/* Section labels. Uppercase tracking is the one typographic flourish this
   system allows itself, because it separates a label from its value without
   needing a colour or a rule. */
.label {
  font-size: var(--t-11); font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-3);
}

.dim    { color: var(--text-2); }
.dimmer { color: var(--text-3); }
.strong { font-weight: 600; }
.tnum   { font-variant-numeric: tabular-nums; }

/* Technical values — domains, IPs, ports, commit SHAs, IDs, connection
   strings. Monospace here is information, not decoration: it tells the reader
   "this is a literal you may need to copy exactly". */
.tech { font-family: var(--mono); font-size: var(--t-13); letter-spacing: -.01em; }

.trunc { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ────────────────────────────────────────────────────────────── layout ── */

/* ONE sidebar, always present. The previous rail + conditional-sidebar grid
   collapsed for seven of twelve sections, so the content shifted sideways as
   you navigated. A layout that moves under you does not read as premium no
   matter how it is styled. */

.shell {
  display: grid;
  grid-template-columns: var(--side) minmax(0, 1fr);
  min-height: 100vh; min-height: 100dvh;
}
.shell-main { min-width: 0; display: flex; flex-direction: column; }

/* sidebar ------------------------------------------------------------- */
.nav {
  position: sticky; top: 0;
  height: 100vh; height: 100dvh;
  display: flex; flex-direction: column;
  background: var(--surface-1);
  border-right: 1px solid var(--border);
}
.nav-brand {
  display: flex; align-items: center; gap: var(--s2);
  height: 52px; padding: 0 var(--s4);
  border-bottom: 1px solid var(--border);
  flex: none;
}
.nav-close { display: none; margin-left: auto; }

.brand {
  display: inline-flex; align-items: center; gap: var(--s2);
  color: var(--text-1); font-weight: 600; font-size: var(--t-13);
  letter-spacing: -.01em; text-decoration: none;
}
.brand:hover { text-decoration: none; }
.brand-sub {
  color: var(--text-3); font-weight: 500; font-size: var(--t-11);
  text-transform: uppercase; letter-spacing: .08em; margin-left: 2px;
}

/* workspace switcher */
.ws { position: relative; padding: var(--s3); border-bottom: 1px solid var(--border); flex: none; }
.ws-btn {
  display: flex; align-items: center; gap: var(--s3); width: 100%;
  padding: var(--s2); border: 1px solid transparent; border-radius: var(--r3);
  background: transparent; color: inherit; cursor: pointer; text-align: left;
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.ws-btn:hover:not(:disabled) { background: var(--surface-2); border-color: var(--border); }
.ws-btn:disabled { cursor: default; }
.ws-mark {
  display: grid; place-items: center; flex: none;
  width: 26px; height: 26px; border-radius: var(--r2);
  background: var(--accent-wash); color: var(--accent);
  font-weight: 600; font-size: var(--t-12);
}
.ws-mark.sm { width: 20px; height: 20px; font-size: var(--t-11); border-radius: var(--r1); }
.ws-text { min-width: 0; display: flex; flex-direction: column; }
.ws-name { font-size: var(--t-13); font-weight: 500; overflow: hidden;
           text-overflow: ellipsis; white-space: nowrap; }
.ws-plan { font-size: var(--t-11); color: var(--text-3); }
.ws-chev { width: 14px; height: 14px; color: var(--text-3); flex: none; margin-left: auto; }
.ws-menu { position: absolute; left: var(--s3); right: var(--s3); top: calc(100% - var(--s2));
           z-index: 30; }

/* nav items */
.nav-scroll { flex: 1; overflow-y: auto; padding: var(--s3) var(--s2) var(--s4); }
.nav-group { margin-bottom: var(--s4); }
.nav-label {
  padding: var(--s2) var(--s3) var(--s1);
  font-size: var(--t-11); font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-3);
}
.nav-item {
  display: flex; align-items: center; gap: var(--s3);
  height: 32px; padding: 0 var(--s3);
  border-radius: var(--r2);
  color: var(--text-2); font-size: var(--t-13); font-weight: 500;
  text-decoration: none;
  transition: color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.nav-item:hover { color: var(--text-1); background: var(--surface-2); text-decoration: none; }
.nav-item svg { width: 15px; height: 15px; flex: none; opacity: .75; }
/* Current state is a gold left marker plus a lifted ground -- not colour
   alone, and not a full gold fill that would compete with the page's one
   primary button. */
.nav-item[aria-current="page"] {
  color: var(--text-1); background: var(--surface-2); font-weight: 600;
  box-shadow: inset 2px 0 0 var(--accent);
}
.nav-item[aria-current="page"] svg { opacity: 1; color: var(--accent); }
.nav-count {
  margin-left: auto; font-size: var(--t-11); color: var(--text-3);
  font-variant-numeric: tabular-nums;
}

.nav-foot { flex: none; padding: var(--s2); border-top: 1px solid var(--border); }
.acct { position: relative; margin-top: var(--s1); }
.acct-btn {
  display: flex; align-items: center; gap: var(--s3); width: 100%;
  height: 32px; padding: 0 var(--s3); border: 0; border-radius: var(--r2);
  background: transparent; color: var(--text-2); cursor: pointer;
  font: 500 var(--t-13)/1 var(--sans); text-align: left;
}
.acct-btn:hover { background: var(--surface-2); color: var(--text-1); }
.acct-text { min-width: 0; }
.acct-menu { position: absolute; left: 0; right: 0; bottom: calc(100% + var(--s1)); z-index: 30; }
.menu-head { padding: var(--s2) var(--s3); }

/* topbar -------------------------------------------------------------- */
.topbar {
  position: sticky; top: 0; z-index: var(--z-sticky);
  display: flex; align-items: center; gap: var(--s3);
  height: 52px; padding: 0 var(--s6);
  background: color-mix(in srgb, var(--bg) 90%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--border);
}
.nav-open { display: none; }

.searchbtn {
  display: inline-flex; align-items: center; gap: var(--s2);
  height: 28px; padding: 0 var(--s2) 0 var(--s3);
  border: 1px solid var(--border); border-radius: var(--r2);
  background: var(--surface-2); color: var(--text-3);
  font: 400 var(--t-12)/1 var(--sans); cursor: pointer;
  transition: border-color var(--fast) var(--ease), color var(--fast) var(--ease);
}
.searchbtn:hover { border-color: var(--border-strong); color: var(--text-1); }
.searchbtn svg { width: 13px; height: 13px; }

.crumb { display: flex; align-items: center; gap: var(--s2); font-size: var(--t-12);
         min-width: 0; overflow: hidden; }
.crumb a { color: var(--text-3); text-decoration: none; white-space: nowrap; }
.crumb a:hover { color: var(--text-1); }
.crumb .sep { color: var(--text-3); opacity: .45; }
.crumb .here { color: var(--text-1); font-weight: 500; white-space: nowrap; }

.banner-wrap { padding: var(--s5) var(--s6) 0; max-width: var(--content-max);
               margin-inline: auto; width: 100%; }

/* signed-out ---------------------------------------------------------- */
.shell-auth { min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; }
.auth-bar {
  display: flex; align-items: center; gap: var(--s3);
  height: 56px; padding: 0 var(--s6); border-bottom: 1px solid var(--border);
}
.shell-auth main { flex: 1; }

/* page ---------------------------------------------------------------- */
/* margin-inline: auto is not cosmetic. Without it a max-width container pins
   itself to the left edge, and on a 1920 screen the dashboard rendered with
   624px of dead space down the right-hand side -- 1264px at 2560. */
.page {
  width: 100%;
  max-width: var(--content-max);
  margin-inline: auto;
  padding: var(--s7) var(--s6) var(--s10);
}
.page-head {
  display: flex; align-items: flex-start; gap: var(--s4);
  padding-bottom: var(--page-head-y);
  flex-wrap: wrap;
}
.page-head .grow { flex: 1; min-width: 240px; }
.page-head .sub { margin-top: var(--s2); color: var(--text-2); font-size: var(--t-12); }

.stack   { display: flex; flex-direction: column; gap: var(--s4); }
.stack-2 { display: flex; flex-direction: column; gap: var(--s2); }
.stack-6 { display: flex; flex-direction: column; gap: var(--section-gap); }
.row     { display: flex; align-items: center; gap: var(--s3); }
.row-2   { display: flex; align-items: center; gap: var(--s2); }
.wrap    { flex-wrap: wrap; }
.grow    { flex: 1; min-width: 0; }

.grid { display: grid; gap: var(--s4); }
.grid-2 { grid-template-columns: repeat(2, minmax(0,1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0,1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0,1fr)); }
.grid-5 { grid-template-columns: repeat(5, minmax(0,1fr)); }

.scroll-x { overflow-x: auto; overscroll-behavior-x: contain; }

/* ───────────────────────────────────────────────────────────── buttons ── */

.btn {
  --btn-h: 34px;
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  height: var(--btn-h); padding: 0 var(--s4);
  border: 1px solid transparent; border-radius: var(--r2);
  font: 500 var(--t-13)/1 var(--sans);
  white-space: nowrap; cursor: pointer;
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease),
              color var(--fast) var(--ease), opacity var(--fast) var(--ease);
}
.btn svg { width: 15px; height: 15px; flex: none; }
.btn.sm { --btn-h: 28px; padding: 0 var(--s3); font-size: var(--t-12); }
.btn.lg { --btn-h: 40px; padding: 0 var(--s5); font-size: var(--t-14); }
.btn.block { width: 100%; }

/* Tier 1 — one per view, at most. */
.btn-primary { background: var(--accent-fill); color: var(--accent-ink); font-weight: 600; }
.btn-primary:hover { background: var(--accent-hi); }

/* Tier 2 — the workhorse. */
.btn-secondary { background: var(--surface-2); color: var(--text-1); border-color: var(--border-strong); }
.btn-secondary:hover { background: var(--surface-3); border-color: var(--accent-line); }

/* Tier 3 — for actions that should not compete. */
.btn-ghost { background: transparent; color: var(--text-2); }
.btn-ghost:hover { background: var(--surface-2); color: var(--text-1); }

.btn-danger { background: transparent; color: var(--fail); border-color: color-mix(in srgb, var(--fail) 40%, transparent); }
.btn-danger:hover { background: var(--fail-wash); border-color: var(--fail); }

.btn-icon { width: var(--btn-h); padding: 0; }

/* Disabled is 'not yet valid', not 'broken' — it keeps its shape and loses
   its emphasis, and the form says WHY next to it rather than leaving the
   user to guess. */
.btn:disabled, .btn[aria-disabled="true"] {
  opacity: .45; cursor: not-allowed; pointer-events: none;
}

/* Loading keeps the button's width so the layout does not jump. */
.btn[data-loading="true"] { color: transparent !important; position: relative; pointer-events: none; }
.btn[data-loading="true"]::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid color-mix(in srgb, currentColor 25%, transparent);
  border-top-color: var(--accent-ink);
  animation: spin 600ms linear infinite;
}
.btn-secondary[data-loading="true"]::after,
.btn-ghost[data-loading="true"]::after { border-top-color: var(--text-1); }

@keyframes spin { to { rotate: 360deg; } }

/* segmented control — for view switches, not navigation */
.seg { display: inline-flex; padding: 2px; gap: 2px; background: var(--surface-2);
       border: 1px solid var(--border); border-radius: var(--r2); }
.seg button {
  height: 26px; padding: 0 var(--s3); border: 0; border-radius: var(--r1);
  background: transparent; color: var(--text-2);
  font: 500 var(--t-12)/1 var(--sans); cursor: pointer;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.seg button:hover { color: var(--text-1); }
.seg button[aria-selected="true"] { background: var(--surface-1); color: var(--text-1); box-shadow: var(--shadow-1); }

/* ─────────────────────────────────────────────────────────────── forms ── */

.field { display: flex; flex-direction: column; gap: var(--s2); }
.field > label { font-size: var(--t-13); font-weight: 500; }
.field .hint { font-size: var(--t-12); color: var(--text-3); }

.input, .select, .textarea {
  width: 100%; padding: var(--s2) var(--s3); min-height: 34px;
  background: var(--surface-2); color: var(--text-1);
  border: 1px solid var(--border-strong); border-radius: var(--r2);
  font: 400 var(--t-13)/1.5 var(--sans);
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.input::placeholder, .textarea::placeholder { color: var(--text-3); }
.input:hover, .select:hover, .textarea:hover { border-color: var(--accent-line); }
.input:focus-visible, .select:focus-visible, .textarea:focus-visible { border-color: var(--accent-ui); }
.textarea { min-height: 84px; resize: vertical; }
.input.tech, .textarea.tech { font-family: var(--mono); }

.select {
  appearance: none; padding-right: var(--s7);
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 16px) 15px, calc(100% - 11px) 15px;
  background-size: 5px 5px; background-repeat: no-repeat;
}

/* An invalid field is announced by border, icon and text -- never by colour
   alone, and never only on submit. */
.field[data-invalid="true"] .input,
.field[data-invalid="true"] .select,
.field[data-invalid="true"] .textarea { border-color: var(--fail); }
.field-error { display: flex; align-items: center; gap: var(--s1); font-size: var(--t-12); color: var(--fail); }
.field-error svg { width: 13px; height: 13px; flex: none; }

/* A prefix that cannot be edited, so the shape of the value is obvious
   before it is typed. */
.input-group { display: flex; align-items: stretch; }
.input-group .affix {
  display: grid; place-items: center; padding: 0 var(--s3);
  background: var(--surface-3); color: var(--text-3);
  border: 1px solid var(--border-strong); border-right: 0;
  border-radius: var(--r2) 0 0 var(--r2);
  font-family: var(--mono); font-size: var(--t-12); white-space: nowrap;
}
.input-group .affix + .input { border-radius: 0 var(--r2) var(--r2) 0; }

.toggle { display: inline-flex; align-items: center; gap: var(--s3); cursor: pointer; }
.toggle input { position: absolute; opacity: 0; width: 0; height: 0; }
.toggle .track {
  width: 34px; height: 20px; padding: 2px; border-radius: var(--rp);
  background: var(--surface-3); border: 1px solid var(--border-strong);
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.toggle .knob {
  width: 14px; height: 14px; border-radius: 50%; background: var(--text-3);
  transition: translate var(--base) var(--ease), background var(--fast) var(--ease);
}
.toggle input:checked + .track { background: var(--accent-wash); border-color: var(--accent-ui); }
.toggle input:checked + .track .knob { translate: 14px 0; background: var(--accent); }
.toggle input:focus-visible + .track { outline: 2px solid var(--accent-ui); outline-offset: 2px; }

.check { display: inline-flex; align-items: flex-start; gap: var(--s3); cursor: pointer; font-size: var(--t-13); }
.check input { margin: 2px 0 0; accent-color: var(--accent-fill); width: 15px; height: 15px; }

/* choice card — a radio that is big enough to describe itself */
.choice {
  display: flex; gap: var(--s3); padding: var(--s4);
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: var(--r3); cursor: pointer;
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.choice:hover { border-color: var(--accent-line); }
.choice[aria-checked="true"] { border-color: var(--accent-ui); background: var(--accent-wash); }
.choice .t { font-size: var(--t-13); font-weight: 600; }
.choice .d { font-size: var(--t-12); color: var(--text-2); margin-top: var(--s1); }

/* drop zone */
.drop {
  display: grid; place-items: center; gap: var(--s2);
  padding: var(--s8) var(--s5); text-align: center;
  background: var(--surface-2); border: 1px dashed var(--border-strong);
  border-radius: var(--r3); cursor: pointer;
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.drop:hover, .drop[data-over="true"] { border-color: var(--accent-ui); background: var(--accent-wash); }
.drop svg { width: 22px; height: 22px; color: var(--text-3); }

/* copy field — for a value the user will need to paste exactly */
.copy-field {
  display: flex; align-items: center; gap: var(--s2);
  padding: var(--s2) var(--s2) var(--s2) var(--s3);
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--r2);
  font-family: var(--mono); font-size: var(--t-12);
}
.copy-field .v { flex: 1; min-width: 0; overflow-x: auto; white-space: nowrap; }

/* secret field — masked by default. Credentials are never rendered in plain
   text without an explicit action, because a screenshot or a shoulder is a
   real disclosure path. */
.secret .v { filter: blur(5px); user-select: none; transition: filter var(--fast) var(--ease); }
.secret[data-revealed="true"] .v { filter: none; user-select: all; }

/* ────────────────────────────────────────────────────────── containers ── */

.card {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--r4);
}
/* A card is for a SUMMARY. Data belongs in a table or a hairline-separated
   list -- six identical boxes stacked down a page is the templated look this
   system is trying not to have, and it was exactly what the first dashboard
   did. */
.panel { border-top: 1px solid var(--border); padding-top: var(--s5); }
.panel-head {
  display: flex; align-items: center; gap: var(--s3);
  margin-bottom: var(--s4);
}
.panel-head h2 { font-size: var(--t-13); font-weight: 600; }
.card-head {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s4) var(--s5); border-bottom: 1px solid var(--border);
}
.card-head h3 { font-size: var(--t-14); font-weight: 600; }
.card-body { padding: var(--s5); }
.card-foot {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) var(--s5); border-top: 1px solid var(--border);
  background: var(--surface-2); border-radius: 0 0 var(--r4) var(--r4);
}
.card.flush .card-body { padding: 0; }

/* key/value list — the "Essentials" pattern: read top to bottom, no table */
.kv { display: grid; grid-template-columns: minmax(120px, auto) 1fr; gap: var(--s3) var(--s5); }
.kv dt { color: var(--text-3); font-size: var(--t-12); }
.kv dd { font-size: var(--t-13); min-width: 0; }

/* stat tile — a number that means something, with the context to read it */
.stat { padding: var(--s4) var(--s5); }
.stat .k { font-size: var(--t-11); font-weight: 600; letter-spacing: .08em;
           text-transform: uppercase; color: var(--text-3); }
.stat .v { margin-top: var(--s2); font-size: var(--t-26); font-weight: 600; letter-spacing: -.02em; line-height: 1.1; }
.stat .v .unit { font-size: var(--t-13); font-weight: 500; color: var(--text-3); margin-left: 2px; }
.stat .d { margin-top: var(--s1); font-size: var(--t-12); color: var(--text-3); }
.delta { display: inline-flex; align-items: center; gap: 2px; font-size: var(--t-12); font-weight: 500; }
.delta.up { color: var(--live); } .delta.down { color: var(--fail); } .delta.flat { color: var(--text-3); }

/* ────────────────────────────────────────────────── status and badges ── */

/* Always dot + label. Never colour alone -- it has to survive greyscale,
   colour blindness and a printed screenshot. */
.pill {
  display: inline-flex; align-items: center; gap: var(--s2);
  height: 22px; padding: 0 var(--s2) 0 var(--s2);
  border-radius: var(--rp); border: 1px solid var(--border);
  background: var(--surface-2);
  font-size: var(--t-12); font-weight: 500; color: var(--text-2);
  white-space: nowrap;
}
.pill .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
.pill.live  { color: var(--live);  background: var(--live-wash);  border-color: color-mix(in srgb, var(--live) 28%, transparent); }
.pill.build { color: var(--build); background: var(--build-wash); border-color: color-mix(in srgb, var(--build) 28%, transparent); }
.pill.fail  { color: var(--fail);  background: var(--fail-wash);  border-color: color-mix(in srgb, var(--fail) 28%, transparent); }
.pill.info  { color: var(--info);  background: var(--info-wash);  border-color: color-mix(in srgb, var(--info) 28%, transparent); }
.pill.idle  { color: var(--text-3); }

/* A building state pulses; nothing else animates, so the motion means
   "work is happening" rather than "look at me". */
.pill.build .dot { animation: pulse 1.6s var(--ease) infinite; }
@keyframes pulse { 50% { opacity: .35; } }

.badge {
  display: inline-flex; align-items: center; height: 20px; padding: 0 var(--s2);
  border-radius: var(--r1); background: var(--surface-3); color: var(--text-2);
  font-size: var(--t-11); font-weight: 600; letter-spacing: .03em;
}
.badge.accent { background: var(--accent-wash); color: var(--accent); }
.badge.plan   { text-transform: uppercase; letter-spacing: .07em; }

.kbd {
  display: inline-grid; place-items: center; min-width: 20px; height: 20px; padding: 0 var(--s1);
  border: 1px solid var(--border-strong); border-bottom-width: 2px; border-radius: var(--r1);
  background: var(--surface-2); color: var(--text-2);
  font-family: var(--mono); font-size: var(--t-11);
}

/* ─────────────────────────────────────────────────── meters and rings ── */

.meter { display: flex; flex-direction: column; gap: var(--s2); }
.meter-top { display: flex; align-items: baseline; gap: var(--s2); font-size: var(--t-13); }
.meter-top .k { color: var(--text-2); }
.meter-top .v { margin-left: auto; font-weight: 500; }
.meter-top .v .of { color: var(--text-3); font-weight: 400; }

.bar { position: relative; height: 6px; border-radius: var(--rp); background: var(--surface-3); overflow: hidden; }
.bar > i {
  display: block; height: 100%; border-radius: var(--rp);
  background: var(--accent-ui);
  transition: width var(--slow) var(--ease);
}
/* Approaching a limit is amber, at the limit is red -- and the meter says
   the number too, so the colour is reinforcement rather than the message. */
.bar[data-state="near"] > i { background: var(--build); }
.bar[data-state="full"] > i { background: var(--fail); }

.ring { display: grid; place-items: center; position: relative; width: 76px; height: 76px; }
.ring svg { rotate: -90deg; width: 100%; height: 100%; }
.ring circle { fill: none; stroke-width: 6; stroke-linecap: round; }
.ring .track { stroke: var(--surface-3); }
.ring .fill  { stroke: var(--accent-ui); transition: stroke-dashoffset var(--slow) var(--ease); }
.ring[data-state="near"] .fill { stroke: var(--build); }
.ring[data-state="full"] .fill { stroke: var(--fail); }
.ring .mid { position: absolute; text-align: center; }
.ring .mid .n { font-size: var(--t-16); font-weight: 600; line-height: 1; }
.ring .mid .u { font-size: var(--t-11); color: var(--text-3); }


/* ──────────────────────────────────────────────────── overview patterns ── */

/* A panel is a hairline and a heading. No box.
   Six identical bordered cards stacked down a page is the templated look, and
   it is exactly what the first version of this dashboard did. */
.panel { border-top: 1px solid var(--border); padding-top: var(--s6); margin-top: var(--s6); }
.panel.bare { border-top: 0; padding-top: 0; margin-top: 0; }
.panel-head { display: flex; align-items: center; gap: var(--s3); margin-bottom: var(--s4); }
.panel-head h2 { font-size: var(--t-13); font-weight: 600; letter-spacing: -.005em; }

.count-chip {
  display: inline-grid; place-items: center; min-width: 18px; height: 18px;
  padding: 0 5px; border-radius: var(--rp);
  background: var(--surface-3); color: var(--text-2);
  font-size: var(--t-11); font-weight: 600;
}

.dot-sep::before { content: "·"; margin: 0 var(--s2); color: var(--text-3); opacity: .6; }

/* attention strip — hairline rows, not a stack of alert boxes ---------- */
.attn {
  border: 1px solid var(--border); border-radius: var(--r4);
  overflow: hidden; margin-bottom: var(--s6);
  background: var(--surface-1);
}
.attn-row {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) var(--s4);
  font-size: var(--t-12);
  border-top: 1px solid var(--border);
}
.attn-row:first-child { border-top: 0; }
.attn-row > svg { width: 15px; height: 15px; flex: none; }
.attn-row.info  > svg { color: var(--info); }
.attn-row.warn  > svg { color: var(--build); }
.attn-row.error > svg { color: var(--fail); }
.attn-row.error { background: var(--fail-wash); }
.attn-row.warn  { background: var(--build-wash); }
.attn-i { display: inline-grid; place-items: center; color: var(--text-3); }
.attn-i svg { width: 13px; height: 13px; }

/* the website row ------------------------------------------------------ */
.site-link {
  display: flex; flex-direction: column; gap: 1px;
  color: var(--text-1); text-decoration: none; min-width: 0;
}
.site-link:hover { text-decoration: none; }
.site-link:hover .strong { color: var(--accent); }
.site-link .tech { font-size: var(--t-11); }
.col-act { width: 1%; white-space: nowrap; }

/* first-run — LEFT aligned. A centred empty state inside a bordered card
   is one of the clearest template tells, and it wastes the one moment where
   you have the customer's whole attention. */
.first { max-width: 62ch; padding: var(--s5) 0 var(--s6); }
.first h3 { font-size: var(--t-16); font-weight: 600; letter-spacing: -.01em; }
.first p { margin-top: var(--s2); color: var(--text-2); font-size: var(--t-13); line-height: 1.6; }
.first .btn { margin-top: var(--s5); }

.steps-list { margin-top: var(--s5); display: flex; flex-direction: column; gap: var(--s3); }
.steps-list li {
  display: flex; align-items: center; gap: var(--s3);
  font-size: var(--t-12); color: var(--text-2);
}
.steps-list li span {
  display: grid; place-items: center; flex: none;
  width: 20px; height: 20px; border-radius: var(--rp);
  border: 1px solid var(--border-strong);
  font-size: var(--t-11); font-weight: 600; color: var(--text-3);
}

/* usage — one inline row, not six boxes ------------------------------- */
.usage {
  display: grid; gap: var(--s5) var(--s7);
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
}
.usage-item { display: flex; flex-direction: column; gap: var(--s2); min-width: 0; }
.usage-top { display: flex; align-items: baseline; gap: var(--s2); font-size: var(--t-12); }
.usage-k { color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.usage-v { margin-left: auto; white-space: nowrap; }

/* activity feed ------------------------------------------------------- */
.feed { display: flex; flex-direction: column; }
.feed li {
  display: flex; align-items: center; gap: var(--s3);
  height: var(--row-h); font-size: var(--t-12);
  border-bottom: 1px solid var(--border);
}
.feed li:last-child { border-bottom: 0; }
.feed-i {
  display: grid; place-items: center; flex: none;
  width: 22px; height: 22px; border-radius: var(--rp);
  background: var(--surface-2); color: var(--text-3);
}
.feed-i svg { width: 12px; height: 12px; }
.feed-i.ok  { background: var(--live-wash); color: var(--live); }
.feed-i.bad { background: var(--fail-wash); color: var(--fail); }
.feed time { font-variant-numeric: tabular-nums; white-space: nowrap; }


/* ── website cards ──────────────────────────────────────────────────────── */

/* The shape Vercel and hPanel both land on for a project list: a card per
   site carrying the four things you actually came to check -- is it up, what
   domain, what shipped last, and how long ago. A table is right for thirty
   deployments; for six websites a card gives the domain and the commit room
   to breathe and gives each site a click target the size of a card. */
.sites {
  display: grid; gap: var(--s4);
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
}
.site {
  display: flex; flex-direction: column;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--r4);
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.site:hover { border-color: var(--border-strong); }
.site:focus-within { border-color: var(--accent-ui); }

.site-top {
  display: flex; align-items: flex-start; gap: var(--s3);
  padding: var(--s4) var(--s4) var(--s3);
}
.site-favi {
  display: grid; place-items: center; flex: none;
  width: 32px; height: 32px; border-radius: var(--r2);
  background: var(--surface-2); border: 1px solid var(--border);
  color: var(--text-3);
}
.site-favi svg { width: 15px; height: 15px; }
.site-id { min-width: 0; flex: 1; }
.site-name {
  display: block; color: var(--text-1); font-size: var(--t-13); font-weight: 600;
  letter-spacing: -.005em; text-decoration: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.site-name:hover { color: var(--accent); text-decoration: none; }
/* The card is one big target: the name's ::after covers it, so a click
   anywhere that is not another control opens the site. */
.site-name::after { content: ""; position: absolute; inset: 0; }
.site { position: relative; }
.site-host {
  display: block; margin-top: 2px;
  font-family: var(--mono); font-size: var(--t-11); color: var(--text-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.site-mid {
  padding: 0 var(--s4) var(--s3);
  font-size: var(--t-12); color: var(--text-2);
  display: flex; flex-direction: column; gap: 3px;
}
.site-commit { display: flex; align-items: center; gap: var(--s2); min-width: 0; }
.site-commit .sha { font-family: var(--mono); font-size: var(--t-11); color: var(--text-3); flex: none; }
.site-commit .msg { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.site-when { color: var(--text-3); font-size: var(--t-11); }

.site-foot {
  margin-top: auto;
  display: flex; align-items: center; gap: var(--s2);
  padding: var(--s2) var(--s3) var(--s2) var(--s4);
  border-top: 1px solid var(--border);
}
/* Controls sit above the card-wide click target. */
.site-foot > * { position: relative; z-index: 1; }

/* new-website tile, the same size as a card so the grid does not jump */
.site-new {
  display: flex; flex-direction: column; align-items: flex-start; justify-content: center;
  gap: var(--s2); min-height: 132px; padding: var(--s5);
  border: 1px dashed var(--border-strong); border-radius: var(--r4);
  color: var(--text-2); text-decoration: none;
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.site-new:hover { border-color: var(--accent-ui); background: var(--accent-wash);
                  text-decoration: none; color: var(--text-1); }
.site-new svg { width: 18px; height: 18px; color: var(--accent); }
.site-new .t { font-size: var(--t-13); font-weight: 600; }
.site-new .d { font-size: var(--t-11); color: var(--text-3); }

/* toolbar above a list: search + filters + sort */
.toolbar { display: flex; align-items: center; gap: var(--s2); margin-bottom: var(--s4); flex-wrap: wrap; }
.toolbar .field-search { position: relative; flex: 1; min-width: 180px; max-width: 320px; }
.toolbar .field-search svg {
  position: absolute; left: var(--s3); top: 50%; translate: 0 -50%;
  width: 14px; height: 14px; color: var(--text-3); pointer-events: none;
}
.toolbar .field-search input { padding-left: 32px; min-height: 30px; height: 30px; }
/* Shown when a filter matches nothing. Spans the grid so it reads as a
   statement about the list rather than as a card in it. */
.filter-none { grid-column: 1 / -1; padding: var(--s6) 0; }

/* ────────────────────────────────────────────────────────────── tables ── */

.table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: var(--t-13); }
/* 40px rows: dense enough to scan thirty websites without scrolling,
   tall enough that a status pill and a row action still fit. */
.table th {
  position: sticky; top: 0; z-index: 1;
  height: 34px; padding: 0 var(--s4); text-align: left;
  background: var(--surface-2); color: var(--text-3);
  font-size: var(--t-11); font-weight: 600; letter-spacing: .07em; text-transform: uppercase;
  border-bottom: 1px solid var(--border); white-space: nowrap;
}
.table th.sortable { cursor: pointer; user-select: none; }
.table th.sortable:hover { color: var(--text-1); }
.table th[aria-sort] { color: var(--text-1); }
.table th .arrow { opacity: .5; margin-left: var(--s1); }

/* Tall borderless rows. A hairline between rows and generous height reads as
   more considered than a boxed grid, and scans faster. */
.table td { padding: 0 var(--s4); height: var(--row-h); border-bottom: 1px solid var(--border); vertical-align: middle; }
.table tbody tr { transition: background var(--fast) var(--ease); }
.table tbody tr:hover { background: var(--surface-2); }
.table tbody tr:last-child td { border-bottom: 0; }
.table td.num { text-align: right; font-variant-numeric: tabular-nums; }
.table td.tech { font-family: var(--mono); font-size: var(--t-12); }

/* Row actions appear on hover but are always reachable by keyboard. */
.row-actions { display: flex; gap: var(--s1); justify-content: flex-end; opacity: 0; transition: opacity var(--fast) var(--ease); }
tr:hover .row-actions, tr:focus-within .row-actions { opacity: 1; }

.table-foot {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) var(--s4); border-top: 1px solid var(--border);
  font-size: var(--t-12); color: var(--text-3);
}
.table-foot .grow { flex: 1; }

/* ─────────────────────────────────────────────────────── tabs / stepper ── */

.tabs { display: flex; gap: var(--s5); border-bottom: 1px solid var(--border); overflow-x: auto; }
.tabs a, .tabs button {
  position: relative; padding: var(--s3) 0; border: 0; background: none;
  color: var(--text-2); font: 500 var(--t-13)/1 var(--sans);
  white-space: nowrap; cursor: pointer;
  transition: color var(--fast) var(--ease);
}
.tabs a:hover, .tabs button:hover { color: var(--text-1); text-decoration: none; }
.tabs [aria-current="page"], .tabs [aria-selected="true"] { color: var(--text-1); font-weight: 600; }
.tabs [aria-current="page"]::after, .tabs [aria-selected="true"]::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px;
  height: 2px; background: var(--accent); border-radius: var(--rp);
}

.steps { display: flex; align-items: center; gap: var(--s3); }
.step { display: flex; align-items: center; gap: var(--s2); font-size: var(--t-12); color: var(--text-3); }
.step .n {
  display: grid; place-items: center; width: 22px; height: 22px; border-radius: 50%;
  border: 1px solid var(--border-strong); font-size: var(--t-11); font-weight: 600;
}
.step[data-state="current"] { color: var(--text-1); font-weight: 600; }
.step[data-state="current"] .n { border-color: var(--accent-ui); background: var(--accent-wash); color: var(--accent); }
.step[data-state="done"] { color: var(--text-2); }
.step[data-state="done"] .n { border-color: transparent; background: var(--live-wash); color: var(--live); }
.steps .link { width: 24px; height: 1px; background: var(--border-strong); }

/* ───────────────────────────────────────────────────── alerts / banners ── */

/* An alert says what happened, why, and what to do next. The action slot is
   part of the component because an alert without one is just an apology. */
.alert {
  display: flex; gap: var(--s3); padding: var(--s4);
  border: 1px solid var(--border); border-left-width: 2px;
  border-radius: var(--r2); background: var(--surface-1);
  font-size: var(--t-13);
}
.alert svg.ico { width: 16px; height: 16px; flex: none; margin-top: 1px; }
.alert .t { font-weight: 600; }
.alert .m { color: var(--text-2); margin-top: var(--s1); }
.alert .act { margin-left: auto; display: flex; gap: var(--s2); align-items: flex-start; flex: none; }
.alert.info  { border-left-color: var(--info);  background: var(--info-wash);  }
.alert.info svg.ico  { color: var(--info); }
.alert.warn  { border-left-color: var(--build); background: var(--build-wash); }
.alert.warn svg.ico  { color: var(--build); }
.alert.error { border-left-color: var(--fail);  background: var(--fail-wash);  }
.alert.error svg.ico { color: var(--fail); }
.alert.ok    { border-left-color: var(--live);  background: var(--live-wash);  }
.alert.ok svg.ico    { color: var(--live); }

/* toasts */
.toasts {
  position: fixed; right: var(--s5); bottom: var(--s5); z-index: var(--z-toast);
  display: flex; flex-direction: column; gap: var(--s2); width: min(360px, calc(100vw - var(--s8)));
}
.toast {
  display: flex; gap: var(--s3); padding: var(--s3) var(--s4);
  background: var(--surface-3); border: 1px solid var(--border-strong);
  border-radius: var(--r2); box-shadow: var(--shadow-3);
  font-size: var(--t-13);
  animation: toast-in var(--slow) var(--ease);
}
@keyframes toast-in { from { opacity: 0; translate: 0 8px; } }
.toast svg { width: 16px; height: 16px; flex: none; margin-top: 1px; }
.toast.ok svg { color: var(--live); } .toast.error svg { color: var(--fail); }

/* tooltip */
.tip { position: relative; }
.tip::after {
  content: attr(data-tip);
  position: absolute; bottom: calc(100% + 6px); left: 50%; translate: -50% 0;
  padding: var(--s1) var(--s2); border-radius: var(--r1);
  background: var(--surface-3); border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-2);
  font-size: var(--t-12); color: var(--text-1); white-space: nowrap;
  opacity: 0; pointer-events: none; transition: opacity var(--fast) var(--ease);
}
.tip:hover::after, .tip:focus-visible::after { opacity: 1; }

/* ─────────────────────────────────────────────── empty / error / skeleton ── */

/* "No data" is not an empty state. An empty state names what is missing,
   says what it would do for you, and offers the one action that fixes it. */
.empty {
  display: grid; place-items: center; gap: var(--s3);
  padding: var(--s10) var(--s5); text-align: center;
}
.empty .ico {
  display: grid; place-items: center; width: 44px; height: 44px;
  border-radius: var(--r3); background: var(--surface-2);
  border: 1px solid var(--border); color: var(--text-3);
}
.empty .ico svg { width: 20px; height: 20px; }
.empty h3 { font-size: var(--t-14); font-weight: 600; }
.empty p { max-width: 42ch; color: var(--text-2); font-size: var(--t-13); }

/* Content-shaped, not a grey box: the skeleton should occupy the same
     geometry as what replaces it, so nothing reflows on arrival. */
.sk { background: var(--surface-2); border-radius: var(--r1); position: relative; overflow: hidden; }
.sk::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--text-3) 12%, transparent), transparent);
  animation: shimmer 1.4s linear infinite;
}
@keyframes shimmer { from { translate: -100% 0; } to { translate: 100% 0; } }
.sk.text  { height: 12px; }
.sk.title { height: 18px; width: 40%; }
.sk.line  { height: 12px; width: 100%; }
.sk.line.short { width: 60%; }
.sk.pill  { height: 22px; width: 72px; border-radius: var(--rp); }
.sk.stat  { height: 26px; width: 96px; }

/* ────────────────────────────────────────── overlays: menu, modal, drawer ── */

.scrim {
  position: fixed; inset: 0; z-index: var(--z-modal);
  display: grid; place-items: center; padding: var(--s5);
  background: rgba(5,5,8,.66);
  background: light-dark(rgba(20,20,31,.34), rgba(5,5,8,.66));
  backdrop-filter: blur(3px);
  animation: fade var(--base) var(--ease);
}
@keyframes fade { from { opacity: 0; } }

.modal {
  width: min(480px, 100%); background: var(--surface-1);
  border: 1px solid var(--border-strong); border-radius: var(--r3);
  box-shadow: var(--shadow-3);
  animation: modal-in var(--slow) var(--ease);
}
@keyframes modal-in { from { opacity: 0; translate: 0 8px; scale: .99; } }
.modal .card-head { padding: var(--s5); }
.modal .card-body { padding: 0 var(--s5) var(--s5); }
.modal .card-foot { justify-content: flex-end; }

/* A destructive confirm states the consequence in plain English and makes
   the user type the resource name. Both are deliberate friction: this is the
   same discipline the panel's site-destroy flow already uses. */
.confirm .consequence {
  padding: var(--s3) var(--s4); margin-bottom: var(--s4);
  background: var(--fail-wash); border: 1px solid color-mix(in srgb, var(--fail) 26%, transparent);
  border-radius: var(--r2); font-size: var(--t-13);
}

.menu {
  min-width: 190px; padding: var(--s1); background: var(--surface-3);
  border: 1px solid var(--border-strong); border-radius: var(--r2);
  box-shadow: var(--shadow-3);
}
.menu button, .menu a {
  display: flex; align-items: center; gap: var(--s3); width: 100%;
  padding: var(--s2) var(--s3); border: 0; border-radius: var(--r1);
  background: none; color: var(--text-1);
  font: 400 var(--t-13)/1.4 var(--sans); text-align: left; cursor: pointer;
}
.menu button:hover, .menu a:hover { background: var(--surface-2); text-decoration: none; }
.menu svg { width: 15px; height: 15px; opacity: .7; flex: none; }
.menu .sep { height: 1px; margin: var(--s1) 0; background: var(--border); }
.menu .danger { color: var(--fail); }

.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: var(--z-drawer);
  width: min(520px, 100vw); display: flex; flex-direction: column;
  background: var(--surface-1); border-left: 1px solid var(--border-strong);
  box-shadow: var(--shadow-3);
  animation: drawer-in var(--slow) var(--ease);
}
@keyframes drawer-in { from { translate: 100% 0; } }

/* command palette — the expert's path through the product */
.palette {
  position: fixed; inset: 0; z-index: var(--z-palette);
  display: grid; place-items: start center; padding: 12vh var(--s5) var(--s5);
  background: rgba(5,5,8,.6); backdrop-filter: blur(3px);
}
.palette .box {
  width: min(560px, 100%); background: var(--surface-1);
  border: 1px solid var(--border-strong); border-radius: var(--r3);
  box-shadow: var(--shadow-3); overflow: hidden;
}
.palette-input {
  display: flex; align-items: center; gap: var(--s3);
  padding: 0 var(--s4); border-bottom: 1px solid var(--border);
}
.palette-input svg { width: 15px; height: 15px; color: var(--text-3); flex: none; }
.palette input {
  width: 100%; height: 48px; padding: 0; border: 0;
  background: transparent; color: var(--text-1); font: 400 var(--t-14)/1.4 var(--sans);
}
.palette input::placeholder { color: var(--text-3); }
.palette-foot {
  display: flex; align-items: center; gap: var(--s2);
  padding: var(--s2) var(--s4); border-top: 1px solid var(--border);
  font-size: var(--t-11); color: var(--text-3);
}
.palette-none { padding: var(--s5) var(--s4); font-size: var(--t-12); color: var(--text-3); }
.palette ul { max-height: 46vh; overflow-y: auto; padding: var(--s1); }
.palette li > * {
  display: flex; align-items: center; gap: var(--s3); width: 100%;
  padding: var(--s2) var(--s3); border: 0; border-radius: var(--r1);
  background: none; color: var(--text-1); font: 400 var(--t-13)/1.4 var(--sans);
  text-align: left; cursor: pointer;
}
.palette li[aria-selected="true"] > * { background: var(--accent-wash); }
.palette li .grp { margin-left: auto; font-size: var(--t-11); color: var(--text-3); }

/* ────────────────────────────────────────────────── log viewer / timeline ── */

/* This has to look like a serious developer tool, because the people reading
   it are debugging a failed build and everything else is noise. */
.logs {
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--r2);
  font-family: var(--mono); font-size: var(--t-12); line-height: 1.65;
}
.logs-head {
  display: flex; align-items: center; gap: var(--s2);
  padding: var(--s2) var(--s3); border-bottom: 1px solid var(--border);
  background: var(--surface-1); border-radius: var(--r2) var(--r2) 0 0;
}
.logs-body { max-height: 460px; overflow: auto; padding: var(--s2) 0; }
.log-line { display: flex; gap: var(--s3); padding: 0 var(--s3); white-space: pre-wrap; word-break: break-word; }
.log-line:hover { background: var(--surface-1); }
.log-line .ts  { color: var(--text-3); flex: none; }
.log-line .lvl { flex: none; width: 44px; font-weight: 500; }
.log-line .svc { flex: none; color: var(--info); }
.log-line.info  .lvl { color: var(--text-3); }
.log-line.warn  .lvl { color: var(--build); }
.log-line.error .lvl { color: var(--fail); }
.log-line.error { background: var(--fail-wash); }
.log-line.ok    .lvl { color: var(--live); }

/* deployment timeline — each stage carries its own duration, because "it
   was slow" is not actionable but "dependencies took 38s" is */
.timeline { display: flex; flex-direction: column; }
.tl-step { display: flex; align-items: flex-start; gap: var(--s3); padding: var(--s3) 0; }
.tl-step .mark {
  display: grid; place-items: center; width: 20px; height: 20px; border-radius: 50%;
  border: 1px solid var(--border-strong); background: var(--surface-2); flex: none;
}
.tl-step .mark svg { width: 11px; height: 11px; }
.tl-step[data-state="done"]    .mark { border-color: transparent; background: var(--live-wash); color: var(--live); }
.tl-step[data-state="running"] .mark { border-color: var(--build); color: var(--build); animation: pulse 1.6s var(--ease) infinite; }
.tl-step[data-state="failed"]  .mark { border-color: transparent; background: var(--fail-wash); color: var(--fail); }
.tl-step[data-state="idle"]    .mark { color: var(--text-3); }
.tl-step .n { font-size: var(--t-13); font-weight: 500; }
.tl-step .dur { margin-left: auto; font-family: var(--mono); font-size: var(--t-12); color: var(--text-3); }
.tl-rail { width: 1px; height: var(--s4); margin-left: 10px; background: var(--border-strong); }

/* plan comparison — shows exactly what changes, which is the only question
   the reader actually has */
.compare { width: 100%; font-size: var(--t-13); border-collapse: collapse; }
.compare th { padding: var(--s3) var(--s4); text-align: left; font-size: var(--t-12); color: var(--text-3); font-weight: 600; }
.compare td { padding: var(--s3) var(--s4); border-top: 1px solid var(--border); }
.compare .to { font-weight: 600; }
.compare .arrow { color: var(--text-3); padding: 0 var(--s2); }
.compare .gain { color: var(--live); }


/* ── collapsed sidebar ──────────────────────────────────────────────────── */

/* An icon rail you choose, not one a breakpoint chose for you. Vercel's 2026
   redesign made the same move for the same reason: reading a deploy log or a
   DNS table you want the width back; navigating, you want the labels.
   Remembered per browser, toggled with "[" as in VS Code. */
.shell[data-collapsed="true"] { grid-template-columns: var(--rail-w) minmax(0, 1fr); }
.shell[data-collapsed="true"] .nav-text,
.shell[data-collapsed="true"] .nav-label,
.shell[data-collapsed="true"] .nav-count,
.shell[data-collapsed="true"] .brand span,
.shell[data-collapsed="true"] .ws-text,
.shell[data-collapsed="true"] .ws-chev,
.shell[data-collapsed="true"] .acct-text,
.shell[data-collapsed="true"] .nav-collapse { display: none; }

.shell[data-collapsed="true"] .nav-brand { justify-content: center; padding: 0; }
.shell[data-collapsed="true"] .nav-brand .grow { display: none; }
.shell[data-collapsed="true"] .ws { padding: var(--s2); display: grid; place-items: center; }
.shell[data-collapsed="true"] .ws-btn { width: auto; padding: var(--s1); }
.shell[data-collapsed="true"] .nav-scroll { padding: var(--s3) var(--s2); }
.shell[data-collapsed="true"] .nav-group { margin-bottom: var(--s3); }
/* The groups lose their labels, so a hairline keeps the grouping legible. */
.shell[data-collapsed="true"] .nav-group + .nav-group {
  border-top: 1px solid var(--border); padding-top: var(--s3);
}
.shell[data-collapsed="true"] .nav-item {
  justify-content: center; padding: 0; width: 36px; margin-inline: auto;
}
.shell[data-collapsed="true"] .acct-btn { justify-content: center; padding: 0; }
.shell[data-collapsed="true"] .acct-menu { left: calc(100% + var(--s2)); right: auto;
                                           bottom: 0; min-width: 220px; }
.shell[data-collapsed="true"] .ws-menu { left: calc(100% + var(--s2)); right: auto;
                                         top: var(--s2); min-width: 220px; }
/* Tooltips are not decoration here: an icon rail with no labels and no
   tooltips is a memory test. They sit to the RIGHT because there is no room
   above inside a 56px column. */
.shell[data-collapsed="true"] .nav-item.tip::after {
  bottom: auto; left: calc(100% + 8px); top: 50%; translate: 0 -50%;
}
.nav-expand { display: none; }
.shell[data-collapsed="true"] .nav-expand { display: inline-flex; }

/* ────────────────────────────────────────────────────────── responsive ── */

/* Recomposed, not shrunk. The sidebar becomes a sheet you open, rather than
   a column that silently disappears -- so the content never shifts sideways
   between one page and the next. */

@media (max-width: 1180px) {
  :root { --side: 224px; }
  .grid-5 { grid-template-columns: repeat(3, minmax(0,1fr)); }
  .grid-4 { grid-template-columns: repeat(2, minmax(0,1fr)); }
}

@media (max-width: 1024px) {
  /* Below this the sidebar is a sheet, so "collapsed" has no meaning and must
     not leave the page rendering behind a 56px gutter. */
  .shell, .shell[data-collapsed="true"] { grid-template-columns: minmax(0, 1fr); }
  .shell[data-collapsed="true"] .nav-text,
  .shell[data-collapsed="true"] .nav-label,
  .shell[data-collapsed="true"] .brand span,
  .shell[data-collapsed="true"] .ws-text { display: revert; }
  .shell[data-collapsed="true"] .nav-item { justify-content: flex-start;
    padding: 0 var(--s3); width: auto; margin-inline: 0; }
  .nav-expand { display: none !important; }
  .nav {
    position: fixed; top: 0; bottom: 0; left: 0; z-index: var(--z-drawer);
    width: min(280px, 86vw);
    box-shadow: var(--shadow-3);
    translate: -102% 0;
    transition: translate var(--base) var(--ease);
  }
  .nav[data-open="true"] { translate: 0 0; }
  .nav-close, .nav-open { display: inline-flex; }
  .nav-scrim {
    position: fixed; inset: 0; z-index: calc(var(--z-drawer) - 1);
    background: rgba(5,5,8,.5);
    background: light-dark(rgba(20,20,31,.28), rgba(5,5,8,.5));
    backdrop-filter: blur(2px);
  }
  .grid-3 { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .page { padding: var(--s6) var(--s5) var(--s9); }
  .topbar { padding: 0 var(--s5); }
  .banner-wrap { padding: var(--s4) var(--s5) 0; }
}

@media (max-width: 768px) {
  .grid-2, .grid-3, .grid-4, .grid-5 { grid-template-columns: minmax(0,1fr); }
  .page { padding: var(--s5) var(--s4) var(--s9); }
  .topbar, .auth-bar { padding: 0 var(--s4); }
  .banner-wrap { padding: var(--s4) var(--s4) 0; }
  .searchbtn span:not(.kbd) { display: none; }
  .toasts { left: var(--s4); right: var(--s4); width: auto; }

  /* A horizontally scrolling table is not a mobile design. Each row becomes
     a record card carrying its own column names. */
  .table.stackable thead { display: none; }
  .table.stackable tbody tr {
    display: grid; gap: var(--s2); padding: var(--s4) 0;
    border-bottom: 1px solid var(--border);
  }
  .table.stackable td { display: flex; gap: var(--s3); padding: 0; height: auto; border: 0; }
  .table.stackable td::before {
    content: attr(data-label); flex: none; min-width: 92px;
    color: var(--text-3); font-size: var(--t-12);
  }
  .table.stackable td.num { text-align: left; }
  .row-actions { opacity: 1; }
}

@media (max-width: 480px) {
  :root { --t-24: 20px; --t-30: 24px; }
  .page-head { padding-bottom: var(--s6); }
  .modal { width: 100%; }
}

/* ─────────────────────────────────────────────────────── reduced motion ── */

/* Everything above uses motion to signal a state change. Someone who has
   asked for less of it still needs the signal, so the transitions collapse
   to near-instant rather than being removed and leaving no feedback. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  .sk::after { animation: none; }
  .pill.build .dot, .tl-step[data-state="running"] .mark { animation: none; }
}

/* ─────────────────────────────────────────────────────────────── print ── */

@media print {
  .nav, .topbar, .toasts, .row-actions, .nav-scrim { display: none !important; }
  .shell { display: block; }
  body { background: #fff; color: #000; }
  .card { border-color: #ccc; break-inside: avoid; }
}
