/* dashboard-design — house tokens
 *
 * Plain CSS custom properties. No build step, no dependency, no network
 * request. Works in a Tailwind project, a server-rendered admin theme, a Go
 * template, or a single HTML file.
 *
 * Values and the reasoning behind them: references/tokens.md
 * Why the greys are tinted and the borders are alpha: references/craft.md
 *
 * If the project already has tokens, match those instead and delete this file.
 */

/* --- fonts: IBM Plex Sans + JetBrains Mono ------------------------------
 * Self-hosted latin subsets, SIL OFL. No runtime network request, so a strict
 * CSP is satisfied. Licence and attribution: assets/fonts/ATTRIBUTION.md —
 * the OFL requires both travel with the files.
 *
 * Paths are relative to this stylesheet. Adjust if you vendor it elsewhere.
 */
@font-face {
  font-family: "IBM Plex Sans";
  src: url("fonts/plex.woff2") format("woff2");
  font-weight: 400 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("fonts/jetbrains.woff2") format("woff2");
  font-weight: 400 600; font-style: normal; font-display: swap;
}

:root {
  /* --- spacing: 4 8 12 16 24 32 48 64 ------------------------------ */
  --space-1:  4px;
  --space-2:  8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-6: 24px;
  --space-8: 32px;
  --space-12: 48px;
  --space-16: 64px;

  /* shell only: page padding, and the gap between the nav, the content and
     its rows. Hatch AI's measure, off the 4px scale on purpose. */
  --shell-gap: 18px;

  /* --- type ---------------------------------------------------------
   * Two weights, seven roles. Tracking is part of the role: small text
   * loosens, large numbers tighten. 12px is the floor, charts included.
   */
  --font-sans:  "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", Roboto,
                "Helvetica Neue", Arial, sans-serif;
  --font-mono:  "JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono",
                Menlo, Consolas, "Liberation Mono", monospace;

  --text-metric:      36px;  --leading-metric:      40px;  --tracking-metric:    -0.02em;
  --text-title:       26px;  --leading-title:       32px;  --tracking-title:     -0.015em;
  --text-metric-sm:   20px;  --leading-metric-sm:   24px;  --tracking-metric-sm: -0.01em;
  --text-body-lg:     18px;  --leading-body-lg:     24px;  --tracking-body-lg:    0;
  --text-body:        14px;  --leading-body:        20px;  --tracking-body:       0.01em;
  --text-body-sm:     13px;  --leading-body-sm:     18px;  --tracking-body-sm:    0.01em;
  --text-caption:     12px;  --leading-caption:     16px;  --tracking-caption:    0.03em;
  /* uppercase labels need more than lowercase at the same size */
  --tracking-caption-upper: 0.06em;

  /* named to match Tailwind v4's --font-weight-* namespace, so the same
     spellings generate utilities when these move into @theme */
  --font-weight-regular: 400;
  --font-weight-semibold: 600;

  /* --- radius: graded by the size of the thing ---------------------- */
  --radius-sm:  4px;   /* pills, inputs, buttons, chips */
  --radius-md:  6px;   /* cards, panels, menus, modals */
  --radius-lg:  6px;   /* full-screen surfaces, drawers */
  --radius-field: 2px; /* inputs, selects, textareas and their unit addons */

  --border-hairline: 1px;

  /* --- motion -------------------------------------------------------
   * No major design system publishes motion tokens publicly. House choice,
   * picked to be fast enough not to feel laggy on a hover.
   */
  --ease-out: cubic-bezier(.2, 0, 0, 1);
  --dur-fast: 120ms;   /* colour, background, border, opacity */
  --dur-med:  180ms;   /* transform, size, position */

  /* --- elevation ----------------------------------------------------
   * Never for grouping. Grouping is a border plus a surface step.
   */
  --shadow-raised: 0 1px 2px rgb(15 23 42 / .06);
  --shadow-float:  0 8px 24px rgb(15 23 42 / .12);
  --shadow-pin:   -8px 0 8px -8px rgb(15 23 42 / .18);

  /* --- colour: light -------------------------------------------------
   * Cool grey: a faintly blue-grey ground, white cards with a visible edge,
   * cool greys, and charcoal for the one primary action. The two washes are
   * faint radial glows behind the page.
   * Values and their contrast: references/tokens.md.
   */
  --color-page:            #f6f6f8;
  --color-surface:         #ffffff;
  --color-surface-inset:   #f0f0f3;
  --color-surface-raised:  #ffffff;

  --color-border:          #ececf0;
  --color-border-strong:   #d2d2d7;
  --color-border-control:  #87878c;
  --color-border-card:     #e5e5e9;
  --color-table-header:    #f9f9fb;
  --color-field-border:    #ccd1d8;
  --color-field-label:     #67768a;
  --color-field-focus:     #101010;

  /* the side nav is a charcoal strip in both themes, so these have no dark
     override. .side-nav maps them onto the surface and text tokens. */
  --color-nav:             #242322;
  --color-nav-inset:       #363534;
  --color-nav-border:      #3e3d3d;
  --color-nav-text:        #f4efe6;
  --color-nav-text-muted:  #cfcfce;
  --color-nav-text-subtle: #a3a3a2;
  --color-nav-raised:      #4a4947;   /* a count chip on a hovered row */
  --color-nav-success:     #65b38b;   /* the check on a charcoal toast, 6.26:1 */

  --color-text:            #1b1b1f;
  --color-text-muted:      #545458;
  --color-text-subtle:     #67676c;

  --color-accent:          #da1e4d;
  --color-accent-hover:    #c01a44;
  --color-accent-active:   #a8163b;
  --color-accent-subtle:   #fcebef;
  --color-accent-fg:       #ffffff;
  --color-accent-text:     #c01a44;

  --color-success-bg:      #e3eee7;
  --color-success-fg:      #136a46;
  --color-success-dot:     #3f8e68;
  --color-warning-bg:      #f9ede3;
  --color-warning-fg:      #8a5b0b;
  --color-warning-dot:     #c07a2c;
  --color-danger-bg:       #fce8e8;
  --color-danger-fg:       #ae3c4c;
  --color-danger-dot:      #d7616e;
  --color-info-bg:         #e3eaf4;
  --color-info-fg:         #29558a;
  --color-info-dot:        #4b78b0;

  --color-chart-1:         #097d9e;

  --color-wash-a:          #eef0fa;
  --color-wash-b:          #eaf3f3;
}

/* --- colour: dark ----------------------------------------------------
 * Separately chosen, not an inversion. Neutral graphite ground, and the
 * charcoal accent turns into a cream one. In dark mode
 * shadows read poorly, so elevation leans on the surface step instead.
 */
:root[data-theme="dark"] {
  --color-page:            #181817;
  --color-surface:         #20201f;
  --color-surface-inset:   #292928;
  --color-surface-raised:  #3a3a38;

  --color-border:          #2f2f2e;
  --color-border-strong:   #444443;
  --color-border-control:  #74736f;
  --color-border-card:     #353534;
  --color-table-header:    #242423;
  --color-field-border:    #444443;
  --color-field-label:     #8e9bab;
  --color-field-focus:     #efeeec;

  --color-text:            #efeeec;
  --color-text-muted:      #b8b6b2;
  --color-text-subtle:     #999793;

  --color-accent:          #da1e4d;
  --color-accent-hover:    #c01a44;
  --color-accent-active:   #a8163b;
  --color-accent-subtle:   #3a1f26;
  --color-accent-fg:       #ffffff;
  --color-accent-text:     #ff6f8c;

  --color-success-bg:      #29362e;
  --color-success-fg:      #65b38b;
  --color-success-dot:     #59a77f;
  --color-warning-bg:      #403528;
  --color-warning-fg:      #f8b064;
  --color-warning-dot:     #eba357;
  --color-danger-bg:       #422f30;
  --color-danger-fg:       #fe8c95;
  --color-danger-dot:      #f57c87;
  --color-info-bg:         #2b323a;
  --color-info-fg:         #70a1db;
  --color-info-dot:        #6290ca;

  --color-chart-1:         #32a0c4;

  --color-wash-a:          #232221;
  --color-wash-b:          #1e2021;

  --shadow-raised: 0 1px 2px rgb(0 0 0 / .4);
  --shadow-float:  0 8px 24px rgb(0 0 0 / .5);
  --shadow-pin:   -8px 0 8px -8px rgb(0 0 0 / .6);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --color-page:            #181817;
    --color-surface:         #20201f;
    --color-surface-inset:   #292928;
    --color-surface-raised:  #3a3a38;

    --color-border:          #2f2f2e;
    --color-border-strong:   #444443;
    --color-border-control:  #74736f;
    --color-border-card:     #353534;
    --color-table-header:    #242423;
    --color-field-border:    #444443;
    --color-field-label:     #8e9bab;
    --color-field-focus:     #efeeec;

    --color-text:            #efeeec;
    --color-text-muted:      #b8b6b2;
    --color-text-subtle:     #999793;

    --color-accent:          #da1e4d;
    --color-accent-hover:    #c01a44;
    --color-accent-active:   #a8163b;
    --color-accent-subtle:   #3a1f26;
    --color-accent-fg:       #ffffff;
    --color-accent-text:     #ff6f8c;

    --color-success-bg:      #29362e;
    --color-success-fg:      #65b38b;
    --color-success-dot:     #59a77f;
    --color-warning-bg:      #403528;
    --color-warning-fg:      #f8b064;
    --color-warning-dot:     #eba357;
    --color-danger-bg:       #422f30;
    --color-danger-fg:       #fe8c95;
    --color-danger-dot:      #f57c87;
    --color-info-bg:         #2b323a;
    --color-info-fg:         #70a1db;
    --color-info-dot:        #6290ca;

    --color-chart-1:         #32a0c4;

    --color-wash-a:          #232221;
    --color-wash-b:          #1e2021;

    --shadow-raised: 0 1px 2px rgb(0 0 0 / .4);
    --shadow-float:  0 8px 24px rgb(0 0 0 / .5);
    --shadow-pin:   -8px 0 8px -8px rgb(0 0 0 / .6);
  }
}

/* --- the minimum that makes the tokens do their job ------------------ */

/* The page washes are part of the palette: two faint glows, top left and
   top right, over the cream ground. They sit on body only; cards stay flat. */
body {
  margin: 0;   /* the shell owns the page edge */
  background: radial-gradient(1100px 560px at 12% -8%, var(--color-wash-a), transparent 62%),
              radial-gradient(900px 520px at 96% 4%, var(--color-wash-b), transparent 58%),
              var(--color-page);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: var(--text-body);
  line-height: var(--leading-body);
  letter-spacing: var(--tracking-body);
  font-optical-sizing: auto;
}

/* The hero number. It is carried by size, weight and tight tracking rather
   than by a second typeface — the serif that used to do this job was deleted
   because it earned its 20 kB on exactly one element, and a screen with no
   KPI tile shipped a font nothing used. */
.metric, .metric-sm {
  font-weight: var(--font-weight-semibold);
  font-variant-numeric: tabular-nums lining-nums;
}
.metric    { font-size: var(--text-metric);    line-height: var(--leading-metric);    letter-spacing: var(--tracking-metric); }
.metric-sm { font-size: var(--text-metric-sm); line-height: var(--leading-metric-sm); letter-spacing: var(--tracking-metric-sm); }

/* [hidden] must win. Any `display` rule beats the browser's
   `[hidden] { display: none }`, and this house gives every control
   `display: inline-flex` — so a button with the hidden attribute stays
   visible unless this rule exists. It is the one !important here, and it
   earns it. */
[hidden] { display: none !important; }

/* Reserve the scrollbar's space always. Without it, a filter that shortens the
   page removes the scrollbar and every table and card jumps ~15px wider. */
html { scrollbar-gutter: stable; }

/* every number in a table or a KPI tile */
.num {
  font-variant-numeric: tabular-nums slashed-zero;
}

/* identifiers: order numbers, SKUs, request IDs, commit hashes, IPs.
   Not customer names, not money.

   Trap: the `font:` shorthand resets font-family. A component rule like
   `.field input { font: inherit }` has higher specificity than `.mono` and
   will silently defeat this, with no error and no visible clue beyond the
   wrong typeface. Set font-family explicitly in component CSS instead of
   using the shorthand. */
.mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums slashed-zero;
}

/* The small uppercase label: table headers, field groups, nav section
   headings, the label above a filter control. One class, because every part
   of the role travels together — a caller who sets the size and forgets the
   tracking gets 12px uppercase at body spacing, which reads as cramped
   without looking obviously wrong.

   Uppercase is a text-transform, never capitals typed into the string:
   `voice.md` keeps the underlying text in sentence case so it reads correctly
   to a screen reader. */
.caption-upper {
  font-size: var(--text-caption);
  line-height: var(--leading-caption);
  letter-spacing: var(--tracking-caption-upper);
  text-transform: uppercase;
  font-weight: var(--font-weight-semibold);
}

/* a ring, layered over the resting border — never a border swap, which
   shifts layout by a pixel and loses the resting edge */
:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Elements that exist only to receive focus are exempt. A single-page app
   moves focus to the page heading after a navigation so the new page is
   announced, and Chrome counts that scripted focus as :focus-visible — so the
   rule above drew an accent box around the h1 on every page load. Focus still
   moves and is still announced; only the ring goes. This is for
   tabindex="-1" and nothing else: a real control without a ring is a worse
   defect than the one being fixed. */
[tabindex="-1"]:focus,
[tabindex="-1"]:focus-visible {
  outline: none;
}

@media (prefers-reduced-motion: no-preference) {
  :where(button, a, input, select, textarea, tr, [role="menuitem"]) {
    transition: background-color var(--dur-fast) var(--ease-out),
                border-color     var(--dur-fast) var(--ease-out),
                color            var(--dur-fast) var(--ease-out);
  }
}

/* --- status pill: a dot, a word, a tint ------------------------------
   Three tokens per status. -bg is the tint, -fg the word, -dot the solid
   mark. The dot is the shape that keeps status readable in greyscale, so the
   pill never needs a border. Values and contrast: references/tokens.md. */
.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  font-size: var(--text-caption);
  line-height: var(--leading-caption);
  letter-spacing: var(--tracking-caption);
  font-weight: var(--font-weight-semibold);
  white-space: nowrap;
}
.pill::before {
  content: "";
  flex-shrink: 0;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--pill-dot);
}
.pill-success { background: var(--color-success-bg); color: var(--color-success-fg); --pill-dot: var(--color-success-dot); }
.pill-warning { background: var(--color-warning-bg); color: var(--color-warning-fg); --pill-dot: var(--color-warning-dot); }
.pill-danger  { background: var(--color-danger-bg);  color: var(--color-danger-fg);  --pill-dot: var(--color-danger-dot); }
.pill-info    { background: var(--color-info-bg);    color: var(--color-info-fg);    --pill-dot: var(--color-info-dot); }

/* --- icon tile: the square beside a card or row title ----------------
   Neutral by default. A status tint only when the card counts that status;
   a blue tile on "Machines" because blue looks nice is decoration, and
   semantic colour carries status only. Never inside the metric itself. */
.tile {
  display: inline-grid;
  place-items: center;
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-md);
  background: var(--color-surface-inset);
  color: var(--color-text-muted);
}
.tile > svg { width: 16px; height: 16px; }
.tile-success { background: var(--color-success-bg); color: var(--color-success-fg); }
.tile-warning { background: var(--color-warning-bg); color: var(--color-warning-fg); }
.tile-danger  { background: var(--color-danger-bg);  color: var(--color-danger-fg); }
.tile-info    { background: var(--color-info-bg);    color: var(--color-info-fg); }

/* --- card: white sheet, hairline edge, 6px corners --------------------
   Grouped by its border plus the surface step, never a shadow. */
.card {
  background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-card);
  border-radius: var(--radius-md);
}

/* a card footer sits on the card's own white, under a faint rule */
.card-foot {
  padding: var(--space-3) var(--space-4);
  border-top: var(--border-hairline) solid var(--color-border);
  color: var(--color-text-muted);
  font-size: var(--text-body-sm);
  line-height: var(--leading-body-sm);
}

/* --- table header: near-white, held by its hairlines ------------------- */
.table th {
  padding: var(--space-2) var(--space-4);
  background: var(--color-table-header);
  border-block: var(--border-hairline) solid var(--color-border);
  color: var(--color-text-subtle);
  text-align: left;
}

/* --- buttons: 40px, three variants ------------------------------------
   .btn alone is the secondary: the primary turned inside out, no fill, an
   accent-text border and label. .btn-primary is the accent fill, one per
   screen. .btn-ghost is a bare label for dense row actions.
   box-sizing matters: an <a> otherwise adds its border to the 32px and comes
   out 2px taller than a <button> beside it. 40px matches the field height,
   so a field and a button side by side line up. Rules: references/craft.md */
.btn {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 40px;
  padding: 0 var(--space-4);
  border: var(--border-hairline) solid var(--color-accent-text);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-accent-text);
  font: inherit;
  font-size: var(--text-body-sm);
  font-weight: var(--font-weight-semibold);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}
.btn > svg { width: 16px; height: 16px; flex-shrink: 0; }
.btn:hover { background: var(--color-accent-subtle); }
.btn-primary { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-accent-fg); }
.btn-primary:hover { background: var(--color-accent-hover); border-color: var(--color-accent-hover); }
.btn-primary:active { background: var(--color-accent-active); border-color: var(--color-accent-active); }
.btn-ghost { border-color: transparent; color: var(--color-text-muted); padding: 0 var(--space-3); }
.btn-ghost:hover { background: var(--color-surface-inset); color: var(--color-text); }

/* --- segmented control: a few options in one track, the pressed one raised.
   For a time range and other short, exclusive choices. The pressed segment
   is a raised surface, not an accent fill. */
.segmented {
  box-sizing: border-box;
  display: inline-flex;
  gap: 2px;
  height: 40px;
  padding: 2px;
  background: var(--color-surface-inset);
  border: var(--border-hairline) solid var(--color-border);
  border-radius: var(--radius-sm);
}
.segmented > button {
  display: inline-flex;
  align-items: center;
  padding: 0 var(--space-3);
  border: 0;
  border-radius: 3px;   /* nested: 4px outer minus the 2px inset, rounded up */
  background: transparent;
  color: var(--color-text-muted);
  font: inherit;
  font-size: var(--text-body-sm);
  font-weight: var(--font-weight-semibold);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.segmented > button:hover { color: var(--color-text); }
.segmented > button[aria-pressed="true"] {
  background: var(--color-surface-raised);
  color: var(--color-text);
  box-shadow: var(--shadow-raised), 0 0 0 var(--border-hairline) var(--color-border);
}

/* --- form fields ------------------------------------------------------
   Stack, top to bottom: label, input, error, help. Nothing sits between the
   label and the input; explaining text goes below the field as help.
   Fields are 40px, left-aligned (numbers too), 2px corners, a faint border,
   and a 1px near-black outline on focus. Rules: references/forms.md */
.field { display: flex; flex-direction: column; gap: 6px; min-width: 0; margin: 0; padding: 0; border: 0; }
.field-label {
  padding: 0;
  color: var(--color-field-label);
  font-size: var(--text-body-sm);
  line-height: var(--leading-body-sm);
  font-weight: var(--font-weight-regular);
}
.req { color: var(--color-accent-text); font-weight: var(--font-weight-semibold); }
.field-help { margin: 0; color: var(--color-text-subtle); font-size: var(--text-body-sm); line-height: var(--leading-body-sm); }
.field-error {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin: 0;
  color: var(--color-danger-fg);
  font-size: var(--text-body-sm);
  line-height: var(--leading-body-sm);
  font-weight: var(--font-weight-semibold);
}
.field-error > svg { width: 14px; height: 14px; flex-shrink: 0; }

.input {
  box-sizing: border-box;
  width: 100%;
  height: 40px;
  padding: 0 var(--space-3);
  border: var(--border-hairline) solid var(--color-field-border);
  border-radius: var(--radius-field);
  background: var(--color-surface);
  color: var(--color-text);
  font: inherit;
  font-size: var(--text-body);
  line-height: var(--leading-body);
  text-align: left;
  font-variant-numeric: tabular-nums;
}
.input:hover { border-color: var(--color-text-subtle); }
/* focus: the border and a flush 1px outline go near-black, no gap, no halo.
   This replaces the 2px offset ring for fields only; buttons keep the ring. */
.input:focus, .input:focus-visible {
  outline: var(--border-hairline) solid var(--color-field-focus);
  outline-offset: 0;
  border-color: var(--color-field-focus);
}
textarea.input { height: auto; min-height: 80px; padding: var(--space-2) var(--space-3); resize: vertical; }
.field.has-error .input,
.field.has-error .addon { border-color: var(--color-danger-fg); }
.field.has-error .input:focus,
.field.has-error .input-group:focus-within { outline-color: var(--color-danger-fg); }

/* a value with a unit: the unit always sits on the left, so every value in a
   form starts at the same edge, and the focus outline wraps unit and input */
.input-group { display: flex; min-width: 0; border-radius: var(--radius-field); }
.input-group > .input { flex: 1; min-width: 0; border-top-left-radius: 0; border-bottom-left-radius: 0; }
.addon {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 36px;
  padding: 0 var(--space-3);
  border: var(--border-hairline) solid var(--color-field-border);
  border-right: 0;
  border-radius: var(--radius-field) 0 0 var(--radius-field);
  background: var(--color-surface-inset);
  color: var(--color-text-muted);
  font-size: var(--text-body-sm);
}
.input-group:focus-within { outline: var(--border-hairline) solid var(--color-field-focus); outline-offset: 0; }
.input-group:focus-within > .input { outline: none; }
.input-group:focus-within > .input,
.input-group:focus-within > .addon { border-color: var(--color-field-focus); }

.choice { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-body); cursor: pointer; }
.choice > input { width: 16px; height: 16px; margin: 0; accent-color: var(--color-accent); }

/* --- form action bar: fixed to the bottom of the content area ----------
   From the side nav's edge (232px) to the viewport edge, always visible.
   The failed-submit alert lives in it, directly above the buttons. Reserve
   the bar's height at the bottom of .app-main (it grows when the alert
   shows) or the last card hides under it. Below 768px it spans the full
   width. A 64px rail would need left: 64px. */
.form-bar {
  position: fixed;
  left: 232px;
  right: 0;
  bottom: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-3) var(--shell-gap) calc(var(--space-3) + env(safe-area-inset-bottom, 0px));
  background: var(--color-surface);
  border-top: var(--border-hairline) solid var(--color-border);
  box-shadow: 0 -6px 12px -10px rgb(15 23 42 / .25);
}
.form-bar-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
@media (max-width: 767px) { .form-bar { left: 0; } }
.form-alert {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  font-size: var(--text-body-sm);
  line-height: var(--leading-body-sm);
  font-weight: var(--font-weight-semibold);
}
.form-alert > svg { width: 16px; height: 16px; flex-shrink: 0; margin-top: 1px; }
.form-alert-error { background: var(--color-danger-bg); color: var(--color-danger-fg); }
.form-alert-success { background: var(--color-success-bg); color: var(--color-success-fg); }

/* a status with no semantic colour (stopped, archived, draft): grey, still dot + word */
.pill-neutral { background: var(--color-surface-inset); color: var(--color-text-muted); --pill-dot: var(--color-text-subtle); }

/* a quiet destructive action: a ghost button in the danger colour */
.btn-danger { color: var(--color-danger-fg); }
.btn-danger:hover { background: var(--color-danger-bg); color: var(--color-danger-fg); }

/* --- filter panel: the secondary sidebar on a single-table page ---------
   Flush against the side nav, full height, the card surface with a right
   hairline. Holds the table's filters: a search field, checkbox facets with
   counts, selects, a segmented range. Below 1024px it becomes a drawer behind
   a .filters-toggle button, over a .panel-scrim. Rules: references/profile-crud.md */
.filter-panel {
  box-sizing: border-box;
  position: sticky;
  top: 0;
  align-self: flex-start;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  width: 248px;
  height: 100dvh;
  overflow-y: auto;
  padding: 22px var(--space-4);
  background: var(--color-surface);
  color: var(--color-text);
  border-right: var(--border-hairline) solid var(--color-border);
}
.filter-head { display: flex; align-items: center; justify-content: space-between; min-height: 34px; }
.filter-title { margin: 0; font-size: var(--text-body-lg); line-height: var(--leading-body-lg); font-weight: var(--font-weight-semibold); }
.filter-clear {
  border: 0;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text-muted);
  font: inherit;
  font-size: var(--text-body-sm);
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
}
.filter-clear:hover { background: var(--color-surface-inset); color: var(--color-text); }
.filter-group { min-width: 0; margin: 0; padding: var(--space-4) 0 0; border: 0; border-top: var(--border-hairline) solid var(--color-border); }
.filter-legend {
  float: left;
  width: 100%;
  margin: 0 0 var(--space-2);
  padding: 0;
  color: var(--color-field-label);
  font-size: var(--text-body-sm);
  line-height: var(--leading-body-sm);
}
.filter-options { clear: both; display: flex; flex-direction: column; }
.filter-option { display: flex; align-items: center; gap: var(--space-2); min-height: 32px; font-size: var(--text-body); cursor: pointer; }
.filter-option > input { width: 16px; height: 16px; margin: 0; accent-color: var(--color-accent); }
.filter-option .count { margin-left: auto; color: var(--color-text-subtle); font-size: var(--text-body-sm); font-variant-numeric: tabular-nums; }
.filter-option.is-empty { color: var(--color-text-subtle); }
.segmented-fill { display: flex; }
.segmented-fill > button { flex: 1; justify-content: center; }
.filters-toggle { display: none; }
.panel-scrim { position: fixed; inset: 0; z-index: 25; background: rgb(0 0 0 / .35); }
@media (max-width: 1023px) {
  .filters-toggle { display: inline-flex; }
  .filter-panel { position: fixed; z-index: 30; left: 0; top: 0; transform: translateX(-100%); box-shadow: var(--shadow-float); }
  .filter-panel.is-open { transform: none; }
}

/* --- fixed-layout table: columns that do not move when the rows change ---
   A filterable table sets its column widths in a <colgroup>: the first,
   widest column auto, the rest in px sized to their longest content. With
   the auto layout every filter re-measures the columns and the table shifts. */
.table-fixed { table-layout: fixed; }
.table-fixed td { overflow: hidden; text-overflow: ellipsis; }
.table-fixed td.actions { overflow: visible; }
/* an icon-only button and a text link sit on different baselines; centre both */
td.actions > * { vertical-align: middle; }

/* --- row drawer: per-row actions under the row ------------------------
   A click on the row (not on a link or button) or its .row-toggle chevron
   opens a <tr class="row-drawer"> below it. One open at a time; the other
   rows dim while it is open. Optional per row: a row without actions gets
   .row-toggle-none and does not open. Rules: references/profile-crud.md */
tr.has-drawer { cursor: pointer; }
.row-toggle { width: 40px; padding: 0; justify-content: center; }
.row-toggle > svg { width: 16px; height: 16px; transition: transform var(--dur-med) var(--ease-out); }
.row-toggle[aria-expanded="true"] > svg { transform: rotate(180deg); }
.row-toggle-none { display: inline-block; width: 40px; vertical-align: middle; }
tr.is-open > td { background: var(--color-table-header); border-bottom: 0; }
.row-drawer > td {
  padding: 0;
  white-space: normal;
  overflow: visible;
  background: var(--color-surface-inset);
  border-top: var(--border-hairline) solid var(--color-border-strong);
  border-bottom: var(--border-hairline) solid var(--color-border-strong);
  box-shadow: inset 0 6px 8px -8px rgb(15 23 42 / .22);
}
.drawer-body {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-4) var(--space-8);
  padding: var(--space-4);
}
.drawer-facts { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-8); margin: 0; }
.drawer-facts dt { color: var(--color-field-label); font-size: var(--text-body-sm); line-height: var(--leading-body-sm); }
.drawer-facts dd { margin: 2px 0 0; font-size: var(--text-body-sm); line-height: var(--leading-body-sm); }
.drawer-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
/* keeps the base colour transition on rows and adds opacity */
tbody > tr { transition: background-color var(--dur-fast) var(--ease-out), opacity var(--dur-fast) var(--ease-out); }
tbody.has-open > tr:not(.is-open):not(.row-drawer) { opacity: .45; }
tbody.has-open > tr:not(.is-open):not(.row-drawer):hover { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .row-toggle > svg, tbody > tr { transition: none; } }

/* --- modal: a one-field edit or a destructive confirm, nothing else ------
   Native <dialog> with showModal(): focus trap, Escape, the inert page and
   focus return come from the browser. Head (title naming the record + close),
   body (.modal-record, .modal-consequences, or one field), foot (Cancel and
   the action labelled with verb and record). Rules: references/interaction.md */
.modal {
  box-sizing: border-box;
  width: min(480px, calc(100vw - 32px));
  padding: 0;
  border: var(--border-hairline) solid var(--color-border-card);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  color: var(--color-text);
  box-shadow: var(--shadow-float);
}
.modal::backdrop { background: rgb(15 23 42 / .45); }
.modal-head { display: flex; align-items: flex-start; gap: var(--space-3); padding: var(--space-6) var(--space-6) 0; }
.modal-title {
  flex: 1;
  margin: 0;
  padding-top: var(--space-2);
  font-size: var(--text-body-lg);
  line-height: var(--leading-body-lg);
  font-weight: var(--font-weight-semibold);
}
.modal-close { width: 40px; padding: 0; justify-content: center; margin: calc(-1 * var(--space-1)) calc(-1 * var(--space-2)) 0 0; }
.modal-body { display: flex; flex-direction: column; gap: var(--space-4); padding: var(--space-3) var(--space-6) var(--space-6); }
.modal-body p { margin: 0; color: var(--color-text-muted); }
.modal-record {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  background: var(--color-surface-inset);
}
.modal-record b { display: block; font-weight: var(--font-weight-semibold); }
.modal-record .mono { color: var(--color-text-subtle); font-size: var(--text-body-sm); }
.modal-record .pill { margin-left: auto; }
.modal-consequences { margin: 0; padding-left: 18px; color: var(--color-text-muted); }
.modal-consequences li + li { margin-top: var(--space-1); }
.modal-foot {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-6);
  border-top: var(--border-hairline) solid var(--color-border);
}
@media (prefers-reduced-motion: no-preference) {
  .modal[open] { animation: modal-in var(--dur-med) var(--ease-out); }
  @keyframes modal-in { from { opacity: 0; transform: translateY(8px); } }
}

/* --- toast: confirms a finished action with no other place on screen ----
   Charcoal like the side nav, one sentence naming what happened to which
   record. A success toast leaves after 5s; an error never auto-dismisses. */
.toast-region {
  position: fixed;
  right: var(--shell-gap);
  bottom: var(--shell-gap);
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.toast {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  background: var(--color-nav);
  color: var(--color-nav-text);
  box-shadow: var(--shadow-float);
  font-size: var(--text-body-sm);
}
.toast > svg { width: 16px; height: 16px; flex-shrink: 0; color: var(--color-nav-success); }

/* --- breadcrumbs: the trail above a detail page's title ----------------- */
.breadcrumbs ol { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: 0; padding: 0 var(--space-1); list-style: none;
  color: var(--color-text-subtle); font-size: var(--text-body-sm); line-height: var(--leading-body-sm); }
.breadcrumbs li + li::before { content: "›"; margin-right: var(--space-2); }
.breadcrumbs a { color: var(--color-text-muted); text-decoration: none; }
.breadcrumbs a:hover { color: var(--color-text); text-decoration: underline; }
.breadcrumbs [aria-current] { color: var(--color-text); }

/* --- tabs: sections of one record ------------------------------------
   40px labels on a hairline; the selected one is charcoal with a 2px
   underline — not the accent, which belongs to the primary action. The
   selected tab lives in the URL hash. Rules: references/navigation.md */
.tabs { display: flex; gap: var(--space-1); overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
  border-bottom: var(--border-hairline) solid var(--color-border-strong); }
.tab { display: inline-flex; align-items: center; gap: var(--space-2); height: 40px; margin-bottom: -1px; padding: 0 var(--space-3);
  border: 0; border-bottom: 2px solid transparent; background: transparent; color: var(--color-text-muted);
  font: inherit; font-size: var(--text-body); font-weight: var(--font-weight-semibold); white-space: nowrap; cursor: pointer; }
.tab:hover { color: var(--color-text); }
.tab[aria-selected="true"] { color: var(--color-text); border-bottom-color: var(--color-text); }
.tab:focus-visible { outline-offset: -2px; }
.tab-count { padding: 0 7px; border-radius: 999px; background: var(--color-surface-inset); color: var(--color-text-muted);
  font-size: var(--text-caption); line-height: 18px; font-variant-numeric: tabular-nums; }

/* --- facts: label left, value right, a hairline between ---------------- */
.facts { margin: 0; padding: 0 var(--space-4) var(--space-4); }
.facts > div { display: flex; justify-content: space-between; gap: var(--space-4); padding: var(--space-2) 0; border-top: var(--border-hairline) solid var(--color-border); }
.facts > div:first-child { border-top: 0; }
.facts dt { color: var(--color-field-label); font-size: var(--text-body-sm); line-height: var(--leading-body-sm); }
.facts dd { min-width: 0; margin: 0; text-align: right; font-size: var(--text-body-sm); line-height: var(--leading-body-sm); }

/* --- selected row: the row whose detail is open in the side panel ------- */
tr.is-selected > td { background: var(--color-accent-subtle); }
tr.is-selected > td:first-child { box-shadow: inset 2px 0 0 var(--color-accent); }

/* --- side panel: one record's detail from the right ---------------------
   A non-modal <dialog> opened with show(): no scrim, the list behind stays
   readable and clickable. Escape and the close button shut it; focus goes
   to the close button and back to the row. From 1200px the content makes
   room (body.has-panel) so the list reflows beside it, without animating.
   Rules: references/profile-crud.md */
.side-panel {
  position: fixed; inset: 0 0 0 auto; z-index: 20;
  box-sizing: border-box; width: min(440px, 100vw); height: 100dvh; max-height: none; margin: 0; padding: 0;
  display: none; flex-direction: column;
  border: 0; border-left: var(--border-hairline) solid var(--color-border-card);
  background: var(--color-surface); color: var(--color-text); box-shadow: var(--shadow-float);
}
.side-panel[open] { display: flex; }
.side-panel-head { display: flex; align-items: flex-start; gap: var(--space-3); padding: var(--space-6) var(--space-6) var(--space-4);
  border-bottom: var(--border-hairline) solid var(--color-border); }
.side-panel-head > div { flex: 1; min-width: 0; }
.side-panel-kicker { margin: 0; color: var(--color-field-label); font-size: var(--text-body-sm); }
.side-panel-title { margin: 2px 0 0; font-size: var(--text-body-lg); line-height: var(--leading-body-lg); font-weight: var(--font-weight-semibold); }
.side-panel-body { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: var(--space-4); padding: var(--space-4) var(--space-6) var(--space-6); }
.side-panel-body .facts { padding: 0; }
.side-panel-foot { display: flex; flex-wrap: wrap; gap: var(--space-2); padding: var(--space-4) var(--space-6) calc(var(--space-4) + env(safe-area-inset-bottom, 0px));
  border-top: var(--border-hairline) solid var(--color-border); }
.side-panel-foot:empty { display: none; }
@media (min-width: 1200px) {
  body.has-panel .app-main { padding-right: calc(min(440px, 100vw) + var(--shell-gap)); }
}
@media (prefers-reduced-motion: no-preference) {
  .side-panel[open] { animation: panel-in var(--dur-med) var(--ease-out); }
  @keyframes panel-in { from { transform: translateX(24px); opacity: 0; } }
}

/* --- secrets and logs --------------------------------------------------- */
.secret { font-family: var(--font-mono); font-size: var(--text-body-sm); }
.secret-mask { letter-spacing: 0.15em; color: var(--color-text-subtle); }
.log { margin: 0; padding: var(--space-3) var(--space-4); overflow: auto; border-radius: var(--radius-sm);
  background: var(--color-surface-inset); font-family: var(--font-mono); font-size: var(--text-body-sm); line-height: 1.7; white-space: pre; }
.side-panel .log { white-space: pre-wrap; overflow-wrap: anywhere; }
.log .t { color: var(--color-text-subtle); }
.log .w { color: var(--color-warning-fg); }
.log .e { color: var(--color-danger-fg); }

/* --- search field: a magnifier inside the input ------------------------- */
.search { position: relative; display: block; }
.search > svg { position: absolute; left: 12px; top: 50%; width: 16px; height: 16px; transform: translateY(-50%); color: var(--color-text-subtle); pointer-events: none; }
.search > .input { padding-left: 36px; }

/* --- loading: skeletons at the final size of what they replace ----------
   A skeleton inside a line of text is inline-block, so the line keeps its
   height; table rows that load get a fixed height so skeleton and content
   rows match. Rules: references/states.md */
.skel { display: block; height: 12px; border-radius: var(--radius-sm); background: var(--color-surface-inset); }
.skel-inline { display: inline-block; vertical-align: middle; }
.skel-metric { width: 55%; height: 36px; margin: 2px 0; }
.skel-pill { width: 72px; height: 20px; }
@media (prefers-reduced-motion: no-preference) {
  .skel { background: linear-gradient(90deg, var(--color-surface-inset) 0%, color-mix(in srgb, var(--color-surface-inset) 40%, var(--color-surface)) 50%, var(--color-surface-inset) 100%);
    background-size: 200% 100%; animation: skel 1.4s linear infinite; }
  @keyframes skel { from { background-position: 100% 0; } to { background-position: -100% 0; } }
}
/* a refresh keeps the old values and dims them */
.is-refreshing { opacity: .55; transition: opacity var(--dur-fast) var(--ease-out); }

/* regions.js: the "still loading" notice past 10 s, and a KPI tile's
   one-line error that keeps the tile its size */
.region-slow { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); margin: 0 var(--space-4) var(--space-3);
  padding: var(--space-1) var(--space-1) var(--space-1) var(--space-3); border-radius: var(--radius-sm);
  background: var(--color-warning-bg); color: var(--color-warning-fg); font-size: var(--text-body-sm); font-weight: var(--font-weight-semibold); }
.region-slow > .btn { height: 32px; color: var(--color-warning-fg); }
.region-err { display: flex; align-items: center; gap: var(--space-1); margin-top: var(--space-2); color: var(--color-danger-fg);
  font-size: var(--text-body-sm); font-weight: var(--font-weight-semibold); }
.region-err > svg { width: 14px; height: 14px; flex-shrink: 0; }
.region-err-retry { align-self: flex-start; height: 32px; margin-left: calc(-1 * var(--space-2)); padding: 0 var(--space-2); }

/* --- a region's state: empty, zero results, error, no permission --------
   Inside the card, under its header: a tile, what happened, one sentence,
   the action. The error variant adds a mono detail line and a Retry. */
.state { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2); margin: 0 var(--space-4) var(--space-4);
  padding: var(--space-8) var(--space-6); border-radius: var(--radius-sm); background: var(--color-surface-inset); }
.state > .tile { background: var(--color-surface); }
.state-title { margin: var(--space-2) 0 0; font-weight: var(--font-weight-semibold); }
.state-text { max-width: 52ch; margin: 0 0 var(--space-2); color: var(--color-text-muted); }
.state-detail { margin: 0; color: var(--color-text-subtle); font-family: var(--font-mono); font-size: var(--text-caption); line-height: var(--leading-caption); }
.state-error { background: var(--color-danger-bg); }
.state-error > .state-text { color: var(--color-text); }
.state-inline { margin-top: var(--space-4); }

/* --- not found: inside the shell, with a way back ----------------------- */
.not-found { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-3); max-width: 60ch; padding: var(--space-16) var(--space-4); }
.not-found[hidden] { display: none; }
.not-found-code { margin: 0; color: var(--color-text-subtle); font-family: var(--font-mono); font-size: var(--text-body-lg); letter-spacing: 0.1em; }
.not-found-text { margin: 0 0 var(--space-3); color: var(--color-text-muted); }

/* --- page header: the 26px title and its context line -----------------
   Hatch AI's measures. Title left, actions right. Rules: references/navigation.md */
.page-header {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  padding: 6px var(--space-1) 0;
}
.page-header-text { flex: 1; min-width: 0; }
.page-title {
  margin: 0;
  font-size: var(--text-title);
  line-height: var(--leading-title);
  letter-spacing: var(--tracking-title);
  font-weight: var(--font-weight-semibold);
}
.page-context {
  margin: 3px 0 0;
  color: var(--color-text-muted);
  font-size: var(--text-body-sm);
  line-height: var(--leading-body-sm);
}

/* --- app shell: fluid, no top bar -------------------------------------
   A flex row: the side nav flush to the left edge, then the content, which
   takes every remaining pixel and carries the page padding. No max-width and
   no centring. Identity sits at the top of the nav and the account at its
   foot, so there is no top bar. Rules: references/navigation.md,
   references/layout.md */
.app-shell {
  box-sizing: border-box;
  display: flex;
  min-height: 100dvh;
}
.app-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--shell-gap);
  padding: var(--shell-gap);
}

/* --- side nav: a full-height strip on the left edge -------------------
   No radius and no inset: it fills the viewport height and a hairline on its
   right separates it from the content. No shadow. The current item takes the
   accent fill, the one other place the accent appears besides the primary
   action. The nav is charcoal: it remaps the surface, text and border tokens
   to the --color-nav-* set, so every rule below reads the usual names. The
   accent is not remapped: the red current item and the red focus ring both
   clear 3:1 on the charcoal. */
.side-nav {
  --color-surface:       var(--color-nav);
  --color-surface-inset: var(--color-nav-inset);
  --color-border:        var(--color-nav-border);
  --color-text:          var(--color-nav-text);
  --color-text-muted:    var(--color-nav-text-muted);
  --color-text-subtle:   var(--color-nav-text-subtle);
  box-sizing: border-box;
  position: sticky;
  top: 0;
  align-self: flex-start;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  width: 232px;
  height: 100dvh;
  padding: 22px 14px;
  overflow-y: auto;
  background: var(--color-surface);
  border-right: var(--border-hairline) solid var(--color-border);
}
.nav-brand {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 0 var(--space-2) var(--space-6);
  color: var(--color-text);
  text-decoration: none;
}
.nav-brand-mark {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-md);
  background: var(--color-nav-text);
  color: var(--color-nav);
}
.nav-brand-mark > svg { width: 18px; height: 18px; }
.nav-label {
  margin: 0 0 var(--space-2);
  padding: 0 10px;
  color: var(--color-text-subtle);
  font-size: var(--text-caption);
  line-height: var(--leading-caption);
  letter-spacing: var(--tracking-caption-upper);
  font-weight: var(--font-weight-semibold);
  text-transform: uppercase;
}
.nav-group { display: flex; flex-direction: column; gap: 3px; }
.nav-group + .nav-label { margin-top: 22px; }
.nav-item {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 9px 10px;
  border-radius: var(--radius-md);
  color: var(--color-text-muted);
  font-size: var(--text-body);
  line-height: var(--leading-body);
  text-decoration: none;
}
button.nav-item {
  width: 100%;
  border: 0;
  background: transparent;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}
.nav-item > svg { width: 18px; height: 18px; flex-shrink: 0; }
.nav-item:hover { background: var(--color-surface-inset); color: var(--color-text); }
.nav-item[aria-current="page"] {
  background: var(--color-accent);
  color: var(--color-accent-fg);
  font-weight: var(--font-weight-semibold);
}
.nav-count {
  margin-left: auto;
  padding: 1px var(--space-2);
  border-radius: 999px;
  background: var(--color-surface-inset);
  color: var(--color-text-muted);
  font-size: var(--text-caption);
  line-height: var(--leading-caption);
  font-weight: var(--font-weight-semibold);
  font-variant-numeric: tabular-nums slashed-zero;
}
/* the hover fill and the chip are both --color-nav-inset, so a hovered row
   lifts its chip one step or the chip disappears */
.nav-item:not([aria-current="page"]):hover .nav-count {
  background: var(--color-nav-raised);
  color: var(--color-text);
}
.nav-item[aria-current="page"] .nav-count {
  background: rgb(255 255 255 / .2);
  color: inherit;
}
/* the foot: who is signed in, then sign out. Pushed to the bottom. */
.nav-foot {
  margin-top: auto;
  padding-top: 14px;
  border-top: var(--border-hairline) solid var(--color-border);
}
.nav-foot .nav-item { margin-top: var(--space-1); }
.nav-user {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: var(--space-2) var(--space-2) var(--space-1);
}
.nav-user-text { min-width: 0; }
.nav-user-name,
.nav-user-meta {
  display: block;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.nav-user-name { color: var(--color-text); font-size: var(--text-body-sm); line-height: var(--leading-body-sm); }
.nav-user-meta { color: var(--color-text-subtle); font-size: var(--text-caption); line-height: var(--leading-caption); }
.avatar {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--color-surface-inset);
  color: var(--color-text-muted);
  font-size: var(--text-caption);
  font-weight: var(--font-weight-semibold);
}

/* --- settings pages --------------------------------------------------------
   Section nav: <nav class="filter-panel sub-nav"> takes the filter panel's
   shell and holds links to the sections of one long page. Current = inset
   fill + a 2px charcoal bar; the accent stays on the main nav. */
.sub-nav { gap: var(--space-4); }
.sub-nav-list { display: flex; flex-direction: column; gap: 2px; }
.sub-nav-item { display: flex; align-items: center; height: 36px; padding: 0 var(--space-3); border-radius: var(--radius-sm);
  color: var(--color-text-muted); text-decoration: none; font-size: var(--text-body);
  transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out); }
.sub-nav-item:hover { background: var(--color-surface-inset); color: var(--color-text); }
.sub-nav-item[aria-current="true"] { background: var(--color-surface-inset); color: var(--color-text); font-weight: var(--font-weight-semibold);
  box-shadow: inset 2px 0 0 var(--color-text); }
.sub-nav-item .nav-count { margin-left: auto; color: var(--color-text-subtle); font-size: var(--text-body-sm); font-weight: var(--font-weight-regular); }
@media (max-width: 1023px) { .sub-nav { display: none; } }

/* form sections inside a card; settings fields stay at a readable width */
.form-body { display: flex; flex-direction: column; gap: var(--space-6); padding: 0 var(--space-4) var(--space-6); }
.field-row { display: flex; flex-wrap: wrap; gap: var(--space-4); }
.field-row > .field { flex: 1 1 200px; }
.settings-card { scroll-margin-top: var(--shell-gap); }
.settings-card .form-body > .field, .settings-card .field-row { max-width: 480px; }

/* per-section save: state on the left, Discard + Save on the right. The
   footer has the same height clean or dirty; Save is primary only while dirty. */
.settings-foot { display: flex; align-items: center; gap: var(--space-2); }
.save-state { flex: 1; }
.settings-card.is-dirty .save-state { color: var(--color-warning-fg); font-weight: var(--font-weight-semibold); }

/* switch: saves at once. Knob position and the label carry the state */
.switch { position: relative; flex-shrink: 0; width: 36px; height: 20px; margin-top: 2px; padding: 0; border: 0; border-radius: 10px;
  background: var(--color-text-subtle); cursor: pointer; transition: background-color var(--dur-fast) var(--ease-out); }
.switch::after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%;
  background: var(--color-surface); transition: transform var(--dur-fast) var(--ease-out); }
.switch[aria-checked="true"] { background: var(--color-text); }
.switch[aria-checked="true"]::after { transform: translateX(16px); }
.switch[aria-busy="true"] { opacity: .55; cursor: progress; }
.switch:focus-visible { outline: 2px solid var(--color-field-focus); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .switch, .switch::after { transition: none; } }
.switch-list, .danger-list { padding: 0 var(--space-4) var(--space-2); }
.switch-row { display: flex; align-items: flex-start; gap: var(--space-3); padding: var(--space-3) 0; border-top: var(--border-hairline) solid var(--color-border); }
.danger-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); padding: var(--space-3) 0;
  border-top: var(--border-hairline) solid var(--color-border); }
.switch-row:first-child, .danger-row:first-child { border-top: 0; }
.switch-label { display: block; margin: 0; font-weight: var(--font-weight-semibold); line-height: var(--leading-body); }
label.switch-label { cursor: pointer; }
.switch-desc { margin: 0; color: var(--color-text-muted); font-size: var(--text-body-sm); line-height: var(--leading-body-sm); }

/* a secret shown once (a new token): mono value and a Copy button on inset */
.new-secret { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-2) var(--space-2) var(--space-2) var(--space-3);
  background: var(--color-surface-inset); border-radius: var(--radius-sm); }
.new-secret .secret { flex: 1; min-width: 0; overflow-wrap: anywhere; }

/* --- auth pages: sign in, register, reset ----------------------------------
   No side nav. A charcoal brand panel on the left (the nav's identity, full
   height) and one 400px form on the plain ground. Below 900px the panel is
   a band on top. */
html:has(.auth-shell) { scrollbar-gutter: auto; } /* nothing to keep still; a gutter would show beside the panel */
.auth-shell { display: grid; grid-template-columns: minmax(320px, 2fr) 3fr; min-height: 100dvh; }
.auth-brand { position: relative; isolation: isolate; overflow: hidden; display: flex; flex-direction: column; gap: var(--space-6);
  padding: var(--space-8) var(--space-8) var(--space-6); background: var(--color-nav); color: var(--color-nav-text); }
.auth-brand .nav-brand { padding: 0; color: var(--color-nav-text); }
.auth-pitch { margin: auto 0 0; max-width: 360px; color: var(--color-nav-text-muted); font-size: var(--text-body-lg); line-height: var(--leading-body-lg); }
.auth-status { display: flex; align-items: center; gap: var(--space-2); margin: 0; color: var(--color-nav-text-muted); font-size: var(--text-body-sm); }
.auth-status::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--color-nav-success); }
.auth-status a, .auth-legal a { color: inherit; }
.auth-legal { margin: 0; color: var(--color-nav-text-subtle); font-size: var(--text-caption); }
/* the house texture: a honeycomb from the lower-left corner, fading out.
   The box is the whole panel and the fade ends inside it, so no edge shows.
   White at .14 alpha: the panel is charcoal in both themes. */
.auth-brand::before { content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='27.71' height='48' viewBox='0 0 27.71 48'%3E%3Cpath d='M13.86 0.00 L27.71 8.00 L27.71 24.00 L13.86 32.00 L-0.00 24.00 L0.00 8.00Z M0.00 24.00 L13.86 32.00 L13.86 48.00 L0.00 56.00 L-13.86 48.00 L-13.86 32.00Z M27.71 24.00 L41.57 32.00 L41.57 48.00 L27.71 56.00 L13.86 48.00 L13.86 32.00Z M13.86 48.00 L27.71 56.00 L27.71 72.00 L13.86 80.00 L-0.00 72.00 L0.00 56.00Z' fill='none' stroke='%23ffffff' stroke-opacity='.14' stroke-width='1'/%3E%3C/svg%3E");
  background-position: left bottom;
  -webkit-mask-image: radial-gradient(110% 80% at 0 100%, #000 0%, rgb(0 0 0 / .5) 45%, transparent 100%);
  mask-image: radial-gradient(110% 80% at 0 100%, #000 0%, rgb(0 0 0 / .5) 45%, transparent 100%); }

.auth-main { position: relative; display: flex; align-items: center; justify-content: center; padding: var(--space-8) var(--space-4); }
.auth-theme { position: absolute; top: var(--space-4); right: var(--space-4); }
.auth-form { display: flex; flex-direction: column; gap: var(--space-6); width: 100%; max-width: 400px; }
.auth-form[hidden] { display: none; }
.auth-title { margin: 0; font-size: var(--text-title); line-height: var(--leading-title); font-weight: var(--font-weight-semibold); letter-spacing: -0.01em; }
.auth-sub { margin: var(--space-1) 0 0; color: var(--color-text-muted); }
.btn-block { width: 100%; justify-content: center; }
.btn[aria-busy="true"] { cursor: progress; }
.auth-link { color: var(--color-accent-text); font-weight: var(--font-weight-semibold); text-decoration: none; }
.auth-link:hover { text-decoration: underline; }
.auth-or { display: flex; align-items: center; gap: var(--space-3); color: var(--color-text-subtle); font-size: var(--text-body-sm); }
.auth-or::before, .auth-or::after { content: ""; flex: 1; border-top: var(--border-hairline) solid var(--color-border); }
.auth-switch { margin: 0; color: var(--color-text-muted); text-align: center; }
/* a label with a link on the same line: "Password" ... "Forgot password?" */
.label-row { display: flex; justify-content: space-between; align-items: baseline; }
.label-row .auth-link { font-size: var(--text-body-sm); }
/* password: the Show/Hide button sits inside the field, on the right */
.password { position: relative; }
.password > .input { width: 100%; padding-right: 72px; }
.password > .btn { position: absolute; top: 4px; right: 4px; height: 32px; padding: 0 var(--space-2); }
.caps { color: var(--color-warning-fg); font-weight: var(--font-weight-semibold); }

@media (max-width: 899px) {
  .auth-shell { grid-template-columns: 1fr; grid-template-rows: auto 1fr; }
  .auth-brand { flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2) var(--space-4); padding: var(--space-4); }
  .auth-brand::before, .auth-pitch, .auth-legal { display: none; }
  .auth-main { flex-direction: column; align-items: center; justify-content: flex-start; gap: var(--space-4); }
  .auth-theme { position: static; align-self: flex-end; }
}

/* --- table extras: bulk select, sort, pagination, column picker -----------
   The card head (.table-head) swaps to a bulk bar while rows are selected,
   at the same height. */
.table-head { min-height: 74px; box-sizing: border-box; transition: background-color var(--dur-fast) var(--ease-out); }
.table-head.is-bulk { background: var(--color-surface-inset); }
.bulk { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.bulk[hidden] { display: none; }
.bulk-count { margin-right: var(--space-2); font-weight: var(--font-weight-semibold); }
/* selection column, and the "select all N that match" row under the header */
.check-cell { width: 44px; padding-right: 0 !important; }
.check-cell input { width: 16px; height: 16px; margin: 0; vertical-align: middle; accent-color: var(--color-accent); cursor: pointer; }
.select-all-row td { padding: var(--space-2) var(--space-4); background: var(--color-surface-inset); color: var(--color-text-muted);
  font-size: var(--text-body-sm); text-align: center; white-space: normal; }
/* row actions while a bulk selection exists: one scope at a time */
td.actions [aria-disabled="true"], td.actions .btn:disabled { color: var(--color-text-subtle); pointer-events: none; }
.link-btn { padding: 0; border: 0; background: none; color: var(--color-accent-text); font: inherit; font-weight: var(--font-weight-semibold); cursor: pointer; }
.link-btn:hover { text-decoration: underline; }
/* sortable header: the whole label is the button; the arrow shows the current sort */
.th-sort { display: inline-flex; align-items: center; gap: var(--space-1); padding: 0; border: 0; background: none; color: inherit;
  font: inherit; letter-spacing: inherit; text-transform: inherit; cursor: pointer; }
.th-sort:hover { color: var(--color-text); }
.th-sort svg { width: 14px; height: 14px; }
.th-sort .sort-idle { opacity: 0; }
.th-sort:hover .sort-idle, .th-sort:focus-visible .sort-idle { opacity: .5; }
/* pagination in the card footer: range and page size left, pages right.
   The current page is a charcoal fill, never the accent. */
.pager-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2) var(--space-4); }
.pager-range { display: flex; align-items: center; gap: var(--space-3); }
.pager-size { display: flex; align-items: center; gap: var(--space-2); }
.pager-size .input { width: auto; height: 32px; padding-top: 0; padding-bottom: 0; }
.pager { display: flex; align-items: center; gap: 2px; }
.pager a, .pager .gap { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-1); min-width: 32px; height: 32px;
  padding: 0 var(--space-2); box-sizing: border-box; border-radius: var(--radius-sm); color: var(--color-text-muted); text-decoration: none;
  font-variant-numeric: tabular-nums; transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out); }
.pager a:hover { background: var(--color-surface-inset); color: var(--color-text); }
.pager a[aria-current="page"] { background: var(--color-text); color: var(--color-surface); font-weight: var(--font-weight-semibold); }
.pager a[aria-disabled="true"] { color: var(--color-text-subtle); pointer-events: none; }
.pager svg { width: 14px; height: 14px; }
.pager .prev svg { transform: rotate(180deg); }
/* menu: a native [popover] placed under its button by script (column picker) */
.menu { margin: 0; inset: auto; min-width: 220px; padding: var(--space-2) var(--space-3); box-sizing: border-box;
  border: var(--border-hairline) solid var(--color-border-card); border-radius: var(--radius-lg);
  background: var(--color-surface); color: var(--color-text); box-shadow: var(--shadow-float); }
.menu-title { display: block; margin: var(--space-1) 0 var(--space-1); color: var(--color-field-label); font-size: var(--text-body-sm); }
.menu .filter-option.is-fixed { color: var(--color-text-subtle); cursor: default; }
