/* GENER8V design system — GENERATED by packages/tokens/build.mjs.
   Do not edit. Change packages/tokens/src/theme.mjs (tokens),
   src/base.css (base layer) or src/components/*.css (component layer). */

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

:root {
/* Colour roles */
  --color-bg: #FFFFFF;
  --color-surface: #F7F9FC;
  --color-text: #111827;
  --color-accent: #2563EB;
  --color-accent-2: #0891B2;
  --color-divider: color-mix(in srgb, #182030 12%, transparent);

  /* Neutral ramp */
  --color-neutral-100: #F2F4F7;
  --color-neutral-200: #E2E6ED;
  --color-neutral-300: #CAD0DA;
  --color-neutral-400: #ABB2C0;
  --color-neutral-500: #8991A0;
  --color-neutral-600: #697181;
  --color-neutral-700: #4B5464;
  --color-neutral-800: #2F3848;
  --color-neutral-900: #182030;

  /* Accent ramp */
  --color-accent-100: #EEF4FF;
  --color-accent-200: #D9E6FF;
  --color-accent-300: #B7D0FF;
  --color-accent-400: #88B1FF;
  --color-accent-500: #528AFF;
  --color-accent-600: #2664EC;
  --color-accent-700: #1547B7;
  --color-accent-800: #0C307E;
  --color-accent-900: #071C4A;

  /* Accent-2 ramp */
  --color-accent-2-100: #E4F8FF;
  --color-accent-2-200: #C0EEFF;
  --color-accent-2-300: #91DCF6;
  --color-accent-2-400: #5EC0E0;
  --color-accent-2-500: #289FC1;
  --color-accent-2-600: #007D9B;
  --color-accent-2-700: #005D73;
  --color-accent-2-800: #003F4F;
  --color-accent-2-900: #01252F;

  /* Action */
  --color-action: #2664EC;
  --color-action-hover: #1547B7;
  --color-action-active: #0C307E;

  /* Chart series */
  --series-1: #2664EC;
  --series-2: #EB6834;
  --series-3: #289FC1;
  --series-4: #EDA100;
  --series-5: #E87BA4;
  --series-6: #008300;
  --series-7: #4A3AA7;
  --series-8: #E34948;

  /* Section band */
  --color-section: #071C4A;
  --color-section-glow: #0C307E;
  --color-section-ghost: #1547B7;

  /* Status */
  --color-success: #00865D;
  --color-success-100: #DDFDEC;
  --color-success-300: #91E4BD;
  --color-success-700: #006344;
  --color-success-800: #00432D;
  --color-warning: #A85A00;
  --color-warning-100: #FFF1E7;
  --color-warning-300: #FFC091;
  --color-warning-700: #7D4200;
  --color-warning-800: #562B00;
  --color-danger: #C13B34;
  --color-danger-100: #FFF0EE;
  --color-danger-300: #FFBCB3;
  --color-danger-700: #942521;
  --color-danger-800: #651814;

  /* The panel that is dark in both modes */
  --color-inverse: #182030;
  --color-on-inverse: #E2E6ED;

  /* The scrim behind a modal, dark in both modes */
  --color-scrim: #182030;

  /* Syntax */
  --code-comment: #ABB2C0;
  --code-string: #91E4BD;
  --code-keyword: #B7D0FF;
  --code-number: #FFC091;
  --code-name: #D9E6FF;

  /* Type */
  --font-heading: "Inter", system-ui, sans-serif;
  --font-heading-weight: 600;
  --font-body: "Inter", system-ui, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --text-xs: 12px;
  --leading-xs: 1.5;
  --tracking-xs: 0.01em;
  --text-sm: 14px;
  --leading-sm: 1.55;
  --tracking-sm: 0.005em;
  --text-base: 16px;
  --leading-base: 1.65;
  --tracking-base: 0;
  --text-lg: 18px;
  --leading-lg: 1.6;
  --tracking-lg: -0.005em;
  --text-xl: 20px;
  --leading-xl: 1.55;
  --tracking-xl: -0.01em;
  --text-2xl: 24px;
  --leading-2xl: 1.4;
  --tracking-2xl: -0.015em;
  --text-3xl: 30px;
  --leading-3xl: 1.3;
  --tracking-3xl: -0.02em;
  --text-4xl: 36px;
  --leading-4xl: 1.2;
  --tracking-4xl: -0.022em;
  --text-5xl: 48px;
  --leading-5xl: 1.12;
  --tracking-5xl: -0.025em;
  --text-6xl: 60px;
  --leading-6xl: 1.05;
  --tracking-6xl: -0.03em;

  /* Space */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;
  --space-20: 80px;
  --space-24: 96px;
  --space-32: 128px;

  /* Radius */
  --radius-xs: 2px;
  --radius-sm: 4px;
  --radius-md: 6px;
  --radius-lg: 10px;
  --radius-full: 9999px;

  /* Elevation */
  --shadow-sm: 0 1px 2px rgba(24, 32, 48, 0.05), 0 0 0 1px rgba(24, 32, 48, 0.05);
  --shadow-md: 0 4px 14px -2px rgba(24, 32, 48, 0.08), 0 0 0 1px rgba(24, 32, 48, 0.05);
  --shadow-lg: 0 18px 44px -10px rgba(24, 32, 48, 0.14), 0 0 0 1px rgba(24, 32, 48, 0.05);

  /* Motion */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --dur-fast: 140ms;
  --dur-base: 200ms;
  --dur-slow: 420ms;

  /* Controls */
  --control-h-xs: 24px;
  --control-h-sm: 30px;
  --control-h-md: 36px;
  --control-h-lg: 44px;
  --control-h-xl: 52px;

  /* Layout */
  --rail-w: 260px;
  --container-sm: 640px;
  --container-md: 768px;
  --container-lg: 1024px;
  --container-xl: 1280px;
  --container-2xl: 1440px;

  /* Focus */
  --ring-width: 2px;
  --ring-offset: 2px;
  --ring-color: var(--color-accent);

  /* Progress */
  --progress-track: var(--color-neutral-200);
  --progress-fill: var(--color-action);
  --progress-fill-success: var(--color-success-700);
  --progress-fill-warning: var(--color-warning-700);
  --progress-fill-danger: var(--color-danger-700);
  --progress-h-sm: 4px;
  --progress-h-md: 6px;
  --progress-h-lg: 8px;
  --progress-gap: 2px;
  --progress-radius: var(--radius-full);
  --progress-circle-xs: 16px;
  --progress-circle-sm: 24px;
  --progress-circle-md: 32px;
  --progress-circle-lg: 48px;
  --progress-circle-xl: 64px;
}


/* DARK MODE.
   Three states, and the middle one is the default: an explicit choice stamps
   [data-theme], and "system" stamps nothing — so only prefers-color-scheme
   separates light from dark, and [data-theme="light"] has to win over it. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    /* Ground, pinned in theme.mjs — a page's ground is a decision, not a ramp step. */
    --color-bg: #0F1522;
    --color-surface: #182030;
    --color-text: #F2F4F7;
    --color-divider: color-mix(in srgb, #FFFFFF 14%, transparent);

    /* The ramps, mirrored. */
    --color-neutral-100: #182030;
    --color-neutral-200: #2F3848;
    --color-neutral-300: #4B5464;
    --color-neutral-400: #697181;
    --color-neutral-500: #8991A0;
    --color-neutral-600: #ABB2C0;
    --color-neutral-700: #CAD0DA;
    --color-neutral-800: #E2E6ED;
    --color-neutral-900: #F2F4F7;
    --color-accent-100: #071C4A;
    --color-accent-200: #0C307E;
    --color-accent-300: #1547B7;
    --color-accent-400: #2664EC;
    --color-accent-500: #528AFF;
    --color-accent-600: #88B1FF;
    --color-accent-700: #B7D0FF;
    --color-accent-800: #D9E6FF;
    --color-accent-900: #EEF4FF;
    --color-accent-2-100: #01252F;
    --color-accent-2-200: #003F4F;
    --color-accent-2-300: #005D73;
    --color-accent-2-400: #007D9B;
    --color-accent-2-500: #289FC1;
    --color-accent-2-600: #5EC0E0;
    --color-accent-2-700: #91DCF6;
    --color-accent-2-800: #C0EEFF;
    --color-accent-2-900: #E4F8FF;
    --color-success-100: #002819;
    --color-success-300: #006344;
    --color-success-700: #91E4BD;
    --color-success-800: #BCF5D9;
    --color-warning-100: #341800;
    --color-warning-300: #7D4200;
    --color-warning-700: #FFC091;
    --color-warning-800: #FFDFC7;
    --color-danger-100: #3C0F0C;
    --color-danger-300: #942521;
    --color-danger-700: #FFBCB3;
    --color-danger-800: #FFDCD7;

    /* A raised panel reads by being LIGHTER than the page here, not darker. */
    --color-inverse: #182030;
    --color-on-inverse: #F2F4F7;

    /* Elevation is a shadow on white and a lighter surface on black; a shadow on a dark
       ground is invisible, so the ring carries it instead. */
    --shadow-sm: 0 0 0 1px color-mix(in srgb, #FFFFFF 10%, transparent);
    --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.5), 0 0 0 1px color-mix(in srgb, #FFFFFF 10%, transparent);

    /* Chart series, re-stepped for a dark surface rather than flipped. */
    --series-1: #5C8FF5;
    --series-2: #D95926;
    --series-3: #007D9B;
    --series-4: #C98500;
    --series-5: #D55181;
    --series-6: #008300;
    --series-7: #9085E9;
    --series-8: #E66767;
  }
}
:root[data-theme="dark"] {
    /* Ground, pinned in theme.mjs — a page's ground is a decision, not a ramp step. */
    --color-bg: #0F1522;
    --color-surface: #182030;
    --color-text: #F2F4F7;
    --color-divider: color-mix(in srgb, #FFFFFF 14%, transparent);

    /* The ramps, mirrored. */
    --color-neutral-100: #182030;
    --color-neutral-200: #2F3848;
    --color-neutral-300: #4B5464;
    --color-neutral-400: #697181;
    --color-neutral-500: #8991A0;
    --color-neutral-600: #ABB2C0;
    --color-neutral-700: #CAD0DA;
    --color-neutral-800: #E2E6ED;
    --color-neutral-900: #F2F4F7;
    --color-accent-100: #071C4A;
    --color-accent-200: #0C307E;
    --color-accent-300: #1547B7;
    --color-accent-400: #2664EC;
    --color-accent-500: #528AFF;
    --color-accent-600: #88B1FF;
    --color-accent-700: #B7D0FF;
    --color-accent-800: #D9E6FF;
    --color-accent-900: #EEF4FF;
    --color-accent-2-100: #01252F;
    --color-accent-2-200: #003F4F;
    --color-accent-2-300: #005D73;
    --color-accent-2-400: #007D9B;
    --color-accent-2-500: #289FC1;
    --color-accent-2-600: #5EC0E0;
    --color-accent-2-700: #91DCF6;
    --color-accent-2-800: #C0EEFF;
    --color-accent-2-900: #E4F8FF;
    --color-success-100: #002819;
    --color-success-300: #006344;
    --color-success-700: #91E4BD;
    --color-success-800: #BCF5D9;
    --color-warning-100: #341800;
    --color-warning-300: #7D4200;
    --color-warning-700: #FFC091;
    --color-warning-800: #FFDFC7;
    --color-danger-100: #3C0F0C;
    --color-danger-300: #942521;
    --color-danger-700: #FFBCB3;
    --color-danger-800: #FFDCD7;

    /* A raised panel reads by being LIGHTER than the page here, not darker. */
    --color-inverse: #182030;
    --color-on-inverse: #F2F4F7;

    /* Elevation is a shadow on white and a lighter surface on black; a shadow on a dark
       ground is invisible, so the ring carries it instead. */
    --shadow-sm: 0 0 0 1px color-mix(in srgb, #FFFFFF 10%, transparent);
    --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.5), 0 0 0 1px color-mix(in srgb, #FFFFFF 10%, transparent);

    /* Chart series, re-stepped for a dark surface rather than flipped. */
    --series-1: #5C8FF5;
    --series-2: #D95926;
    --series-3: #007D9B;
    --series-4: #C98500;
    --series-5: #D55181;
    --series-6: #008300;
    --series-7: #9085E9;
    --series-8: #E66767;
}

/* ------------------------------------------------------------------ reset */

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

/* `[hidden]` HAS TO BEAT THE COMPONENT LAYER, OR IT BEATS NOTHING.
 *
 * The user agent hides `[hidden]` with `display: none` from its own origin, which any author rule
 * setting `display` outranks — and 100 classes in this system set one. So `<div class="tabs"
 * hidden>` computes `display: flex` and draws, and a consumer reaching for the platform's own way
 * of hiding something gets a panel that is still on the page. Measured: `.tabs`, `.stepper`,
 * `.tag`, `.alert` and `.summary` all kept drawing; `.panel` and a bare `<p>` did not — which is
 * the worst version of this, because it works until the element happens to be a flex container.
 *
 * `!important` is the only weapon that reaches, the competition being specificity rather than
 * order. It is scoped to one attribute nothing else should be fighting over, and anything that
 * genuinely wants a hidden element laid out has `visibility` and `.sr-only` below. */
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Native chrome follows the theme: form controls, scrollbars, the flash before first paint.
     `light dark` says the page handles both; the [data-theme] overrides below pin it when a
     reader has chosen, so a dark scrollbar cannot sit beside a light page. */
  color-scheme: light dark;
}
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }

/* A SCROLLBAR IS CHROME, NOT CONTENT.
 *
 * The platform default is a mid-grey bar that, on a light surface, reads as a
 * filled column beside the page — heavier than any rule, divider or border this
 * system draws, and the strongest vertical line on a screen whose hairlines are
 * all one pixel of neutral-200.
 *
 * `scrollbar-color` takes thumb then track. The track is the surface it sits on
 * so it disappears, and the thumb is a neutral just dark enough to find. It
 * darkens on hover, which is where a scrollbar earns its contrast: when
 * somebody is reaching for it.
 *
 * QUIETER THAN IT WAS. A 12px neutral-300 pill read, in use, as "too
 * contrasted" and rounder than anything else on a system whose corners are
 * 6px or square: the strongest shape on screens that are meant to be quiet.
 * So it is 8px, square-ended, neutral-200 at rest — still findable, because it
 * is the only vertical bar on the ground — and neutral-300 when reached for.
 *
 * Firefox and Chromium honour this; WebKit needs the pseudo-elements below and
 * ignores the shorthand, so both are written rather than one being assumed. */
html {
  scrollbar-width: thin;
  scrollbar-color: var(--color-neutral-200) transparent;
}
::-webkit-scrollbar { inline-size: var(--space-2); block-size: var(--space-2); }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--color-neutral-200);
  border-radius: 0;
  /* Inset from the edge, so the bar reads as floating on the surface rather
     than as a second border welded to it. */
  border: 2px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--color-neutral-300); background-clip: content-box; }
::-webkit-scrollbar-corner { background: transparent; }

body {
  min-height: 100svh;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-base);
  font-weight: 400;
  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img, svg, video, canvas { display: block; max-width: 100%; }
input, button, textarea, select { font: inherit; color: inherit; }
p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; }

/* -------------------------------------------------------------- typography */

/* Weight 500, never 700+. Hierarchy is carried by size and space —
   that restraint is the whole reason this system reads as airy. */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  color: var(--color-neutral-900);
}

h1 { font-size: var(--text-5xl); line-height: var(--leading-5xl); letter-spacing: var(--tracking-5xl); }
h2 { font-size: var(--text-4xl); line-height: var(--leading-4xl); letter-spacing: var(--tracking-4xl); }
h3 { font-size: var(--text-3xl); line-height: var(--leading-3xl); letter-spacing: var(--tracking-3xl); }
h4 { font-size: var(--text-2xl); line-height: var(--leading-2xl); letter-spacing: var(--tracking-2xl); }
h5 { font-size: var(--text-xl);  line-height: var(--leading-xl);  letter-spacing: var(--tracking-xl); }
h6 { font-size: var(--text-lg);  line-height: var(--leading-lg);  letter-spacing: var(--tracking-lg); }

p { color: var(--color-neutral-700); }
strong, b { font-weight: 600; color: var(--color-neutral-900); }
small { font-size: var(--text-sm); }
code, kbd, samp, pre { font-family: var(--font-mono); font-size: 0.92em; }

a {
  color: var(--color-accent-700);
  text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
  text-underline-offset: 0.2em;
  transition: color var(--dur-fast) var(--ease-out),
              text-decoration-color var(--dur-fast) var(--ease-out);
}
a:hover { color: var(--color-accent-800); text-decoration-color: currentColor; }

/* A SECTION MARKER: the small line above a thing that says what kind of thing
   it is. Uppercase and tracked, in the body family.
 *
 * It was MONO — the capability-statement voice carried over from gener8v.com.
 * In a product it put a third typeface on every surface for text that is not
 * technical: "GOVERNANCE" over a nav group is a category, not an identifier.
 * Mono stays for the things that ARE identifiers — `.mono`, `code`, a slug, a
 * version — where the fixed width is doing work.
 *
 * AND THE WORD IS UNDERLINED — the word, not the container.
 *
 * A `border-block-end` on a block element rules off the whole width, which is a
 * DIVIDER: it separates the marker from what follows instead of marking the
 * marker. What the marker wants is the typographic device that has always meant
 * "this names the thing below": an underline on the text itself, ending where
 * the word ends. */
.label {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-neutral-600);
  text-decoration: underline;
  text-decoration-color: var(--color-neutral-300);
  text-decoration-thickness: 1px;
  /* Clear of the descenders and of the tracking, so it reads as a rule under the
     word rather than a strike through its feet. */
  text-underline-offset: 0.45em;
}

/* The same voice with no rule, for a marker sitting INSIDE a row rather than
   over a block — a kind beside a name, a version beside a title. */
.label-bare {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-neutral-600);
}

.lede { font-size: var(--text-xl); line-height: var(--leading-xl); color: var(--color-neutral-600); }
.muted { color: var(--color-neutral-600); }
.num { font-variant-numeric: tabular-nums; font-feature-settings: "tnum"; }
/* Technical identifiers — codes, refs, hashes. Mono, and never broken across lines. */
.mono { font-family: var(--font-mono); font-size: 0.94em; white-space: nowrap; }

/* ------------------------------------------------------------------ states */

::selection { background: var(--color-accent-200); color: var(--color-accent-900); }

:focus-visible { outline: none; }
:focus { outline: none; }

[disabled], .is-disabled { opacity: 0.45; cursor: not-allowed; }

/* ----------------------------------------------------------------- layout */

.container {
  width: 100%;
  max-width: var(--container-xl);
  margin-inline: auto;
  padding-inline: var(--space-6);
}
.container-lg { max-width: var(--container-lg); }
.container-md { max-width: var(--container-md); }

/* Section rhythm is deliberately large — 96px and up. Whitespace is the
   primary design element in this system; reach for it before a divider. */
.section { padding-block: var(--space-20); }
.section-tight { padding-block: var(--space-12); }
.section-loose { padding-block: var(--space-24); }

.stack { display: grid; gap: var(--space-4); }
.stack-sm { gap: var(--space-2); }
.stack-lg { gap: var(--space-8); }
.row { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }

.hr { height: 1px; border: 0; background: var(--color-divider); }

/* Visually hidden, still announced. Clip-path rather than display:none — the
   latter removes it from the accessibility tree, which defeats the purpose. */
.sr-only {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* -------------------------------------------------------------- atmosphere */

/* The full-bleed band where saturation becomes presence. Everywhere else the
   ground stays near-white; this is the one place the accent floods. */
.section-band {
  background: var(--color-section);
  color: #fff;
}
.section-band h1, .section-band h2, .section-band h3,
.section-band h4, .section-band h5, .section-band h6 { color: #fff; }
.section-band p { color: color-mix(in srgb, #fff 82%, transparent); }
.section-band .label { color: color-mix(in srgb, #fff 68%, transparent); }

.bg-dot-grid {
  background-image: radial-gradient(circle,
    color-mix(in srgb, var(--color-accent) 14%, transparent) 1px, transparent 1px);
  background-size: 24px 24px;
}

/* Fine grain — keeps large washes from banding. */
.bg-noise {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
}

/* --- floating surfaces ------------------------------------------------ */

/* A POPUP IS NOT PART OF ITS TRIGGER.
 *
 * A menu, a tooltip, a listbox and a popover are all positioned away from the thing that opened
 * them — and all still DESCEND from it in the DOM. So each one inherits whatever typography the
 * trigger's container happens to set, which is a property of where the trigger sits rather than of
 * the popup.
 *
 * The account menu found this: `.nav` sets `white-space: nowrap` so a header row cannot break, the
 * menu inherited it, and the identity's second line — an email — ran straight out of the panel and
 * was clipped. `max-inline-size` did nothing, because nothing was allowed to wrap.
 *
 * The same trap is set in a toolbar (`text-transform: uppercase`), a table cell (`text-align: end`),
 * a mono rail (`font-family`), a breadcrumb bar (nowrap again) and any surface that leans its type.
 * A component that looks different depending on where its trigger sits is not a component.
 *
 * COLOUR IS NOT RESET. That is the theme, and the theme is meant to reach in here.
 */
.menu, .tooltip, .listbox, .popover, .command {
  white-space: normal;
  text-align: start;
  text-transform: none;
  letter-spacing: normal;
  font-family: var(--font-body);
  font-style: normal;
  font-weight: 400;
}

@keyframes fadeUp { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
.animate-fade-up { animation: fadeUp var(--dur-slow) var(--ease-out) both; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* --- a control that waits to be asked for ---------------------------- */

/* WHAT IS TRUE OF A ROW IS ALWAYS VISIBLE. WHAT CAN BE DONE TO IT WAITS.
 *
 * A list of sixty-six requirements with two controls on every row is a hundred
 * and thirty-two controls, and the reader is scanning for the four rows that
 * need them. Worse, a quiet control sitting beside a status tag at the same size
 * reads as a THIRD STATUS — the row then says three things about itself, one of
 * which is a button nobody can tell is a button.
 *
 * So: marks stay, controls appear when the row is pointed at or focused.
 *
 * `opacity`, NEVER `display: none` OR `visibility: hidden`. Both remove the
 * control from the tab order, and a keyboard reader would have a list they can
 * read and cannot act on — a whole class of user locked out of the work rather
 * than merely inconvenienced. `:focus-within` is what makes the keyboard path
 * work, and it is on the SAME rule as `:hover` so the two cannot drift apart.
 *
 * The reveal is per ROW, not per control: the container carries `.on-demand-row`
 * and every `.on-demand` inside it appears together. A control that appeared
 * only when pointed at directly would be one the reader has to find first. */
.on-demand {
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.on-demand-row:hover .on-demand,
.on-demand-row:focus-within .on-demand {
  opacity: 1;
}

/* A POINTER THAT NEVER ARRIVES. On a touch screen there is no hover, so the
   reveal never fires and the control is unreachable — the same lockout as
   `display: none`, arrived at from the other side. */
@media (hover: none) {
  .on-demand { opacity: 1; }
}

/* --------------------------------------------------------------- accordion */

.accordion { border-block-start: 1px solid var(--color-neutral-200); }
.accordion-item { border-block-end: 1px solid var(--color-neutral-200); }

.accordion-trigger {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  inline-size: 100%;
  padding: var(--space-4) 0;
  border: 0;
  background: transparent;
  font-family: var(--font-body);
  font-size: var(--text-base);
  font-weight: 500;
  color: var(--color-neutral-900);
  text-align: start;
  cursor: pointer;
}
.accordion-trigger:hover { color: var(--color-action); }

/* Chevron from borders — rotates on aria-expanded, so the open state and the
   announcement come from one attribute. */
.accordion-trigger::after {
  content: "";
  margin-inline-start: auto;
  inline-size: 0.4em;
  block-size: 0.4em;
  flex: none;
  border-inline-end: 1.5px solid var(--color-neutral-500);
  border-block-end: 1.5px solid var(--color-neutral-500);
  transform: translateY(-0.15em) rotate(45deg);
  transition: transform var(--dur-base) var(--ease-out);
}
.accordion-trigger[aria-expanded="true"]::after { transform: translateY(0.1em) rotate(-135deg); }

/* No `[hidden]` rule of its own: the base layer answers for the attribute everywhere now, and a
   second rule here would be one component's local fix for a thing the system handles. */
.accordion-panel {
  padding-block-end: var(--space-4);
  font-size: var(--text-sm);
  color: var(--color-neutral-700);
}

/* ------------------------------------------------------------------- agent */

/* What an agent did and what it wants, inside a conversation.
 *
 * A TOOL CALL IS A CHIP, NOT A PARAGRAPH. The result is the line; the arguments are a press away.
 * A CHANGE THAT NEEDS A PERSON ASKS ONE, on a card with the change written on it and two answers. */

/* --- tool chip ------------------------------------------------------ */

.tool-chip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-xs);
  color: var(--color-neutral-700);
}
.tool-chip-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-block-size: var(--control-h-xs);
  max-inline-size: 100%;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-full);
  background: var(--color-neutral-100);
  color: var(--color-neutral-800);
  font: inherit;
  text-align: start;
}
button.tool-chip-toggle { cursor: pointer; }
button.tool-chip-toggle:hover { border-color: var(--color-neutral-400); }
.tool-chip-caret {
  flex: none;
  inline-size: var(--space-3);
  block-size: var(--space-3);
  transition: transform var(--dur-fast) var(--ease-out);
}
.tool-chip-toggle[aria-expanded="true"] .tool-chip-caret { transform: rotate(90deg); }
.tool-chip-label { min-inline-size: 0; overflow-wrap: anywhere; }
/* Running: the words are the tool's, not yet its answer, so they recede a step. */
.tool-chip[data-state="running"] .tool-chip-toggle { color: var(--color-neutral-700); }
.tool-chip[data-state="refused"] .tool-chip-toggle,
.tool-chip[data-state="failed"] .tool-chip-toggle { border-color: var(--color-danger-300); }

.tool-chip-detail {
  flex-basis: 100%;
  display: grid;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-inline-start: 2px solid var(--color-neutral-200);
  color: var(--color-neutral-700);
}
.tool-chip-args { display: grid; gap: var(--space-1); margin: 0; }
.tool-chip-args > div { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-3); }
.tool-chip-args dt { font-family: var(--font-mono); color: var(--color-neutral-600); }
.tool-chip-args dd { margin: 0; color: var(--color-neutral-900); overflow-wrap: anywhere; }
.tool-chip-more { overflow-wrap: anywhere; }

/* --- confirm card --------------------------------------------------- */

.confirm-card {
  border-color: var(--color-accent-300);
}
/* Margins rather than a gap: the outcome is a live region that must exist, empty, before it is
   filled — and an empty grid item still takes a gap. */
.confirm-card > * + * { margin-block-start: var(--space-3); }
.confirm-card-outcome:empty { margin: 0; }
.confirm-card[data-spend] { border-color: var(--color-danger-300); }
/* Answered, it is a record of what was asked, not a question: it steps back to the hairline. */
.confirm-card:not([data-state="open"]) { border-color: var(--color-neutral-200); }
.confirm-card:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.confirm-card-flags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.confirm-card-spend { font-size: var(--text-xs); color: var(--color-neutral-700); }
.confirm-card-summary {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-neutral-900);
}
.confirm-card-values {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--space-1) var(--space-4);
  margin: 0;
  font-size: var(--text-sm);
}
.confirm-card-values > div { display: contents; }
.confirm-card-values dt { color: var(--color-neutral-600); }
.confirm-card-values dd { margin: 0; color: var(--color-neutral-900); font-variant-numeric: tabular-nums; }
.confirm-card-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.confirm-card-outcome {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.confirm-card-problem { font-size: var(--text-xs); color: var(--color-neutral-700); }

@media (max-width: 480px) {
  /* A label above its value, on a phone: two columns of a long value wrap to a word a line. */
  .confirm-card-values { grid-template-columns: 1fr; }
  .confirm-card-values dd { margin-block-end: var(--space-1); }
}

/* ------------------------------------------------------------------ button */

.btn {
  --btn-pad-y: var(--space-2);
  --btn-pad-x: var(--space-4);
  --btn-h: var(--control-h-md);
  min-block-size: var(--btn-h);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--btn-pad-y) var(--btn-pad-x);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: 0;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  background: transparent;
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}

.btn svg { width: 1.15em; height: 1.15em; flex: none; }
.btn:active:not([disabled]) { transform: translateY(1px); }
.btn[disabled] { pointer-events: none; }

/* --- sizes --- */
.btn-xs { --btn-h: var(--control-h-xs); --btn-pad-y: var(--space-1); --btn-pad-x: var(--space-2); font-size: var(--text-xs); }
.btn-sm { --btn-h: var(--control-h-sm); --btn-pad-y: var(--space-1); --btn-pad-x: var(--space-3); font-size: var(--text-sm); }
.btn-lg { --btn-h: var(--control-h-lg); --btn-pad-y: var(--space-3); --btn-pad-x: var(--space-5); font-size: var(--text-base); }
.btn-xl { --btn-h: var(--control-h-xl); --btn-pad-y: var(--space-3); --btn-pad-x: var(--space-6); font-size: var(--text-lg); }

/* --- variants --- */
/* A FILLED BUTTON HAS AN EDGE.
 *
 * The border was the same colour as the fill, so the shape had no boundary — a saturated slab
 * bleeding into the page, and at `xl` a large one. The rail next to it reads as considered because
 * its active item has a defined edge and a shadow doing quiet work; a filled button had neither
 * doing anything.
 *
 * One ramp step down, same hue. It costs a pixel and gives the shape a limit, which is most of what
 * "less harsh" turns out to mean here — the alternative, lightening the fill, would take the
 * primary action off `--color-action` and it is the one thing that must stay put. */
.btn-primary {
  background: var(--color-action);
  border-color: var(--color-action-hover);
  color: #fff;
  box-shadow: var(--shadow-sm);
}
.btn-primary:hover:not([disabled]) { background: var(--color-action-hover); border-color: var(--color-action-active); }
.btn-primary:active:not([disabled]) { background: var(--color-action-active); border-color: var(--color-action-active); }

.btn-secondary {
  background: var(--color-bg);
  border-color: var(--color-neutral-300);
  color: var(--color-neutral-800);
  box-shadow: var(--shadow-sm);
}
.btn-secondary:hover:not([disabled]) { background: var(--color-neutral-100); border-color: var(--color-neutral-400); }
.btn-secondary:active:not([disabled]) { background: var(--color-neutral-200); }

.btn-ghost { color: var(--color-neutral-700); }
.btn-ghost:hover:not([disabled]) { background: var(--color-neutral-100); color: var(--color-neutral-900); }
.btn-ghost:active:not([disabled]) { background: var(--color-neutral-200); }

.btn-subtle { background: var(--color-accent-100); color: var(--color-accent-800); }
.btn-subtle:hover:not([disabled]) { background: var(--color-accent-200); }
.btn-subtle:active:not([disabled]) { background: var(--color-accent-300); }

.btn-destructive {
  background: var(--color-danger);
  border-color: var(--color-danger-700);
  color: #fff;
  box-shadow: var(--shadow-sm);
}
.btn-destructive:hover:not([disabled]) { background: var(--color-danger-700); border-color: var(--color-danger-800); }
.btn-destructive:active:not([disabled]) { background: var(--color-danger-800); border-color: var(--color-danger-800); }

.btn-link {
  padding: 0;
  border-radius: var(--radius-xs);
  color: var(--color-accent-700);
  box-shadow: none;
  min-block-size: 0;
}
.btn-link:hover:not([disabled]) { color: var(--color-accent-800); text-decoration: underline; text-underline-offset: 0.2em; }

/* --- modifiers --- */
.btn-icon { --btn-pad-x: var(--btn-pad-y); inline-size: var(--btn-h); min-inline-size: var(--btn-h); }
.btn-block { display: flex; width: 100%; }

/* A waiting button keeps its label's width and draws the DS spinner in the
   label's place: the same track-and-arc ring as `.spinner` (stroke = size / 8,
   a quarter arc, one turn in 800ms), in the colour the label would have been.
   It used to be a bordered circle with a gap, which read as a different
   control from every other spinner in the system. */
.btn.is-loading { position: relative; color: transparent; pointer-events: none; }
.btn.is-loading::after {
  --ring-size: 1.125em;
  --ring-stroke: max(2px, calc(var(--ring-size) / 8));
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  inline-size: var(--ring-size);
  block-size: var(--ring-size);
  border-radius: var(--radius-full);
  background: conic-gradient(
    var(--btn-spin, var(--color-neutral-700)) 0 25%,
    color-mix(in oklab, var(--btn-spin, var(--color-neutral-700)) 25%, transparent) 0
  );
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--ring-stroke) - 1px), black calc(100% - var(--ring-stroke)));
  mask: radial-gradient(farthest-side, transparent calc(100% - var(--ring-stroke) - 1px), black calc(100% - var(--ring-stroke)));
  animation: btn-spin 800ms linear infinite;
}
.btn-primary.is-loading,
.btn-destructive.is-loading { --btn-spin: #fff; }
.btn-secondary.is-loading,
.btn-ghost.is-loading { --btn-spin: var(--color-neutral-700); }
.btn-subtle.is-loading { --btn-spin: var(--color-accent-800); }
.btn-link.is-loading { --btn-spin: var(--color-accent-700); }

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

/* --- group --- */
.btn-group { display: inline-flex; }
.btn-group .btn { border-radius: 0; margin-inline-start: -1px; box-shadow: none; }
.btn-group .btn:first-child { border-start-start-radius: var(--radius-md); border-end-start-radius: var(--radius-md); margin-inline-start: 0; }
.btn-group .btn:last-child { border-start-end-radius: var(--radius-md); border-end-end-radius: var(--radius-md); }
.btn-group .btn:focus-visible { z-index: 1; }

/* ---------------------------------------------------------------- calendar */

.calendar {
  inline-size: fit-content;
  padding: var(--space-3);
  background: var(--color-bg);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  z-index: 70;
}

.calendar-head { display: flex; align-items: center; gap: var(--space-1); margin-block-end: var(--space-2); }
.calendar-title {
  flex: 1;
  text-align: center;
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-neutral-900);
}
.calendar-nav {
  display: grid;
  place-items: center;
  inline-size: var(--space-8);
  block-size: var(--space-8);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-neutral-700);
  cursor: pointer;
}
.calendar-nav:hover { background: var(--color-neutral-100); color: var(--color-neutral-900); }
.calendar-nav svg { inline-size: var(--space-4); block-size: var(--space-4); }

.calendar-grid { border-collapse: collapse; }
.calendar-grid th {
  inline-size: var(--space-8);
  padding-block: var(--space-1);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 500;
  color: var(--color-neutral-600);
}
.calendar-grid td { padding: 1px; }

.calendar-day {
  inline-size: var(--space-8);
  block-size: var(--space-8);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  color: var(--color-neutral-800);
  cursor: pointer;
}
.calendar-day:hover { background: var(--color-neutral-100); }
.calendar-day[data-outside] { color: var(--color-neutral-400); }

/* Today is an outline; selected is a fill. Both can be true at once, so they
   must not use the same channel. */
.calendar-day[data-today] { box-shadow: inset 0 0 0 1px var(--color-accent-400); }
.calendar-grid td[aria-selected="true"] .calendar-day {
  background: var(--color-action);
  color: var(--color-bg);
  font-weight: 500;
}
.calendar-day[aria-disabled="true"] {
  color: var(--color-neutral-300);
  pointer-events: none;
  text-decoration: line-through;
}

/* -------------------------------------------------------------------- card */

/* A hairline and a radius, not a shadow. On a light ground the border does the
   separating; elevation is reserved for things that genuinely float. */
.card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-6);
  background: var(--color-bg);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-lg);
}

.card-quiet { background: var(--color-surface); border-color: transparent; }
.card-raised { border-color: transparent; box-shadow: var(--shadow-md); }
.card-pad-sm { padding: var(--space-4); }
.card-pad-lg { padding: var(--space-8); }

/* Only for a card that is genuinely a single link or button target. */
.card-interactive {
  cursor: pointer;
  text-align: start;
  text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.card-interactive:hover { border-color: var(--color-neutral-300); box-shadow: var(--shadow-sm); }
.card-interactive:active { border-color: var(--color-neutral-400); }

.card-kicker {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-neutral-600);
}
.card-title {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--text-xl);
  line-height: var(--leading-xl);
  letter-spacing: var(--tracking-xl);
  color: var(--color-neutral-900);
}
.card-body { color: var(--color-neutral-700); font-size: var(--text-sm); line-height: var(--leading-sm); }
.card-meta {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-2);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-divider);
  font-size: var(--text-xs);
  color: var(--color-neutral-600);
}

/* --- panel: a card that hosts a region rather than a snippet --- */
.panel {
  background: var(--color-bg);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--color-neutral-200);
  background: var(--color-surface);
}
.panel-title { font-family: var(--font-heading); font-weight: var(--font-heading-weight); font-size: var(--text-base); color: var(--color-neutral-900); }
.panel-body { padding: var(--space-5); }

/* ------------------------------------------------------------------- chart */

/* Marks are thin, grid and axes are recessive, and text never wears the series
   colour — a coloured swatch beside a label carries identity, the label itself
   stays in ink. */

.chart { display: grid; gap: var(--space-4); }

.chart-head { display: flex; align-items: baseline; gap: var(--space-4); flex-wrap: wrap; }
.chart-title {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--text-base);
  color: var(--color-neutral-900);
}
.chart-note { font-size: var(--text-xs); color: var(--color-neutral-600); }
.chart-actions { margin-inline-start: auto; display: flex; gap: var(--space-2); }

.chart-legend { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); }
.chart-legend-key { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-xs); color: var(--color-neutral-700); }
.chart-swatch { inline-size: 10px; block-size: 10px; border-radius: 3px; flex: none; }

.chart-plot { position: relative; }
.chart-svg { display: block; inline-size: 100%; block-size: auto; overflow: visible; }

/* Recessive: the grid orients, it does not compete. */
.chart-grid line { stroke: var(--color-divider); stroke-width: 1; }
.chart-baseline { stroke: var(--color-neutral-300); stroke-width: 1; }
.chart-axis text {
  font-family: var(--font-mono);
  font-size: 10px;
  fill: var(--color-neutral-600);
  font-variant-numeric: tabular-nums;
}

/* 4px rounded data-end, anchored square to the baseline. */
.chart-bar { rx: 4; ry: 4; transition: opacity var(--dur-fast) var(--ease-out); }
.chart-bar-foot { transition: opacity var(--dur-fast) var(--ease-out); }

.chart-line { fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.chart-area { opacity: 0.1; }
.chart-dot { stroke: var(--color-bg); stroke-width: 2; }

.chart-label { font-family: var(--font-mono); font-size: 10px; fill: var(--color-neutral-700); font-variant-numeric: tabular-nums; }

/* Dim non-focused series rather than hiding them — the shape of the whole is
   still information. */
.chart-series[data-dim] { opacity: 0.22; }

.chart-crosshair { stroke: var(--color-neutral-400); stroke-width: 1; stroke-dasharray: 3 3; }
.chart-hit { fill: transparent; cursor: crosshair; }

.chart-tooltip {
  position: absolute;
  pointer-events: none;
  z-index: 5;
  min-inline-size: 140px;
  padding: var(--space-2) var(--space-3);
  background: var(--color-inverse);
  color: var(--color-on-inverse);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  font-size: var(--text-xs);
  line-height: 1.6;
}
.chart-tooltip-head { font-family: var(--font-mono); color: var(--color-neutral-400); margin-block-end: var(--space-1); }
.chart-tooltip-row { display: flex; align-items: center; gap: var(--space-2); }
.chart-tooltip-row .chart-swatch { inline-size: 8px; block-size: 8px; }
.chart-tooltip-value { margin-inline-start: auto; font-variant-numeric: tabular-nums; }

.chart-empty { display: grid; place-items: center; padding: var(--space-16); font-size: var(--text-sm); color: var(--color-neutral-600); }

/* Sparkline: no axes, no legend, no tooltip — it is a shape beside a figure. */
.sparkline { display: inline-block; block-size: var(--space-8); inline-size: var(--space-24); }
.sparkline path { fill: none; stroke: var(--color-action); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.sparkline .sparkline-end { fill: var(--color-action); stroke: var(--color-bg); stroke-width: 2; }

/* HORIZONTAL BARS. Drawn in HTML, so the category name is set at the body's
   size on a phone rather than shrinking with a viewBox, and a long one wraps.
   The name sits above its bar, the figure at the bar's end; the zero is a rule
   down the track rather than an axis, because every value is written. */
.chart-hbars { display: grid; gap: var(--space-4); }
.chart-hbar-row { display: grid; gap: var(--space-1); transition: opacity var(--dur-fast) var(--ease-out); }
.chart-hbar-row[data-dim] { opacity: 0.4; }
.chart-hbar-label { font-size: var(--text-sm); color: var(--color-neutral-800); overflow-wrap: anywhere; }
.chart-hbar-line {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(var(--space-8), max-content);
  align-items: center;
  gap: var(--space-3);
}
.chart-hbar-track {
  position: relative;
  display: block;
  block-size: var(--space-3);
  border-radius: var(--radius-xs);
  background: var(--color-neutral-100);
}
/* The zero. At the start edge for an all-positive set; inside the track when
   there are negatives. */
.chart-hbar-track::before {
  content: "";
  position: absolute;
  inset-block: calc(-1 * var(--space-1));
  inset-inline-start: var(--zero, 0%);
  inline-size: 1px;
  background: var(--color-neutral-300);
}
/* 4px rounded data-end, as the vertical bars; two fills in a stack keep the
   surface gap between them. */
.chart-hbar {
  position: absolute;
  inset-block: 0;
  border-radius: var(--radius-xs);
  box-shadow: 1px 0 0 var(--color-bg);
}
.chart-hbar-value {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-neutral-700);
  font-variant-numeric: tabular-nums;
}

/* In forced colours the fills collapse; texture is the fallback encoding. */
@media (forced-colors: active) {
  .chart-bar, .chart-area, .chart-hbar { forced-color-adjust: none; }
  .chart-hbar-track { border: 1px solid CanvasText; }
  .chart-line { forced-color-adjust: none; }
  .chart-swatch { forced-color-adjust: none; border: 1px solid CanvasText; }
}

/* ---------------------------------------------------------------- citation */

/* A SOURCE NUMBER IN PROSE, followed in place to what it names. A small tinted
   chip that sits on the line rather than raising it. A source that could not be
   found is dashed, unlinked and says so in words — never a link to a page that
   will not load. */
.citation {
  display: inline-block;
  margin-inline: 1px;
  padding: 0 var(--space-1);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--color-accent-100);
  color: var(--color-accent-700);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 500;
  line-height: 1.5;
  text-decoration: none;
  vertical-align: baseline;
  white-space: nowrap;
}
a.citation:hover { background: var(--color-accent-200); color: var(--color-accent-800); }
a.citation[aria-describedby] { border-color: var(--color-accent-300); }
.citation[data-missing] {
  background: transparent;
  border: 1px dashed var(--color-neutral-300);
  color: var(--color-neutral-600);
  cursor: help;
}

/* WHAT IT POINTS AT: the file, the place in it, the words cited, a way to open
   it there. Held to a reading measure inside a hover card or a list. */
.source-card { display: grid; gap: var(--space-2); font-size: var(--text-sm); }
.source-card-file {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin: 0;
  color: var(--color-neutral-900);
  font-weight: 500;
  overflow-wrap: anywhere;
}
.source-card-id { flex: none; font-family: var(--font-mono); font-size: var(--text-xs); font-weight: 400; color: var(--color-neutral-600); }
.source-card-locator { margin: 0; }
.source-card-excerpt {
  margin: 0;
  padding-inline-start: var(--space-3);
  border-inline-start: 2px solid var(--color-neutral-200);
  color: var(--color-neutral-700);
  line-height: 1.55;
}
.source-card[data-missing] .source-card-excerpt { border-inline-start-style: dashed; color: var(--color-neutral-600); }
.source-card .skeleton { inline-size: 100%; }
.source-card-action {
  justify-self: start;
  padding: 0;
  border: 0;
  background: none;
  color: var(--color-accent-700);
  font: inherit;
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
}
.source-card-action:hover { text-decoration: underline; }

/* THE PASSAGE A CITATION POINTED AT, marked where it opens. The browser's own
   yellow is a highlighter on a light page and a glare on a dark one; this is the
   accent's tint, in both themes, with the text keeping its own colour. */
mark {
  background: var(--color-accent-100);
  color: inherit;
  border-radius: var(--radius-xs);
  box-shadow: 0 0 0 2px var(--color-accent-100);
}

/* ------------------------------------------------------------------- claim */

/* A PROPOSITION SOMEBODY HAS TO ADJUDICATE.
 *
 * `Item` is a list of records — a NAME, a line of provenance, a mark or two. Its
 * title is one line and never wraps, because a name that wraps is a name too
 * long. That is right for a document, a note, a shelf row.
 *
 * This is the other thing, and the consumer hand-rolled it because the system had
 * no word for it: an assertion in PROSE, the argument for it, where it currently
 * stands, and what can be done about it. A coverage finding, a held-open
 * decision, a disputed reading — each is a claim with a case attached, waiting on
 * a person.
 *
 * THE CLAIM TAKES THE MEASURE, AND THE FIRST ATTEMPT GOT THIS EXACTLY BACKWARDS.
 *
 * Hand-rolled, the quiet rationale was capped at a measure and the loud assertion
 * ran the full nineteen hundred pixels of the surface. The most important
 * sentence on the row was the least readable thing on it. Prose is prose whatever
 * weight it is set in, and the louder it is the more certain somebody will try to
 * read it. */
.claim {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-md);
  background: var(--color-bg);
}

.claim-body { display: flex; flex-direction: column; min-inline-size: 0; flex: 1; }

/* `ch` rather than a token: a measure is a measure in CHARACTERS, which is what
   `.reader` and `.section-lede` already say. */
.claim-text {
  max-inline-size: 72ch;
  font-size: var(--text-sm);
  line-height: var(--leading-sm);
  color: var(--color-neutral-900);
}

/* THE ARGUMENT, WHICH IS NOT A FOOTNOTE.
 *
 * Disagreeing with it is usually the whole job on a screen full of these, so it
 * is set to be read: its own measure, its own line, and a rule marking it as a
 * different voice from the claim rather than a continuation of it. */
.claim-reason {
  margin-block-start: var(--space-3);
  padding-inline-start: var(--space-3);
  border-inline-start: 2px solid var(--color-neutral-200);
  max-inline-size: 72ch;
  font-size: var(--text-xs);
  line-height: var(--leading-sm);
  color: var(--color-neutral-700);
}

/* WHOSE ARGUMENT IT IS. A rationale with no author is an assertion, and on a
   surface where a machine and a person both write them, which one wrote this is
   the first thing a reader needs. Its own line — an inline label ahead of prose
   reads as a run-on, which is how the hand-rolled version read. */
.claim-reason-by {
  display: block;
  margin-block-end: var(--space-1);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-neutral-600);
}

/* WHERE IT STANDS, SAID ONCE.
 *
 * The hand-rolled version put a tag, a state and an attribution in one row at
 * three different weights — "proposed · Not a capability · read by assistant, not
 * yet confirmed" — which reads as three facts rather than one standing. */
.claim-standing {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block-start: var(--space-3);
  font-size: var(--text-xs);
  color: var(--color-neutral-600);
}
.claim-standing-what { font-weight: 500; color: var(--color-neutral-800); }

/* ALIGNED TO THE CLAIM'S FIRST LINE, not centred on a block whose height depends
   on how much somebody said. A control that moves when the prose grows is one a
   reader has to look for. */
.claim-actions {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-block-size: var(--leading-sm);
}

/* What is settled sits back; what is waiting does not. */
.claim[data-settled] { background: var(--color-surface); }

/* ------------------------------------------------------- a code editor */

/* PAINT UNDER A TEXTAREA.
 *
 * The highlighted layer and the textarea render the same string, and every
 * metric that decides where a glyph lands has to match between them: family,
 * size, line height, padding, tab size, wrapping, letter spacing. The moment one
 * differs the caret sits somewhere the letters are not — which is the only way
 * this technique fails, and it fails invisibly until somebody types.
 *
 * So the two share one custom property for each of them, and neither is
 * overridable from outside. A className that could change the font here would
 * be a className that breaks the caret. */
.code-editor {
  --editor-font: var(--font-mono);
  --editor-size: var(--text-sm);
  --editor-leading: 1.65;
  --editor-pad: var(--space-3);
  display: grid;
  gap: var(--space-2);
}

/* THE WHOLE WINDOW, for the one document somebody actually writes.
 *
 * A standard every project is measured against, edited through a twenty-eight-row box on a
 * scrolling page, is a keyhole. `position: fixed` rather than a class on some ancestor, so a
 * consumer does not have to restructure its layout to offer this — the editor takes the screen
 * from wherever it is mounted.
 *
 * The frame grows to fill what is left after the label and the actions, which is why the height
 * lives on the frame rather than on the textarea: the gutter has to match it. */
.code-editor[data-full] {
  position: fixed;
  inset: 0;
  z-index: 60;
  margin: 0;
  padding: var(--space-5);
  background: var(--color-bg);
  grid-template-rows: auto minmax(0, 1fr);
}
.code-editor[data-full] .code-editor-frame {
  min-block-size: 0;
  block-size: 100%;
  border-radius: var(--radius-md);
}
.code-editor[data-full] .code-editor-body,
.code-editor[data-full] .code-editor-input,
.code-editor[data-full] .code-editor-paint {
  block-size: 100%;
}

.code-editor-label {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-neutral-900);
}

/* THE HEIGHT `rows` PROMISES, and until now did not deliver.
 *
 * `rows` was set on the textarea and meant nothing, because the GUTTER renders
 * one element per line and the frame stretched to fit it. On a nine-hundred-line
 * standard that made the editor 9,379px tall — every control below it (save,
 * cancel, the way into full screen) pushed nine thousand pixels down a scrolling
 * page, reachable only by scrolling through the entire document first.
 *
 * So the height is stated, from the same metrics both layers already share. The
 * gutter and the paint scroll with the textarea, which they already did; what
 * was missing was anything for them to scroll INSIDE.
 *
 * `1lh` would be the modern spelling and is not safe here: it resolves against
 * the element's own font, and this must resolve against the EDITOR's — the one
 * both layers are locked to. */
.code-editor-frame {
  display: flex;
  align-items: stretch;
  min-inline-size: 0;
  block-size: calc(
    var(--editor-rows, 18) * var(--editor-size) * var(--editor-leading) + 2 * var(--editor-pad)
  );
  border: 1px solid var(--color-neutral-300);
  border-radius: var(--radius-md);
  background: var(--color-bg);
  overflow: hidden;
  transition: border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
/* The FRAME takes the focus ring, because the textarea inside it has no border
   of its own — a ring on the textarea would be drawn inside the frame and read
   as a second box. */
.code-editor-frame:focus-within {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 18%, transparent);
}

/* THE NUMBERS. Hidden entirely when lines wrap — see the component: a number
   beside a wrapped line can only point at one of its rows, and one that points
   at the wrong row is worse than none. */
.code-editor-gutter {
  flex: none;
  display: flex;
  flex-direction: column;
  padding-block: var(--editor-pad);
  padding-inline: var(--space-3) var(--space-2);
  border-inline-end: 1px solid var(--color-neutral-200);
  background: var(--color-surface);
  font-family: var(--editor-font);
  font-size: var(--editor-size);
  line-height: var(--editor-leading);
  font-variant-numeric: tabular-nums;
  color: var(--color-neutral-500);
  text-align: end;
  overflow: hidden;
  user-select: none;
}

.code-editor-body {
  position: relative;
  flex: 1;
  min-inline-size: 0;
}

/* Both layers, identically. Every property here is one the caret depends on. */
.code-editor-paint,
.code-editor-input {
  margin: 0;
  padding: var(--editor-pad);
  border: 0;
  font-family: var(--editor-font);
  font-size: var(--editor-size);
  line-height: var(--editor-leading);
  letter-spacing: normal;
  tab-size: 2;
  white-space: pre;
  overflow-wrap: normal;
  word-break: normal;
}
.code-editor[data-wrap] .code-editor-paint,
.code-editor[data-wrap] .code-editor-input {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.code-editor-paint {
  position: absolute;
  inset: 0;
  overflow: hidden;
  color: var(--color-neutral-800);
  pointer-events: none;
}

.code-editor-input {
  position: relative;
  display: block;
  inline-size: 100%;
  block-size: 100%;
  min-block-size: 100%;
  resize: none;
  background: transparent;
  overflow: auto;
  /* THE TEXT IS INVISIBLE AND THE CARET IS NOT. `color: transparent` would take
     the caret with it in some engines, so the caret colour is stated. */
  color: transparent;
  caret-color: var(--color-neutral-900);
  outline: none;
}
/* A selection over transparent text still has to be visible. */
.code-editor-input::selection { background: var(--color-accent-200); color: transparent; }

.code-editor-input:disabled { cursor: not-allowed; }
.code-editor-frame:has(.code-editor-input:disabled) { background: var(--color-surface); }

/* THE WAY OUT OF FULL SCREEN. Pinned to the corner of the surface it leaves, so
   it is where a reader looks for a close and not somewhere in the page behind. */
.code-editor-leave {
  position: absolute;
  inset-block-start: var(--space-5);
  inset-inline-end: var(--space-5);
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  min-block-size: var(--control-h-sm);
  border: 1px solid var(--color-neutral-300);
  border-radius: var(--radius-md);
  background: var(--color-bg);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--color-neutral-700);
  cursor: pointer;
}
.code-editor-leave:hover { background: var(--color-neutral-100); color: var(--color-neutral-900); }
.code-editor-leave kbd {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-neutral-600);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-xs);
  padding: 0 var(--space-1);
}

/* ---------------------------------------------------------------- combobox */

/* A LABELLED FIELD STACKS THE SAME WAY WHOEVER DRAWS IT.
 *
 * `.field` is a grid with `gap: var(--space-2)` between a label and its
 * control. This was `display: block`, so its own label sat flush on its input
 * and its input — `display: inline-block` — carried a line box's descender
 * space underneath instead. Same total height, control 8px lower.
 *
 * Nobody sees that until a Field and a Combobox stand in one row, and then it
 * is unmistakable: measured on a consumer's filter row, two comboboxes with
 * their inputs at y=639 beside a search field with its input at y=647, under
 * three labels that agreed exactly.
 *
 * The same grid as `.field`, so the two agree by construction rather than by
 * both being adjusted until they matched. The trigger is absolutely positioned
 * and the listbox is fixed, so neither is a grid item and neither moves. */
.combobox {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2);
}

/* The clear/expand affordance sits inside the field, so it must not overlap
   the text. The input reserves room for it rather than the button covering it. */
.combobox .input { padding-inline-end: var(--space-10); }
/* THE CARET CENTRES ON THE FIELD, NOT ON THE COMPONENT.
 *
 * `inset-block-start: 50%` centred it in `.combobox` — and `.combobox` contains
 * the LABEL as well as the input, so the caret sat half a label's height above
 * the middle of the box it belongs to. Measured on a consumer: field centre
 * 727px, caret centre 714px.
 *
 * It was invisible in the demo because that example hides its label. Every
 * consumer that shows one — which is most of them, a labelled field being the
 * normal case — got a caret riding high in its own input.
 *
 * Anchored to the field's bottom edge instead. The input's height is a known
 * quantity and the label's is not: a label wraps onto two lines at a narrow
 * width, and anything measured from the top moves when it does. */
.combobox-trigger {
  position: absolute;
  inset-inline-end: var(--space-2);
  inset-block-end: calc((var(--control-h-md) - var(--space-6)) / 2);
  display: grid;
  place-items: center;
  inline-size: var(--space-6);
  block-size: var(--space-6);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-neutral-600);
  cursor: pointer;
}
.combobox-trigger:hover { background: var(--color-neutral-100); color: var(--color-neutral-900); }
.combobox-trigger svg { inline-size: var(--space-4); block-size: var(--space-4); }

.listbox {
  margin: 0;
  list-style: none;
  /* THE LIST IS THE WIDTH OF ITS FIELD.
     It sized to its content, so a short option list rendered a narrow box hanging
     off the left of a wide input — which reads as a detached popup rather than as
     the field's own list. The anchor's width is measured and passed in as
     `--anchor-w`; the max is the fallback when nothing set it. */
  inline-size: var(--anchor-w, auto);
  max-inline-size: min(max(var(--anchor-w, 420px), 420px), calc(100vw - var(--space-8)));
  max-block-size: calc(var(--space-24) * 2.4);
  overflow-y: auto;
  padding: var(--space-1);
  background: var(--color-bg);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  z-index: 70;
}

/* A ROW WHEN THE NOTE IS A MARK, A BLOCK WHEN IT IS A SENTENCE.
   This was `display: flex; align-items: center` with the note pushed to the far
   end, which is right for the short mark it was built for — "use as typed".
   Given a definition it fell apart: the label wrapped to two lines, the note
   wrapped to two lines, and the pair sat at opposite ends of the row with a
   ragged gutter between them. Consumers pass prose because prose is what the
   reader needs to choose, so the note stacks under the label and the row grows
   with it. */
.listbox-option {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--color-neutral-800);
  cursor: pointer;
}
/* Pointer hover and keyboard active look identical — focus never leaves the
   input in this pattern, so the highlight is the only cue either way. */
.listbox-option:hover, .listbox-option[data-active] {
  background: var(--color-neutral-100);
  color: var(--color-neutral-900);
}
.listbox-option[aria-selected="true"] { color: var(--color-action); font-weight: 500; }
.listbox-option[aria-disabled="true"] { color: var(--color-neutral-400); pointer-events: none; }
/* NOT MONO. The note is the thing that explains the option, and mono says
   "identifier" — a definition set in it reads as code the reader is expected to
   match rather than a sentence they are meant to understand. It keeps the
   measure so a long one wraps into a column instead of running the width of the
   popover. */
.listbox-option-note {
  /* `ch` rather than a token, as `.section-lede` and `.reader` already do: this
     is a measure in CHARACTERS, which is what a measure is. */
  max-inline-size: 64ch;
  font-size: var(--text-xs);
  line-height: var(--leading-xs);
  color: var(--color-neutral-600);
}

/* Left, like every option above it. Centred, it read as a placeholder in an empty
   box rather than as the list saying something. */
.listbox-empty { padding: var(--space-4) var(--space-3); font-size: var(--text-sm); color: var(--color-neutral-600); }

/* ----------------------------------------------------------------- command */

/* A command palette is a dialog, not a dropdown: it takes over the page and
   must be dismissed. Anchored near the top because the eye is already there
   after the shortcut, and because results grow downward. */
.command-backdrop {
  position: fixed;
  inset: 0;
  display: grid;
  justify-items: center;
  align-content: start;
  padding: clamp(var(--space-8), 12vh, var(--space-32)) var(--space-6) var(--space-6);
  background: color-mix(in srgb, var(--color-scrim) 28%, transparent);
  z-index: 90;
}
.command {
  inline-size: min(560px, 100%);
  background: var(--color-bg);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.command-search { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-4); border-block-end: 1px solid var(--color-neutral-200); }
.command-search svg { inline-size: var(--space-5); block-size: var(--space-5); flex: none; color: var(--color-neutral-500); }
.command-input {
  flex: 1;
  min-inline-size: 0;
  border: 0;
  outline: none;
  background: transparent;
  font-family: var(--font-body);
  font-size: var(--text-base);
  color: var(--color-neutral-900);
}
.command-input::placeholder { color: var(--color-neutral-500); }
.command-hint { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-neutral-500); flex: none; }

.command-list { max-block-size: min(46vh, 380px); overflow-y: auto; padding: var(--space-2); margin: 0; list-style: none; }
.command-group { padding: var(--space-2) var(--space-2) var(--space-1); font-family: var(--font-mono); font-size: var(--text-xs); font-weight: 500; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-neutral-600); }
.command-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--color-neutral-800);
  cursor: pointer;
}
.command-item svg { inline-size: var(--space-4); block-size: var(--space-4); flex: none; color: var(--color-neutral-600); }
.command-item:hover, .command-item[data-active] { background: var(--color-neutral-100); color: var(--color-neutral-900); }
.command-item-note { margin-inline-start: auto; font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-neutral-500); }
.command-empty { padding: var(--space-8) var(--space-4); text-align: center; font-size: var(--text-sm); color: var(--color-neutral-600); }
.command-footer { display: flex; align-items: center; gap: var(--space-4); padding: var(--space-2) var(--space-4); border-block-start: 1px solid var(--color-neutral-200); background: var(--color-surface); font-size: var(--text-xs); color: var(--color-neutral-600); }
.command-key { font-family: var(--font-mono); padding: 1px var(--space-1); border: 1px solid var(--color-neutral-300); border-radius: var(--radius-xs); background: var(--color-bg); }

/* -------------------------------------------------------------------- tree */

.tree, .tree-group { margin: 0; padding: 0; list-style: none; }
.tree-group { padding-inline-start: var(--space-4); border-inline-start: 1px solid var(--color-neutral-200); margin-inline-start: var(--space-3); }

.tree-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  inline-size: 100%;
  padding: var(--space-2) var(--space-2);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--color-neutral-800);
  text-align: start;
  cursor: pointer;
}
.tree-item:hover { background: var(--color-neutral-100); }
.tree-item[aria-selected="true"] { background: var(--color-accent-100); color: var(--color-action); font-weight: 500; }

/* Twisty from borders; rotates on aria-expanded so one attribute drives both
   the glyph and the announcement. A leaf gets a spacer, not a hidden twisty —
   the alignment has to hold either way. */
.tree-twisty {
  inline-size: var(--space-4);
  block-size: var(--space-4);
  flex: none;
  display: grid;
  place-items: center;
}
.tree-twisty::before {
  content: "";
  inline-size: 0.34em;
  block-size: 0.34em;
  border-inline-end: 1.5px solid var(--color-neutral-500);
  border-block-end: 1.5px solid var(--color-neutral-500);
  transform: translateX(-0.08em) rotate(-45deg);
  transition: transform var(--dur-fast) var(--ease-out);
}
[aria-expanded="true"] > .tree-twisty::before { transform: translateY(-0.08em) rotate(45deg); }
.tree-item[data-leaf] .tree-twisty::before { content: none; }

.tree-label { flex: 1; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tree-count { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-neutral-600); flex: none; }

/* ------------------------------------------------------------------- code */

.code {
  margin: 0;
  padding: var(--space-4);
  background: var(--color-inverse);
  color: var(--color-on-inverse);
  border-radius: var(--radius-md);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.7;
  overflow-x: auto;
  tab-size: 2;
}
.code-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  background: color-mix(in srgb, var(--color-inverse) 88%, #FFFFFF);
  border-start-start-radius: var(--radius-md);
  border-start-end-radius: var(--radius-md);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  /* The head sits ON the inverse panel, so its label follows the panel's foreground rather than a
     ramp step that mirrors away from it. */
  color: color-mix(in srgb, var(--color-on-inverse) 72%, transparent);
}
.code-head + .code { border-start-start-radius: 0; border-start-end-radius: 0; }
.code-copy { margin-inline-start: auto; }

/* SYNTAX TOKENS.
 *
 * Colours come from the ramps the system already has — a highlighter that invented its own palette
 * would be a second palette, and the one thing this system will not have is two of anything. Every
 * pair is gated in `lint:contrast` against the code ground, which until now was the one surface
 * whose text contrast nothing checked.
 *
 * Six kinds, not twenty. A snippet needs a comment to recede and a string to stand apart; the rest
 * is decoration that makes code harder to read, not easier. */
/* SCOPED TO BOTH SURFACES THAT PAINT CODE, and it was scoped to one.
 *
 * `CodeEditor` renders exactly these classes into `.code-editor-paint`, and every rule here named
 * `.code` — so the editor tokenized its content correctly, produced the right spans, and painted
 * every one of them the same colour. A markdown standard opened for editing showed headings,
 * bold, tables and rules as undifferentiated grey, which reads as an editor that does not
 * highlight rather than as a stylesheet that missed a selector.
 *
 * The list is explicit rather than a `[class*="tok-"]`: an attribute match would also catch a
 * consumer's own class that happens to contain the string, and this palette is contrast-gated
 * against the code ground specifically. */
.code .tok-comment,
.code-editor-paint .tok-comment { color: var(--code-comment); font-style: italic; }
.code .tok-string,
.code-editor-paint .tok-string  { color: var(--code-string); }
.code .tok-keyword,
.code-editor-paint .tok-keyword { color: var(--code-keyword); }
.code .tok-number,
.code-editor-paint .tok-number  { color: var(--code-number); }
.code .tok-fn,
.code-editor-paint .tok-fn      { color: var(--code-name); }

/* ---------------------------------------------------- gap-fill: transaction */
/* Components the three example workflows needed and the system did not have. */

/* --- card media slot ------------------------------------------------- */

/* Card is padded; media must bleed to its edges, so it cancels the padding
   with a negative margin rather than the card losing its padding. */
.card-media {
  margin: calc(var(--space-6) * -1) calc(var(--space-6) * -1) 0;
  border-start-start-radius: calc(var(--radius-lg) - 1px);
  border-start-end-radius: calc(var(--radius-lg) - 1px);
  overflow: hidden;
  background: var(--color-neutral-100);
  aspect-ratio: 16 / 10;
}
.card-pad-sm > .card-media { margin: calc(var(--space-4) * -1) calc(var(--space-4) * -1) 0; }
.card-media img { inline-size: 100%; block-size: 100%; object-fit: cover; }

/* --- quantity stepper ------------------------------------------------ */

.qty {
  display: inline-flex;
  align-items: stretch;
  border: 1px solid var(--color-neutral-300);
  border-radius: var(--radius-md);
  overflow: hidden;
  block-size: var(--control-h-md);
}
.qty-btn {
  inline-size: var(--control-h-md);
  border: 0;
  background: var(--color-bg);
  color: var(--color-neutral-700);
  font-size: var(--text-base);
  cursor: pointer;
  display: grid;
  place-items: center;
}
.qty-btn:hover:not(:disabled) { background: var(--color-neutral-100); color: var(--color-neutral-900); }
.qty-btn:disabled { color: var(--color-neutral-300); cursor: not-allowed; }
.qty-input {
  inline-size: var(--space-12);
  border: 0;
  border-inline: 1px solid var(--color-neutral-300);
  background: var(--color-bg);
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  color: var(--color-neutral-900);
  -moz-appearance: textfield;
}
.qty-input::-webkit-outer-spin-button,
.qty-input::-webkit-inner-spin-button { appearance: none; margin: 0; }

/* --- radio card ------------------------------------------------------ */

/* A choice big enough to carry a description and a price. The whole card is
   the hit target, so the radio itself is only a marker. */
.radio-cards { display: grid; gap: var(--space-3); }
/* SIDE BY SIDE. Three short options stacked down a page read as three separate
   questions; in a row they read as one question with three answers. */
@media (min-width: 720px) {
  .radio-cards[data-columns="2"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .radio-cards[data-columns="3"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.radio-card {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-md);
  background: var(--color-bg);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
.radio-card:hover { border-color: var(--color-neutral-300); }
.radio-card:has(:checked) { border-color: var(--color-action); background: var(--color-accent-100); }
.radio-card:has(:focus-visible) { outline: none; }
.radio-card-body { flex: 1; min-inline-size: 0; }
/* BLOCK, BECAUSE THE COMPONENT RENDERS THESE AS SPANS.
   `RadioCard` puts the title and the note in `<span>`s, and a span is an inline box — vertical
   margin does nothing on one. So the note's `margin-block-start` was inert and the two ran together
   on a single line: "TranscriptAn interview or a meeting." Declaring the display here rather than
   changing the elements keeps the class names doing the same thing in the static HTML and in
   React, which is the promise the whole library rests on. */
.radio-card-title { display: block; font-size: var(--text-sm); font-weight: 500; color: var(--color-neutral-900); }
.radio-card-note { display: block; font-size: var(--text-xs); color: var(--color-neutral-600); margin-block-start: var(--space-1); }
.radio-card-meta {
  margin-inline-start: auto;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  color: var(--color-neutral-900);
  flex: none;
}

/* --- summary --------------------------------------------------------- */

.summary { display: grid; gap: var(--space-2); font-size: var(--text-sm); }
.summary-row { display: flex; align-items: baseline; gap: var(--space-4); }
.summary-row dt, .summary-label { color: var(--color-neutral-700); }
.summary-row dd, .summary-value {
  margin: 0;
  margin-inline-start: auto;
  font-variant-numeric: tabular-nums;
  color: var(--color-neutral-900);
}
.summary-total {
  margin-block-start: var(--space-2);
  padding-block-start: var(--space-3);
  border-block-start: 1px solid var(--color-neutral-200);
  font-size: var(--text-base);
  font-weight: 500;
}
.summary-total dd, .summary-total .summary-value {
  font-family: var(--font-heading);
  font-size: var(--text-xl);
}
.summary-note { font-size: var(--text-xs); color: var(--color-neutral-600); }

/* ---------------------------------------------------------------- contents */

/* WHERE YOU ARE IN A LONG DOCUMENT, AND HOW TO GET ELSEWHERE IN IT.
 *
 * A hairline down the side that the current entry marks itself against — the
 * only thing on the list that says which section you are in — and a line of air
 * between entries, so fifteen two-line headings do not read as one paragraph.
 * Lives on the ground beside the sheet (`.paper-aside`), never on the paper. */
.contents { font-size: var(--text-xs); }
/* KEEP THE BOX. Tailwind generates a `.contents { display: contents }` utility whenever any
   source file contains the word "contents", and a class selector alone does not beat it. The
   element selector and the second class do, and the tailwind-preset also blocks the utility. */
nav.contents, .contents-nav { display: block; }

.contents-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  margin: 0 0 var(--space-4);
}
.contents-title {
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-neutral-600);
}
.contents-count { font-family: var(--font-mono); color: var(--color-neutral-600); }

.contents-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
  border-inline-start: 1px solid var(--color-neutral-200);
}

.contents-item {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin-inline-start: -1px;
  padding-inline-start: var(--space-3);
  border-inline-start: 2px solid transparent;
}
.contents-item[data-depth="2"] { padding-inline-start: calc(var(--space-3) + var(--space-3)); }
.contents-item[data-depth="3"] { padding-inline-start: calc(var(--space-3) + var(--space-6)); }
.contents-item[data-depth="4"] { padding-inline-start: calc(var(--space-3) + var(--space-8)); }
.contents-item:has(.contents-link[aria-current]) { border-inline-start-color: var(--color-accent); }
.contents-item .skeleton { inline-size: 80%; }

.contents-check { flex: none; margin-block-start: 0.1em; }

/* Two lines, then the link's title carries the rest. `-webkit-box` is what makes
   the clamp work, so nothing else may set this element's display. */
.contents-link {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  min-inline-size: 0;
  flex: 1 1 auto;
  line-height: 1.6;
  color: var(--color-neutral-600);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out);
}
/* Underlined on hover: these are links, and grey text with a cursor is not a sign. */
.contents-link:hover { color: var(--color-neutral-900); text-decoration: underline; }
.contents-link[aria-current] { color: var(--color-accent-700); font-weight: 500; }

/* The same marks as the sections, before the words. */
.contents-live {
  display: inline-block;
  margin-inline-end: var(--space-1);
  vertical-align: middle;
}
/* "Rewritten in this run" is a dot, not a tick: a tick is what the review box draws. */
svg.contents-live { inline-size: 1em; block-size: 1em; color: var(--color-success-700); vertical-align: -0.125em; }
svg.contents-written { inline-size: 0.9em; block-size: 0.9em; }
/* The pulse's ring spreads past the dot, so it needs more room than a still mark or it sits on
   the section number. */
.live-pulse.contents-live { margin-inline-end: var(--space-3); }
.contents-item[data-live="writing"] .contents-link { color: var(--color-accent-700); font-weight: 500; }
.contents-item[data-live="queued"] .contents-link { color: var(--color-neutral-600); opacity: 0.7; }
.contents-item[data-live="failed"] .contents-link { color: var(--color-danger-700); }
.contents-after { flex: none; }

/* ------------------------------------------------------------ conversation */

/* A CONVERSATION IS NOT A COMMENT THREAD, AND THE TWO SIDES ARE NOT SYMMETRIC.
 *
 * `Comment` is one shape repeated: author, time, body, all the same width and
 * all the same weight. That is right for people talking to each other, where
 * every turn is comparable.
 *
 * This is a person asking and a system answering, and those turns differ by an
 * order of magnitude. A question is a sentence. An answer is a document — with
 * headings, lists, citations and the reasoning that produced it. Rendering both
 * as bubbles makes the answer unreadable, because the bubble is a shape that
 * PROMISES brevity; rendering both as prose loses track of who is speaking.
 *
 * So: what the reader sends is a bubble, sized to a sentence and set to the
 * end. What comes back is prose at the full measure, structured however it
 * needs to be. The asymmetry is the design, not an oversight in it. */

.conversation {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

.turn { display: flex; flex-direction: column; gap: var(--space-1); }

/* --- what the reader said ------------------------------------------- */

.turn[data-side="sent"] { align-items: flex-end; }
.turn[data-side="sent"] .turn-body {
  padding: var(--space-3) var(--space-4);
  background: var(--color-accent-100);
  color: var(--color-neutral-900);
  /* Square on the speaking corner. A uniform radius reads as a card that
     happens to be over there; the notch is what makes it a speech shape. */
  border-radius: var(--radius-lg) var(--radius-xs) var(--radius-lg) var(--radius-lg);
  /* A question is a sentence. A pasted paragraph must not become a bubble the
     width of the window — past this it wraps, which is what a long quote does. */
  max-inline-size: min(52ch, 100%);
  font-size: var(--text-sm);
  line-height: var(--leading-sm);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* --- what came back -------------------------------------------------- */

/* No bubble, no tint, no indent. An answer is the page's content, and dressing
   it as a message would cap it at a width its own headings and tables break. */
.turn[data-side="received"] { align-items: stretch; }
.turn[data-side="received"] .turn-body { color: var(--color-neutral-900); }

.turn-meta {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-neutral-600);
}
.turn[data-side="sent"] .turn-meta { text-align: end; }

/* Still in flight. The dots are decorative — `TurnPending` carries the word. */
.turn-pending { display: inline-flex; align-items: center; gap: var(--space-2); }
.turn-pending::before {
  content: "";
  inline-size: var(--space-2);
  block-size: var(--space-2);
  border-radius: var(--radius-full);
  background: var(--color-action);
  animation: turn-pulse 1.1s var(--ease-in-out) infinite;
}
@keyframes turn-pulse { 50% { opacity: 0.25; } }

@media (max-width: 720px) {
  /* A 52ch bubble is most of a phone. Let it use the width it has. */
  .turn[data-side="sent"] .turn-body { max-inline-size: 100%; }
}

/* -------------------------------------------------------------------- data */

/* --- stat / KPI ----------------------------------------------------- */

.stat { display: grid; gap: var(--space-1); }
.stat-label {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-neutral-600);
}
/* A FIGURE BESIDE A PAGE NAME, NOT A SECOND HEADLINE. At `--text-3xl` a row of
   four of them out-shouted the `h1` they sat next to — and four zeros at 30px
   is a masthead announcing that nothing has happened. Two steps down: still
   the largest thing in its own block, and plainly subordinate to the name of
   the page. */
.stat-value {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--text-xl);
  line-height: var(--leading-xl);
  letter-spacing: var(--tracking-3xl);
  font-variant-numeric: tabular-nums;
  color: var(--color-neutral-900);
}
.stat-note { font-size: var(--text-xs); color: var(--color-neutral-600); }
.stat-delta { font-size: var(--text-xs); font-variant-numeric: tabular-nums; }
/* The tone, not the direction. A book that shed a hundred and fifty policies is
   down and bad; a loss ratio that fell four points is down and good, and
   colouring by direction alone asserts the first about both. `good` defaults to
   true, so up is still green and down still red wherever nobody has said
   otherwise. */
.stat-delta[data-tone="good"] { color: var(--color-success-700); }
.stat-delta[data-tone="bad"] { color: var(--color-danger-700); }

/* A FIGURE THAT IS ITSELF THE BAD NEWS.
 *
 * `StatDelta` tones the movement; this tones the level. A tie rate of 73.8% has not moved anywhere
 * and is still the thing on the screen somebody has to deal with, and saying so needed a second
 * control — a meter under the row repeating the same number so that its fill could be red.
 *
 * The note still has to say what makes it bad, because colour alone is not a sentence and fails
 * WCAG 1.4.1. */
.stat[data-tone="success"] .stat-value { color: var(--color-success-700); }
.stat[data-tone="warning"] .stat-value { color: var(--color-warning-700); }
.stat[data-tone="danger"]  .stat-value { color: var(--color-danger-700); }

/* A ROW OF FIGURES IS ONE READING, NOT FOUR.
 *
 * Four `.stat`s side by side are four columns floating in a gap, and a reader has to decide for
 * themselves whether they belong together. They do — "0 covered, 0 gaps, 0 not applicable, 66
 * nobody has looked" is one sentence about one thing, and the rules between them say so.
 *
 * A rule between rather than a box around: a box would make the figures compete with the content
 * beneath them, and they are a summary of it rather than a panel beside it.
 *
 * EQUAL COLUMNS, BECAUSE A ROW OF FIGURES IS A RHYTHM. As a flex row with nothing sizing the
 * children, each figure took the width of its own longest line — seven of them came out at seven
 * widths, one whose note ran to a sentence took a quarter of the screen, and the rest were squeezed
 * into columns too narrow for their own captions, which then wrapped onto three and four lines
 * each.
 *
 * Flowed in columns rather than templated with `auto-fit`: a row of figures is often a flex item,
 * beside a verdict and a set of actions, and `auto-fit` resolves against an indefinite width by
 * collapsing to a single column — so the band's max-content became one figure wide and four figures
 * stacked vertically down the middle of a reconciliation header. `grid-auto-flow: column` keeps
 * max-content at the sum of the figures, which is what the flex row around it needs in order to
 * give it the room. */
.stat-row {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  align-items: start;
  gap: var(--space-5) 0;
}
.stat-row > .stat {
  padding-inline: var(--space-5);
  border-inline-start: 1px solid var(--color-divider);
}
/* The first column carries no rule, because there is nothing to its left to be divided from. */
.stat-row > .stat:first-child { padding-inline-start: 0; border-inline-start: 0; }
/* ON A PHONE, TWO BY TWO. Four figures in 358px are four captions wrapped onto three lines each.
   Viewport rather than container: the row is often a flex item whose width it sizes itself (see
   above), and a size container cannot take its width from its content. */
@media (max-width: 560px) {
  .stat-row { grid-auto-flow: row; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .stat-row > .stat:nth-child(odd) { padding-inline-start: 0; border-inline-start: 0; }
}

/* --- meter — the capability bar, generalised ------------------------ */

/* Distinct from .progress: progress reports a task advancing, a meter reports
   a level that is simply true. Different meaning, different role. */
.meter { display: grid; gap: var(--space-2); }
.meter-head { display: flex; align-items: baseline; gap: var(--space-3); }
.meter-label { font-size: var(--text-sm); color: var(--color-neutral-800); }
.meter-value {
  margin-inline-start: auto;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--color-neutral-600);
}
.meter-track {
  block-size: var(--space-2);
  background: var(--color-neutral-200);
  border-radius: var(--radius-full);
  overflow: hidden;
}
.meter-fill {
  block-size: 100%;
  background: var(--color-action);
  border-radius: var(--radius-full);
  transition: inline-size var(--dur-slow) var(--ease-out);
}
/* Label and track on one row — for a panel header, where the panel title
   already says what the meter is about. */
.meter-compact { grid-auto-flow: column; align-items: center; gap: var(--space-3); }
.meter-compact .meter-track { inline-size: var(--space-24); }
.meter-compact .meter-value { margin-inline-start: 0; }

/* A METER IN A TABLE CELL.
 *
 * Stacked, the figure sits on its own line above the track and pushes itself to the right-hand end
 * of it — so a column of these reads as a column of bars with a column of numbers floating over it
 * at a second alignment, and the row is twice as tall as every other cell in it.
 *
 * Track first and figure after, both on one line: the bars are compared by length down the column,
 * the figures line up as a numeric column does, and the cell is one line like its neighbours.
 * Placed rather than ordered, because the head comes first in the DOM — which is right for a screen
 * reader and backwards for the eye. */
.meter.in-a-cell {
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-2);
}
.meter.in-a-cell .meter-track { grid-row: 1; grid-column: 1; }
.meter.in-a-cell .meter-head { grid-row: 1; grid-column: 2; }
.meter.in-a-cell .meter-value { margin-inline-start: 0; }

/* A LEVEL AGAINST WHERE IT OUGHT TO BE.
 *
 * Colour alone cannot say "below target": a warning-tinted bar reads as a warning about something,
 * and the reader still has to guess what. So the target is said twice — a tick across the track for
 * the eye that compares lengths, and the words beside the figure for everybody else (the meter
 * points at them with `aria-describedby`). The tick overhangs the track, so the track stops
 * clipping; the fill carries its own radius and never needed the clip. */
.meter-target {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--color-neutral-600);
}
.meter-track:has(.meter-threshold) { position: relative; overflow: visible; }
.meter-threshold {
  position: absolute;
  inset-block: calc(-1 * var(--space-1));
  inline-size: 2px;
  margin-inline-start: -1px;
  border-radius: var(--radius-full);
  background: var(--color-neutral-800);
}
.meter.in-a-cell .meter-target { display: none; }

.meter[data-tone="success"] .meter-fill { background: var(--color-success); }
.meter[data-tone="warning"] .meter-fill { background: var(--color-warning); }
.meter[data-tone="danger"]  .meter-fill { background: var(--color-danger); }

/* --- description list ----------------------------------------------- */

/* A PAIR IS A ROW, AND A ROW HAS CELLS.
 *
 * Set as a bare grid with a gap, a name and its value were two pieces of text at two indents with
 * nothing joining them, and a reader scanning for one name had to track across whitespace to find
 * which value belonged to it. Nothing said where a pair ended either, so a value that ran to two
 * lines merged into the next name.
 *
 * A rule under each pair and padding inside it: the same cell the table draws, for the same reason.
 * The gap goes, because the padding is now what spaces them, and a gap on top of padding doubles
 * the rhythm. */
.dl {
  display: grid;
  grid-template-columns: minmax(9rem, 28%) 1fr;
  gap: 0;
  margin: 0;
  font-size: var(--text-sm);
  border-block-start: 1px solid var(--color-divider);
}
/* A row's name is a heading, and set like one — the same rule as `.table thead
   th`. Mono is for values a reader compares character by character. */
.dl dt {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-neutral-600);
  padding: var(--space-3) var(--space-5) var(--space-3) 0;
  border-block-end: 1px solid var(--color-divider);
}
.dl dd {
  margin: 0;
  color: var(--color-neutral-800);
  padding-block: var(--space-3);
  border-block-end: 1px solid var(--color-divider);
}
/* Stacked is a narrow column, where a name over its value is the only thing that fits. The rule
   stays and goes under the pair rather than between the two halves of it. */
.dl-stacked { grid-template-columns: 1fr; border-block-start: 0; }
.dl-stacked dt { padding: var(--space-3) 0 0; border-block-end: 0; }
.dl-stacked dd { margin-block-end: 0; padding-block: var(--space-1) var(--space-3); }

/* --- avatar ---------------------------------------------------------- */

.avatar {
  display: grid;
  place-items: center;
  inline-size: var(--space-10);
  block-size: var(--space-10);
  flex: none;
  border-radius: var(--radius-full);
  background: var(--color-accent-100);
  color: var(--color-accent-700);
  font-size: var(--text-sm);
  font-weight: 500;
  overflow: hidden;
  user-select: none;
}
.avatar img { inline-size: 100%; block-size: 100%; object-fit: cover; }
.avatar-sm { inline-size: var(--space-6); block-size: var(--space-6); font-size: var(--text-xs); }
.avatar-lg { inline-size: var(--space-16); block-size: var(--space-16); font-size: var(--text-xl); }

/* Overlapping stack. The ring is the page ground, so avatars read as separate
   objects rather than one blob. */
.avatar-group { display: flex; }
.avatar-group .avatar { margin-inline-start: calc(var(--space-2) * -1); box-shadow: 0 0 0 2px var(--color-bg); }
.avatar-group .avatar:first-child { margin-inline-start: 0; }

/* --- segmented control ----------------------------------------------- */

/* A NAMED GROUP.
 *
 * The name is a `span`, not a `label`: a label must reference a control and a
 * radiogroup is not one. `aria-labelledby` on the group is what carries it to a
 * screen reader, so the visible name and the accessible name are the same string
 * rather than two that drift.
 *
 * It matches `.field-label` on purpose — a choice and a text input are both a
 * field, and naming them in two different type sizes is how a form starts
 * reading as two forms. */
.segmented-field { display: flex; flex-direction: column; gap: var(--space-2); align-items: flex-start; }
.segmented-label {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-neutral-900);
}

.segmented {
  display: inline-flex;
  gap: 2px;
  padding: var(--space-1);
  background: var(--color-neutral-100);
  border-radius: var(--radius-md);
}
.segmented-item {
  padding: var(--space-2) var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-neutral-700);
  cursor: pointer;
  white-space: nowrap;
}
.segmented-item:hover { color: var(--color-neutral-900); }
.segmented-item[aria-checked="true"], .segmented-item[aria-selected="true"] {
  background: var(--color-bg);
  color: var(--color-neutral-900);
  box-shadow: var(--shadow-sm);
}

/* -------------------------------------------------------------------- deck */

/* Slides are 16:9 and size their own type in container units, so one deck
   reads identically in a thumbnail grid, on a laptop, and projected. Fixed px
   type would need a separate stylesheet per context, which is how decks end up
   with a "presentation version" that has drifted from the source. */

.deck { display: grid; gap: var(--space-8); }

.slide {
  container-type: inline-size;
  position: relative;
  aspect-ratio: 16 / 9;
  display: flex;
  flex-direction: column;
  padding: 6cqw 6cqw 11cqw;
  overflow: hidden;
  background: var(--color-bg);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-md);
}

.slide-kicker {
  font-family: var(--font-mono);
  font-size: 1.4cqw;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-neutral-600);
}
.slide-title {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: 4.4cqw;
  line-height: 1.12;
  letter-spacing: -0.022em;
  color: var(--color-neutral-900);
  margin-block-start: 2cqw;
  max-inline-size: 22ch;
}
.slide-lede { font-size: 2cqw; line-height: 1.5; color: var(--color-neutral-700); margin-block-start: 2cqw; max-inline-size: 52ch; }
.slide-body { font-size: 1.7cqw; line-height: 1.6; color: var(--color-neutral-700); }

.slide-list { margin: 3cqw 0 0; padding: 0; list-style: none; display: grid; gap: 1.6cqw; }
.slide-list li { display: flex; gap: 1.6cqw; font-size: 1.9cqw; line-height: 1.5; color: var(--color-neutral-800); }
.slide-list .slide-marker {
  font-family: var(--font-mono);
  font-size: 1.4cqw;
  color: var(--color-action);
  flex: none;
  padding-block-start: 0.35cqw;
}

/* Pinned rather than pushed: `margin-block-start: auto` does not reach the
   bottom inside a centred slide, so the cover's footer floated mid-slide. */
.slide-foot {
  position: absolute;
  inset-inline: 6cqw;
  inset-block-end: 4.5cqw;
  display: flex;
  align-items: baseline;
  gap: 2cqw;
  font-family: var(--font-mono);
  font-size: 1.3cqw;
  color: var(--color-neutral-500);
}
.slide-num { margin-inline-start: auto; font-variant-numeric: tabular-nums; }

/* The one saturated ground in a deck: section dividers. Everywhere else stays
   near-white, so a divider genuinely marks a change of gear. */
.slide-divider { background: var(--color-section); border-color: transparent; justify-content: center; }
.slide-divider .slide-title, .slide-divider .slide-lede { color: #fff; }
.slide-divider .slide-kicker, .slide-divider .slide-foot { color: color-mix(in srgb, #fff 66%, transparent); }

.slide-cover { justify-content: center; }
.slide-cover .slide-rule { display: block; block-size: 0.4cqw; inline-size: 12cqw; background: var(--color-action); margin-block-end: 3cqw; }

.slide-split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 4cqw; flex: 1; align-items: center; }
.slide-quote { font-family: var(--font-heading); font-weight: var(--font-heading-weight); font-size: 3.2cqw; line-height: 1.28; color: var(--color-neutral-900); max-inline-size: 26ch; }

@media print {
  .deck { gap: 0; }
  .slide { break-after: page; border: 0; border-radius: 0; }
}

/* -------------------------------------------------------------------- diff */

/* Two versions of a text and what changed. A removal is struck through and an addition underlined
   — the tint is the second signal, never the only one, so the change survives greyscale, a
   colour-blind reader, print and forced colours. Tints come off the status ramps, which mirror for
   dark mode, so a dark page gets a dark tint and light text with no second rule. */
.diff {
  display: grid;
  gap: var(--space-4);
  font-size: var(--text-sm);
  color: var(--color-neutral-800);
  min-inline-size: 0;
}
/* Side by side where there is room, stacked where there is not — never a pane squeezed to a word. */
.diff[data-mode="split"] { grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); }
.diff-pane {
  min-inline-size: 0;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-md);
  background: var(--color-bg);
}
.diff-pane-head {
  margin-block-end: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-neutral-600);
}
.diff-text { white-space: pre-wrap; overflow-wrap: anywhere; line-height: var(--leading-sm); }

.diff-del, .diff-ins {
  border-radius: var(--radius-sm);
  padding-inline: 1px;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}
.diff-del {
  background: var(--color-danger-100);
  color: var(--color-danger-800);
  text-decoration-line: line-through;
}
.diff-ins {
  background: var(--color-success-100);
  color: var(--color-success-800);
  text-decoration-line: underline;
}

/* Line granularity: a gutter carries + and −, so a changed line is marked before it is read. */
.diff-lines {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  overflow-x: auto;
}
.diff-line {
  display: grid;
  grid-template-columns: var(--space-5) minmax(0, 1fr);
  border-radius: var(--radius-sm);
}
.diff-sign { text-align: center; color: var(--color-neutral-600); user-select: none; }
.diff-line-text { white-space: pre-wrap; overflow-wrap: anywhere; padding-inline-end: var(--space-2); }
.diff-line[data-change="removed"] { background: var(--color-danger-100); }
.diff-line[data-change="added"] { background: var(--color-success-100); }
.diff-line[data-change="removed"] .diff-sign { color: var(--color-danger-800); }
.diff-line[data-change="added"] .diff-sign { color: var(--color-success-800); }
/* The row already carries the tint; the words inside keep only the line. */
.diff-line .diff-del, .diff-line .diff-ins { background: transparent; padding-inline: 0; }

/* -------------------------------------------------------------------- dock */

/* A LONG COLUMN OF RECORDS, READ BY SCROLLING.
 *
 * Twenty-four standards in a 240px column truncate every name —
 * `ECOA-0a-Fair-Lending-Re…`, `FRAME-00-Overview-and-…` — and a reader cannot
 * tell two documents apart without opening one. Widening the column takes the
 * width from the thing they came to look at.
 *
 * So this trades PERSISTENT width for MOMENTARY width: everything stays small,
 * and whatever is at the focus point gets the room to be legible.
 *
 * Growing is READING, not opening. Nothing here selects anything — the
 * component refuses to, and `Dock.tsx` says why. */
.dock { overflow-y: auto; display: flex; flex-direction: column; gap: var(--space-1); }

.dock-item > button {
  inline-size: 100%;
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  text-align: start;
  color: var(--color-neutral-700);
  background: none;
  border: 0;
  cursor: pointer;
  transition: font-size var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.dock-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dock-note { font-size: var(--text-xs); color: var(--color-neutral-600); }

/* THE ONE AT THE FOCUS POINT. Bigger, darker, and allowed to wrap — the
   truncation is the defect this control exists for, so the grown row is the one
   place it stops. A bigger row still ending in an ellipsis has solved nothing. */
.dock-item[data-grown] > button {
  font-size: var(--text-base);
  color: var(--color-neutral-900);
}
.dock-item[data-grown] .dock-name { white-space: normal; }

/* Collapsed: every row is its mark until it reaches the focus point. A collapsed
   column of identical dots is a column of nothing, which is why an item carries
   a mark rather than the component drawing one. */
.dock[data-collapsed] .dock-item:not([data-grown]) .dock-name { font-size: 0; }
.dock[data-collapsed] .dock-item:not([data-grown]) .dock-note { display: none; }

/* REDUCED MOTION IS NOT NO INFORMATION.
 *
 * A magnification that tracks scroll is motion, and somebody who asked for none
 * of it must still be able to tell two documents apart — which is the problem
 * this control exists to solve, not a decoration on top of it. So the growth
 * stops being animated AND stops being the only way to read a name: every row
 * shows its name in full, wrapped, at one size. */
@media (prefers-reduced-motion: reduce) {
  .dock-item > button { transition: none; }
  .dock-item[data-grown] > button { font-size: var(--text-sm); }
  .dock .dock-name,
  .dock[data-collapsed] .dock-item:not([data-grown]) .dock-name {
    font-size: var(--text-sm);
    white-space: normal;
  }
}

/* ------------------------------------------------------------------- entry */

/* THE PAGE THAT STANDS BETWEEN SOMEBODY AND THE PRODUCT.
 *
 * Not an empty state and not a document. A gate has one thing to do, it is the
 * first thing anybody sees, and everything on it is arranged around the single
 * action rather than around reading. */

.entry-ground {
  display: grid;
  place-items: center;
  min-block-size: 100svh;
  padding: var(--space-6);
  /* THE GROUND, so the panel reads as sitting ON something — the same move
     `Paper` makes, and the reason a centred card looks placed rather than
     stranded. */
  background: var(--color-surface);
}

.entry {
  inline-size: 100%;
  /* NARROW ON PURPOSE. A sign-in form is two fields; at a document's width the
     eye travels the whole window between the label and the box. */
  max-inline-size: 26rem;
  padding: var(--space-8);
  background: var(--color-bg);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  text-align: start;
}

/* THE PRODUCT, NAMED — and above the title rather than beside it. Beside, at the
   same size, they read as one phrase and the name stops being a name. */
.entry-mark {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-neutral-600);
}

.entry-title {
  margin: var(--space-2) 0 0;
  font-size: var(--text-2xl);
  font-weight: 600;
  line-height: var(--leading-base);
  color: var(--color-neutral-900);
}

.entry-lede {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  line-height: var(--leading-lg);
  color: var(--color-neutral-600);
}

.entry-way { margin-block-start: var(--space-6); }

/* A DIFFERENT DOOR, NOT A SMALLER BUTTON.
 *
 * A second sign-in control drawn slightly smaller reads as a variant of the
 * first — "sign in, but quieter" — rather than as another way in entirely. The
 * rule is what says they are different things, and it costs no words. */
.entry-alternatives {
  margin-block-start: var(--space-6);
  padding-block-start: var(--space-5);
  border-block-start: 1px solid var(--color-divider);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.entry-note {
  margin: var(--space-5) 0 0;
  font-size: var(--text-xs);
  line-height: var(--leading-lg);
  color: var(--color-neutral-600);
}

@media (max-width: 480px) {
  /* NO SHEET ON A PHONE. The ground would be two visible strips either side of
     something already the full width, which reads as a rendering fault. */
  .entry-ground { padding: 0; background: var(--color-bg); }
  .entry {
    max-inline-size: none;
    min-block-size: 100svh;
    padding: var(--space-8) var(--space-5);
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
}

/* ----------------------------------------------------------- error summary */

/* An error summary at the top of a step, with each entry a link to the field.
   This is the pattern government accessibility guidance settles on, and it is
   the one that works when a form is long enough that the first invalid field
   is off-screen — which is exactly when validation matters. */

.error-summary {
  padding: var(--space-4) var(--space-5);
  background: var(--color-danger-100);
  border: 1px solid var(--color-danger-300);
  border-inline-start: 3px solid var(--color-danger);
  border-radius: var(--radius-md);
}
.error-summary-title {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--text-base);
  color: var(--color-danger-700);
}
.error-summary-list {
  margin: var(--space-3) 0 0;
  padding: 0 0 0 var(--space-4);
  display: grid;
  gap: var(--space-2);
  font-size: var(--text-sm);
}
.error-summary-list a { color: var(--color-danger-700); text-decoration: underline; text-underline-offset: 0.2em; }
.error-summary-list a:hover { color: var(--color-danger-800); }

/* ------------------------------------------------------------------- facet */

/* AN ATTRIBUTE YOU CAN ACT ON.
 *
 * A `Tag` says what something IS — a status, a state, a severity. It is a span,
 * and correctly so: nothing happens when you press it.
 *
 * A facet is the other half of that idea and the system had no name for it: a
 * recorded fact about a row — who wrote it, when, what it was built against —
 * rendered where the reader is already looking, which NARROWS the list when
 * pressed. The dimension a reader wants to filter by is almost always one they
 * are already looking at, so the attribute itself is the control and there is no
 * filter panel to go and find.
 *
 * NAME AND VALUE, ALWAYS BOTH. A bare "friction-extractor" is a string a reader
 * has to place; "Agent · friction-extractor" places itself. The name is set
 * small and quiet and the value in the row's own colour, so a column of facets
 * down a list reads as a column even when the values run to wildly different
 * lengths.
 */

.facets {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.facet {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  line-height: 1.2;
  white-space: nowrap;
  color: var(--color-neutral-700);
  text-align: start;
}

/* The dimension. Quieter than its own value, and never louder — it is the label
   on the fact, not the fact. */
/* The system's own `.label-bare` values, not a near-miss of them: a label above a
   figure and a label inside a facet are the same idea, and two settings that
   differ by a hair are how a design system stops being one. The hierarchy comes
   from case and colour, which is how every other label/value pair here works. */
.facet-name {
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-weight: 500;
  color: var(--color-neutral-600);
}

.facet-value { color: var(--color-neutral-800); font-weight: 500; }

/* A VALUE THAT IS AN ABSENCE. "No standard named" is a real answer and belongs
   in the slot, in a quieter voice — a blank where an attribute should be is the
   one thing a reader never notices. */
.facet-muted .facet-value { color: var(--color-neutral-500); font-weight: 400; font-style: italic; }

/* PRESSABLE ONLY WHEN IT NARROWS. An attribute with nothing behind it must not
   look like a control; the tag hover is what promises that pressing does
   something. */
button.facet { cursor: pointer; transition: background-color 120ms ease, border-color 120ms ease; }
button.facet:hover { background: var(--color-neutral-100); border-color: var(--color-neutral-200); }

/* THE ONE THAT IS NARROWING THE LIST reads as HELD DOWN rather than as a
   different colour of tag. It is a state of the control, not a status of the
   data, and the accent is the system's "this is the live thing" everywhere else. */
.facet[aria-pressed='true'] {
  background: var(--color-accent-100);
  border-color: var(--color-accent-300);
}
.facet[aria-pressed='true'] .facet-name { color: var(--color-accent-700); }
.facet[aria-pressed='true'] .facet-value { color: var(--color-accent-900); }
button.facet[aria-pressed='true']:hover { background: var(--color-accent-200); }

/* -------------------------------------------------------------------- feed */

/* Who did what, grouped by who. A timeline with people in it: the group head carries the actor
   once, and the run of things they did hangs off the same rule a timeline draws — so the boundary
   between two people is a gap and a name in the layout, not a name repeated on every row. */
.feed { display: grid; gap: var(--space-6); list-style: none; margin: 0; padding: 0; }
.feed-group { min-inline-size: 0; }
.feed-head { display: flex; align-items: center; gap: var(--space-2); min-inline-size: 0; }
.feed-actor { font-size: var(--text-sm); font-weight: 500; color: var(--color-neutral-900); }
.feed-when {
  margin-inline-start: auto;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-neutral-600);
  white-space: nowrap;
}
.feed-items {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  counter-reset: feed-item;
}
.feed-item {
  position: relative;
  display: grid;
  grid-template-columns: var(--space-6) minmax(0, 1fr);
  gap: var(--space-3);
  padding-block-end: var(--space-4);
  counter-increment: feed-item;
}
.feed-item:last-child { padding-block-end: 0; }
.feed-item::before {
  content: "";
  position: absolute;
  inset-block: var(--space-6) 0;
  inset-inline-start: calc(var(--space-3) - 0.5px);
  inline-size: 1px;
  background: var(--color-neutral-200);
}
.feed-item:last-child::before { display: none; }
.feed-marker {
  display: grid;
  place-items: center;
  inline-size: var(--space-6);
  block-size: var(--space-6);
  border-radius: var(--radius-full);
  color: var(--color-neutral-700);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
}
.feed-marker svg { inline-size: var(--space-4); block-size: var(--space-4); }
/* A dot is the timeline's dot, so the two read as one family. */
.feed-item[data-marker="dot"] .feed-marker::before {
  content: "";
  inline-size: var(--space-3);
  block-size: var(--space-3);
  border-radius: var(--radius-full);
  background: var(--color-neutral-300);
}
.feed-item[data-marker="number"] .feed-marker,
.feed-item[data-marker="icon"] .feed-marker {
  background: var(--color-bg);
  border: 1px solid var(--color-neutral-300);
}
/* Counted by the list unless the item says its own number. */
.feed-item[data-marker="number"] .feed-marker:empty::before { content: counter(feed-item); }
.feed-item[data-state="current"] .feed-marker { border-color: var(--color-action); color: var(--color-action); }
.feed-item[data-state="current"][data-marker="dot"] .feed-marker::before {
  background: var(--color-action);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-action) 16%, transparent);
}
.feed-content { min-inline-size: 0; padding-block-start: 2px; }
.feed-line { display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; }
.feed-kind { font-size: var(--text-sm); font-weight: 500; color: var(--color-neutral-900); }
.feed-time { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-neutral-600); }
.feed-body { font-size: var(--text-sm); color: var(--color-neutral-700); margin-block-start: var(--space-1); }

/* ---------------------------------------------------------------- feedback */

/* --- dialog --------------------------------------------------------- */

.dialog-backdrop {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  padding: var(--space-6);
  /* A MODAL IS THE ONLY THING, OR IT IS A CARD. At 34% the page behind stayed fully legible and
     the dialog read as floating over live content rather than replacing it. */
  background: color-mix(in srgb, var(--color-scrim) 56%, transparent);
  z-index: 50;
}
.dialog {
  inline-size: min(520px, 100%);
  max-block-size: calc(100vh - var(--space-16));
  /* A phone's browser bar comes and goes; `dvh` is the height actually on show, where it exists. */
  max-block-size: calc(100dvh - var(--space-16));
  /* THE BODY SCROLLS, NOT THE DIALOG.
     `overflow-y` on the box itself scrolled the title out of view and the actions off the bottom —
     so a long explanation could put the confirm button somewhere a reader had to go looking for
     it, on the one surface where the controls are the entire point.

     A COLUMN, NOT A GRID OF COUNTED ROWS. This was `grid-template-rows: auto auto minmax(0, 1fr)
     auto`, which counted the tone band as the first row. Without a tone the title took the first
     `auto`, the body the second — where it grew to its content and never scrolled — and the
     actions the `1fr`, so a long form pushed its own buttons off the dialog on a phone. A flex
     column has no row to miscount: the band, the title and the actions keep their size, and the
     body alone gives way and scrolls, whichever of them are there. */
  display: flex;
  flex-direction: column;
  padding: var(--space-6);
  background: var(--color-bg);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.dialog::before, .dialog-title, .dialog-actions { flex: none; }
.dialog-title {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--text-xl);
  letter-spacing: var(--tracking-xl);
  color: var(--color-neutral-900);
}
.dialog-body {
  /* Shrinks below its content (min-size 0), never grows past it: a short dialog stays short. */
  flex: 0 1 auto;
  min-block-size: 0;
  margin-block-start: var(--space-2); /* space-3, less the step of ring room inside */
  overflow-y: auto;
  overscroll-behavior: contain;
  /* A field's focus ring is drawn outside it, and a scrolling box clips what is outside it. A step
     of room on each side, taken back by the margin, keeps the ring whole at the body's edge. */
  padding: var(--space-1);
  margin-inline: calc(var(--space-1) * -1);
  margin-block-end: calc(var(--space-1) * -1);
  font-size: var(--text-sm);
  color: var(--color-neutral-700);
}
/* Prose in a dialog is prose. Without a measure it ran the full 520px in one block, which is the
   width this system caps everywhere else it asks somebody to read a paragraph. */
.dialog-body > p { max-inline-size: 60ch; }
/* TONE, THE SAME ATTRIBUTE `.alert` AND `.state` CARRY — one vocabulary, as this file already
   argues. Without it a destructive confirmation had to be WRITTEN into seriousness: three lines of
   prose and a red-ish button, with nothing on the surface itself saying what kind of decision this
   is. A reader skims the title and the buttons; the paragraph is the part they skip.

   A band across the head rather than a tinted panel. The dialog stays a sheet you read — tinting
   the whole thing makes the body harder to read at exactly the moment it matters most. */
.dialog[data-tone]::before {
  content: "";
  display: block;
  block-size: var(--space-1);
  margin: calc(var(--space-6) * -1) calc(var(--space-6) * -1) var(--space-5);
  border-start-start-radius: var(--radius-lg);
  border-start-end-radius: var(--radius-lg);
}
.dialog[data-tone="accent"]::before  { background: var(--color-accent-600); }
.dialog[data-tone="success"]::before { background: var(--color-success); }
.dialog[data-tone="warning"]::before { background: var(--color-warning); }
.dialog[data-tone="danger"]::before  { background: var(--color-danger); }
.dialog[data-tone="danger"] .dialog-title  { color: var(--color-danger-700); }
.dialog[data-tone="warning"] .dialog-title { color: var(--color-warning-700); }

.dialog-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-3);
  /* The decision is separated from the explanation by air rather than a rule — the layout
     foundation's own instruction: reach for a larger space step before reaching for a divider. */
  margin-block-start: var(--space-8);
}
/* On a phone the air above the decision is a step smaller, so the body keeps the height. */
@media (max-width: 480px) {
  .dialog-actions { margin-block-start: var(--space-5); }
}

/* --- alert ---------------------------------------------------------- */

/* An inline banner. Tone is an attribute, matching .state — one vocabulary
   for "something is wrong" across the whole system. */
.alert {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-md);
  background: var(--color-neutral-100);
  font-size: var(--text-sm);
  color: var(--color-neutral-800);
}
.alert svg { inline-size: var(--space-5); block-size: var(--space-5); flex: none; color: var(--color-neutral-600); }
/* THE STACK BESIDE THE ICON.
 *
 * `.alert` is a flex ROW so the icon sits beside the message. That only works if the message is
 * one flex item — and the React component rendered the title and the body as SEPARATE children,
 * so they became two items side by side and `.alert-body`'s `margin-block-start` did nothing. A
 * title ran into its own description, on the component whose job is to be read quickly.
 *
 * The documented HTML always wrapped them in a bare `<div>`. This gives that wrapper a name, so
 * the markup and the component can no longer disagree about it.
 *
 * AND IT TAKES THE WIDTH THAT IS LEFT. At the default `flex: 0 1 auto` the stack sits at its own
 * longest line while the alert fills whatever it is given — so the rule under the title, which
 * spans the title's box, stopped partway across a band of colour and read as something half
 * drawn. Every alert whose longest line is shorter than its measure drew it that way. */
.alert-content { flex: 1 1 auto; min-inline-size: 0; }
.alert-title { font-weight: 500; color: var(--color-neutral-900); }
/* A RULE UNDER THE TITLE, AND ONLY WHERE SOMETHING FOLLOWS IT.
 *
 * The same treatment the rail's group headings carry, for the same reason: a heading and the thing
 * it heads need a boundary, not just a gap. `:not(:last-child)` because an alert that is only a
 * title has nothing to be separated FROM, and a rule under the last line of a card is a rule
 * hanging in space.
 *
 * The colour is the tone's own border, so the line belongs to the alert rather than cutting across
 * it in a neutral that appears nowhere else on the card. */
.alert-content > .alert-title:not(:last-child) {
  padding-block-end: var(--space-2);
  border-block-end: 1px solid color-mix(in srgb, currentColor 28%, transparent);
  margin-block-end: var(--space-2);
}
.alert-body { margin-block-start: var(--space-1); color: var(--color-neutral-700); }
.alert-actions { display: flex; gap: var(--space-3); margin-block-start: var(--space-3); }

.alert[data-tone="accent"]  { background: var(--color-accent-100);  border-color: var(--color-accent-300); }
.alert[data-tone="accent"]  svg, .alert[data-tone="accent"] .alert-title  { color: var(--color-accent-700); }
.alert[data-tone="success"] { background: var(--color-success-100); border-color: var(--color-success-300); }
.alert[data-tone="success"] svg, .alert[data-tone="success"] .alert-title { color: var(--color-success-700); }
.alert[data-tone="warning"] { background: var(--color-warning-100); border-color: var(--color-warning-300); }
.alert[data-tone="warning"] svg, .alert[data-tone="warning"] .alert-title { color: var(--color-warning-700); }
.alert[data-tone="danger"]  { background: var(--color-danger-100);  border-color: var(--color-danger-300); }
.alert[data-tone="danger"]  svg, .alert[data-tone="danger"] .alert-title  { color: var(--color-danger-700); }

/* --- toast ---------------------------------------------------------- */

.toast-region {
  position: fixed;
  inset-block-end: var(--space-6);
  inset-inline-end: var(--space-6);
  display: grid;
  gap: var(--space-3);
  inline-size: min(380px, calc(100vw - var(--space-12)));
  z-index: 60;
}
.toast {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--color-bg);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  font-size: var(--text-sm);
  color: var(--color-neutral-800);
  animation: toast-in var(--dur-base) var(--ease-out);
}
.toast svg { inline-size: var(--space-5); block-size: var(--space-5); flex: none; }
.toast-title { font-weight: 500; color: var(--color-neutral-900); }
.toast-body { margin-block-start: var(--space-1); color: var(--color-neutral-700); }
.toast-close { margin-inline-start: auto; }
/* The one action — Undo — sits at the end beside the dismiss, not under the text: it is the thing
   to do about the line it follows, and a reader's eye ends the line there. */
.toast-action { flex: none; margin-inline-start: auto; }
.toast-action + .toast-close { margin-inline-start: 0; }
.toast[data-tone="success"] svg { color: var(--color-success-700); }
.toast[data-tone="warning"] svg { color: var(--color-warning-700); }
.toast[data-tone="danger"]  svg { color: var(--color-danger-700); }
@keyframes toast-in { from { opacity: 0; transform: translateY(var(--space-3)); } }

/* --- tooltip -------------------------------------------------------- */

/* Inverted, because a tooltip floats over content and must read as a layer
   above the page rather than another surface on it. */
.tooltip {
  display: inline-block;
  max-inline-size: 28ch;
  padding: var(--space-2) var(--space-3);
  background: var(--color-inverse);
  color: var(--color-on-inverse);
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  line-height: var(--leading-xs);
  box-shadow: var(--shadow-md);
  z-index: 80;
  pointer-events: none;
}

/* --- callout -------------------------------------------------------- */

/* A quiet aside inside prose. A rule, not a box — a filled box here would
   compete with the cards around it. */
.callout {
  padding-inline-start: var(--space-4);
  border-inline-start: 2px solid var(--color-neutral-300);
  color: var(--color-neutral-700);
  font-size: var(--text-sm);
}
/* ONE TONE VOCABULARY, OR IT IS NOT A VOCABULARY.
 *
 * `.alert`, `.toast` and `.state` all speak the same five — neutral, accent, success, warning,
 * danger. `.callout` spoke two, so a consumer with a quiet aside that flags an ABSENCE had the
 * choice of a tone that says "here is some context" or an alert, which is a box in the middle of
 * prose shouting about a missing picture. They reached for `.label` and a warning colour instead,
 * setting a two-line sentence in uppercase mono, because that was the only warning-coloured thing
 * available at prose scale.
 *
 * The rule takes the tone and nothing else does. A callout is still a rule, not a box — a tinted
 * background here would make it the alert it is deliberately not. */
.callout[data-tone="accent"]  { border-inline-start-color: var(--color-action); }
.callout[data-tone="success"] { border-inline-start-color: var(--color-success-700); }
.callout[data-tone="warning"] { border-inline-start-color: var(--color-warning-700); }
.callout[data-tone="danger"]  { border-inline-start-color: var(--color-danger-700); }
.callout-title { font-weight: 500; color: var(--color-neutral-900); margin-block-end: var(--space-1); }

/* ------------------------------------------------------------------- field */

/* minmax(0,1fr): grid items default to min-width:auto and refuse to shrink
   below their content, so any wide child of a field — a credential row, a long
   control group — would push the page wider instead of ellipsing. */
.field { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-2); }

.field-label {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-neutral-900);
}
.field-label[data-optional]::after {
  content: " optional";
  font-weight: 400;
  color: var(--color-neutral-500);
}

.field-hint { font-size: var(--text-xs); color: var(--color-neutral-600); }
.field-error { font-size: var(--text-xs); color: var(--color-danger-700); }

/* --- text controls -------------------------------------------------- */

/* Every text control sits on the same --control-h-* scale as .btn, so a field
   and the button beside it line up without a per-page nudge. */
.input,
.select,
.textarea {
  inline-size: 100%;
  min-block-size: var(--control-h-md);
  padding: var(--space-2) var(--space-3);
  background: var(--color-bg);
  border: 1px solid var(--color-neutral-300);
  border-radius: var(--radius-md);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  line-height: var(--leading-sm);
  color: var(--color-neutral-900);
  transition: border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}

.textarea { min-block-size: calc(var(--control-h-md) * 2); resize: vertical; }
.select { appearance: none; padding-inline-end: var(--space-8); cursor: pointer; }

.input::placeholder, .textarea::placeholder { color: var(--color-neutral-500); }

/* Focus is deliberately :focus, not :focus-visible — clicking into a field
   should show the ring, unlike clicking a button. */
.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 18%, transparent);
}

.input[aria-invalid="true"], .select[aria-invalid="true"], .textarea[aria-invalid="true"] {
  border-color: var(--color-danger);
}
.input[aria-invalid="true"]:focus,
.select[aria-invalid="true"]:focus,
.textarea[aria-invalid="true"]:focus {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-danger) 18%, transparent);
}

.input:read-only, .textarea:read-only { background: var(--color-surface); }

/* --- a field with words attached to it -------------------------------- */

/* ONE UNIT, NOT A ROW OF THINGS THAT HAPPEN TO BE NEAR EACH OTHER. The parts
   share their borders: only the outer edges are rounded, and the seam between
   them is a single line rather than two sitting against each other. */
.input-group {
  display: flex;
  align-items: stretch;
  min-inline-size: 0;
}
.input-group > * { border-radius: 0; }
.input-group > *:first-child { border-start-start-radius: var(--radius-md); border-end-start-radius: var(--radius-md); }
.input-group > *:last-child { border-start-end-radius: var(--radius-md); border-end-end-radius: var(--radius-md); }
.input-group > .input { flex: 1 1 auto; min-inline-size: 0; }
/* The seam, once. Without this the two 1px borders stack into a 2px rule that
   reads as a gap between two controls. */
.input-group > * + * { margin-inline-start: -1px; }
/* The focused field owns the seam, or its ring is drawn under the neighbour. */
.input-group > .input:focus { position: relative; z-index: 1; }

.input-addon {
  display: flex;
  align-items: center;
  flex: none;
  padding-inline: var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-neutral-300);
  font-size: var(--text-sm);
  line-height: var(--leading-sm);
  color: var(--color-neutral-600);
  white-space: nowrap;
}

.input-sm, .select-sm { min-block-size: var(--control-h-sm); padding-block: var(--space-1); font-size: var(--text-xs); }
.input-lg, .select-lg { min-block-size: var(--control-h-lg); font-size: var(--text-base); }

/* Chevron drawn from borders so it inherits tokens rather than baking a hex
   into a background-image data URI. */
.select-wrap { position: relative; display: block; }
.select-wrap::after {
  content: "";
  position: absolute;
  inset-inline-end: var(--space-4);
  inset-block-start: 50%;
  inline-size: 0.42em;
  block-size: 0.42em;
  border-inline-end: 1.5px solid var(--color-neutral-600);
  border-block-end: 1.5px solid var(--color-neutral-600);
  transform: translateY(-70%) rotate(45deg);
  pointer-events: none;
}

/* --- choice controls ------------------------------------------------ */

.checkbox, .radio {
  appearance: none;
  flex: none;
  inline-size: var(--space-5);
  block-size: var(--space-5);
  margin: 0;
  background: var(--color-bg);
  border: 1px solid var(--color-neutral-400);
  cursor: pointer;
  display: grid;
  place-content: center;
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.checkbox { border-radius: var(--radius-xs); }
.radio { border-radius: var(--radius-full); }

.checkbox:checked, .radio:checked {
  background: var(--color-action);
  border-color: var(--color-action);
}

/* Tick drawn from two borders — no icon dependency for a control this small.
 *
 * `place-content: center` centres the pseudo-element's BOX, and the ink is not
 * the box: it is an L hugging two of its four edges. Rotated 45°, the ink no
 * longer sits where the box does, so it needs a translate back.
 *
 * Derived rather than eyeballed: the offset of the ink's bounding box from the
 * control's centre, found by rasterising this exact geometry at 20× and scanning
 * the painted pixels. It is NOT the same number as the step marker's tick, which
 * has different proportions — sharing one value is how both ended up wrong.
 * Horizontally the ink centres itself, because the box's width and height enter
 * the rotation symmetrically. */
.checkbox:checked::after {
  content: "";
  inline-size: 0.3em;
  block-size: 0.6em;
  border-inline-end: 2px solid var(--color-bg);
  border-block-end: 2px solid var(--color-bg);
  transform: translateY(-0.059em) rotate(45deg);
}
.radio:checked::after {
  content: "";
  inline-size: 0.4em;
  block-size: 0.4em;
  border-radius: var(--radius-full);
  background: var(--color-bg);
}

.checkbox:indeterminate { background: var(--color-action); border-color: var(--color-action); }
.checkbox:indeterminate::after {
  content: "";
  inline-size: 0.55em;
  block-size: 2px;
  background: var(--color-bg);
  transform: none;
  border: 0;
}

/* The label is the hit target, so the whole row is clickable at ≥44px. */
.choice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  min-block-size: var(--control-h-md);
  padding-block: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-neutral-800);
  cursor: pointer;
}
.choice-hint { display: block; font-size: var(--text-xs); color: var(--color-neutral-600); }

/* --- switch --------------------------------------------------------- */

.switch {
  appearance: none;
  position: relative;
  flex: none;
  inline-size: calc(var(--space-6) * 2 - var(--space-2));
  block-size: var(--space-6);
  margin: 0;
  background: var(--color-neutral-300);
  border: 0;
  border-radius: var(--radius-full);
  cursor: pointer;
  transition: background-color var(--dur-base) var(--ease-out);
}
.switch::after {
  content: "";
  position: absolute;
  inset-block-start: var(--space-1);
  inset-inline-start: var(--space-1);
  inline-size: calc(var(--space-6) - var(--space-2));
  block-size: calc(var(--space-6) - var(--space-2));
  border-radius: var(--radius-full);
  background: var(--color-bg);
  box-shadow: var(--shadow-sm);
  transition: translate var(--dur-base) var(--ease-out);
}
.switch:checked { background: var(--color-action); }
.switch:checked::after { translate: calc(var(--space-6) - var(--space-2)) 0; }

/* ------------------------------------------------- the name of a section */

/* A HEADING IS THE LOUD PART. `h2` is a page heading at 36px, so a section
   inside a dense surface reached for `.label` — 11px mono uppercase grey — with
   a 20px `.lede` underneath. The name of the thing set smaller than the
   commentary about it, on every surface that has sections. */
.section-head { margin-block-end: var(--space-4); }
.section-head-line {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}
.section-head-action { margin-inline-start: auto; display: flex; gap: var(--space-2); }
.section-title {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--text-lg);
  line-height: var(--leading-lg);
  letter-spacing: var(--tracking-lg);
  color: var(--color-neutral-900);
}
.section-lede {
  margin-block-start: var(--space-1);
  /* A line of commentary that runs the width of a wide surface is one nobody
     finishes. `ch` rather than a token: this is a measure in CHARACTERS, which
     is what a measure is, and `.reader` already caps prose the same way. */
  max-inline-size: 68ch;
  font-size: var(--text-sm);
  line-height: var(--leading-sm);
  color: var(--color-neutral-600);
}

/* ------------------------------------------------------------------ filter */

/* A ROW OF DIMENSIONS, WRAPPING.
   They sit beside a search field as often as they sit alone, so the row aligns
   on the centre rather than the baseline: a filter next to a taller input
   should look set into the same band, not hung off its text. */
.filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

/* THE CONTROL IS A FIELD, NOT A TAG.
   It is bordered and paper-coloured like the inputs it sits with, because it
   IS an input — a dimension holding a value — and the reader should reach for
   it the way they reach for a select. `Tag` and `Facet` are the flat, tinted
   shapes in this system, and both of them mean *a fact about a row*; borrowing
   either would say this filter was something the data said rather than
   something the reader set. */
.filter {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-block-size: var(--control-h-md);
  padding-inline: var(--space-3);
  border: 1px solid var(--color-neutral-300);
  border-radius: var(--radius-md);
  background: var(--color-bg);
  color: var(--color-neutral-700);
  font: inherit;
  font-size: var(--text-sm);
  line-height: 1;
  cursor: pointer;
  transition: border-color 120ms ease-out, background 120ms ease-out;
}
.filter:hover:not(:disabled) { border-color: var(--color-neutral-400); }
.filter:disabled { opacity: 0.5; cursor: not-allowed; }
.filter[aria-expanded='true'] { border-color: var(--color-action); }

/* NARROWED LOOKS DIFFERENT FROM NOT NARROWED.
   A list is being hidden from the reader, and the control doing it must say so
   without being read. The border takes the action colour and the value goes to
   full text weight; the dimension stays quiet beside it. */
.filter-on { border-color: var(--color-action); color: var(--color-text); }
.filter-name { color: var(--color-neutral-600); }
.filter-on .filter-name::after { content: ':'; }
.filter-on .filter-value { color: var(--color-text); font-weight: 500; }

/* Down while shut, up while open — so the control says which way it will move
   before it is pressed. */
.filter-caret {
  inline-size: var(--space-4);
  block-size: var(--space-4);
  margin-inline-start: auto;
  color: var(--color-neutral-500);
  transition: transform 140ms ease-out;
}
.filter[aria-expanded='true'] .filter-caret { transform: rotate(180deg); }

/* A value and the line that settles it, stacked inside the row. */
.filter-option { display: flex; flex-direction: column; gap: 2px; min-inline-size: 0; }
.filter-option-note {
  font-size: var(--text-xs);
  line-height: var(--leading-xs);
  color: var(--color-neutral-600);
}

/* SEVERAL CHOSEN READS AS HOW MANY. "Kind · 2" — the dimension at full text weight, because with
   no single value to show, the dimension IS what the control says; and no colon, because a count
   is not an answer to it. */
.filter-on[data-many] .filter-name { color: var(--color-text); }
.filter-on[data-many] .filter-name::after { content: none; }
.filter-on[data-many] .filter-value { font-variant-numeric: tabular-nums; }

/* How many rows carry a value, at the end of its row in the menu — mono and tabular so a column
   of them lines up and can be compared down the list. */
.filter-option-count {
  margin-inline-start: auto;
  padding-inline-start: var(--space-4);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--color-neutral-600);
}

/* -------------------------------------------------------------------- flow */
/* Flow-control parts. Composed on their own, or by the .flow shell below. */

/* --- stepper -------------------------------------------------------- */

/* Position is carried by aria-current="step" and data-state, so the marker,
   the connector and the screen-reader announcement read one source.

   The marker's states are the progress family's states, drawn by the same rules
   as `.progress-mark` in progress.css: a tonal disc with one glyph from the
   family's glyph set. A step and a checklist task that are both complete look
   the same because they ARE the same rule. */

/* THE AIR EITHER SIDE OF A CONNECTOR IS THE SAME AIR.
 *
 * A connector sits after the label of the step it leaves and before the marker of
 * the step it points at, so the space on its two sides comes from two different
 * rules: `.step`'s own column gap on the left, the `.stepper`'s gap on the right.
 * They are one value, so a line never sits closer to one marker than the other. */
.stepper {
  --step-marker: var(--progress-circle-md);
  /* The stepper measures itself, so it can lay out by the room IT has — a stepper in a 360px
     dialog on a wide screen needs the narrow form as much as one on a phone. */
  container: stepper / inline-size;
  display: flex;
  /* Wrap, never clip. A label is the step's name; an ellipsis on it is a step with no name. */
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-3);
}
/* Compact: a 24px marker for a dense header or a dialog. */
.stepper-sm { --step-marker: var(--progress-circle-sm); }

/* A STEP IS A SMALL GRID, NOT A ROW OF WHATEVER IT HOLDS.
 *
 * Marker, label and connector share the first row, which is exactly the marker's
 * height, so the label and the line both sit on the marker's centre whatever the
 * label's font. An optional description takes the second row under the label and
 * moves nothing above it.
 *
 * A CONNECTOR IS A LENGTH, NOT WHATEVER THE LABELS LEFT OVER: the connector column
 * is `1fr` with its own floor, so every line is the same length until the rail
 * runs out of room, and then they all shorten together. */
.step {
  display: grid;
  grid-template-columns: auto auto minmax(var(--space-4), 1fr);
  grid-template-rows: var(--step-marker) auto;
  align-items: center;
  column-gap: var(--space-3);
  flex: 1 1 auto;
  min-inline-size: 0;
}
.step:last-child { flex: none; grid-template-columns: auto auto; }

.step-marker {
  position: relative;
  display: grid;
  place-items: center;
  grid-row: 1;
  grid-column: 1;
  box-sizing: border-box;
  inline-size: var(--step-marker);
  block-size: var(--step-marker);
  flex: none;
  border: 1px solid var(--color-neutral-300);
  border-radius: var(--radius-full);
  background: var(--color-bg);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  line-height: 1;
  color: var(--color-neutral-600);
  transition: background-color var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              color var(--dur-base) var(--ease-out);
}
.step-label {
  grid-row: 1;
  grid-column: 2;
  font-size: var(--text-sm);
  color: var(--color-neutral-700);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.step-description {
  grid-row: 2;
  grid-column: 2;
  margin-block-start: calc(-1 * var(--space-1));
  font-size: var(--text-xs);
  color: var(--color-neutral-600);
}
.step-connector {
  grid-row: 1;
  grid-column: 3;
  block-size: 1px;
  background: var(--color-neutral-200);
}

/* THE GLYPH IS NOT A GRID ITEM.
 *
 * The tick used to be `::after` inside a grid whose number had been hidden with
 * `font-size: 0`. The number's text node was still a grid item, so the grid had
 * two rows and the tick was placed in the second: 9.7px low, while the failed
 * cross, pinned to the first row, sat 8px high. A hand-tuned translate was
 * blamed and adjusted; the grid was the cause.
 *
 * Now the glyph is absolutely positioned over the whole marker and centred by
 * `inset: 0; margin: auto` with an explicit size, so the number's box cannot
 * touch it. The number stays in the DOM, transparent, so the marker keeps its
 * size and a screen reader still reaches it. The glyph's ink is centred in its
 * own box by construction (see the glyph set in progress.css), so nothing here
 * is tuned. */
.step-marker::after {
  content: none;
  position: absolute;
  inset: 0;
  margin: auto;
  inline-size: calc(var(--step-marker) / 2);
  block-size: calc(var(--step-marker) / 2);
  background: currentColor;
  -webkit-mask: var(--progress-glyph-check) center / contain no-repeat;
  mask: var(--progress-glyph-check) center / contain no-repeat;
}

.step[data-state="complete"] .step-marker,
.step[data-state="failed"] .step-marker,
.step[data-state="stopped"] .step-marker,
.step[data-state="skipped"] .step-marker { font-size: 0; color: transparent; }
.step[data-state="complete"] .step-marker::after,
.step[data-state="failed"] .step-marker::after,
.step[data-state="stopped"] .step-marker::after,
.step[data-state="skipped"] .step-marker::after { content: ""; }

.step[data-state="complete"] .step-marker {
  background: var(--color-success-100);
  border-color: var(--color-success-300);
}
.step[data-state="complete"] .step-marker::after { background: var(--color-success-700); }
.step[data-state="complete"] .step-label { color: var(--color-neutral-800); }
/* A connector belongs to the step it LEAVES, so it fills only when that step
   is complete. Colouring the connector of the step that follows a complete one
   would claim the current step is finished. */
.step[data-state="complete"] .step-connector { background: var(--color-success-300); }

/* A STEP THAT WAS ATTEMPTED AND REFUSED IS NEITHER CURRENT NOR COMPLETE.
 *
 * The vocabulary was complete / current / upcoming, which has no word for the one outcome a reader
 * most needs to see. Without it a refused step sits at `current` forever or is marked complete,
 * which is a lie. Danger rather than warning: the step did not happen, and the sequence stopped.
 * The connector stays neutral: the step did not complete, so nothing left it. */
.step[data-state="failed"] .step-marker {
  background: var(--color-danger-100);
  border-color: var(--color-danger-300);
}
.step[data-state="failed"] .step-marker::after {
  background: var(--color-danger-700);
  -webkit-mask-image: var(--progress-glyph-cross);
  mask-image: var(--progress-glyph-cross);
}
.step[data-state="failed"] .step-label { color: var(--color-danger-700); }

/* Stopped short — interrupted or cancelled, not refused. Warning, and the `!` glyph. */
.step[data-state="stopped"] .step-marker {
  background: var(--color-warning-100);
  border-color: var(--color-warning-300);
}
.step[data-state="stopped"] .step-marker::after {
  background: var(--color-warning-700);
  -webkit-mask-image: var(--progress-glyph-alert);
  mask-image: var(--progress-glyph-alert);
}
.step[data-state="stopped"] .step-label { color: var(--color-warning-800); }

/* Skipped — passed over on purpose. Dashed and quiet, with a dash. The connector
   leaving it fills, because the sequence moved past it. */
.step[data-state="skipped"] .step-marker {
  border-style: dashed;
  background: transparent;
}
.step[data-state="skipped"] .step-marker::after {
  background: var(--color-neutral-600);
  -webkit-mask-image: var(--progress-glyph-minus);
  mask-image: var(--progress-glyph-minus);
}
.step[data-state="skipped"] .step-label { color: var(--color-neutral-600); }
.step[data-state="skipped"] .step-connector { background: var(--color-success-300); }

.step[aria-current="step"] .step-marker {
  border-color: var(--progress-fill);
  color: var(--progress-fill);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--progress-fill) 14%, transparent);
}
.step[aria-current="step"] .step-label { color: var(--color-neutral-900); font-weight: 500; }

/* Vertical rail — the shape the .flow shell uses. The line runs from just under
   one marker to just above the next, through whatever height the step's
   description gives it, so it never runs past the last step. */
.stepper-vertical { flex-direction: column; align-items: stretch; gap: 0; }
.stepper-vertical .step,
.stepper-vertical .step:last-child {
  position: relative;
  flex: none;
  grid-template-columns: auto minmax(0, 1fr);
  padding-block: var(--space-3);
}
.stepper-vertical .step-connector { display: none; }
.stepper-vertical .step:not(:last-child)::before {
  content: "";
  position: absolute;
  inset-block-start: calc(var(--space-3) + var(--step-marker) + var(--space-1));
  inset-block-end: calc(var(--space-1) - var(--space-3));
  inset-inline-start: calc(var(--step-marker) / 2 - 0.5px);
  inline-size: 1px;
  background: var(--color-neutral-200);
}
.stepper-vertical .step[data-state="complete"]:not(:last-child)::before,
.stepper-vertical .step[data-state="skipped"]:not(:last-child)::before { background: var(--color-success-300); }
.stepper-vertical .step-label { white-space: normal; }

/* THE RAIL FORM — A SEQUENCE READ AT A GLANCE RATHER THAN COUNTED.
 *
 * The marker form pairs a numbered circle with a label beside it, and that
 * pairing is what breaks when a sequence spans a page. The rail answers the
 * other question: same states, same `data-state` vocabulary, drawn as segments
 * of the family's bar — the same 4px track, the same 2px gap and the same state
 * colours as a segmented progress bar. How much is behind you reads without
 * counting, and each label sits under its own segment.
 *
 * Use the marker form when a reader steps through and each step is an act they
 * perform. Use the rail when the sequence is a position they are somewhere in.
 *
 * `--stepper-complete` is declared nowhere and referenced with a fallback,
 * which is this system's signal for a property the caller sets.
 */
.stepper-rail { display: flex; align-items: stretch; gap: var(--progress-gap); }
.stepper-rail .step,
.stepper-rail .step:last-child { display: block; flex: 1 1 0; min-inline-size: 0; }
.stepper-rail .step-marker {
  display: block;
  inline-size: auto;
  block-size: var(--progress-h-sm);
  border: 0;
  border-radius: var(--progress-radius);
  background: var(--progress-track);
  /* The segment is the marker here, so the index has nothing to sit in. Hidden
     by size rather than removed, so a screen reader still reaches it. */
  font-size: 0;
  color: transparent;
}
.stepper-rail .step .step-marker::after,
.stepper-rail .step[data-state] .step-marker::after,
.stepper-rail .step .step-marker::before { content: none; }
.stepper-rail .step-label {
  display: block;
  margin-block-start: var(--space-2);
  font-size: var(--text-xs);
}
.stepper-rail .step-description { display: block; margin-block-start: 0; }
.stepper-rail .step-connector { display: none; }

/* The segment's colour is the state's; the label carries the state's glyph, so
   the rail is never colour alone. */
.stepper-rail .step[data-state="complete"] .step-marker { background: var(--stepper-complete, var(--progress-fill-success)); box-shadow: none; }
.stepper-rail .step[data-state="failed"] .step-marker { background: var(--progress-fill-danger); }
.stepper-rail .step[data-state="stopped"] .step-marker { background: var(--progress-fill-warning); }
.stepper-rail .step[data-state="skipped"] .step-marker { background: var(--color-neutral-300); }
.stepper-rail .step[aria-current="step"] .step-marker { background: var(--progress-fill); box-shadow: none; }

.stepper-rail .step[data-state] .step-label::before {
  content: "";
  display: inline-block;
  inline-size: 1em;
  block-size: 1em;
  margin-inline-end: var(--space-1);
  vertical-align: -0.15em;
  background: var(--color-success-700);
  -webkit-mask: var(--progress-glyph-check) center / contain no-repeat;
  mask: var(--progress-glyph-check) center / contain no-repeat;
}
.stepper-rail .step[aria-current="step"] .step-label::before { content: none; }
.stepper-rail .step[data-state="failed"] .step-label::before {
  background: var(--color-danger-700);
  -webkit-mask-image: var(--progress-glyph-cross);
  mask-image: var(--progress-glyph-cross);
}
.stepper-rail .step[data-state="stopped"] .step-label::before {
  background: var(--color-warning-700);
  -webkit-mask-image: var(--progress-glyph-alert);
  mask-image: var(--progress-glyph-alert);
}
.stepper-rail .step[data-state="skipped"] .step-label::before {
  background: var(--color-neutral-600);
  -webkit-mask-image: var(--progress-glyph-minus);
  mask-image: var(--progress-glyph-minus);
}

/* --- the stepper in a narrow container ------------------------------ */

/* BELOW 30REM THE MARKER STEPPER GOES VERTICAL; THE RAIL SHOWS ONE LABEL.
 *
 * Four steps across a phone leave each label about 55px, and the labels were clipped to an
 * ellipsis — a step with half a name. The two ways out were to keep the row and show only the
 * current label with an `n of N`, or to stack the steps. The marker stepper stacks:
 *
 *   - Its job is to name every step, and the stacked form keeps every name, whole, with its
 *     description, at a size a thumb can read. A count and one label would hide the very thing
 *     the markers exist to pair with.
 *   - It is already a first-class form here (the `.flow` shell's rail), with the same marks and
 *     connectors, so a reader who rotates the phone sees the same object turn, not a new one.
 *   - It needs no count, no knowledge of N, and nothing from React: the same markup does it, so
 *     a static page and `<Stepper>` behave alike.
 *
 * Phones have height to spare and no width; the stacked form spends the one they have.
 *
 * The rail keeps its row — its whole point is how much is behind you at a glance — and shows only
 * the current step's label, whole, with the others kept for assistive tech. */
@container stepper (max-width: 30rem) {
  .stepper:not(.stepper-rail):not(.stepper-vertical) > .step,
  .stepper:not(.stepper-rail):not(.stepper-vertical) > .step:last-child {
    position: relative;
    flex: 1 0 100%;
    grid-template-columns: auto minmax(0, 1fr);
    padding-block: var(--space-2);
  }
  .stepper:not(.stepper-rail):not(.stepper-vertical) > .step .step-connector { display: none; }
  .stepper:not(.stepper-rail):not(.stepper-vertical) > .step .step-label { white-space: normal; }
  .stepper:not(.stepper-rail):not(.stepper-vertical) > .step:not(:last-child)::before {
    content: "";
    position: absolute;
    inset-block-start: calc(var(--space-2) + var(--step-marker) + var(--space-1));
    inset-block-end: calc(var(--space-1) - var(--space-2) - var(--space-3));
    inset-inline-start: calc(var(--step-marker) / 2 - 0.5px);
    inline-size: 1px;
    background: var(--color-neutral-200);
  }
  .stepper:not(.stepper-rail):not(.stepper-vertical) > .step[data-state="complete"]:not(:last-child)::before,
  .stepper:not(.stepper-rail):not(.stepper-vertical) > .step[data-state="skipped"]:not(:last-child)::before {
    background: var(--color-success-300);
  }

  /* The rail: one label, whole. The others leave the layout but not the accessibility tree.
     The current label may run past its own segment; near the end of the rail it runs back
     from the segment's end instead, so it never leaves the rail. */
  .stepper-rail > .step { position: relative; display: flex; flex-direction: column; align-items: flex-start; }
  .stepper-rail > .step:nth-last-child(-n + 2) { align-items: flex-end; }
  .stepper-rail > .step > .step-marker { align-self: stretch; }
  .stepper-rail > .step:not([aria-current="step"]) > .step-label,
  .stepper-rail > .step:not([aria-current="step"]) > .step-description {
    /* Sized to nothing rather than clipped: a clipped label still overflows its segment, and
       the text at font-size 0 is still what a screen reader reads — the same reason the
       marker's index is kept. */
    position: absolute;
    font-size: 0;
  }
  .stepper-rail > .step[aria-current="step"] > .step-label {
    inline-size: max-content;
    overflow: visible;
    white-space: nowrap;
  }
}

/* --- skeleton ------------------------------------------------------- */

/* Progress, rings and the spinner moved to progress.css, where the whole progress family shares
   one anatomy. */

.skeleton {
  display: block;
  block-size: var(--space-4);
  border-radius: var(--radius-sm);
  background: linear-gradient(90deg,
    var(--color-neutral-200) 0%,
    var(--color-neutral-100) 50%,
    var(--color-neutral-200) 100%);
  background-size: 200% 100%;
  animation: skeleton-sweep 1.4s var(--ease-in-out) infinite;
}
.skeleton-text { block-size: var(--space-3); }
.skeleton-title { block-size: var(--space-6); }
.skeleton-circle { border-radius: var(--radius-full); inline-size: var(--space-10); block-size: var(--space-10); }
@keyframes skeleton-sweep { to { background-position: -200% 0; } }

/* --- pagination ----------------------------------------------------- */

.pagination { display: flex; align-items: center; gap: var(--space-1); }
.pagination-item {
  display: inline-grid;
  place-items: center;
  min-inline-size: var(--control-h-sm);
  block-size: var(--control-h-sm);
  padding-inline: var(--space-2);
  border-radius: var(--radius-md);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-neutral-700);
  text-decoration: none;
  cursor: pointer;
  border: 0;
  background: transparent;
}
.pagination-item:hover { background: var(--color-neutral-100); color: var(--color-neutral-900); }
.pagination-item[aria-current] { background: var(--color-action); color: var(--color-bg); }
.pagination-gap { color: var(--color-neutral-400); padding-inline: var(--space-1); user-select: none; }

/* --- timeline ------------------------------------------------------- */

.timeline { display: grid; gap: 0; }
.timeline-item {
  position: relative;
  display: grid;
  grid-template-columns: var(--space-6) 1fr;
  gap: var(--space-4);
  padding-block-end: var(--space-6);
}
.timeline-item:last-child { padding-block-end: 0; }
.timeline-item::before {
  content: "";
  position: absolute;
  inset-block: var(--space-5) 0;
  inset-inline-start: calc(var(--space-3) - 0.5px);
  inline-size: 1px;
  background: var(--color-neutral-200);
}
.timeline-item:last-child::before { display: none; }
.timeline-marker {
  position: relative;
  inline-size: var(--space-3);
  block-size: var(--space-3);
  margin-block-start: var(--space-2);
  margin-inline-start: calc(var(--space-2) - var(--space-1) / 2);
  border-radius: var(--radius-full);
  background: var(--color-neutral-300);
}
.timeline-item[data-state="current"] .timeline-marker { background: var(--color-action); box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-action) 16%, transparent); }
.timeline-time { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-neutral-600); }
.timeline-title { font-size: var(--text-sm); font-weight: 500; color: var(--color-neutral-900); margin-block-start: var(--space-1); }
.timeline-body { font-size: var(--text-sm); color: var(--color-neutral-700); margin-block-start: var(--space-1); }

/* --- the flow shell -------------------------------------------------- */

/* The prescribed structure: a step rail, one content region, and a persistent
   footer that always holds back/next in the same place. Engagements fill in
   the steps; anything that does not fit drops back to the parts above. */

.flow {
  display: grid;
  grid-template-columns: minmax(0, var(--flow-rail-w, 264px)) minmax(0, 1fr);
  min-block-size: 100%;
  background: var(--color-bg);
}

.flow-rail {
  padding: var(--space-6);
  background: var(--color-surface);
  border-inline-end: 1px solid var(--color-neutral-200);
}
.flow-rail-title {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-neutral-600);
  margin-block-end: var(--space-4);
}

.flow-main { display: flex; flex-direction: column; min-inline-size: 0; }

.flow-header {
  padding: var(--space-6) var(--space-8);
  border-block-end: 1px solid var(--color-neutral-200);
}
.flow-step-of { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-neutral-600); }
.flow-title {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--text-2xl);
  letter-spacing: var(--tracking-2xl);
  color: var(--color-neutral-900);
  margin-block-start: var(--space-2);
}
.flow-lede { font-size: var(--text-sm); color: var(--color-neutral-700); margin-block-start: var(--space-2); max-inline-size: 62ch; }

.flow-content { flex: 1; padding: var(--space-8); min-block-size: 0; }
.flow-fields { display: grid; gap: var(--space-6); max-inline-size: var(--container-sm); }

/* Back sits left, forward sits right, always. A user should never hunt for
   the control that advances a flow. */
.flow-footer {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-8);
  border-block-start: 1px solid var(--color-neutral-200);
  background: var(--color-bg);
}
.flow-footer-status { margin-inline: auto; font-size: var(--text-xs); color: var(--color-neutral-600); }

/* Below the rail breakpoint the vertical rail becomes a progress bar on top —
   a 264px rail on a phone is most of the screen. */
@media (max-width: 720px) {
  .flow { grid-template-columns: minmax(0, 1fr); }
  .flow-rail {
    border-inline-end: 0;
    border-block-end: 1px solid var(--color-neutral-200);
    padding: var(--space-4) var(--space-6);
  }
  .flow-rail .stepper-vertical { flex-direction: row; align-items: center; gap: var(--space-2); }
  .flow-rail .stepper-vertical .step-label,
  .flow-rail .stepper-vertical .step-description,
  .flow-rail .stepper-vertical .step::before { display: none; }
  .flow-rail .stepper-vertical .step { padding-block: 0; }
  .flow-header, .flow-content, .flow-footer { padding-inline: var(--space-6); }

  /* Several controls will not fit one narrow row. Wrap, and give the status
     line the full width rather than letting it squeeze the actions. */
  .flow-footer { flex-wrap: wrap; }
  .flow-footer-status { order: -1; inline-size: 100%; margin-inline: 0; text-align: start; }
}

/* @parity-exempt: the live component paints to canvas, so it emits no per-node
   or per-edge DOM. These classes style the SVG snapshot used in the preview
   page and in static exports. The exemption is narrow on purpose — every other
   component must still be producible by @gener8v/ui. */

/* ------------------------------------------------------------------- graph */

/* A force-directed knowledge graph. Nodes are entities, edges are stated
   relationships. Colour encodes kind, size encodes degree — nothing encodes
   anything by position, because a force layout has no stable axes. */

.graph {
  position: relative;
  inline-size: 100%;
  block-size: clamp(320px, 52vh, 560px);
  background: var(--color-bg);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.graph-canvas { display: block; inline-size: 100%; block-size: 100%; cursor: grab; touch-action: none; }
.graph-canvas:active { cursor: grabbing; }

.graph-edge {
  stroke: var(--color-neutral-300);
  stroke-width: 1;
  transition: stroke var(--dur-fast) var(--ease-out), opacity var(--dur-fast) var(--ease-out);
}
.graph-edge[data-active] { stroke: var(--color-action); stroke-width: 1.5; }
.graph-edge[data-dim] { opacity: 0.18; }

.graph-node { cursor: pointer; }
.graph-node circle {
  fill: var(--color-accent-500);
  stroke: var(--color-bg);
  stroke-width: 2;
  transition: fill var(--dur-fast) var(--ease-out), opacity var(--dur-fast) var(--ease-out);
}
.graph-node text {
  font-family: var(--font-body);
  font-size: 10px;
  fill: var(--color-neutral-700);
  text-anchor: middle;
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.graph-node[data-dim] circle, .graph-node[data-dim] text { opacity: 0.22; }
.graph-node[data-selected] circle { stroke: var(--color-action); stroke-width: 3; }
.graph-node[data-selected] text { fill: var(--color-neutral-900); font-weight: 500; }

/* Kind is a data attribute so a consuming app can add kinds without touching
   the component — it only has to add one rule. */
/* The kind's colour is set BY THE CALLER, from the kind's declared position in the categorical
   palette — `--kind: var(--series-3)`. It used to be five rules keyed on the spelling of five
   governance-domain nouns, in a component whose whole point is that the caller names its own
   kinds: a graph of standards, deliverables and decisions matched none of them and rendered in one
   colour. The fallback is a neutral rather than an accent, so a kind nobody coloured looks
   uncoloured instead of looking like the first series. */
.graph-node circle { fill: var(--kind, var(--color-neutral-400)); }

.graph-toolbar {
  position: absolute;
  inset-block-start: var(--space-3);
  inset-inline-end: var(--space-3);
  display: flex;
  gap: var(--space-1);
  padding: var(--space-1);
  background: color-mix(in srgb, var(--color-bg) 88%, transparent);
  backdrop-filter: blur(6px);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-md);
}

.graph-legend {
  position: absolute;
  inset-block-end: var(--space-3);
  inset-inline-start: var(--space-3);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: color-mix(in srgb, var(--color-bg) 88%, transparent);
  backdrop-filter: blur(6px);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-md);
  font-size: var(--text-xs);
  color: var(--color-neutral-700);
}
.graph-legend-key { display: inline-flex; align-items: center; gap: var(--space-2); }
/* NO PER-KIND RULES HERE, AND THAT IS THE FIX.
   There were five, keyed on the spelling of five governance-domain nouns — engagement, control,
   finding, evidence, person — in a component whose `kinds` prop exists so a caller can name its
   own. A graph of standards, deliverables and decisions matched none of them: every dot rendered
   with no background, beside a canvas where every node had fallen through to one fallback colour.
   The colour is now set from the kind's DECLARED POSITION, by the component, from the same
   arithmetic the canvas uses — so the legend and the nodes cannot disagree about what blue means. */
.graph-legend-dot {
  inline-size: var(--space-2);
  block-size: var(--space-2);
  border-radius: var(--radius-full);
  background: var(--color-neutral-400);
}

/* The keyboard equivalent of the canvas: a real list of entities and their
   stated relationships. Shown on focus so it is discoverable, not a secret. */
.graph-index { position: absolute; inset: 0; overflow-y: auto; padding: var(--space-4); background: var(--color-bg); }
.graph-index-item { display: block; inline-size: 100%; text-align: start; }

/* THE CAPTION IS PROSE, SO IT IS SET AS PROSE.
 *
 * It carried `.label` — small, uppercase, letter-spaced, which is right for one
 * or two words naming what follows and wrong for two sentences. Set that way it
 * became a wall of capitals across the top of the list, harder to read than the
 * list it was introducing. */
.graph-index-caption {
  max-inline-size: 72ch;
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--leading-lg);
  color: var(--color-neutral-600);
}

@media (prefers-reduced-motion: reduce) {
  .graph-edge, .graph-node circle, .graph-node text { transition: none; }
}

/* ------------------------------------------------------------ integrations */

/* Connections, credentials and event rules. Status here is genuinely a status,
   so it uses the reserved status colours — this is the case they exist for. */

.connection {
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-md);
  background: var(--color-bg);
}
.connection-mark {
  display: grid;
  place-items: center;
  inline-size: var(--space-10);
  block-size: var(--space-10);
  flex: none;
  border-radius: var(--radius-sm);
  background: var(--color-neutral-100);
  color: var(--color-neutral-700);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 500;
}
.connection-mark svg { inline-size: var(--space-5); block-size: var(--space-5); }
.connection-body { flex: 1 1 60%; min-inline-size: 0; }
.connection-name { display: flex; align-items: center; gap: var(--space-3); font-size: var(--text-sm); font-weight: 500; color: var(--color-neutral-900); }
.connection-note { font-size: var(--text-xs); color: var(--color-neutral-600); margin-block-start: var(--space-1); }
/* Wraps under the body when the card is narrow; margin-inline-start:auto keeps
   it right-aligned on the wrapped row too. */
.connection-actions { flex: none; align-self: center; display: flex; gap: var(--space-2); align-items: center; margin-inline-start: auto; }
.connection-name { flex-wrap: wrap; }

/* CENTRED AGAINST THE CONTROLS IT SITS BESIDE. `.connection` is `flex-start`,
   so the status sat at the top of its line box while the buttons next to it
   were 36px tall — three things on one row at three heights. It is a fact
   about the whole card, not about the first line of it. */
.connection-status { align-self: center; display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-xs); font-weight: 500; }
.connection-status::before { content: ""; inline-size: var(--space-2); block-size: var(--space-2); border-radius: var(--radius-full); background: currentColor; flex: none; }
.connection-status[data-state="connected"] { color: var(--color-success-700); }
.connection-status[data-state="degraded"] { color: var(--color-warning-700); }
.connection-status[data-state="error"]     { color: var(--color-danger-700); }
.connection-status[data-state="pending"]   { color: var(--color-accent-700); }
.connection-status[data-state="off"]       { color: var(--color-neutral-500); }

/* --- credentials ------------------------------------------------------ */

/* A secret is masked until deliberately revealed, and copy never requires a
   reveal — asking someone to display a key on screen in order to copy it is
   how keys end up in screenshots. */
.credential {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0 var(--space-2) 0 var(--space-3);
  min-block-size: var(--control-h-md);
  border: 1px solid var(--color-neutral-300);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
.credential-value {
  flex: 1;
  min-inline-size: 0;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-neutral-900);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  letter-spacing: 0.02em;
}
.credential[data-masked] .credential-value { letter-spacing: 0.16em; color: var(--color-neutral-600); }
.credential-actions { display: flex; gap: var(--space-1); flex: none; }

.key-list { display: grid; gap: 1px; margin: 0; padding: 0; list-style: none; background: var(--color-neutral-200); border: 1px solid var(--color-neutral-200); border-radius: var(--radius-md); overflow: hidden; }
.key-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-4); padding: var(--space-3) var(--space-4); background: var(--color-bg); font-size: var(--text-sm); }
.key-name { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--color-neutral-900); }
.key-fingerprint { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-neutral-600); flex: none; }
.key-used { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-neutral-600); margin-inline-start: auto; flex: none; white-space: nowrap; }
/* An expiring key is a warning, not an error — it still works today. */
.key-row[data-expiring] .key-used { color: var(--color-warning-700); }
.key-row[data-expired] { background: var(--color-danger-100); }
.key-row[data-expired] .key-used { color: var(--color-danger-700); }

.scopes { display: flex; flex-wrap: wrap; gap: var(--space-1); }
.scope {
  font-family: var(--font-mono);
  font-size: 10px;
  padding: 1px var(--space-2);
  border: 1px solid var(--color-neutral-300);
  border-radius: var(--radius-xs);
  color: var(--color-neutral-700);
}
/* A write scope is the one worth noticing in a list of twelve. */
.scope[data-write] { border-color: var(--color-warning-300); background: var(--color-warning-100); color: var(--color-warning-700); }

/* --- event rules ------------------------------------------------------ */

/* trigger → transform → action, read left to right. The arrow is a real glyph
   rather than a border, because this line is the sentence the rule makes. */
/* A SENTENCE'S WORDS SIT NEXT TO EACH OTHER.
 *
 * This was `minmax(0,1fr) auto minmax(0,1fr)` — two halves pushed to opposite
 * ends with the arrow stranded between them. At a container width the design
 * was drawn for that reads as a rule; at 1400px it puts 700px between the
 * trigger and what it triggers, and "read as a sentence" stops being true of
 * it. The parts now size to their content and pack from the start.
 *
 * `trailing` still goes to the end, because an action on the row is not part
 * of the sentence. */
.rule {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr);
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-md);
  background: var(--color-bg);
}
.rule-node { display: flex; align-items: center; gap: var(--space-2); min-inline-size: 0; }
/* The third column holds what the rule DOES plus anything trailing, so the
   action stays beside the arrow and the action-button goes to the end. */
.rule-node:last-child { gap: var(--space-3); }
.rule-node:last-child .rule-label { flex: none; }
.rule-node:last-child > :not(.rule-label):not(svg) { margin-inline-start: auto; }
.rule-node svg { inline-size: var(--space-4); block-size: var(--space-4); flex: none; color: var(--color-neutral-600); }
.rule-label { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--text-sm); color: var(--color-neutral-900); }
.rule-event { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-neutral-600); }
.rule-arrow { color: var(--color-neutral-400); flex: none; }
.rule-arrow svg { inline-size: var(--space-4); block-size: var(--space-4); }
.rule[data-off] { opacity: 0.55; }

.endpoint {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: var(--color-inverse);
  color: var(--color-on-inverse);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  overflow-x: auto;
}
.endpoint-method { color: var(--color-accent-300); flex: none; }

/* ------------------------------------------------------------ item list */

/* A LIST OF RECORDS, each read on its own.
 *
 * `.table` is for values compared down a column; `.doc-list` is for files. This
 * is for the third shape — a name, a line of provenance, a mark or two — which
 * three surfaces had each invented separately before it was written down. */
.item-list {
  display: flex;
  flex-direction: column;
  list-style: none;
  margin: 0;
  padding: 0;
}
/* A HAIRLINE BETWEEN, NOT AROUND. A box per row turns a list of six into six
   boxes; the rule is the least a reader needs to see where one ends. */
.item + .item { border-block-start: 1px solid var(--color-neutral-200); }

.item {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding-block: var(--space-3);
  min-inline-size: 0;
}

.item-lead {
  flex: none;
  display: grid;
  place-items: center;
  color: var(--color-neutral-600);
  /* Aligned to the TITLE'S line, not to the top of a row that may be three lines
     tall — an icon floating above a two-line meta reads as belonging to neither. */
  block-size: var(--leading-sm);
}

.item-body { display: flex; flex-direction: column; min-inline-size: 0; flex: 1; }

/* The marks share the title's line and wrap with it: a tag on its own line reads
   as a second fact about the row rather than as a qualification of its name. */
.item-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  min-inline-size: 0;
}
.item-title { font-size: var(--text-sm); color: var(--color-neutral-900); min-inline-size: 0; }
.item-trailing { display: inline-flex; align-items: center; gap: var(--space-2); }

.item-meta {
  margin-block-start: var(--space-1);
  font-size: var(--text-xs);
  line-height: var(--leading-xs);
  color: var(--color-neutral-600);
}
.item-detail {
  margin-block-start: var(--space-2);
  max-inline-size: 68ch;
  font-size: var(--text-sm);
  line-height: var(--leading-sm);
  color: var(--color-neutral-700);
}

/* THE MEASURE BELONGS TO THE COLUMN, NOT TO THE BLOCK.

   68ch keeps a paragraph readable, and a detail holding one is capped at it. A
   detail holding two positions BEING COMPARED is not a paragraph: capping the
   pair gives each side 34ch and leaves the rest of the row empty, so the reader
   scrolls between two things whose whole point is that they are opposed.

   No breakpoint. A row is not always the width of the page — it can sit in a
   panel or a drawer — so `auto-fit` answers the space this row actually has,
   and folds back to one column when there is not enough for two. */
.item-detail[data-layout='columns'] {
  max-inline-size: none;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 44ch), 1fr));
  gap: var(--space-3) var(--space-6);
  align-items: start;
}

/* -------------------------------------------------------------------- keys */

/* A KEY, DRAWN AS THE KEY. `<kbd>`, a step raised by a heavier bottom edge, in
   the mono face. Not `.key-list` — that is the integrations family's list of API
   keys; this is keyboard keys. */
.kbd {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: 1.75em;
  min-block-size: 1.75em;
  padding: 0 var(--space-1);
  border: 1px solid var(--color-neutral-300);
  border-block-end-width: 2px;
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-neutral-800);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  vertical-align: baseline;
}

/* What each key does: the keys, then the words. A description list, so the
   relationship is announced as well as drawn. */
.shortcut-list { display: grid; gap: var(--space-3); margin: 0; }
.shortcut {
  display: grid;
  grid-template-columns: minmax(7rem, max-content) minmax(0, 1fr);
  align-items: center;
  gap: var(--space-4);
}
.shortcut-keys { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); margin: 0; }
.shortcut-does { margin: 0; font-size: var(--text-sm); color: var(--color-neutral-700); }
.shortcut-or, .shortcut-plus { font-size: var(--text-xs); color: var(--color-neutral-600); padding-inline: var(--space-1); }

/* WHERE THE KEYBOARD IS, on a list moved through with J and K (`useListKeys`).
   The item is focused for real; with the system's focus ring retired, this is
   what makes the focus as visible as the item is large — the same edge a
   matrix cell takes. */
[data-list-item]:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

@media (max-width: 480px) {
  .shortcut { grid-template-columns: minmax(0, 1fr); gap: var(--space-1); }
}

/* ------------------------------------------------------------ layered graph */

/* LAYERS READ LEFT TO RIGHT, AND THE LINES ARE A DRAWING OF WHAT THE LISTS ALREADY SAY.
 *
 * Each layer is a list of real buttons; the lines between them are one svg stretched across the
 * same box, hidden from assistive technology. The layout is arithmetic in graph units — a
 * sixteenth of a rem, so a pixel at the default size and more when the reader turns text up — and
 * the nodes and the lines are placed with the same numbers.
 *
 * NARROW FIRST. The default is the phone: the layers stacked as plain lists, the drawing gone, and
 * every node saying in words what covers it. From 30rem of its own width — a container query, so a
 * graph in a side pane stacks on a desktop too — the layers stand side by side and the lines come
 * back. */

.layered-graph {
  container: layered-graph / inline-size;
  --layered-graph-unit: 0.0625rem;
}

.layered-graph-canvas {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}
.layered-graph-edges { display: none; }

.layered-graph-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-block-end: var(--space-3);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-neutral-600);
}
/* A tag in an uppercase head keeps its own voice. */
.layered-graph-head .tag, .layered-graph-group-head .tag { letter-spacing: normal; text-transform: none; }

.layered-graph-list, .layered-graph-group-list {
  display: grid;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}
.layered-graph-list { gap: var(--space-2); }
.layered-graph-group-head {
  display: flex;
  align-items: flex-end;
  gap: var(--space-2);
  margin-block: var(--space-2) var(--space-1);
  font-size: var(--text-xs);
  color: var(--color-neutral-700);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* --- a node --------------------------------------------------------- */

.layered-graph-node {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-2);
  inline-size: 100%;
  min-block-size: var(--space-6);
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-sm);
  background: var(--color-bg);
  color: var(--color-neutral-900);
  font: inherit;
  font-size: var(--text-xs);
  line-height: 1.3;
  text-align: start;
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out),
    background-color var(--dur-fast) var(--ease-out);
}
.layered-graph-node[draggable="true"] { cursor: grab; }
.layered-graph-node[data-size="md"] {
  align-items: flex-start;
  padding-block: var(--space-2);
  border-color: var(--color-neutral-300);
  font-size: var(--text-sm);
}
/* Where a sequence ends. Dashed, because it is not one more step but the thing the steps make. */
.layered-graph-node[data-variant="terminal"] {
  justify-content: space-between;
  font-size: var(--text-sm);
  border-style: dashed;
  border-color: var(--color-neutral-400);
}
.layered-graph-code {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-neutral-700);
}
.layered-graph-label { flex: 1 1 0; min-inline-size: 0; }
.layered-graph-meta { flex: none; display: inline-flex; gap: var(--space-1); }
/* What the lines say, as words. Only where there are no lines. */
.layered-graph-links {
  flex-basis: 100%;
  font-size: var(--text-xs);
  color: var(--color-neutral-600);
}

.layered-graph-node:hover,
.layered-graph-node:focus-visible { border-color: var(--color-accent-400); }
.layered-graph-node[aria-pressed="true"] {
  border-color: var(--color-accent);
  background: var(--color-accent-100);
}
/* UNCOVERED IS A SHAPE AS WELL AS A TINT. The dashed edge survives forced colours and a reader who
   does not see the warning hue; the name says "Not covered" to everyone else. */
.layered-graph-node[data-uncovered] {
  border-style: dashed;
  border-color: var(--color-warning-300);
  background: var(--color-warning-100);
}
.layered-graph-node[data-uncovered] .layered-graph-links { color: var(--color-warning-800); }

/* Brought forward by `highlight`: a ring, not a fade. Fading the rest would take their text under
   contrast, and they are still the reader's to press. */
.layered-graph[data-highlight] .layered-graph-node[data-highlighted] {
  box-shadow: 0 0 0 2px var(--color-accent-600);
}
.layered-graph[data-highlight] .layered-graph-node[data-uncovered][data-highlighted] {
  box-shadow: 0 0 0 2px var(--color-warning-700);
}

/* A drag, or a pick-up from the keyboard, in progress: where it can go is marked. */
.layered-graph[data-dragging] .layered-graph-node[data-droppable],
.layered-graph-node[data-target] {
  border-style: dashed;
  border-color: var(--color-accent-500);
}
.layered-graph-node[data-over] {
  border-style: solid;
  border-color: var(--color-accent);
  background: var(--color-accent-100);
}
.layered-graph-node[data-carried] {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 2px var(--color-accent-300);
}

/* --- the drawing ---------------------------------------------------- */

.layered-graph-edges path {
  fill: none;
  stroke: var(--color-neutral-300);
  stroke-width: 1;
}
.layered-graph-edges path[data-lit] {
  stroke: var(--color-accent);
  stroke-width: 2;
}
.layered-graph-edges path[data-kind="sequence"] {
  stroke: var(--color-neutral-400);
  stroke-dasharray: 3 3;
}

/* --- side by side --------------------------------------------------- */

@container layered-graph (min-width: 30rem) {
  .layered-graph-canvas {
    display: grid;
    grid-template-columns: var(--layered-graph-columns, 1fr 1fr);
    grid-template-rows: auto auto;
    column-gap: 10%;
  }
  .layered-graph-layer {
    display: grid;
    grid-template-rows: subgrid;
    grid-row: 1 / span 2;
    grid-column: var(--layered-graph-column, auto);
    min-inline-size: 0;
  }
  .layered-graph-edges {
    display: block;
    grid-row: 2;
    grid-column: 1 / -1;
    inline-size: 100%;
    max-inline-size: none;
    block-size: calc(var(--layered-graph-height, 0) * var(--layered-graph-unit));
    overflow: visible;
    pointer-events: none;
  }
  .layered-graph-list {
    display: block;
    position: relative;
    grid-row: 2;
    block-size: calc(var(--layered-graph-height, 0) * var(--layered-graph-unit));
  }
  /* The groups keep their list semantics and give up their boxes: a group's heading and nodes are
     placed against the layer's list, so the groups line up with nothing between them. */
  .layered-graph-group, .layered-graph-group-list { display: block; }
  .layered-graph-item, .layered-graph-group-head {
    position: absolute;
    inset-inline: 0;
    inset-block-start: calc(var(--layered-graph-y, 0) * var(--layered-graph-unit));
    block-size: calc(var(--layered-graph-h, 0) * var(--layered-graph-unit));
    margin: 0;
  }
  .layered-graph-group-head { padding-block-end: var(--space-1); }
  .layered-graph-group-head .layered-graph-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .layered-graph-node {
    flex-wrap: nowrap;
    block-size: 100%;
    min-block-size: 0;
    padding-block: 0;
  }
  .layered-graph-node[data-size="sm"] .layered-graph-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .layered-graph-node[data-size="md"] { padding-block: var(--space-2); }
  .layered-graph-node[data-size="md"] .layered-graph-label {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
  }
  .layered-graph-links { display: none; }
}

/* ----------------------------------------------------------------- library */

/* A document library. File type is carried by a mono badge and a glyph, not by
   colour: a dozen tinted extension chips is exactly the wall of colour this
   system exists to avoid, and colour-coding by extension fails for anyone who
   cannot separate the hues anyway. */

.doc-list { display: grid; gap: 1px; margin: 0; padding: 0; list-style: none; background: var(--color-neutral-200); border: 1px solid var(--color-neutral-200); border-radius: var(--radius-md); overflow: hidden; }
.doc-row {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  inline-size: 100%;
  padding: var(--space-3) var(--space-4);
  border: 0;
  background: var(--color-bg);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--color-neutral-800);
  text-align: start;
  cursor: pointer;
}
.doc-row:hover { background: var(--color-neutral-100); }
.doc-row[aria-selected="true"] { background: var(--color-accent-100); }

.doc-type {
  display: inline-grid;
  place-items: center;
  inline-size: var(--space-10);
  block-size: var(--space-10);
  flex: none;
  border-radius: var(--radius-sm);
  background: var(--color-neutral-100);
  color: var(--color-neutral-600);
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.doc-type svg { inline-size: var(--space-5); block-size: var(--space-5); }
/* Folders are the one exception: they are navigation, not content. */
.doc-type[data-kind="folder"] { background: var(--color-accent-100); color: var(--color-action); }

.doc-name { flex: 1; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--color-neutral-900); }
.doc-sub { display: block; font-size: var(--text-xs); color: var(--color-neutral-600); }
.doc-meta {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--color-neutral-600);
  white-space: nowrap;
}

.doc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: var(--space-4); }
.doc-card { display: grid; gap: var(--space-3); padding: var(--space-4); border: 1px solid var(--color-neutral-200); border-radius: var(--radius-md); background: var(--color-bg); cursor: pointer; text-align: start; }
.doc-card:hover { border-color: var(--color-neutral-300); box-shadow: var(--shadow-sm); }
.doc-thumb { aspect-ratio: 4 / 3; border-radius: var(--radius-sm); background: var(--color-surface); display: grid; place-items: center; color: var(--color-neutral-400); overflow: hidden; }
.doc-thumb svg { inline-size: var(--space-10); block-size: var(--space-10); }

/* --- preview pane ---------------------------------------------------- */

/* `block-size: 100%` because `.doc-preview-body` is `flex: 1; overflow: auto` — a body that
   scrolls only means something inside a HEIGHT-BOUNDED box, and this component never asked for
   one. Given a fixed parent it now fills it and the body scrolls; given an auto-height parent the
   percentage resolves to auto and nothing changes. Without it the preview grew past its pane and
   the next thing on the page started underneath it. */
.doc-preview { block-size: 100%; display: flex; flex-direction: column; min-block-size: 0; border: 1px solid var(--color-neutral-200); border-radius: var(--radius-lg); overflow: hidden; background: var(--color-bg); }
.doc-preview-head { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-4); border-block-end: 1px solid var(--color-neutral-200); background: var(--color-surface); }
.doc-preview-title { font-size: var(--text-sm); font-weight: 500; color: var(--color-neutral-900); min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.doc-preview-actions { margin-inline-start: auto; display: flex; gap: var(--space-2); flex: none; }
.doc-preview-body { flex: 1; overflow: auto; padding: var(--space-6); min-block-size: 0; }

/* Reader typography for a rendered markdown document. Measure is capped at
   72ch — a document pane widened to a 27-inch monitor is unreadable. */
.reader { max-inline-size: 72ch; color: var(--color-neutral-800); font-size: var(--text-base); line-height: var(--leading-base); }
.reader > * + * { margin-block-start: var(--space-4); }
.reader h1, .reader h2, .reader h3 { margin-block-start: var(--space-8); }
.reader h1:first-child, .reader h2:first-child { margin-block-start: 0; }
/* The reader sets its own h1. Without one a consumer reaches for the base scale — 48px display
   type above 24px section headings and 16px prose, which is two scales meeting in one document.
   30px sits a step above the h2 below it and looks like the same family. */
.reader h1 { font-size: var(--text-3xl); }
.reader h2 { font-size: var(--text-2xl); }
.reader h3 { font-size: var(--text-lg); }
/* A LIST WITHOUT MARKERS IS AN INDENTED PARAGRAPH.
 *
 * This was `display: grid; gap`, reached for to space the items. Grid BLOCKIFIES its children, and
 * a blockified `li` has no `::marker` — so every list in every rendered document lost its bullets
 * and became indented prose. A long answer with three nested lists in it read as one wall of text
 * with no way to see where a list started, which item belonged to which, or where it ended.
 *
 * The gap comes from a margin between items instead, which leaves the list a list. */
.reader ul, .reader ol { padding-inline-start: var(--space-6); }
.reader li + li { margin-block-start: var(--space-2); }
/* Quiet. A marker is there to show you where an item begins, not to be read. */
.reader li::marker { color: var(--color-neutral-500); }
/* A nested level says so by its marker, not only by its indent — two indents of unmarked text at
   different depths is the thing that made the nesting invisible. */
.reader ul { list-style: disc; }
.reader ul ul { list-style: circle; }
.reader ul ul ul { list-style: square; }

/* GROUPING, WHICH IS THE OTHER HALF OF THE WALL.
 *
 * `.reader > * + *` gives every block the same space, so a list sits as far from the sentence
 * introducing it as from the next section, and nothing reads as belonging to anything. A list
 * belongs to the line above it: it hugs that, and the next block gets a clear step instead. */
.reader p + ul, .reader p + ol { margin-block-start: var(--space-2); }
.reader li > ul, .reader li > ol { margin-block-start: var(--space-2); }
.reader ul + p, .reader ol + p { margin-block-start: var(--space-6); }
.reader blockquote { padding-inline-start: var(--space-4); border-inline-start: 2px solid var(--color-neutral-300); color: var(--color-neutral-700); }
.reader hr { border: 0; border-block-start: 1px solid var(--color-divider); margin-block: var(--space-8); }
.reader code { background: var(--color-neutral-100); padding: 0.1em 0.35em; border-radius: var(--radius-xs); }
/* A WIDE TABLE IN PROSE OVERRUNS EVERYTHING TO ITS RIGHT.
 *
 * `inline-size: 100%` is a FLOOR, not a ceiling: a table cannot shrink below the min-content width
 * of its columns, so fifteen of them ignore the reader's measure, escape the article, run under
 * whatever rail sits beside it and give the whole page a horizontal scrollbar. A deliverable did
 * exactly that — a coverage matrix rendered straight through its own contents list.
 *
 * A table cannot scroll itself: `overflow` on a `<table>` needs `display: block`, which makes a
 * narrow table shrink-wrap instead of filling. So it needs a container, and the system already has
 * one — `.table-wrap`. What the stylesheet can do is make the container behave in prose, and make
 * a table that was NOT wrapped stop taking the page with it.
 *
 * `max-inline-size: 100%` on the bare case is a fallback, not the fix: it clips rather than
 * scrolls, which is worse than a scrollbar and better than a broken page. Wrap the table. */
.reader table {
  inline-size: 100%;
  max-inline-size: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}
/* The wrapper takes the prose rhythm, so a wrapped table sits where an unwrapped one did rather
   than losing the spacing `.reader > * + *` was giving it. */
.reader .table-wrap { margin-block: var(--space-4); }
.reader .table-wrap > table { margin-block: 0; }
.reader th, .reader td { padding: var(--space-2) var(--space-3); border-block-end: 1px solid var(--color-divider); text-align: start; }
.reader th { font-size: var(--text-sm); font-weight: 600; color: var(--color-neutral-600); }

/* Sheet preview — a spreadsheet is a grid, so show it as one. */
.sheet { border-collapse: collapse; font-family: var(--font-mono); font-size: var(--text-xs); font-variant-numeric: tabular-nums; }
.sheet th, .sheet td { border: 1px solid var(--color-neutral-200); padding: var(--space-1) var(--space-3); text-align: end; white-space: nowrap; }
.sheet thead th, .sheet tbody th { background: var(--color-surface); color: var(--color-neutral-600); text-align: center; font-weight: 500; }
.sheet td:first-of-type { text-align: start; }

/* -------------------------------------------------------------------- live */

/* WORK HAPPENING NOW, DRAWN WHERE THE WORK IS.
 *
 * A run touches every screen: a row on a list, the section being written, the
 * agents on a history page. Each says "this is moving" in the same voice — a
 * pulse, a word, a bar — so a reader learns it once. Colour and motion are never
 * the only signal: every state carries a word, and under reduced motion the
 * pulse becomes a still halo and nothing slides. */

/* --- pulse ---------------------------------------------------------- */

/* A dot with a ring that keeps leaving it. */
.live-pulse {
  position: relative;
  display: inline-block;
  flex: none;
  inline-size: var(--space-2);
  block-size: var(--space-2);
  border-radius: var(--radius-full);
  /* The progress family's "current" colour, so a pulse and a running bar are one blue. */
  background: var(--progress-fill);
  vertical-align: middle;
}
.live-pulse::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: inherit;
  animation: live-ping 1.6s var(--ease-out) infinite;
}
.live-pulse[data-tone="success"] { background: var(--color-success-700); }
.live-pulse[data-tone="warning"] { background: var(--color-warning-700); }
.live-pulse[data-tone="danger"]  { background: var(--color-danger-700); }
/* Stopped: the dot stays as the state's colour, the ring stops leaving. */
.live-pulse[data-still]::after { animation: none; opacity: 0; }

@keyframes live-ping {
  from { transform: scale(1); opacity: 0.6; }
  to   { transform: scale(3); opacity: 0; }
}
@keyframes live-enter {
  from { opacity: 0; transform: translateY(var(--space-1)) scale(0.96); }
  to   { opacity: 1; transform: none; }
}
@keyframes live-breathe { 50% { opacity: 0.45; } }

/* The pulse with its words. */
.live-indicator {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-neutral-800);
}
.live-indicator-label { font-weight: 500; }

/* --- a section's mark ---------------------------------------------- */

/* A tag with a pulse, tick or cross. Never wraps — it sits on a heading row. */
.live-mark { flex: none; }
.live-mark .live-pulse { background: currentColor; }

/* --- a section being written --------------------------------------- */

/* A bar down its side that breathes while being written; queued sections
   recede; rewritten ones keep their full ink with a green edge. The bar sits in
   the section's own padding, pulled out into the gutter, so the text does not
   move when a run starts. */
.live-section {
  position: relative;
  padding-inline-start: var(--space-4);
  margin-inline-start: calc(-1 * var(--space-4) - var(--space-1));
  /* The tint reaches as far past the text on the right as on the left, and the
     negative margin gives that room back, so the text keeps its measure and
     wraps where a sibling section's does. */
  padding-inline-end: var(--space-4);
  margin-inline-end: calc(-1 * var(--space-4));
  border-inline-start: var(--space-1) solid transparent;
  transition:
    opacity var(--dur-slow) var(--ease-out),
    border-color var(--dur-slow) var(--ease-out),
    background-color var(--dur-slow) var(--ease-out);
}
.live-section[data-live="writing"] {
  border-inline-start-color: var(--color-accent);
  background: var(--color-accent-100);
}
/* The words under the heading are about to change. */
.live-section[data-live="writing"] > :not(:first-child) {
  animation: live-breathe 2.4s var(--ease-in-out) infinite;
}
.live-section[data-live="queued"] {
  opacity: 0.6;
  border-inline-start-color: var(--color-neutral-200);
}
.live-section[data-live="written"] { border-inline-start-color: var(--color-success-300); }
.live-section[data-live="failed"]  { border-inline-start-color: var(--color-danger-300); }

/* --- a row's live progress ----------------------------------------- */

/* Pulse, where, a bar, how far: `• §2.5 +1 ▬▬▬▭ 4/6`. Wraps rather than
   overflowing a narrow cell. */
.live-progress {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-2);
  font-size: var(--text-xs);
  color: var(--color-neutral-700);
}
/* An ACTIVE row is four fixed columns — pulse, where, bar, count — so the bars in a table's rows
   start and end at the same x whether the place reads `§2.5 +1` or `Rewriting`. The other
   states are a tag and a word, and keep the inline flow. */
.live-progress[data-state="active"] {
  display: inline-grid;
  grid-template-columns: var(--space-2) var(--space-20) var(--space-24) auto;
  justify-items: start;
  column-gap: var(--space-2);
}
.live-progress[data-state="active"] .live-progress-where {
  max-inline-size: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.live-progress-where { font-weight: 500; color: var(--color-accent-700); }
/* The family's inline bar: the 4px track, label before it, value after it. */
.progress.live-progress-bar { --progress-h: var(--progress-h-sm); inline-size: var(--space-24); }
.live-progress-count { font-family: var(--font-mono); font-variant-numeric: tabular-nums; color: var(--color-neutral-700); }

/* The row being worked on is found from across the table. */
.table tbody tr:has(.live-progress[data-state="active"]) > * {
  background: var(--color-accent-100);
}
.table tbody tr:has(.live-progress[data-state="active"]) > :first-child {
  box-shadow: inset var(--space-1) 0 0 var(--color-accent);
}

/* --- lanes --------------------------------------------------------- */

/* One row per state, its items as pills. A pill that moves lanes slides there
   (a FLIP, run from the component); a new pill arrives. */
.lanes { display: grid; gap: var(--space-2); }
.lanes-empty { font-size: var(--text-sm); color: var(--color-neutral-600); padding: var(--space-3); }

.lane {
  display: grid;
  grid-template-columns: 7rem minmax(0, 1fr);
  align-items: start;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background: var(--color-neutral-100);
}
.lane[data-tone="accent"]  { background: var(--color-accent-100); }
.lane[data-tone="danger"]  { background: var(--color-danger-100); }
.lane[data-tone="warning"] { background: var(--color-warning-100); }

.lane-name {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  margin: var(--space-1) 0 0;
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-neutral-700);
}
.lane-count { font-family: var(--font-mono); letter-spacing: 0; color: var(--color-neutral-600); }

.lane-items {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

.lane-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-block-size: var(--space-6);
  padding: 0 var(--space-2);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-full);
  background: var(--color-bg);
  font-size: var(--text-xs);
  color: var(--color-neutral-800);
  animation: live-enter var(--dur-slow) var(--ease-out) both;
}
.lane-item svg, .lane-item .progress-glyph { inline-size: 1em; block-size: 1em; flex: none; }
.lane-item[data-tone="accent"] { border-color: var(--color-accent-300); color: var(--color-accent-800); font-weight: 500; }
.lane-item[data-tone="success"] { color: var(--color-neutral-700); }
.lane-item[data-tone="success"] svg,
.lane-item[data-tone="success"] .progress-glyph { color: var(--color-success-700); }
.lane-item[data-tone="danger"] { border-color: var(--color-danger-300); color: var(--color-danger-800); }
.lane-item[data-tone="warning"] { border-color: var(--color-warning-300); color: var(--color-warning-800); }
/* Not started, or nothing to do: dashed, on the lane's own ground. */
.lane-item[data-tone="muted"] { border-style: dashed; background: transparent; color: var(--color-neutral-600); }
.lane-item[data-placeholder] { inline-size: var(--space-24); animation: none; border-color: transparent; background: transparent; }
.lane-item[data-placeholder] .skeleton { inline-size: 100%; }

.lane-item-detail { color: var(--color-neutral-600); font-weight: 400; }
.lane-item-detail::before { content: "· "; }

.lane-more, .lane-none {
  display: inline-flex;
  align-items: center;
  min-block-size: var(--space-6);
  padding: 0 var(--space-2);
  font-size: var(--text-xs);
  color: var(--color-neutral-600);
}
.lane-more { font-family: var(--font-mono); }

/* On a phone the name goes above its pills rather than taking a third of the
   width beside them. */
@media (max-width: 480px) {
  .lane { grid-template-columns: minmax(0, 1fr); gap: var(--space-1); }
  .lane-name { justify-content: flex-start; margin: 0; }
}

/* ------------------------------------------------------------------ matrix */

/* Which things cover which: presence and absence on a grid, read across a row and down a column.
 *
 * It scrolls inside its own frame and never widens the page — on a phone the grid is always wider
 * than the screen, and a page that scrolls sideways takes the header and the navigation with it.
 * The headings and the row labels are pinned, because a mark three screens from its labels is a
 * mark nobody can name. */
.matrix-wrap {
  position: relative;
  max-inline-size: 100%;
  overflow: auto;
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-md);
  background: var(--color-bg);
}
.matrix {
  border-collapse: separate;
  border-spacing: 0;
  font-size: var(--text-sm);
  color: var(--color-neutral-800);
}
.matrix th, .matrix td {
  border-block-end: 1px solid var(--color-divider);
  border-inline-end: 1px solid var(--color-divider);
  background: var(--color-bg);
}
.matrix tr > :last-child { border-inline-end: 0; }
.matrix tbody tr:last-child > * { border-block-end: 0; }
.matrix tfoot > tr > * { border-block-start: 1px solid var(--color-neutral-300); border-block-end: 0; }

.matrix thead th {
  padding: var(--space-2);
  vertical-align: bottom;
  font-size: var(--text-xs);
  font-weight: 500;
  color: var(--color-neutral-800);
  text-align: center;
  white-space: nowrap;
  background: var(--color-surface);
}
.matrix-corner { text-align: start; }

/* COLUMN HEADINGS ON THEIR SIDE, for many narrow columns. Read bottom to top, the way a spine on a
   shelf reads; capped so a long name cannot make the head taller than the grid under it. */
.matrix[data-rotate] .matrix-col { padding-inline: var(--space-1); }
.matrix[data-rotate] .matrix-col-label {
  display: inline-block;
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  max-block-size: var(--space-32);
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: start;
}

.matrix-row {
  padding: var(--space-2) var(--space-3);
  text-align: start;
  font-weight: 400;
  white-space: nowrap;
  max-inline-size: calc(var(--space-32) * 2);
  overflow: hidden;
  text-overflow: ellipsis;
}
.matrix-row-open {
  border: 0;
  padding: 0;
  background: transparent;
  font: inherit;
  color: var(--color-accent-700);
  text-align: start;
  cursor: pointer;
}
.matrix-row-open:hover { text-decoration: underline; text-underline-offset: 0.2em; }
.matrix-flag {
  margin-inline-start: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-warning-800);
}

.matrix-cell {
  min-inline-size: var(--space-8);
  block-size: var(--space-8);
  padding: 0 var(--space-1);
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.matrix-cell[data-empty] { color: var(--color-neutral-400); }
.matrix-cell:hover { box-shadow: inset 0 0 0 1px var(--color-neutral-400); }
.matrix-cell:focus-visible, .matrix-row:focus-visible, .matrix-row-open:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}
.matrix-cell[data-tone="accent"]  { background: var(--color-accent-100);  color: var(--color-accent-700); }
.matrix-cell[data-tone="success"] { background: var(--color-success-100); color: var(--color-success-800); }
.matrix-cell[data-tone="warning"] { background: var(--color-warning-100); color: var(--color-warning-800); }
.matrix-cell[data-tone="danger"]  { background: var(--color-danger-100);  color: var(--color-danger-800); }

.matrix-total {
  padding: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-neutral-700);
  text-align: center;
  background: var(--color-surface);
}
.matrix-row.matrix-total { text-align: start; font-family: var(--font-body); }

/* Pinned. The corner sits above both pinned edges, so it is on top of both. */
.matrix[data-sticky] thead th { position: sticky; inset-block-start: 0; z-index: 2; }
.matrix[data-sticky] .matrix-row { position: sticky; inset-inline-start: 0; z-index: 1; }
.matrix[data-sticky] thead .matrix-corner { inset-inline-start: 0; z-index: 3; }

/* The hover and focus readout is a tooltip; it only needs to clear the pinned headings. */
.matrix-readout { z-index: 60; pointer-events: none; }

/* -------------------------------------------------------------------- menu */

/* Applied by the React Menu, which positions with fixed coordinates so the
   popup can flip away from a viewport edge. The static demo page shows .menu
   in flow, without it. */
.menu-popup { position: fixed; z-index: 70; }

/* A MENU LONGER THAN ITS SIDE HAS ROOM FOR SCROLLS INSIDE ITSELF.
 *
 * The width was held against the viewport and the height was not, so twelve rows of a version
 * picker filled a laptop screen and ran past the bottom of it. `.menu-popup` is FIXED: the page
 * scrolls under it and the rows past the edge can never be reached, by pointer or by arrow key.
 *
 * The ceiling itself is the React Menu's, from `useAnchoredPosition` — it is the room the chosen
 * side actually has, which no static value can know. This is where the overflow goes once there
 * is one. `overscroll-behavior` keeps the page still when the list hits its end. */
.menu {
  min-inline-size: 220px;
  max-inline-size: min(320px, calc(100vw - var(--space-8)));
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--space-1);
  background: var(--color-bg);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
}

.menu-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  inline-size: 100%;
  padding: var(--space-2) var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--color-neutral-800);
  text-align: start;
  text-decoration: none;
  cursor: pointer;
}
.menu-item svg { inline-size: var(--space-4); block-size: var(--space-4); flex: none; color: var(--color-neutral-600); }

/* A MARK MUST NOT MOVE THE THING IT MARKS.
 *
 * A menu of choices renders a tick on the chosen one and nothing on the others,
 * so the chosen row's label sat one icon plus one gap to the right of every
 * other row's. Opening the menu to see what is selected meant reading an
 * indent, and choosing a different row made the whole list shuffle.
 *
 * `selected` on any item turns the mark into a reserved column for the WHOLE
 * menu, so the labels line up whichever row is chosen — and an unmarked menu is
 * untouched, because a list of actions should not carry a gutter for a mark it
 * never draws. */
.menu-mark { inline-size: var(--space-4); block-size: var(--space-4); flex: none; color: var(--color-action); }
/* Hidden, not absent. `visibility` keeps the box, so an unchosen row occupies
   exactly the width a chosen one does and nothing moves when the choice moves. */
.menu-item[aria-checked="false"] .menu-mark { visibility: hidden; }

/* Hover and keyboard highlight must look identical — a menu driven by arrow
   keys should not feel like a different control from one driven by a mouse. */
.menu-item:hover, .menu-item[data-highlighted] { background: var(--color-neutral-100); color: var(--color-neutral-900); }
.menu-item[aria-disabled="true"] { color: var(--color-neutral-400); pointer-events: none; }
.menu-item-danger { color: var(--color-danger-700); }
.menu-item-danger:hover, .menu-item-danger[data-highlighted] { background: var(--color-danger-100); }

/* A MENU ITEM THAT OPENS A MENU.
 *
 * A flat menu that groups with headings grows with every group — the account
 * settings sink further down a list somebody opened for one of them, and a
 * heading over a single row spends a line saying a thing exists and another
 * line being it. A flyout keeps the parent one row per subject.
 *
 * The wrapper carries the hover intent and nothing else: `display: contents`
 * so it does not become a box between the menu and its items, which would take
 * the padding and the highlight off the row. */
.menu-sub { display: contents; }

/* The caret is the affordance. Without it a row that opens a menu looks exactly
 * like a row that does something, and the reader finds out by pressing it. */
.menu-sub-mark {
  margin-inline-start: auto;
  inline-size: var(--space-4);
  block-size: var(--space-4);
  flex: none;
  color: var(--color-neutral-500);
}
/* Open stays lit while the pointer is in the flyout. Otherwise the row a
 * reader is inside of goes quiet the moment they leave it, and the parent
 * stops saying which of its rows the open menu belongs to. */
.menu-sub-trigger[aria-expanded="true"] {
  background: var(--color-neutral-100);
  color: var(--color-neutral-900);
}
.menu-sub-trigger[aria-expanded="true"] .menu-sub-mark { color: var(--color-neutral-800); }

.menu-label {
  padding: var(--space-2) var(--space-3) var(--space-1);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-neutral-600);
}
.menu-sep { block-size: 1px; background: var(--color-divider); margin: var(--space-1) 0; border: 0; }
.menu-shortcut {
  margin-inline-start: auto;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-neutral-500);
}

/* Who you are signed in as, at the top of an account menu. Deliberately not a `.menu-item`: no
   hover, no pointer, no highlight. A row that lights up under the cursor is a row somebody will
   try to click, and an identity is a fact rather than a destination. */
.menu-identity {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
}
/* `min-inline-size: 0` because a grid child's automatic minimum is its content, and an address
   long enough to need wrapping will otherwise widen the popup instead of wrapping inside it. */
.menu-identity-text { display: grid; gap: var(--space-1); min-inline-size: 0; }
.menu-identity-name {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-neutral-900);
}
/* An email is one long word to the line breaker, so it needs permission to break inside itself.
   Truncating instead would hide the half that tells two accounts apart. */
.menu-identity-detail {
  font-size: var(--text-xs);
  color: var(--color-neutral-600);
  overflow-wrap: anywhere;
}

/* --------------------------------------------------------------------- nav */

/* Active state keys off aria-current, so the styling and the accessibility
   tree can never disagree — the same principle as aria-invalid on fields. */

.nav {
  display: flex;
  white-space: nowrap;
  align-items: center;
  gap: var(--space-6);
  min-block-size: var(--space-16);
  padding-inline: var(--space-6);
  background: var(--color-bg);
  border-block-end: 1px solid var(--color-neutral-200);
  /* THE HEADER COLLAPSES BY ITS OWN WIDTH, NOT THE WINDOW'S.
     A viewport breakpoint only knows the window: a header in a 600px pane of a
     1440px screen kept its full link row and scrolled sideways, clipping the last
     place. A header never scrolls sideways — it measures itself, and below the
     threshold the links fold into `.nav-burger`. */
  container: nav / inline-size;
}

.nav-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: var(--text-base);
  letter-spacing: 0.02em;
  color: var(--color-neutral-900);
  text-decoration: none;
  white-space: nowrap;
  flex: none;
  margin-inline-end: auto;
}
.nav-brand img { block-size: var(--space-8); inline-size: auto; }

.nav-links { display: flex; align-items: center; gap: var(--space-1); flex: none; }

/* Shown only once the link row collapses. Hiding the links without providing
   this leaves a header with no navigation at all. */
/* Two classes, so it outranks `.hamburger`'s own `display`, which comes later in the sheet — with
   one class the burger showed beside a full link row. */
.nav .nav-burger { display: none; }

/* A full link row plus a call to action does not fit a phone, and shrinking
   both just makes each unusable. Below this the row collapses and the
   hamburger carries navigation. */
@container nav (max-width: 720px) {
  .nav-links { display: none; }
  .nav .nav-burger { display: inline-grid; }
}
/* Narrower still, the call to action folds into the burger too: a brand, a button and a burger
   do not fit a phone, and a header never scrolls sideways. Only when there is a burger to hold it. */
@container nav (max-width: 480px) {
  .nav:has(.nav-burger) > .btn { display: none; }
}
@media (max-width: 720px) {
  .nav { gap: var(--space-3); padding-inline: var(--space-4); }
}

/* ONE CUE FOR "YOU ARE HERE".
 *
 * The current place was a saturated fill, a 3px bar down its edge AND accent text — three signals
 * for one fact, and in dark mode the blue text on a navy block fell to low contrast. It is now one
 * soft tinted pill with the label in ink at a heavier weight, the same indicator in the header, the
 * rail and the drawer. Hover is a lighter NEUTRAL tint, so it never reads as a second current
 * place; focus stays the ring. Both tints are mixed from tokens over transparency, so they sit on
 * the page ground and on the rail's surface alike, in either theme. */
.nav-item, .sidebar-item {
  --nav-hover: color-mix(in srgb, var(--color-text) 6%, transparent);
  --nav-current: color-mix(in srgb, var(--color-accent-500) 14%, transparent);
  --nav-current-hover: color-mix(in srgb, var(--color-accent-500) 20%, transparent);
}

.nav-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-full);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-neutral-700);
  text-decoration: none;
  white-space: nowrap;
  transition: background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.nav-item:hover { background: var(--nav-hover); color: var(--color-neutral-900); }
.nav-item[aria-current] { color: var(--color-neutral-900); background: var(--nav-current); font-weight: 600; }
.nav-item[aria-current]:hover { background: var(--nav-current-hover); }

/* --- hamburger ------------------------------------------------------ */

.hamburger {
  display: inline-grid;
  place-items: center;
  inline-size: var(--control-h-md);
  block-size: var(--control-h-md);
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-neutral-800);
  cursor: pointer;
}
.hamburger:hover { background: var(--color-neutral-100); }

.hamburger-box { position: relative; inline-size: var(--space-5); block-size: var(--space-3); }
.hamburger-bar {
  position: absolute;
  inset-inline: 0;
  block-size: 1.5px;
  border-radius: var(--radius-full);
  background: currentColor;
  transition: transform var(--dur-base) var(--ease-out),
              opacity var(--dur-fast) var(--ease-out),
              top var(--dur-base) var(--ease-out),
              bottom var(--dur-base) var(--ease-out);
}
.hamburger-bar:nth-child(1) { top: 0; }
.hamburger-bar:nth-child(2) { top: 50%; transform: translateY(-50%); }
.hamburger-bar:nth-child(3) { bottom: 0; }

.hamburger[aria-expanded="true"] .hamburger-bar:nth-child(1) { top: 50%; transform: translateY(-50%) rotate(45deg); }
.hamburger[aria-expanded="true"] .hamburger-bar:nth-child(2) { opacity: 0; }
.hamburger[aria-expanded="true"] .hamburger-bar:nth-child(3) { bottom: 50%; transform: translateY(50%) rotate(-45deg); }

/* --- drawer --------------------------------------------------------- */

/* A MODAL NAMES THE LAYER IT IS ON, OR IT IS NOT MODAL.
   Both of these were positioned with no `z-index`, so the drawer sat above the
   page only for as long as nothing else on it was raised — and the panel says
   `aria-modal="true"`, which is a promise about the whole surface. A sticky
   page header at `z-index: 2` is the most ordinary thing a screen has, and it
   painted its own white band straight across an open drawer in the product that
   reads this system: the panel's heading came out sliced in half and the two
   lines under it were gone. Nothing about the layout was wrong — every box
   measured where it should be. It was the paint order.

   50 is the layer this system already gives `.dialog-backdrop`, because a
   drawer and a dialog are the same kind of thing: the surface, taken. The panel
   comes after the scrim in the DOM, so it stacks above it without a second
   number. */
.drawer-scrim {
  position: absolute;
  inset: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--color-scrim) 32%, transparent);
}
.drawer-panel {
  position: absolute;
  inset-block: 0;
  inset-inline-end: 0;
  z-index: 50;
  inline-size: min(320px, 82%);
  padding: var(--space-6);
  background: var(--color-bg);
  border-inline-start: 1px solid var(--color-neutral-200);
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
/* NO SIZE RULE FOR LINKS IN A DRAWER. They are the header's links, at the header's size and with
   its indicator — a menu that grows its rows when it moves into a drawer reads as a different menu. */

.drawer-close {
  position: absolute;
  inset-block-start: var(--space-4);
  inset-inline-end: var(--space-4);
  z-index: 1;
  display: grid;
  place-items: center;
  inline-size: var(--space-8);
  block-size: var(--space-8);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-neutral-600);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.drawer-close svg { inline-size: var(--space-4); block-size: var(--space-4); }
.drawer-close:hover { background: var(--color-neutral-200); color: var(--color-neutral-900); }

/* A DRAWER WITH A HEAD. The head is pinned — a title, a `PagerChip`, the
   actions for what is shown — and the body scrolls on its own beneath it, so the
   way to the next item never scrolls away from a long transcript. The head
   stops short of the close button. */
.drawer-head {
  flex: none;
  display: grid;
  gap: var(--space-3);
  padding-inline-end: var(--space-10);
  padding-block-end: var(--space-4);
  border-block-end: 1px solid var(--color-neutral-200);
}
.drawer-body {
  flex: 1 1 auto;
  min-block-size: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-block: var(--space-2) var(--space-6);
}

/* A READING WIDTH. A menu's 320px is the wrong width for a transcript; this is
   wide enough to read and leaves the document in view beside it. On a phone
   there is no "beside", so it takes the screen. */
.drawer-panel[data-size="lg"] {
  inline-size: min(46rem, 92%);
  gap: var(--space-4);
  padding-block-end: 0;
}
@media (max-width: 640px) {
  .drawer-panel[data-size="lg"] { inline-size: 100%; padding-inline: var(--space-4); }
}

/* FROM THE START EDGE, for navigation that is a rail on a wide screen and a drawer on a phone: it
   should arrive from the side it lives on, or the reader loses where it went. */
.drawer-panel[data-side="start"] {
  inset-inline-end: auto;
  inset-inline-start: 0;
  border-inline-start: 0;
  border-inline-end: 1px solid var(--color-neutral-200);
}

/* A RAIL INSIDE A DRAWER. The same `Sidebar` the wide screen shows, so a destination, a count or a
   folded group is the same object at both sizes — but the drawer is already the surface, so the
   rail drops its own width, ground and edge, and starts below the close button rather than under
   it. It scrolls inside the drawer when the groups are longer than a phone is tall. */
.drawer-panel > .sidebar {
  inline-size: auto;
  flex: 1 1 auto;
  min-block-size: 0;
  overflow-y: auto;
  margin-block-start: var(--space-8);
  padding: 0;
  border: 0;
  background: transparent;
  transition: none;
}

/* --- sidebar -------------------------------------------------------- */

.sidebar {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  inline-size: 240px;
  padding: var(--space-4);
  background: var(--color-surface);
  border-inline-end: 1px solid var(--color-neutral-200);
  /* The fold is a MOVEMENT, so the reader sees where the rail went instead of
     finding the page rearranged. `flex: none` because a rail is a width, not a
     share of one: without it the flex parent shrinks the rail to fit the main
     pane and the fold changes nothing. The global reduced-motion rule in
     `base.css` cuts the duration for anybody who asked for that. */
  flex: none;
  transition: inline-size var(--dur-base) var(--ease-out);
}
.sidebar-group {
  margin-block-start: var(--space-6);
  padding-inline: var(--space-4);
  padding-block-end: var(--space-1);
  /* THE BODY FAMILY. A rail whose group headings are mono and whose items are not is a navigation
     menu in two typefaces — and a group name is a category, not an identifier. */
  font-family: var(--font-body);
  /* A QUIET LABEL, SEPARATED BY AIR.
   *
   * Uppercase, tracked, near-ink and ruled off underneath, every group heading was louder than the
   * places it grouped and the rail read as a stack of dividers. A group name is a label: small,
   * muted, in sentence case, and the space above it is what separates one group from the next. */
  font-size: var(--text-xs);
  font-weight: 500;
  color: var(--color-neutral-600);
}
.sidebar-group:first-child { margin-block-start: 0; }

/* A GROUP AS A BLOCK.
 *
 * The rail above is flat by design: headings and items are siblings, the rail's own `gap` spaces
 * them, and `:first-child` drops the leading heading's margin. That holds until something renders
 * the groups as units — which is what any list-of-groups does — because a wrapper element takes
 * BOTH rules away at once. The rail's gap then falls between wrappers rather than between items,
 * and every heading is the first child of its own wrapper, so every heading loses its margin
 * instead of only the first.
 *
 * A consumer hit exactly that: items flush against each other, and no separation above
 * "Governance" or "Knowledge". So the wrapper is a component rather than a bare div, and it
 * carries the two rules the rail can no longer apply on its behalf. */
/* --- putting the rail away ------------------------------------------- */

/* A matrix of sixty-six requirements, a document at its measure and a knowledge
   graph all want the 240px this holds, and a reader who knows where they are
   does not need eight destinations in view to stay there.
 *
 * NARROW IS EMPTY, NOT ABBREVIATED. A rail of unlabelled icons is a rail nobody
 * can read without learning it first, and this product has no icon vocabulary to
 * learn — so putting it away puts it away, and the control to bring it back is
 * the only thing left. */
.sidebar-fold {
  align-self: flex-end;
  display: grid;
  place-items: center;
  inline-size: var(--control-h-sm);
  block-size: var(--control-h-sm);
  margin-block-end: var(--space-2);
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-neutral-600);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.sidebar-fold:hover { background: var(--color-neutral-100); color: var(--color-neutral-900); }
.sidebar-fold svg { transition: transform var(--dur-base) var(--ease-out); }

.sidebar[data-narrow] {
  /* THE POINT OF FOLDING IS THE WIDTH, and this rule is what delivers it. Without
     it the rail hid its contents and stayed 240px, so folding traded a useful
     rail for an empty column of the same size — a control that makes the page
     worse and gives nothing back.

     The width is the button plus its padding, computed rather than typed, so it
     stays snug if either token moves. */
  /* AND THE BORDER IS PART OF THE WIDTH.
   *
   * `.sidebar` is `border-box` and carries a 1px `border-inline-end`, so a width
   * of "the button plus its padding" left a content box ONE PIXEL narrower than
   * the button — 36px of fold control in 35px of room. That single pixel of
   * overflow drew a horizontal scrollbar the full height of the rail, because
   * `overflow-y: auto` forces `overflow-x` to compute to `auto` as well: a
   * vertical rail that scrolls sideways, over a pixel.
   *
   * The border is added rather than the box-sizing changed, so the number still
   * follows the tokens above it. */
  inline-size: calc(var(--control-h-sm) + 2 * var(--space-2) + 1px);
  padding-inline: var(--space-2);
}
.sidebar[data-narrow] .sidebar-fold { align-self: center; }
/* The arrow points the way it would move the rail, which is the only thing on a
   control with no words to say what it does. */
.sidebar[data-narrow] .sidebar-fold svg { transform: rotate(180deg); }
/* A FOLDED RAIL IS AN ICON RAIL, NOT AN EMPTY ONE.
 *
 * This hid the sections outright, so folding traded eleven destinations for a
 * blank strip and the only way back to any of them was to unfold. A reader who
 * folds the rail wants their page wider — not their navigation gone.
 *
 * The marks stay. `font-size: 0` collapses the label without removing it, so the
 * accessible name of every link is intact and a screen reader still reads
 * "Deliverables"; the icons carry their own explicit size and are untouched. The
 * GROUP HEADINGS go, because a heading with no words is a rule across the rail
 * saying nothing. */
.sidebar[data-narrow] .sidebar-group { display: none; }
.sidebar[data-narrow] .sidebar-item {
  font-size: 0;
  justify-content: center;
  padding-inline: 0;
  /* THE GAP OUTLIVES THE LABEL IT SEPARATED.
     `font-size: 0` collapses the words and leaves a zero-width element after the
     mark — so the row centred [icon][12px gap][nothing] and put every icon
     exactly half a gap left of centre. Measured on the live console: 6px. */
  gap: 0;
}
.sidebar[data-narrow] .sidebar-item svg { inline-size: var(--space-5); block-size: var(--space-5); }
/* The count is a figure, and a figure with no row to sit in is a number floating
   beside a picture. */
.sidebar[data-narrow] .sidebar-count { display: none; }

.sidebar-section { display: flex; flex-direction: column; gap: var(--space-1); }
.sidebar-section + .sidebar-section { margin-block-start: var(--space-6); }

/* A GROUP THAT COLLAPSES.
 *
 * The heading becomes the control, so it carries a button reset on top of `.sidebar-group` and the
 * SAME chevron the accordion uses — one mechanism, so an expanded state looks the same wherever it
 * appears in the system. */
.sidebar-group-toggle {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  inline-size: 100%;
  min-block-size: var(--control-h-xs);
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  text-align: start;
  cursor: pointer;
}
.sidebar-group-toggle:hover { color: var(--color-neutral-900); }

/* A PARENT YOU OPEN OUTRANKS WHAT IT OPENS.
 *
 * A static `.sidebar-group` is a label over a list of places, and it stays quiet. A collapsible one
 * is navigation: the reader scans the parents to find their way, then opens one. Drawn as the quiet
 * label it was 12px and muted above 14px children, so the hierarchy read upside down. It takes the
 * item's size, row height and shape, ink at 600; its children step in beneath it, so the level shows
 * in the layout as well as the weight. */
.sidebar-group.sidebar-group-toggle {
  min-block-size: var(--control-h-md);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-full);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-neutral-900);
  transition: background-color var(--dur-fast) var(--ease-out);
}
.sidebar-group.sidebar-group-toggle:hover { background: var(--nav-hover); }
.sidebar-group-toggle + .sidebar-section-items .sidebar-item { padding-inline-start: var(--space-8); }
.sidebar-group-toggle::after {
  content: "";
  margin-inline-start: auto;
  inline-size: 0.4em;
  block-size: 0.4em;
  flex: none;
  border-inline-end: 1.5px solid var(--color-neutral-500);
  border-block-end: 1.5px solid var(--color-neutral-500);
  transform: translateY(-0.15em) rotate(-45deg);
  transition: transform var(--dur-base) var(--ease-out);
}
.sidebar-group-toggle[aria-expanded="true"]::after { transform: translateY(0.1em) rotate(45deg); }
/* The items get their own column when they are wrapped for the disclosure, so the gap the section
   provides is not lost to the wrapper — the same trap `.sidebar-section` exists to close. */
.sidebar-section-items { display: flex; flex-direction: column; gap: var(--space-1); }
/* The heading needs no reset inside a section: it is the section's first child, so the
   `:first-child` rule above already zeroes it. An explicit `.sidebar-section > .sidebar-group`
   was written here and proved to change nothing — removing it left every spec green. */

.sidebar-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-block-size: var(--control-h-md);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-full);
  font-size: var(--text-sm);
  color: var(--color-neutral-700);
  text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.sidebar-item:hover { background: var(--nav-hover); color: var(--color-neutral-900); }
.sidebar-item[aria-current] {
  background: var(--nav-current);
  color: var(--color-neutral-900);
  font-weight: 600;
}
.sidebar-item[aria-current]:hover { background: var(--nav-current-hover); }
.sidebar-item svg { inline-size: var(--space-4); block-size: var(--space-4); flex: none; }

/* A DESTINATION THAT IS NOT READY YET.
 *
 * A rail lists everywhere a person can go, in an order that is usually a filing decision — while
 * the product underneath enforces a real one, as a refusal, at the moment somebody tries. So the
 * work has a sequence and the navigation shows none of it, and a reader finds out what is not yet
 * possible by walking into it.
 *
 * QUIETER, NOT DISABLED. It is still a link: arriving to read what is there, or to understand why
 * it is empty, is legitimate — and a rail that removed the row would answer "what happens next"
 * with silence. So this is the weight of a thing that is not ready, not the grey of a thing that
 * cannot be pressed, and hover and current state are untouched because both are still true.
 *
 * NO ICON OF ITS OWN. A second mark in a row that already has one competes with the mark that
 * says what the surface holds; the reason lives in the row's title and on the destination, where
 * somebody who has arrived can act on it. */
.sidebar-item[data-waiting] { color: var(--color-neutral-500); }
.sidebar-item[data-waiting] svg { opacity: 0.55; }
.sidebar-item[data-waiting]:hover { color: var(--color-neutral-800); }
.sidebar-item[data-waiting]:hover svg { opacity: 1; }
/* Where you ARE always reads as where you are, ready or not. */
.sidebar-item[data-waiting][aria-current] { color: var(--color-neutral-900); }
.sidebar-item[data-waiting][aria-current] svg { opacity: 1; }
/* The count stays MONO: it is a figure in a column of figures, and tabular digits
   are the whole reason. */
.sidebar-count { margin-inline-start: auto; font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: var(--text-xs); color: var(--color-neutral-600); }

/* --- breadcrumbs ---------------------------------------------------- */

.breadcrumbs { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); white-space: nowrap; }
.breadcrumbs a { color: var(--color-neutral-600); text-decoration: none; }
.breadcrumbs a:hover { color: var(--color-neutral-900); text-decoration: underline; }
.breadcrumbs [aria-current] { color: var(--color-neutral-900); font-weight: 500; }
.breadcrumb-sep { color: var(--color-neutral-400); user-select: none; }

/* --- tabs ----------------------------------------------------------- */

/* A 1px rule under the strip, with the active tab drawing over it. */
/* A STRIP OF SEPARATE THINGS, NOT ONE BAND.
 *
 * The rule under the strip was `neutral-200` — the same weight the selected tab's own 2px
 * indicator has to stand out against — and the gap between tabs was 20px. Read together at a
 * glance that is one continuous horizontal bar with words sitting on it, and telling one tab from
 * the next takes effort the reader should not be spending.
 *
 * The rule is a hairline now, and the gap is wide enough that the labels separate on their own.
 * The indicator keeps its weight, so the selected tab is the only strong horizontal mark. */
.tabs {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  border-block-end: 1px solid var(--color-divider);
}
.tab {
  position: relative;
  padding-block: var(--space-3);
  border: 0;
  background: transparent;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-neutral-600);
  text-decoration: none;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out);
}
.tab:hover { color: var(--color-neutral-900); }
/* The underline is the indicator; the label goes to ink, not to the accent, so "selected" is said
   once rather than in two blues. */
.tab[aria-selected="true"], .tab[aria-current] { color: var(--color-neutral-900); }
/* A LABEL THAT NEEDS EXPLAINING SAYS SO.
 *
 * "Coverage" and "Library" are nouns, which is right, but a noun can still be opaque to somebody
 * who has not been told what it holds. The mark is the affordance: it says an explanation exists
 * without spending a line of the interface on one, and the tab itself is the tooltip's trigger, so
 * hover and focus both reach it and no interactive element is nested inside a button.
 *
 * Drawn rather than iconographic, for the reason the accordion's chevron is: `@gener8v/ui` carries
 * no icon dependency and should not gain one for a 12px circle. */
/* FILLED, NOT OUTLINED.
 *
 * A 1px ring around a 0.7em letter is two hairlines and a glyph fighting for the same ten pixels,
 * and it renders as a smudge. A filled disc with the label knocked out of it is one shape at this
 * size, which is the only thing that reads. */
.tab-hint {
  display: inline-grid;
  place-items: center;
  inline-size: 1.05em;
  block-size: 1.05em;
  margin-inline-start: var(--space-2);
  border-radius: var(--radius-full);
  /* NOT `currentColor`. Within one rule it resolves against the element's OWN colour — which the
     next line sets to the page ground — so the disc and the glyph came out identical and the mark
     was invisible. The tab's two states are named explicitly instead. */
  background: var(--color-neutral-600);
  color: var(--color-bg);
  font-family: var(--font-body);
  font-size: 0.62em;
  font-style: normal;
  font-weight: 700;
  line-height: 1;
  opacity: 0.5;
}
.tab:hover .tab-hint { opacity: 0.85; }
.tab[aria-selected="true"] .tab-hint { background: var(--color-action); opacity: 1; }

.tab[aria-selected="true"]::after, .tab[aria-current]::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: -1px;
  block-size: 2px;
  background: var(--color-action);
}

/* --- the page band -------------------------------------------------- */

/* WHAT A SURFACE SAYS IT IS, ABOVE WHAT IT HOLDS.
 *
 * `.shell-head` lived in the app-shell template's own `<style>` block — scaffolding, not a
 * component — so every consumer that wanted a page header wrote one. The delivery record's had no
 * boundary of any kind: a picker, an eyebrow, a headline and four figures in undifferentiated
 * space, with nothing saying where the header stopped and the page began.
 *
 * The rule is the whole point. A band is only a band if it has an edge; without one it is just the
 * first few lines of the page, and the reader does the grouping.
 */
.shell-head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding-block-end: var(--space-6);
  border-block-end: 1px solid var(--color-divider);
}

/* Pushed to the end of the row. A caller whose first child takes the free space (`flex: 1`) does
   not need this and it costs nothing there; a caller whose does not is why it exists. */
.shell-actions { margin-inline-start: auto; display: flex; gap: var(--space-2); }

/* ------------------------------------------------------------------- pager */

/* STEPPING THROUGH A SET WITHOUT LEAVING THE PLACE YOU ARE IN: `‹ S4 of 19 ›`.
   One bordered control, so the position and the two ways to change it read as
   one thing. At either end the dead button is disabled, not hidden, so the chip
   does not change shape under the pointer. */
.pager-chip {
  display: inline-flex;
  align-items: stretch;
  block-size: var(--control-h-sm);
  border: 1px solid var(--color-neutral-300);
  border-radius: var(--radius-md);
  background: var(--color-bg);
  overflow: hidden;
}
.pager-chip-label {
  display: inline-flex;
  align-items: center;
  padding-inline: var(--space-3);
  border-inline: 1px solid var(--color-neutral-200);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-neutral-800);
  white-space: nowrap;
}
.pager-chip-step {
  display: inline-grid;
  place-items: center;
  inline-size: var(--control-h-sm);
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--color-neutral-700);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out);
}
.pager-chip-step svg { inline-size: var(--space-4); block-size: var(--space-4); }
.pager-chip-step:hover:not([disabled]) { background: var(--color-neutral-100); color: var(--color-neutral-900); }
.pager-chip-step:active:not([disabled]) { background: var(--color-neutral-200); }

/* UNDER A PAGED TABLE: how much of it you are looking at, and the pages. The
   range is on the start edge, the pages on the end; on a phone they stack. */
.table-pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-block-start: var(--space-3);
}
.table-pager-range { margin: 0; font-size: var(--text-xs); color: var(--color-neutral-600); }
.table-pager .pagination { flex-wrap: wrap; }
.pagination-item svg { inline-size: var(--space-4); block-size: var(--space-4); }

/* ------------------------------------------------------------------- paper */

/* A SHEET A DOCUMENT SITS ON.
 *
 * The record's surfaces used to cap themselves at a fixed column and centre it,
 * which left symmetric dead gutters on any wide screen. Removing the cap gave
 * the width back to the things that wanted it — tables, trees, split panes —
 * and took a wrong lesson with it: a document then ran flush against the
 * sidebar with half the window empty beside it, which is not better, it is the
 * same waste rotated.
 *
 * Prose is not a table. It has a measure, and a measured thing needs somewhere
 * to BE — which is a sheet, on a ground, with the page visible around it. The
 * page is not empty space to be eliminated; it is what makes the sheet read as
 * a document rather than as text that ran out of room.
 *
 * So: `Paper` for anything read as a document. Full width for anything scanned
 * as data. The distinction is what the reader is doing, not how much room there
 * happens to be. */

.paper-ground {
  /* The page behind the sheet. Without it the sheet is the same colour as
     everything else and the whole effect is a shadow nobody can explain. */
  background: var(--color-surface);
  padding: var(--space-8) var(--space-6);
  min-block-size: 100%;
  /* The sheet and anything beside it are centred AS A GROUP, so the document
     does not shift left when a rail appears next to it. */
  display: flex;
  justify-content: center;
  align-items: flex-start;
  gap: var(--space-8);
}

/* NAVIGATION IS NOT THE DOCUMENT, AND DOES NOT GO ON THE SHEET.
 *
 * A table of contents is how you move around the thing; it is not part of what
 * the thing says. Putting it on its own sheet makes two documents where there
 * is one, and a reader then has to work out which of the two they are reading.
 *
 * So it sits on the GROUND beside the paper — present, clearly secondary, and
 * obviously not part of what would print. */
.paper-aside {
  flex: none;
  /* A LAYOUT DIMENSION, NOT A SPACING STEP. This was `--space-24`, which is
     96px — narrow enough that a contents entry wrapped to twelve lines and its
     longest word still could not fit, so the rail overflowed and put a
     horizontal scrollbar under the whole document. */
  inline-size: var(--rail-w);
  min-inline-size: 0;
  /* A heading is one long word more often than anybody expects. */
  overflow-wrap: anywhere;
  position: sticky;
  inset-block-start: var(--paper-aside-top, var(--space-8));
  font-size: var(--text-sm);
  /* A RAIL TALLER THAN THE WINDOW. A contents list of seventy-eight sections is
     longer than any screen, and a sticky element taller than its viewport
     cannot be scrolled to its end — the last entries were unreachable until the
     page itself ran out. So the rail is held to the window and scrolls inside
     itself, and its scrolling stops at its own ends rather than carrying on
     into the page. An app with a fixed top bar sets `--paper-aside-top` to the
     bar's height plus a gap, and the rail's height follows.

     WHEN THE DOCUMENT SCROLLS INSIDE A PANE rather than under the window, the
     pane itself starts below the bar, so `--paper-aside-top` (the rail's gap
     from the pane's top edge) is not the whole story: the pane's own offset
     from the top of the window is lost, and the rail's bottom overflows the
     window by that much. Set `--paper-aside-viewport-offset` to the pane's
     distance from the top of the window (a 64px bar: `64px`). It is 0 by
     default, which is right for a page that scrolls under the window. */
  max-block-size: calc(
    100dvh
    - var(--paper-aside-viewport-offset, 0px)
    - var(--paper-aside-top, var(--space-8))
    - var(--space-8)
  );
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
}

@media (max-width: 1100px) {
  /* Below this the rail steals width the document needs. It goes above, where
     a contents list is still useful and costs the prose nothing. */
  .paper-ground { flex-direction: column; align-items: stretch; }
  .paper-aside { position: static; inline-size: auto; order: -1; max-block-size: none; overflow: visible; }
}

.paper {
  /* NOT `margin-inline: auto`. The ground already centres; an auto margin on a
     flex item ABSORBS the free space instead, which pushed the rail hard
     against the far edge of the window with a chasm between it and the sheet.
     `flex: 1 1 auto` is what makes the sheet reach its own width rather than
     shrink to whatever the prose inside it happened to need — a sheet is a
     size, not an outline of its contents. */
  flex: 1 1 auto;
  /* Wider than the prose measure on purpose: a document carries tables, code
     and figures that the paragraphs must not be narrowed to fit. `.reader`
     still holds the PROSE to 72ch inside this. */
  max-inline-size: var(--container-lg);
  padding: var(--space-10) var(--space-10) var(--space-16);
  background: var(--color-bg);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-md);
  /* A hairline AND a shadow. The border is what survives a printed page and a
     forced-colours mode; the shadow is what makes it sit ON something. */
  box-shadow: var(--shadow-sm);
}

/* Wide content inside a sheet scrolls within itself rather than bursting the
   edge — the same rule `.reader` applies to a table in prose. */
.paper table, .paper pre { max-inline-size: 100%; }

@media print {
  /* Navigation does not print. A contents list on paper is a list of places
     that are not links to anything. */
  .paper-aside { display: none; }
}

@media (max-width: 720px) {
  /* No sheet on a phone. The ground would be two visible strips either side of
     something that is already the full width, which reads as a rendering fault
     rather than as paper. */
  .paper-ground { padding: 0; background: var(--color-bg); }
  .paper {
    max-inline-size: none;
    padding: var(--space-6) var(--space-5) var(--space-10);
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
}

@media print {
  /* Printing a governed document is a real act — it is what somebody attaches
     to something. The ground, the border and the shadow are screen furniture. */
  .paper-ground { padding: 0; background: transparent; }
  .paper {
    max-inline-size: none;
    padding: 0;
    border: 0;
    box-shadow: none;
  }
}

/* ----------------------------------------------------------------- popover */

/* Non-modal: it floats over the page but does not take it over. No backdrop,
   no focus trap — a popover the user cannot Tab out of is a dialog wearing
   the wrong clothes. */
.popover {
  min-inline-size: 240px;
  max-inline-size: 360px;
  padding: var(--space-4);
  background: var(--color-bg);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  font-size: var(--text-sm);
  color: var(--color-neutral-800);
  z-index: 70;
}
.popover-title {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--text-base);
  color: var(--color-neutral-900);
  margin-block-end: var(--space-2);
}
.popover-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-block-start: var(--space-4); }

/* A popover summoned by pointing. The same surface — it is the same kind of thing, a panel over
   the page that does not take it — set a step tighter, because a preview is glanced at rather
   than worked in, and a card that fills the screen on a hover is a card that gets in the way. */
.hover-card {
  padding: var(--space-3) var(--space-4);
  max-inline-size: 320px;
}

/* SIZES. `md` is the menu-sized default above and adds no class. `sm` is a
   hover card's glance. `md` on a hover card gives a cited sentence room — at
   320px most of them broke across five lines. `lg` is a panel with a reading
   width of its own (a run's lanes and what is happening): capped to the window,
   never taller than the screen, and scrolling inside itself. */
.popover-sm { max-inline-size: 320px; }
.popover.popover-md { max-inline-size: min(24rem, calc(100vw - var(--space-8))); }
.popover.popover-lg {
  inline-size: min(36rem, calc(100vw - var(--space-8)));
  max-inline-size: min(36rem, calc(100vw - var(--space-8)));
  max-block-size: min(40rem, calc(100dvh - var(--space-16)));
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* ---------------------------------------------------------------- progress */

/* ONE FAMILY, ONE ANATOMY.
 *
 * Bar, ring, spinner, the stepper's rail, a row's live bar, a segmented bar, a
 * checklist's marks and a file's transfer row are one family. They read the
 * same tokens (`--progress-*` in the theme), the same six states and the same
 * four glyphs, and they place a label and a value by one rule:
 *
 *   LABEL AT THE START, VALUE AT THE END, HELPER UNDERNEATH.
 *   - Stacked (a bar that is the subject of its region): label and value on one
 *     line above the track, helper text below it.
 *   - Inline (a row, a table cell): label · track · value, on one line.
 *   - Ring: the value inside when the ring is 48px or larger; otherwise beside
 *     it, where a label would go. A label and helper sit beside a ring, never
 *     under it.
 *   Values are tabular figures in the mono voice, so a moving number does not
 *   make its row shimmer.
 *
 * THE SIX STATES, ONE MAPPING.
 *
 *   state     word          glyph   tone
 *   queued    Queued/To do  —       neutral, hollow
 *   current   In progress   —       action (a ring; a spinner when work runs)
 *   complete  Done          check   success
 *   failed    Failed        cross   danger
 *   stopped   Stopped       alert   warning — interrupted or cancelled, not refused
 *   skipped   Skipped       minus   neutral, dashed
 *
 * A mark is a tonal disc: the 100 step behind, the 300 step as its edge, the
 * glyph in 700. A segment of a bar is a fill in the 700 step. Colour never
 * carries a state alone — the glyph, the word or both are always there. */

/* --- the glyph set ----------------------------------------------------- */

/* FOUR GLYPHS, ONE SOURCE, ONE STROKE.
 *
 * Phosphor's Bold geometry (the system's icon set is Phosphor) — a 24-unit stroke
 * on a 256 grid with round caps and joins — drawn as strokes and embedded as
 * CSS mask images. A mask rather than inline SVG or a text glyph because:
 *
 *   - It needs no markup. The stepper's existing `<span class="step-marker">1</span>`
 *     draws its tick from CSS alone, so markup written against an earlier version
 *     gets the fix on its next bump, and a static page needs no React and no icon
 *     package.
 *   - It is painted in `background-color`, so a state colours it with one token,
 *     and forced-colours mode can repaint it in a system colour.
 *   - A text glyph (✓ ×) is drawn by whatever font the reader has, at that font's
 *     weight and baseline — which is how three different ticks got into the system.
 *
 * Each viewBox is centred on the INK, not on Phosphor's 256 box: the check's ink
 * spans x 28–236 and y 60–212, centre (132, 136), so its viewBox starts at (4, 8).
 * A glyph's painted centre is therefore its box's centre, by construction, and a
 * container centres the box. Nothing is nudged. */
:root {
  --progress-glyph-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='4 8 256 256'%3E%3Cpath d='M40 144l56 56L224 72' fill='none' stroke='black' stroke-width='24' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  --progress-glyph-cross: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M56 56l144 144M200 56L56 200' fill='none' stroke='black' stroke-width='24' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  --progress-glyph-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M40 128h176' fill='none' stroke='black' stroke-width='24' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  --progress-glyph-alert: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath d='M128 56v92' fill='none' stroke='black' stroke-width='24' stroke-linecap='round' stroke-linejoin='round'/%3E%3Ccircle cx='128' cy='196' r='16'/%3E%3C/svg%3E");
}

/* A glyph inline with text: 1em, in the text's colour. */
.progress-glyph {
  display: inline-block;
  flex: none;
  inline-size: 1em;
  block-size: 1em;
  vertical-align: -0.15em;
  background: currentColor;
  -webkit-mask: var(--progress-glyph-check) center / contain no-repeat;
  mask: var(--progress-glyph-check) center / contain no-repeat;
}
.progress-glyph[data-glyph="cross"] { -webkit-mask-image: var(--progress-glyph-cross); mask-image: var(--progress-glyph-cross); }
.progress-glyph[data-glyph="minus"] { -webkit-mask-image: var(--progress-glyph-minus); mask-image: var(--progress-glyph-minus); }
.progress-glyph[data-glyph="alert"] { -webkit-mask-image: var(--progress-glyph-alert); mask-image: var(--progress-glyph-alert); }

/* --- the mark: a state in a disc --------------------------------------- */

/* The same disc and glyph a stepper marker draws, for a checklist task, a
   legend and anywhere else one item's state needs a mark. `data-state` picks
   the glyph and tone; the disc is 24px, or 16px as `.progress-mark-sm`. */
.progress-mark {
  --mark-size: var(--progress-circle-sm);
  position: relative;
  display: inline-block;
  flex: none;
  box-sizing: border-box;
  inline-size: var(--mark-size);
  block-size: var(--mark-size);
  border: 1px solid var(--color-neutral-300);
  border-radius: var(--radius-full);
  background: var(--color-bg);
  vertical-align: middle;
}
.progress-mark-sm { --mark-size: var(--progress-circle-xs); }
/* Absolutely placed and centred by margin, so nothing in the disc can move it. */
.progress-mark::after {
  content: none;
  position: absolute;
  inset: 0;
  margin: auto;
  inline-size: calc(var(--mark-size) / 2);
  block-size: calc(var(--mark-size) / 2);
  background: currentColor;
  -webkit-mask: var(--progress-glyph-check) center / contain no-repeat;
  mask: var(--progress-glyph-check) center / contain no-repeat;
}
.progress-mark[data-state="complete"]::after,
.progress-mark[data-state="failed"]::after,
.progress-mark[data-state="stopped"]::after,
.progress-mark[data-state="skipped"]::after { content: ""; }

.progress-mark[data-state="complete"] { background: var(--color-success-100); border-color: var(--color-success-300); color: var(--color-success-700); }
.progress-mark[data-state="failed"] { background: var(--color-danger-100); border-color: var(--color-danger-300); color: var(--color-danger-700); }
.progress-mark[data-state="failed"]::after { -webkit-mask-image: var(--progress-glyph-cross); mask-image: var(--progress-glyph-cross); }
.progress-mark[data-state="stopped"] { background: var(--color-warning-100); border-color: var(--color-warning-300); color: var(--color-warning-700); }
.progress-mark[data-state="stopped"]::after { -webkit-mask-image: var(--progress-glyph-alert); mask-image: var(--progress-glyph-alert); }
.progress-mark[data-state="skipped"] { border-style: dashed; background: transparent; color: var(--color-neutral-600); }
.progress-mark[data-state="skipped"]::after { -webkit-mask-image: var(--progress-glyph-minus); mask-image: var(--progress-glyph-minus); }
/* Current: the work is running, so the mark IS the spinner — the same ring,
   track and arc, at the disc's size. See `.spinner` below. */

/* --- linear ------------------------------------------------------------ */

.progress {
  --progress-h: var(--progress-h-md);
  position: relative;
  inline-size: 100%;
  block-size: var(--progress-h);
  background: var(--progress-track);
  border-radius: var(--progress-radius);
  overflow: hidden;
}
.progress-sm { --progress-h: var(--progress-h-sm); }
.progress-lg { --progress-h: var(--progress-h-lg); }
.progress-bar {
  block-size: 100%;
  inline-size: 0;
  background: var(--progress-fill);
  border-radius: inherit;
  transition: inline-size var(--dur-slow) var(--ease-out), background-color var(--dur-base) var(--ease-out);
}

/* A tone replaces the fill only when the whole task has ended. */
.progress[data-tone="success"], .progress-field[data-tone="success"], .progress-ring[data-tone="success"], .progress-ring-field[data-tone="success"] { --progress-tone: var(--progress-fill-success); }
.progress[data-tone="warning"], .progress-field[data-tone="warning"], .progress-ring[data-tone="warning"], .progress-ring-field[data-tone="warning"] { --progress-tone: var(--progress-fill-warning); }
.progress[data-tone="danger"], .progress-field[data-tone="danger"], .progress-ring[data-tone="danger"], .progress-ring-field[data-tone="danger"] { --progress-tone: var(--progress-fill-danger); }
.progress[data-tone] .progress-bar,
.progress-field[data-tone] .progress-bar { background: var(--progress-tone, var(--progress-fill)); }

/* Indeterminate: a third of the track crosses it, and leaves before it
   returns, so it never reads as a bar parked at a value. */
.progress-indeterminate .progress-bar {
  inline-size: 40%;
  animation: progress-slide 1.6s var(--ease-in-out) infinite;
}
@keyframes progress-slide {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(250%); }
}

/* The labelled layout: label and value above the track, helper below. */
.progress-field { display: grid; gap: var(--space-2); min-inline-size: 0; }
.progress-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  min-inline-size: 0;
}
.progress-label {
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-neutral-900);
}
.progress-value {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--color-neutral-700);
}
.progress-helper { margin: 0; font-size: var(--text-xs); color: var(--color-neutral-600); }
/* A value that is a WORD — Uploaded, Failed, Waiting — is set in the body face, not the figures'
   mono, so every status in a list reads in one voice. Figures stay mono and tabular. */
.progress-status { font-family: var(--font-body); font-weight: 500; }
.progress-field[data-tone="success"] .progress-value { color: var(--color-success-700); }
.progress-field[data-tone="warning"] .progress-value { color: var(--color-warning-800); }
.progress-field[data-tone="danger"] .progress-value,
.progress-field[data-tone="danger"] .progress-helper { color: var(--color-danger-700); }

/* Inline: label · track · value on one line, for a row or a cell.
 *
 * THREE FIXED COLUMNS, NOT WHATEVER EACH LABEL LEFT OVER. Inline bars stack — Controls over
 * Evidence over Owners — and a bar that starts wherever its own label happens to end puts every
 * track at a different x, which reads as a list nobody lined up. The label and value tracks are
 * fixed (`--progress-label-w`, `--progress-value-w`, set by the caller when the words are longer),
 * so every bar in a stack starts and ends at the same x and the values right-align. */
.progress-inline {
  display: grid;
  grid-template-columns:
    minmax(0, var(--progress-label-w, var(--space-32)))
    minmax(var(--space-12), 1fr)
    var(--progress-value-w, var(--space-12));
  align-items: center;
  column-gap: var(--space-3);
}
.progress-inline .progress-label { font-weight: 400; color: var(--color-neutral-700); font-size: var(--text-xs); }
.progress-inline .progress-value { justify-self: end; }

/* --- circular ---------------------------------------------------------- */

/* RING, SPINNER AND A RUNNING MARK ARE ONE SHAPE.
 *
 * A track and an arc, stroke = diameter / 8, drawn with a conic gradient and a
 * radial mask that cuts the hole — so the hole is genuinely empty and the ring
 * sits on a card, a tinted lane or a dark page alike. (It used to cover its
 * middle with a disc of `--color-bg`, which showed as a white coin on anything
 * that was not the page.) The spinner is this ring with no value, a quarter arc
 * and a turn. Sizes are the circle scale: xs 16, sm 24, md 32, lg 48, xl 64. */
.progress-ring,
.spinner,
.progress-mark[data-state="current"] {
  --ring-size: var(--progress-circle-xl);
  --ring-stroke: calc(var(--ring-size) / 8);
  --ring-hole: radial-gradient(farthest-side,
    transparent calc(100% - var(--ring-stroke) - 1px),
    black calc(100% - var(--ring-stroke)));
}
.progress-ring {
  position: relative;
  display: inline-grid;
  place-items: center;
  flex: none;
  inline-size: var(--ring-size);
  block-size: var(--ring-size);
  border-radius: var(--radius-full);
}
.progress-ring::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: conic-gradient(
    var(--progress-tone, var(--progress-fill)) calc(var(--progress, 0) * 1%),
    var(--progress-track) 0
  );
  -webkit-mask: var(--ring-hole);
  mask: var(--ring-hole);
}
.progress-ring-xs { --ring-size: var(--progress-circle-xs); }
.progress-ring-sm { --ring-size: var(--progress-circle-sm); }
.progress-ring-md { --ring-size: var(--progress-circle-md); }
.progress-ring-lg { --ring-size: var(--progress-circle-lg); }
.progress-ring-value {
  position: relative;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: var(--color-neutral-900);
}
.progress-ring-lg .progress-ring-value { font-size: var(--text-xs); }
/* An icon in the middle: three-eighths of the ring, the colour of its fill. */
.progress-ring > .progress-glyph,
.progress-ring > svg {
  position: relative;
  inline-size: calc(var(--ring-size) * 0.375);
  block-size: calc(var(--ring-size) * 0.375);
  vertical-align: 0;
  color: var(--progress-tone, var(--progress-fill));
}
.progress-ring-indeterminate::before {
  background: conic-gradient(var(--progress-fill) 0 25%, var(--progress-track) 0);
  animation: spinner-spin 800ms linear infinite;
}

/* A RING WITH ITS WORDS BESIDE IT — ONE RULE.
 *
 * The ring is centred on its text, and the text is always two lines: the title line (label, and
 * the value when the ring is too small to hold it) and one supporting line (the helper). Two
 * rings side by side therefore put their titles on one line whatever their sizes, and no ring
 * sits against a three-line block in one place and a two-line block in another. */
.progress-ring-field {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-inline-size: 0;
}
.progress-ring-text { display: grid; gap: var(--space-1); min-inline-size: 0; }
.progress-ring-text .progress-head { justify-content: flex-start; gap: var(--space-2); }

/* --- spinner ----------------------------------------------------------- */

/* The indeterminate ring at its three small sizes: 16, 24 and 32. */
.spinner,
.progress-mark[data-state="current"] {
  display: inline-block;
  flex: none;
  box-sizing: border-box;
  inline-size: var(--ring-size);
  block-size: var(--ring-size);
  border: 0;
  border-radius: var(--radius-full);
  background: conic-gradient(var(--progress-fill) 0 25%, var(--progress-track) 0);
  -webkit-mask: var(--ring-hole);
  mask: var(--ring-hole);
  vertical-align: middle;
  animation: spinner-spin 800ms linear infinite;
}
.spinner { --ring-size: var(--progress-circle-sm); }
.spinner-sm { --ring-size: var(--progress-circle-xs); }
.spinner-lg { --ring-size: var(--progress-circle-md); }
.progress-mark[data-state="current"] { --ring-size: var(--mark-size); }
@keyframes spinner-spin { to { transform: rotate(360deg); } }

/* --- segmented --------------------------------------------------------- */

/* ONE TRACK, SPLIT BY STATUS. Each segment's length is its count; the gap is
   the rail's 2px, so a segmented bar and a stepper's rail are the same object.
   A segment never shrinks below a dot, so one failure in a hundred is seen. */
.progress-segments {
  display: flex;
  gap: var(--progress-gap);
  block-size: var(--progress-h-md);
}
.progress-segments.progress-sm { block-size: var(--progress-h-sm); }
.progress-segments.progress-lg { block-size: var(--progress-h-lg); }
.progress-segment {
  flex: var(--value, 1) 1 0;
  min-inline-size: var(--progress-h-lg);
  border-radius: var(--progress-radius);
  background: var(--progress-track);
}
.progress-segment[data-state="complete"], .progress-legend-swatch[data-state="complete"] { background: var(--progress-fill-success); }
.progress-segment[data-state="failed"], .progress-legend-swatch[data-state="failed"] { background: var(--progress-fill-danger); }
.progress-segment[data-state="stopped"], .progress-legend-swatch[data-state="stopped"] { background: var(--progress-fill-warning); }
.progress-segment[data-state="current"], .progress-legend-swatch[data-state="current"] { background: var(--progress-fill); }
.progress-segment[data-state="skipped"], .progress-legend-swatch[data-state="skipped"] { background: var(--color-neutral-400); }

/* The legend says every count in words. The swatch is the segment's shape. */
.progress-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--text-xs);
  color: var(--color-neutral-700);
}
.progress-legend-item { display: inline-flex; align-items: center; gap: var(--space-2); }
.progress-legend-swatch {
  flex: none;
  inline-size: var(--space-3);
  block-size: var(--progress-h-md);
  border-radius: var(--progress-radius);
  background: var(--progress-track);
}
.progress-legend-count { font-family: var(--font-mono); font-variant-numeric: tabular-nums; color: var(--color-neutral-900); }

/* --- checklist progress ------------------------------------------------ */

/* Tasks, each with its mark, and the count of how many are done. For a setup
   list, onboarding, or the steps of an agent's run. Not ticked by the reader —
   that is `.checklist`. */
.progress-list { display: grid; gap: var(--space-3); min-inline-size: 0; }
.progress-list-items { display: grid; margin: 0; padding: 0; list-style: none; }
.progress-list-item {
  display: grid;
  grid-template-columns: var(--progress-circle-sm) minmax(0, 1fr);
  grid-template-rows: var(--progress-circle-sm) auto;
  align-items: center;
  column-gap: var(--space-3);
  padding-block: var(--space-2);
}
.progress-list-item > .progress-mark { grid-row: 1; grid-column: 1; }
.progress-list-label {
  grid-row: 1;
  grid-column: 2;
  min-inline-size: 0;
  font-size: var(--text-sm);
  color: var(--color-neutral-800);
}
.progress-list-description {
  grid-row: 2;
  grid-column: 2;
  font-size: var(--text-xs);
  color: var(--color-neutral-600);
}
.progress-list-item[data-state="current"] .progress-list-label { color: var(--color-neutral-900); font-weight: 500; }
.progress-list-item[data-state="queued"] .progress-list-label,
.progress-list-item[data-state="skipped"] .progress-list-label { color: var(--color-neutral-600); }
.progress-list-item[data-state="failed"] .progress-list-label { color: var(--color-danger-700); }
.progress-list-item[data-state="failed"] .progress-list-description { color: var(--color-danger-700); }
.progress-list-item[data-state="stopped"] .progress-list-label { color: var(--color-warning-800); }

/* --- file transfer ----------------------------------------------------- */

/* A file on its way: the stacked layout (name and status above, a 4px track,
 * size or the error below), and two action slots at the end of the row.
 *
 * THE ACTION COLUMNS ARE THE LIST'S, NOT THE ROW'S. A row with a Retry button used to get a
 * shorter bar and a status 66px left of its neighbours'. A list holding transfer rows becomes a
 * three-column grid — body, retry slot, dismiss slot — and each row takes those columns as a
 * subgrid, so every bar ends and every status right-aligns at the same x, and a row with no
 * Retry keeps the slot empty rather than giving it to its bar. */
.file-list:has(> .file-progress) {
  grid-template-columns: minmax(0, 1fr) auto auto;
}
.file-list:has(> .file-progress) > * { grid-column: 1 / -1; }
.file-progress {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  column-gap: var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-md);
  background: var(--color-bg);
}
.file-list > .file-progress { grid-template-columns: subgrid; }
.file-progress > .progress-field { gap: var(--space-2); margin-inline-end: var(--space-1); }
/* A file's name wraps rather than clipping: two files that differ only at the end of a long
   name are told apart by exactly the part an ellipsis would hide. */
.file-progress .progress-label { font-weight: 400; white-space: normal; overflow-wrap: anywhere; }
/* An empty slot still holds its column; the dismiss slot is always one square control wide. */
.file-progress-slot { display: flex; }
.file-progress-slot + .file-progress-slot { inline-size: var(--control-h-sm); }
.file-progress[data-state="failed"] { border-color: var(--color-danger-300); }

/* --- progress inside a button ----------------------------------------- */

/* Not added. `.btn.is-loading` already keeps a button's label and width while
   it waits, and a determinate fill inside a solid button would put a second
   bar on the one control a view has exactly one of. A long action reports its
   progress where its result will land. */

/* ------------------------------------------------------------- select menu */

/* A BUTTON THAT LOOKS LIKE THE FIELD IT REPLACES, AND A LIST THAT IS THE COMBOBOX'S.
 *
 * The button is a `.select` inside a `.select-wrap`, so closed it is the native select: the same
 * height scale, border, focus ring, chevron and sizes. A form that mixes the two does not show a
 * seam. The list is a `.listbox` with `.listbox-option` rows, so open it is the combobox's list.
 * Everything here is what the two do not already have: a button's text alignment, a placeholder,
 * a check on the chosen row, group headings, and a mark for the row the keys are on.
 *
 * All of it is under `.select-menu-*`. Nothing about `.select` or `.listbox` changes. */

/* The same grid as `.field` and `.combobox`, so a label sits the same distance above the button
   whichever of the three draws it. */
.select-menu {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2);
}

/* A button centres its text and a select does not. The value is one line that ellipses: the full
   text is in the list, one press away. */
.select-menu-trigger {
  display: flex;
  align-items: center;
  text-align: start;
  font-weight: 400;
}
.select-menu-value {
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.select-menu-value[data-placeholder] { color: var(--color-neutral-500); }
/* Open, the button keeps the accent edge it had while focused: focus has moved into the list, and
   the edge says which field the list belongs to. */
.select-menu-trigger[aria-expanded="true"] { border-color: var(--color-accent); }
.select-menu-trigger:disabled {
  cursor: not-allowed;
  background: var(--color-surface);
  color: var(--color-neutral-500);
}

/* At least as wide as the button, wider when an option needs it, never wider than the screen.
   `.listbox` is exactly the field's width, which is right for a field you type into and too
   narrow for a short button holding a long option.
   Taller than `.listbox` (320px against 230px): a select menu's rows carry group headings and
   notes, and a list someone scans should show a good part of itself before it scrolls. */
.select-menu-list {
  inline-size: max-content;
  min-inline-size: var(--anchor-w, auto);
  max-block-size: min(calc(var(--space-32) * 2.5), var(--select-menu-room, 100vh));
  white-space: normal;
  text-align: start;
}
/* Focus is on the list, and the row the keys are on is the mark. A ring around the whole list as
   well would point at two things at once. */
.select-menu-list:focus { outline: none; }

/* A ROW: THE TEXT, THEN THE CHECK. The text is the combobox's stack, a label with a note under
   it; the check sits on the first line, so it marks the label rather than floating in the middle
   of a two-line row. */
.select-menu-option {
  flex-direction: row;
  justify-content: space-between;
  gap: var(--space-3);
}
.select-menu-option-text {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-inline-size: 0;
}
.select-menu-option-label { line-height: var(--leading-sm); }

/* The chosen row is marked by a shape as well as by colour and weight, so it survives a colour
   that cannot be told apart. */
.select-menu-check {
  flex: none;
  inline-size: var(--space-4);
  block-size: var(--space-4);
  margin-block-start: calc((var(--text-sm) * var(--leading-sm) - var(--space-4)) / 2);
  visibility: hidden;
}
/* The chosen row keeps the body ink and takes its colour on the check alone. The action blue is
   3.6:1 on the dark ground: enough for a mark (3:1), not for 14px text (4.5:1). `.listbox` itself
   is unchanged. */
.select-menu-option[aria-selected="true"] { color: var(--color-neutral-900); }
.select-menu-option[aria-selected="true"] .select-menu-check { visibility: visible; color: var(--color-action); }

/* The row the keys are on. Hover and keys share the tint already; with the keyboard in use the
   row also takes a bar in the focus colour on its leading edge, because the list holds focus and
   this row is where it is. */
.select-menu-list:focus-visible .select-menu-option[data-active] {
  box-shadow: inset var(--ring-width) 0 0 var(--ring-color);
}

/* A group is its heading and its rows. A rule separates it from whatever comes before it. */
.select-menu-group + .select-menu-group,
.select-menu-option + .select-menu-group,
.select-menu-group + .select-menu-option {
  margin-block-start: var(--space-1);
  padding-block-start: var(--space-1);
  border-block-start: 1px solid var(--color-neutral-200);
}
.select-menu-group-label {
  padding: var(--space-2) var(--space-3) var(--space-1);
  font-size: var(--text-xs);
  font-weight: 500;
  line-height: var(--leading-xs);
  color: var(--color-neutral-600);
}

@media (forced-colors: active) {
  /* The tint is gone; the active row needs an edge of its own. */
  .select-menu-option[data-active] {
    outline: 2px solid Highlight;
    outline-offset: -2px;
  }
}

/* ------------------------------------------------------------------- shelf */

/* MOVING A THING BETWEEN TWO PLACES.
 *
 * Not a list of switches. A switch answers "is this on"; this is somebody
 * taking an item out of one place and putting it in another, and the gesture
 * should be the act. A control that shows only the resulting state makes the
 * reader infer the movement from it.
 *
 * Everything here has a keyboard equivalent, because a drag-only control is one
 * a keyboard cannot operate at all — and in an audited product the people most
 * likely to be using one are exactly the people it is for. */

.shelf { display: grid; gap: var(--space-6); }
/* Side by side once there is room for it. A shelf in a RAIL never has that room
   however wide the window is, and the window is what this query can see — so
   `data-layout="stack"` says so directly rather than guessing from the viewport. */
@media (min-width: 900px) {
  .shelf:not([data-layout="stack"]) { grid-template-columns: 1fr 1fr; }
}

.shelf-zone {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-block-size: var(--space-20);
  padding: var(--space-2);
  border: 1px dashed var(--color-neutral-300);
  border-radius: var(--radius-md);
  background: var(--color-bg);
  transition: border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
/* Where it would land. Marked on the ZONE rather than between items: this is a
   membership question — which of two places does this belong to — and drawing an
   insertion line would promise that the order means something. */
.shelf-zone[data-over] {
  border-style: solid;
  border-color: var(--color-action);
  background: var(--color-accent-100);
}
.shelf-zone-title {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-neutral-600);
  margin-block-end: var(--space-2);
}
.shelf-zone-empty {
  margin: auto;
  padding: var(--space-4);
  font-size: var(--text-sm);
  color: var(--color-neutral-600);
  text-align: center;
}

.shelf-item {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  inline-size: 100%;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-md);
  background: var(--color-bg);
  text-align: start;
  font: inherit;
  color: inherit;
}
/* NO POINTER ON THE ROW.
 *
 * The row opens the item and the grip drags it, and when both switched the cursor
 * the grip's own signal meant nothing — a reader crossing the card was already in
 * "this does something" and the change at the grip read as noise rather than as
 * the one gesture that is not a click.
 *
 * The hover treatment stays, so the row still says it is interactive. What it no
 * longer does is compete with the only control here that needed to be found. */
.shelf-item + .shelf-item { margin-block-start: var(--space-2); }
.shelf-item:hover { border-color: var(--color-neutral-300); box-shadow: var(--shadow-sm); }

/* THE GRIP IS THE DRAG TARGET, NOT THE WHOLE ROW.
 *
 * The row is a link to the item — one click opens it. If the whole row were
 * draggable, every click would begin a drag the reader did not ask for, and the
 * two gestures would fight on every press. */
.shelf-grip {
  flex: none;
  display: grid;
  place-items: center;
  inline-size: var(--space-6);
  block-size: var(--space-6);
  /* CENTRED ON THE ROW IT DRAGS, not on the row's first line.
     `.shelf-item` starts its children at the top, which is right for an icon that
     belongs to the TITLE — and wrong for a handle that represents the whole card.
     A grip level with the first of three lines looks like it grabs that line. */
  align-self: center;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-neutral-400);
  cursor: grab;
}
.shelf-grip:hover { background: var(--color-neutral-100); color: var(--color-neutral-700); }
.shelf-grip:active { cursor: grabbing; }
/* Dots, drawn rather than an icon: this is the one glyph that has to read as
   "grab me" at 12px, and a dependency for it is a dependency in every consumer. */
.shelf-grip::before {
  content: "";
  inline-size: 3px;
  block-size: 3px;
  border-radius: var(--radius-full);
  background: currentColor;
  box-shadow: 0 -5px 0 currentColor, 0 5px 0 currentColor,
              6px 0 0 currentColor, 6px -5px 0 currentColor, 6px 5px 0 currentColor;
  transform: translateX(-3px);
}

/* Picked up — by pointer or by keyboard, and it looks the same either way. A
   keyboard user who cannot see a difference cannot tell whether the pick-up
   worked. */
.shelf-item[data-lifted] {
  border-color: var(--color-action);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-action) 22%, transparent);
  opacity: 0.9;
}

/* CANNOT MOVE, AND SAYS SO BEFORE ANYBODY TRIES.
 *
 * A drag that snaps back teaches nothing. The grip is gone rather than disabled:
 * a control that exists and refuses invites the attempt it refuses. */
.shelf-item[data-fixed] { background: var(--color-surface); cursor: default; }
.shelf-item[data-fixed]:hover { border-color: var(--color-neutral-200); box-shadow: none; }

/* A COLUMN, NOT A RUN OF TEXT. Both were inline spans, so the name and the
   reason ran together into one string — "Corporate PolicyApplies to every
   project" — with no space between them, because a `margin-block-start` does
   nothing to an inline box. */
.shelf-item-body { min-inline-size: 0; flex: 1; display: flex; flex-direction: column; }
.shelf-item-name { font-size: var(--text-sm); color: var(--color-neutral-900); }
.shelf-item-note { margin-block-start: var(--space-1); font-size: var(--text-xs); line-height: var(--leading-xs); color: var(--color-neutral-600); }

/* ------------------------------------------------------------------ slider */

/* A native range input. Styling the pseudo-elements is verbose but keeps every
   keyboard behaviour, the value announcement and the form wiring for free —
   a div-based slider has to reimplement all three and usually gets it wrong. */

.slider {
  inline-size: 100%;
  block-size: var(--space-5);
  appearance: none;
  background: transparent;
  cursor: pointer;
}

/* The filled portion is a gradient stop driven by one custom property the
   caller sets: style="--slider-pct: 62". */
.slider::-webkit-slider-runnable-track {
  block-size: var(--space-2);
  border-radius: var(--radius-full);
  background: linear-gradient(to right,
    var(--color-action) calc(var(--slider-pct, 0) * 1%),
    var(--color-neutral-200) 0);
}
.slider::-webkit-slider-thumb {
  appearance: none;
  inline-size: var(--space-5);
  block-size: var(--space-5);
  margin-block-start: calc((var(--space-2) - var(--space-5)) / 2);
  border: 2px solid var(--color-action);
  border-radius: var(--radius-full);
  background: var(--color-bg);
  box-shadow: var(--shadow-sm);
}
.slider::-moz-range-track {
  block-size: var(--space-2);
  border-radius: var(--radius-full);
  background: var(--color-neutral-200);
}
.slider::-moz-range-progress {
  block-size: var(--space-2);
  border-radius: var(--radius-full);
  background: var(--color-action);
}
.slider::-moz-range-thumb {
  inline-size: var(--space-5);
  block-size: var(--space-5);
  border: 2px solid var(--color-action);
  border-radius: var(--radius-full);
  background: var(--color-bg);
  box-shadow: var(--shadow-sm);
}

.slider:disabled { cursor: not-allowed; }
.slider:disabled::-webkit-slider-thumb { border-color: var(--color-neutral-400); }
.slider:disabled::-moz-range-thumb { border-color: var(--color-neutral-400); }

.slider-head { display: flex; align-items: baseline; gap: var(--space-3); margin-block-end: var(--space-2); }
.slider-value {
  margin-inline-start: auto;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--color-neutral-600);
}

/* ------------------------------------------------------------------- split */

/* A pane whose width is the reader's, not the designer's.
 *
 * A fixed side panel is a guess about content nobody has seen yet. It was wrong
 * on the record's vault, where twenty-four document names — the one thing the
 * panel exists to show — were each clipped mid-word beside an empty gutter. */

.split { display: flex; align-items: stretch; min-inline-size: 0; }
/* `flex: none`, or the width is a suggestion.
 *
 * A flex item shrinks below its `inline-size` when the row is tight, so the
 * panel rendered narrower than the width it had been set to — and reopening
 * after a collapse landed it BELOW its own minimum, which is a panel whose
 * `min` does not mean anything. The width here is the reader's decision, and a
 * decision the layout can overrule is not one. */
.split-aside { flex: none; min-inline-size: 0; overflow: auto; }
.split-main { flex: 1; min-inline-size: 0; overflow: auto; }

/* THE HANDLE IS A CONTROL, SO IT HAS A CONTROL'S HIT AREA.
 *
 * The line is one pixel; the target is not. A 1px separator is a target nobody
 * can hit, so the element is wide enough to grab and draws its line in the
 * middle — the same reason a checkbox is bigger than its tick. */
.split-handle {
  flex: none;
  position: relative;
  inline-size: var(--space-4);
  margin-inline: calc(var(--space-2) * -1);
  z-index: 1;
  border: 0;
  padding: 0;
  background: transparent;
  cursor: col-resize;
  touch-action: none;
}
.split-handle::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 50%;
  inline-size: 1px;
  background: var(--color-neutral-200);
  transition: background-color var(--dur-fast) var(--ease-out),
              inline-size var(--dur-fast) var(--ease-out);
}
.split-handle:hover::before,
.split-handle[data-dragging]::before {
  inline-size: 2px;
  background: var(--color-action);
}
/* Focus lands on the LINE, not on the invisible hit area, or the ring floats
   in the gap between two panels. */
.split-handle:focus-visible { outline: none; }
.split-handle:focus-visible::before {
  inline-size: 2px;
  background: var(--color-action);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-action) 28%, transparent);
}

/* COLLAPSED, THE HANDLE IS THE ONLY WAY BACK.
 *
 * So it stops being an invisible 16px strip. Shut, it sits flush against
 * whatever chrome is beside it — an app sidebar, a window edge — and a target
 * that narrow, unmarked, against another edge is one a reader hunts for and
 * misses. It was missed on the first try on a real screen.
 *
 * Open, the handle is a line you happen to be able to grab. Shut, it is a
 * control, so it looks like one: wider, with a visible grip. */
.split[data-collapsed] .split-aside { display: none; }
.split[data-collapsed] .split-handle {
  inline-size: var(--space-6);
  margin-inline: 0;
  cursor: e-resize;
}
.split[data-collapsed] .split-handle::before {
  inline-size: 3px;
  inset-inline-start: 0;
  border-start-end-radius: var(--radius-full);
  border-end-end-radius: var(--radius-full);
  background: var(--color-neutral-300);
}
.split[data-collapsed] .split-handle:hover::before,
.split[data-collapsed] .split-handle:focus-visible::before { background: var(--color-action); }
/* Two chevron strokes drawn from borders — the same technique the stepper's
   tick uses, and for the same reason: no icon dependency inside a control this
   small. They point at the panel that would come back. */
.split[data-collapsed] .split-handle::after {
  content: "";
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 30%;
  inline-size: 0.4em;
  block-size: 0.4em;
  border-block-start: 1.5px solid var(--color-neutral-500);
  border-inline-end: 1.5px solid var(--color-neutral-500);
  transform: translateY(-50%) rotate(45deg);
  font-size: var(--text-base);
}
.split[data-collapsed] .split-handle:hover::after { border-color: var(--color-action); }

/* Nothing animates mid-drag: a transition on the panel makes the edge lag the
   pointer, which reads as the control being broken rather than smooth. */
.split[data-dragging] { user-select: none; }
.split[data-dragging] .split-aside { transition: none; }

@media (max-width: 720px) {
  /* Two panes side by side on a phone are two panes nobody can read. */
  .split { flex-direction: column; }
  .split-aside { inline-size: auto !important; max-block-size: 40svh; }
  .split-handle { display: none; }
  .split[data-collapsed] .split-aside { display: block; }
}

/* ------------------------------------------------------------------- state */

/* The six states every flow passes through. Most systems ship only the happy
   path, so a client app invents the other five badly and inconsistently.
   Tone is an attribute, not five near-identical classes. */

.state {
  display: grid;
  justify-items: center;
  gap: var(--space-3);
  padding: var(--space-16) var(--space-6);
  text-align: center;
}
.state-tight { padding: var(--space-10) var(--space-6); }

.state-icon {
  display: grid;
  place-items: center;
  inline-size: var(--space-12);
  block-size: var(--space-12);
  border-radius: var(--radius-full);
  background: var(--color-neutral-100);
  color: var(--color-neutral-600);
  margin-block-end: var(--space-1);
}
.state-icon svg { inline-size: var(--space-6); block-size: var(--space-6); }

.state[data-tone="accent"]  .state-icon { background: var(--color-accent-100);  color: var(--color-action); }
.state[data-tone="success"] .state-icon { background: var(--color-success-100); color: var(--color-success-700); }
.state[data-tone="warning"] .state-icon { background: var(--color-warning-100); color: var(--color-warning-700); }
.state[data-tone="danger"]  .state-icon { background: var(--color-danger-100);  color: var(--color-danger-700); }

.state-title {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--text-lg);
  color: var(--color-neutral-900);
}
.state-body { font-size: var(--text-sm); color: var(--color-neutral-700); max-inline-size: 46ch; }
.state-actions { display: flex; gap: var(--space-3); margin-block-start: var(--space-3); flex-wrap: wrap; justify-content: center; }

/* Detail a support engineer needs and a user should not have to read. */
.state-detail {
  margin-block-start: var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-neutral-600);
}

/* --- the empty state's picture ---------------------------------------- */

/* A line drawing, not an illustration: one stroke weight, the quiet neutral of a border, and a
   single accent mark on the part that is missing. Drawn on `currentColor`, so the ramps mirroring
   for dark mode is all the dark theme needs. Hidden from assistive tech — the title says what is
   empty; the picture only makes the screen recognisable at a glance. */
.state-art {
  display: block;
  inline-size: calc(var(--space-24) + var(--space-6));
  color: var(--color-neutral-400);
  margin-block-end: var(--space-1);
}
.state-art svg { display: block; inline-size: 100%; block-size: auto; }
.state-art .state-art-mark { stroke: var(--color-accent); }
.state-tight .state-art { inline-size: var(--space-24); }

/* ------------------------------------------------------------------- table */

/* Hairline rules, never zebra stripes. */

.table-wrap { inline-size: 100%; overflow-x: auto; }

.table {
  inline-size: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
  text-align: start;
}

/* A column heading is a heading, so it is set in the text face.
   It was monospaced, upper-cased and letter-spaced 0.08em — a treatment that
   reads as a terminal, not a report. Three signals were doing the work of one:
   the weight alone separates a heading from the rows under it, and the face
   stays the one the rest of the page is set in. Mono is for values a reader
   compares character by character, and a column's name is not one. */
.table thead th {
  padding: var(--space-3) var(--space-4);
  border-block-start: 1px solid var(--color-neutral-200);
  border-block-end: 1px solid var(--color-neutral-200);
  background: var(--color-surface);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-neutral-900);
  text-align: start;
  white-space: nowrap;
}

.table tbody td {
  padding: var(--space-3) var(--space-4);
  border-block-end: 1px solid var(--color-divider);
  color: var(--color-neutral-800);
  vertical-align: middle;
}
.table tbody tr:last-child td { border-block-end: 0; }
.table tbody tr:hover td { background: var(--color-neutral-100); }

.table thead th:not(:last-child),
.table tbody td:not(:last-child) {
  border-inline-end: 1px solid var(--color-divider);
}

.table-compact thead th, .table-compact tbody td { padding: var(--space-2) var(--space-3); }

/* Figures line up only if they are tabular AND right-aligned. */
.table .num, .table [data-numeric] {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
  text-align: end;
}
.table thead th[data-numeric] { text-align: end; }

/* Opt a cell out of wrapping — identifiers, dates, short codes. */
.table [data-nowrap] { white-space: nowrap; }

/* Sticky header for a table inside a scrolling panel. */
.table-sticky thead th { position: sticky; inset-block-start: 0; z-index: 1; }

/* --- sortable header --- */

.table th button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
  cursor: pointer;
}
.table th button:hover { color: var(--color-neutral-900); }

/* The caret is drawn from borders, so it inherits currentColor and needs no
   icon dependency inside a table header. */
.table th button::after {
  content: "";
  inline-size: 0.4em;
  block-size: 0.4em;
  border-inline-end: 1.5px solid currentColor;
  border-block-end: 1.5px solid currentColor;
  opacity: 0;
  transform: translateY(-0.15em) rotate(45deg);
  transition: opacity var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
/* THE TOOLBAR ABOVE A TABLE.
 *
 * Filtering had no definition at all, so every consumer invented a search box and put it wherever
 * it landed. The count belongs beside it: a filter that hides rows without saying how many it left
 * is a filter that looks like an empty table. */
.table-toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-block-end: var(--space-3);
}
/* A FILTER BOX IS NOT A BANNER.
   `flex: 1 1` let it grow into whatever the table was wide, so a four-column log gave it 400px and
   a twelve-column one gave it 900 — a search field the width of the page, with the count stranded
   at the far edge. It takes the room it needs and gives the rest back. */
.table-toolbar .input { flex: 0 1 20rem; min-inline-size: 0; }
.table-count {
  margin-inline-start: auto;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--color-neutral-600);
}

.table th[aria-sort] button { color: var(--color-action); }
.table th[aria-sort] button::after { opacity: 1; }
.table th[aria-sort="ascending"] button::after { transform: translateY(0.1em) rotate(-135deg); }

/* --- selection --- */

.table td:has(.checkbox), .table th:has(.checkbox) { inline-size: var(--space-12); }
.table tbody tr[aria-selected="true"] td { background: var(--color-accent-100); }

/* --- empty state --- */

.table-empty {
  display: grid;
  place-items: center;
  gap: var(--space-2);
  padding: var(--space-16) var(--space-6);
  text-align: center;
  color: var(--color-neutral-600);
}
.table-empty-title { font-family: var(--font-heading); font-weight: var(--font-heading-weight); font-size: var(--text-base); color: var(--color-neutral-900); }

/* --- a table somebody writes into --- */

/* THE KEYS ARE KNOWN AND THE FACTS ARE NOT.
 *
 * A grid is `.table` with the cells opened up: the rows already exist, their keys came out of the
 * data, and what is missing is what only the business holds. So the chrome is the table's and the
 * only thing added is the writing. */
.grid-table td { padding-block: var(--space-1); }

/* OWNED SAYS WHO FILLS IT.
 *
 * In twenty columns, which ones a source sends and which ones somebody has to key is the whole map
 * of the work — and a tint alone leaves it to be guessed at, so the head says the words. */
.grid-table th[data-owned] { background: var(--color-accent-100); }
.grid-head-owned {
  display: block;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--color-neutral-600);
}

/* A CELL IS A CELL UNTIL SOMEBODY REACHES FOR IT.
 *
 * Two hundred boxed inputs is a wall of boxes, and the values a reader came to compare are lost
 * between the edges. The border is drawn on hover and on focus, which is when it is the
 * affordance rather than decoration. */
.grid-cell {
  inline-size: 100%;
  /* An input has no intrinsic width, so its column would clip every value. */
  min-inline-size: 11rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-2);
  font: inherit;
  text-align: start;
  color: inherit;
  background: none;
  outline: none;
}
.grid-cell::placeholder { color: var(--color-neutral-400); }
.grid-cell:hover { background: var(--color-bg); border-color: var(--color-neutral-200); }
.grid-cell:focus {
  background: var(--color-bg);
  border-color: var(--color-action);
  box-shadow: 0 0 0 var(--ring-width) var(--color-accent-200);
}

/* HOW FAR THE KEYING HAS GOT, on the grid's own edge: a scrollbar says how much page is left and
   not how much work, and the two stop agreeing the moment half the cells are filled. */
.grid-progress {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
  color: var(--color-neutral-700);
}
.grid-progress strong { font-variant-numeric: tabular-nums; color: var(--color-neutral-900); }
.grid-progress-share {
  margin-inline-start: auto;
  font-variant-numeric: tabular-nums;
  color: var(--color-neutral-600);
}
.grid-progress-share[data-done] { color: var(--color-success-700); font-weight: 500; }

/* --------------------------------------------------------------------- tag */

/* Tags carry status, not decoration. Every variant here maps to a meaning;
   there is deliberately no "just make it blue" option. */
.tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0 var(--space-2);
  min-block-size: var(--space-6);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
}

.tag svg { width: 1em; height: 1em; flex: none; }

.tag-neutral { background: var(--color-neutral-100); color: var(--color-neutral-700); }
.tag-accent  { background: var(--color-accent-100);  color: var(--color-accent-700); }
.tag-success { background: var(--color-success-100); color: var(--color-success-700); }
.tag-warning { background: var(--color-warning-100); color: var(--color-warning-700); }
.tag-danger  { background: var(--color-danger-100);  color: var(--color-danger-700); }

/* Outline reads quieter in a dense table than a filled tint does. */
.tag-outline { background: transparent; border-color: var(--color-neutral-300); color: var(--color-neutral-700); }

/* A status dot for rows where even a tint is too much weight. */
.tag-dot::before {
  content: "";
  width: var(--space-2);
  height: var(--space-2);
  border-radius: var(--radius-full);
  background: currentColor;
  flex: none;
}

/* What a set of statements adds up to when they do not agree.
 *
 * THE TRACK IS ALWAYS FULL WIDTH; the segments inside it are not. That is what carries "five people
 * said this and one person said that" — a bar filling a third of its track has said something the
 * proportions alone cannot. */
.tally {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.tally-track {
  display: flex;
  /* No gap. Adjacent segments are parts of ONE quantity; a gutter between them reads as separate
   * bars, which is the reading this component exists to prevent. */
  inline-size: 100%;
  block-size: var(--space-1);
  border-radius: var(--radius-full);
  background: var(--color-neutral-100);
  overflow: hidden;
}

.tally-part {
  display: block;
  block-size: 100%;
  background: var(--color-neutral-400);
  /* A segment rounded to one statement can otherwise vanish entirely at these heights. */
  min-inline-size: 2px;
}

.tally-part[data-tone='success'] { background: var(--color-success); }
.tally-part[data-tone='warning'] { background: var(--color-warning); }
.tally-part[data-tone='danger'] { background: var(--color-danger); }

.tally-read {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  margin: 0;
  font-size: var(--text-xs);
  line-height: var(--leading-xs);
  color: var(--color-neutral-600);
}

.tally-count b {
  font-weight: 500;
  color: var(--color-neutral-900);
}

/* ------------------------------------------------- gap-fill: token input */

/* A removable tag. The × is a real button, not an ::after — a decoration
   cannot be reached by keyboard, and removing a label is a real action. */
.tag-remove {
  display: grid;
  place-items: center;
  inline-size: var(--space-4);
  block-size: var(--space-4);
  margin-inline-start: var(--space-1);
  margin-inline-end: calc(var(--space-1) * -1);
  border: 0;
  border-radius: var(--radius-full);
  background: transparent;
  color: currentColor;
  opacity: 0.75;
  cursor: pointer;
}
.tag-remove:hover { opacity: 1; background: color-mix(in srgb, currentColor 16%, transparent); }
.tag-remove svg { inline-size: 0.8em; block-size: 0.8em; }

/* The field grows with its tokens; the text input is the last flex child so
   the caret always sits after the final token. */
.token-input {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  min-block-size: var(--control-h-md);
  padding: var(--space-1) var(--space-2);
  background: var(--color-bg);
  border: 1px solid var(--color-neutral-300);
  border-radius: var(--radius-md);
  cursor: text;
}
.token-input:focus-within {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 18%, transparent);
}
.token-input[aria-disabled="true"] { background: var(--color-surface); cursor: not-allowed; }
.token-input-field {
  flex: 1;
  min-inline-size: var(--space-16);
  border: 0;
  outline: none;
  background: transparent;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--color-neutral-900);
  padding-block: var(--space-1);
}
.token-input-field::placeholder { color: var(--color-neutral-500); }

/* --- checklist ------------------------------------------------------- */

.checklist { display: grid; gap: var(--space-1); margin: 0; padding: 0; list-style: none; }
.checklist-item {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--color-neutral-800);
  cursor: pointer;
}
.checklist-item:hover { background: var(--color-neutral-100); }
/* Done items recede rather than disappear — the record of what was checked is
   part of the audit trail.
   neutral-600, not 500: 500 is 3.17:1 and this system's own rule is that it carries chrome, never
   text. A struck item is quieter, not unreadable. */
.checklist-item:has(:checked) .checklist-text {
  color: var(--color-neutral-600);
  text-decoration: line-through;
}

/* A MANIFEST IS NOT A TASK LIST.
 *
 * Struck-through-and-quiet says "done, and behind you". On a list enumerating what an irreversible
 * action is ABOUT TO WRITE it says the opposite of the truth: the captured items read as cancelled
 * and the one still missing is the most legible thing in the dialog. Same control, opposite
 * meaning, so the meaning is declared. */
.checklist[data-purpose="manifest"] .checklist-item:has(:checked) .checklist-text {
  color: inherit;
  text-decoration: none;
}
.checklist-text { flex: 1; min-inline-size: 0; }
.checklist-note { display: block; font-size: var(--text-xs); color: var(--color-neutral-600); text-decoration: none; }

/* --- comment / activity --------------------------------------------- */

.comment { display: flex; gap: var(--space-3); }
.comment-body { flex: 1; min-inline-size: 0; }
.comment-head { display: flex; align-items: baseline; gap: var(--space-2); }
.comment-author { font-size: var(--text-sm); font-weight: 500; color: var(--color-neutral-900); }
.comment-time { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-neutral-600); }
.comment-text { font-size: var(--text-sm); color: var(--color-neutral-700); margin-block-start: var(--space-1); }
.comment-compose { display: flex; gap: var(--space-3); align-items: flex-start; }
.comment-compose .textarea { min-block-size: var(--control-h-lg); }
.comment-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-block-start: var(--space-2); }

/* -------------------------------------------------------------- transcript */

/* WHAT PEOPLE SAID, LAID OUT TO BE WORKED THROUGH.
 *
 * Not a chat log and not a comment thread. An hour-long interview is three
 * hundred stretches of somebody talking, and the reader is not following along
 * — they are going through it marking what each stretch is evidence of.
 *
 * So the words get the measure and everything else gets out of their way. */

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

.transcript-turn {
  display: grid;
  /* The words, then a margin for what is known about them. A single column with
     the marks inline would put a tag in the middle of a sentence. */
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-3) var(--space-3);
  border-radius: var(--radius-md);
}

/* A ROW YOU CAN SEE YOURSELF ON. Reading three hundred turns, losing your place
   costs more than the highlight does. */
/* The hover is the SURFACE, not another neutral step. A row lifting to a
   different grey than the one `[data-current]` uses would give the page two
   selection states that mean different things and look nearly the same. */
.transcript-turn:hover { background: var(--color-surface); }
.transcript-turn[data-current] { background: var(--color-neutral-100); }

.transcript-speaker {
  grid-column: 1;
  margin: 0;
  font-size: var(--text-xs);
  font-weight: 600;
  /* QUIETER THAN WHAT WAS SAID. The speaker is how you follow the exchange; the
     words are the evidence. A name at body size and weight makes a transcript
     read as a list of people rather than as a record of what was said — and
     this record never attributes a finding by name anyway. */
  color: var(--color-neutral-600);
  letter-spacing: 0.02em;
}

.transcript-said {
  grid-column: 1;
  /* PROSE, SO IT KEEPS THE MEASURE. A speaker's stretch runs to a paragraph and
     a line that crosses the whole window is a line the eye loses on the return. */
  /* A MEASURE IS IN CHARACTERS, which is why this is `ch` and not a spacing
     token — the same reasoning `.reader` and the field family already follow. */
  max-inline-size: 72ch;
  font-size: var(--text-sm);
  line-height: var(--leading-lg);
  color: var(--color-neutral-900);
}

.transcript-margin {
  grid-column: 2;
  grid-row: 1 / span 2;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  /* The column does not move when a control appears, so the words do not reflow
     under the pointer. */
  min-inline-size: var(--space-24);
  justify-content: flex-end;
}

/* WHAT IT IS EVIDENCE OF: data, so it is always visible. */
.transcript-mark { display: inline-flex; gap: var(--space-1); }

/* WHAT CAN BE DONE WITH IT is revealed on interaction — `.on-demand` in
   `base.css`, because a matrix of sixty-six requirements has the same problem as
   a transcript of three hundred turns. A turn being worked on keeps its control
   out, since the reader is about to use it. */
.transcript-turn[data-current] .on-demand { opacity: 1; }

@media (max-width: 720px) {
  /* The margin stops being a margin. Beside a phone-width column it leaves the
     words about twenty characters, which is not a measure, it is a gutter. */
  .transcript-turn { grid-template-columns: minmax(0, 1fr); }
  .transcript-margin { grid-column: 1; grid-row: auto; justify-content: flex-start; }
}

/* ------------------------------------------------------------------ upload */

/* A label wrapping a real file input. The dashed edge says "target" without a
   fill, which would read as a disabled surface on a light ground. */
.dropzone {
  display: grid;
  place-items: center;
  gap: var(--space-2);
  padding: var(--space-10) var(--space-6);
  border: 1px dashed var(--color-neutral-300);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  text-align: center;
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
.dropzone:hover { border-color: var(--color-neutral-400); }
.dropzone:focus-within { outline: var(--ring-width) solid var(--ring-color); outline-offset: var(--ring-offset); }
.dropzone[data-dragging] { border-color: var(--color-action); background: var(--color-accent-100); }
.dropzone[aria-disabled="true"] { cursor: not-allowed; opacity: 0.45; }

.dropzone-icon { color: var(--color-neutral-500); }
.dropzone-icon svg { inline-size: var(--space-8); block-size: var(--space-8); }
.dropzone-title { font-size: var(--text-sm); font-weight: 500; color: var(--color-neutral-900); }
.dropzone-hint { font-size: var(--text-xs); color: var(--color-neutral-600); }

.file-list {
  display: grid;
  /* minmax(0,1fr): a grid item defaults to min-width:auto and will not shrink
     below its content, so the ellipsis on .file-name never gets a chance. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2);
  margin-block: var(--space-4) 0;
  padding: 0;
  list-style: none;
}
.file-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
}
.file-item svg { inline-size: var(--space-5); block-size: var(--space-5); flex: none; color: var(--color-neutral-500); }
.file-name { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--color-neutral-900); }
.file-size {
  margin-inline-start: auto;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--color-neutral-600);
  flex: none;
}
.file-item[data-state="error"] { border-color: var(--color-danger-300); background: var(--color-danger-100); }
.file-item[data-state="error"] .file-name { color: var(--color-danger-700); }

/* --------------------------------------------------- forced colours & print */
/* Loaded last so these overrides win. Named zz- for that reason. */

/* --- Windows High Contrast / forced-colors ---------------------------- */

/* In forced-colors the OS replaces every colour. Anything that conveyed
   meaning through background alone becomes invisible, so state has to be
   re-expressed with borders and system colours. This matters more than usual
   here: forced-colors use is concentrated in exactly the government and
   accessibility-audited environments this system is built for. */

@media (forced-colors: active) {
  /* Surfaces lose their tint — give them an edge so they remain objects. */
  .card, .panel, .dialog, .toast, .alert, .drawer-panel, .tooltip {
    border: 1px solid CanvasText;
  }

  /* Solid buttons are indistinguishable from ghost ones without a border. */
  .btn { border: 1px solid ButtonBorder; }
  .btn-primary, .btn-destructive { background: ButtonFace; color: ButtonText; }
  .btn:hover:not([disabled]) { background: Highlight; color: HighlightText; }
  .btn[disabled], [disabled], .is-disabled { color: GrayText; opacity: 1; }

  /* The focus ring is the one thing that must never be lost. */
  :focus-visible { outline: 2px solid Highlight; outline-offset: 2px; }
  .input:focus, .select:focus, .textarea:focus {
    outline: 2px solid Highlight;
    outline-offset: 1px;
    box-shadow: none;
  }

  /* Selected / current state is a tint everywhere; make it a real mark. */
  .nav-item[aria-current], .sidebar-item[aria-current],
  .pagination-item[aria-current], .table tbody tr[aria-selected="true"] td {
    background: Highlight;
    color: HighlightText;
    forced-color-adjust: none;
  }
  .tab[aria-selected="true"]::after, .tab[aria-current]::after { background: Highlight; }

  /* Tags read only by fill — they need an outline to survive. */
  .tag { border: 1px solid CanvasText; }

  /* Progress and steps are pure colour; Highlight is the only signal left. */
  .progress { border: 1px solid CanvasText; }
  .progress-bar, .switch:checked, .checkbox:checked, .radio:checked,
  .timeline-item[data-state="current"] .timeline-marker {
    background: Highlight;
    forced-color-adjust: none;
  }
  .step-marker, .progress-mark { border: 1px solid CanvasText; }
  /* The progress family's glyphs are masks painted in a background colour, which forced colours
     would repaint as the page's own ground. They are drawn in the text colour instead, so a tick,
     a cross, a dash and a `!` survive on every disc and beside every label. */
  .step-marker::after, .progress-mark::after, .progress-glyph,
  .stepper-rail .step[data-state] .step-label::before {
    background: CanvasText !important;
    forced-color-adjust: none;
  }
  /* Rings, spinners and running marks: the arc in the highlight, the track in grey text. */
  .progress-ring, .progress-ring-field, .spinner, .progress-mark[data-state="current"] {
    --progress-fill: Highlight !important;
    --progress-tone: Highlight !important;
    --progress-track: GrayText !important;
    forced-color-adjust: none;
  }
  /* A rail and a segmented bar: every segment keeps an edge; done, current and ended ones fill. The
     words under the rail and the legend's counts were always there. */
  .stepper-rail .step-marker, .progress-segment, .progress-legend-swatch {
    border: 1px solid CanvasText;
    background: Canvas !important;
    forced-color-adjust: none;
  }
  .stepper-rail .step[data-state] .step-marker, .stepper-rail .step[aria-current="step"] .step-marker,
  .progress-segment[data-state]:not([data-state="queued"]),
  .progress-legend-swatch[data-state]:not([data-state="queued"]),
  .progress[data-tone] .progress-bar, .progress-field[data-tone] .progress-bar { background: Highlight !important; }
  .file-progress { border-color: CanvasText; }

  /* Decoration carries no meaning and only adds noise here. */
  .bg-dot-grid, .bg-noise { background-image: none; }
  .section-band { background: Canvas; color: CanvasText; border: 1px solid CanvasText; }
  .skeleton { animation: none; background: Canvas; border: 1px solid GrayText; }

  /* A target tick is a filled bar; the system draws it in text colour or it is gone. */
  .meter-threshold { background: CanvasText; forced-color-adjust: none; }
  /* Tints vanish, which is why a matrix cell and a diff never rest on them: the mark, the strike
     and the underline remain. The focused cell still needs an edge. */
  .matrix-cell:focus-visible { outline: 2px solid Highlight; }
  .diff-ins, .diff-del { forced-color-adjust: auto; }

  /* Live work. The pulse is a filled dot; it is drawn in the highlight or it is gone. The lanes
     and the section being written lose their tint, so each keeps an edge; the words were always
     there. */
  .live-pulse, .live-pulse::after { background: Highlight; forced-color-adjust: none; }
  .lane { border: 1px solid CanvasText; }
  .live-section[data-live="writing"] { border-inline-start-color: Highlight; }
  .live-section[data-live="written"], .live-section[data-live="queued"],
  .live-section[data-live="failed"] { border-inline-start-color: CanvasText; }
  .table tbody tr:has(.live-progress[data-state="active"]) > :first-child { box-shadow: inset 4px 0 0 Highlight; }
  /* A key, a citation and the stepping chip are tinted boxes; they keep their edges. */
  .kbd, .citation, .pager-chip { border-color: CanvasText; }
  .contents-item:has(.contents-link[aria-current]) { border-inline-start-color: Highlight; }
  .contents-link[aria-current] { color: Highlight; }
  [data-list-item]:focus-visible { outline: 2px solid Highlight; }

  /* The layered graph. Its lines are drawn in the text colour and a lit one in the highlight; a
     node's state was never the tint alone — pressed is the highlight edge, uncovered and a drop
     target are dashed, and the names say both. */
  .layered-graph-edges path { stroke: CanvasText; }
  .layered-graph-edges path[data-lit] { stroke: Highlight; }
  .layered-graph-node { border-color: CanvasText; }
  .layered-graph-node[aria-pressed="true"], .layered-graph-node[data-over],
  .layered-graph-node[data-carried] { border-color: Highlight; border-width: 2px; }
  .layered-graph[data-highlight] .layered-graph-node[data-highlighted] { box-shadow: none; outline: 2px solid Highlight; }
  /* A tool chip and a card waiting for an answer keep their edges; refused and the spend are
     tags, which carry their own words. */
  .tool-chip-toggle, .confirm-card { border-color: CanvasText; }
  .confirm-card:focus-visible { outline-color: Highlight; }
}

/* --- print ------------------------------------------------------------ */

/* Consulting output gets printed and attached to documents. A design system
   that has never been printed produces deliverables with black page-wide
   headers and invisible link targets. */

@media print {
  :root { --color-bg: #fff; --color-surface: #fff; }

  body { background: #fff; color: #000; }

  /* Interface chrome is not part of a document. */
  .nav, .sidebar, .hamburger, .drawer-scrim, .drawer-panel,
  .toast-region, .pagination, .flow-footer, .skeleton, .spinner {
    display: none !important;
  }

  /* Elevation and tint do not survive a printer; an edge does. */
  .card, .panel, .dialog, .alert, .toast {
    box-shadow: none;
    border: 1px solid #999;
    background: #fff;
  }
  .section-band { background: #fff; color: #000; border: 1px solid #999; }
  .section-band h1, .section-band h2, .section-band h3, .section-band p { color: #000; }
  .bg-dot-grid, .bg-noise { background-image: none; }

  /* Do not split an object across a page boundary. */
  .card, .panel, .state, .timeline-item, .alert, tr, .step { break-inside: avoid; }
  h1, h2, h3, h4, .card-title, .panel-title { break-after: avoid; }

  /* A long table must repeat its header on each page. */
  thead { display: table-header-group; }
  .table-wrap, .matrix-wrap { overflow: visible; }
  .hover-card { display: none !important; }

  /* A run in progress is a screen's state, not the document's. The section prints as text, at
     full ink; a pulse prints as nothing. A contents rail and a stepping chip are navigation. */
  .live-pulse, .pager-chip, .table-pager, .contents-check { display: none !important; }
  .live-section { background: none !important; opacity: 1 !important; border-inline-start-color: transparent !important; }
  .live-section > * { animation: none !important; }
  /* A graph prints as drawn, in ink. A card's buttons and a chip's caret are the screen's. */
  .layered-graph-edges path { stroke: #999 !important; }
  .layered-graph-edges path[data-lit] { stroke: #000 !important; }
  .layered-graph-node { background: none !important; }
  .confirm-card-actions, .tool-chip-caret { display: none !important; }
  .citation { background: none; border: 1px solid #999; color: #000; }
  .matrix th, .matrix td { position: static !important; }

  /* A printed link whose target is invisible is a dead reference. */
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 0.85em; color: #555; }
  a[href^="#"]::after, .btn::after, .nav-brand::after { content: none; }

  @page { margin: 18mm; }
}

/* --- reduced motion, and the three things that must still move -------- */

/* `base.css` collapses every animation to a single 0.01ms iteration when a reader asks for reduced
 * motion. That is the standard recipe and it is right for decoration — a skeleton's sweep, a
 * toast's entrance, a card lifting under the cursor all say the same thing standing still.
 *
 * It is wrong for the three controls whose ENTIRE JOB is to say "something is happening". Collapsed,
 * a spinner freezes mid-turn and reads as hung; an indeterminate progress bar parks at whatever
 * fraction it had reached and reads as a DETERMINATE bar stuck at 40%. Stillness there is not calm,
 * it is a wrong answer to the only question the control exists to answer.
 *
 * `prefers-reduced-motion` asks for less motion, not for none. What triggers a vestibular response
 * is large-area and parallax movement; a 16px ring is neither. So these keep turning — slower, and
 * without the sweep across a wide surface that the skeleton and the progress bar rely on. */
@media (prefers-reduced-motion: reduce) {
  .spinner,
  .progress-ring-indeterminate::before,
  .progress-mark[data-state="current"],
  .btn.is-loading::after {
    animation-duration: 1.8s !important;
    animation-iteration-count: infinite !important;
  }

  /* The bar slides across its whole track, which IS a wide-area sweep. It used to breathe in place
     at full width instead, which read as a pulse, not as progress. Now a third of the track
     appears at the start, then the middle, then the end — it fades out, MOVES WHILE INVISIBLE, and
     fades in one place further on. Nothing travels across the screen, and it still reads as
     getting somewhere. */
  .progress-indeterminate .progress-bar {
    animation: progress-hop 2.4s linear infinite !important;
    inline-size: calc(100% / 3);
  }

  /* A PULSE IS NOT ONE OF THE THREE. It always sits beside words that say the same thing, so it
     can stand still — but a still dot reads as a plain status dot, so the ring stays, as a halo,
     rather than vanishing at the end of a collapsed animation. */
  .live-pulse::after {
    animation: none !important;
    transform: scale(2);
    opacity: 0.25;
  }
  .live-pulse[data-still]::after { opacity: 0; }

  /* A chip's caret turns and a graph node's edge eases; both are decoration, and both simply
     arrive. Nothing here indicates activity — a running tool call says so with a pulse and words. */
  .tool-chip-caret, .layered-graph-node { transition: none; }
}
@keyframes progress-hop {
  0%      { transform: translateX(0);    opacity: 0; }
  8%      { opacity: 1; }
  25%     { opacity: 1; }
  33.32%  { transform: translateX(0);    opacity: 0; }
  33.33%  { transform: translateX(100%); opacity: 0; }
  41%     { opacity: 1; }
  58%     { opacity: 1; }
  66.65%  { transform: translateX(100%); opacity: 0; }
  66.66%  { transform: translateX(200%); opacity: 0; }
  75%     { opacity: 1; }
  92%     { opacity: 1; }
  100%    { transform: translateX(200%); opacity: 0; }
}
