/* The theme axis: colour, and only colour. Selected by `data-theme` on <html>
 * (persistence/appStartup.ts). See docs/ui-design/tokens.md § Token axes for the contract with
 * tokens-style.css, the primitives/derived split, the bridge tokens, and dark-mode precedence.
 */
:root {
  --bg: #ffffff;        --bg-subtle: #fafafa;   --bg-hover: #f6f6f6;   --bg-selected: #eeeeee;
  --border: #e2e2e2;    --border-strong: #c9c9c9;
  --text: #242424;      --text-muted: #747474;  --text-faint: #a8a8a8;
  --accent: #242424;    --focus: #242424;
  --add-bg: #e9f7ee;    --add-marker: #1a7f37;
  --del-bg: #fceef0;    --del-marker: #cf222e;
  --add-word-bg: #aceebb; --del-word-bg: #f5b7bd;
  --hunk-bg: #eef4fb;   --hunk-text: #57606a;
  --warn: #9a6700;      --badge-border: #d4d4d4;
  --shadow-popover: rgba(0, 0, 0, 0.12);

  /* docs/ui-design/tokens.md § Token axes covers why these two are self-description rather than
     colour. Dark blocks below flip both. */
  --is-dark: 0;
  --color-scheme: light;
}

/* Derived colour, declared once, following every theme (docs/ui-design/tokens.md § Token axes). These
   also fill gaps that used to be referenced-but-undefined, where each call site invented its own
   fallback hex (two different reds for --danger; a dark-only #111 for --surface-sunken that broke
   under light themes). */
:root {
  --danger: var(--del-marker);
  --danger-fg: var(--del-marker);
  --success: var(--add-marker);
  --success-fg: var(--add-marker);
  --surface-sunken: var(--bg-subtle);
  /* Text drawn on --accent. The page background is the established contrast here (see
     .notify-count). Was a hardcoded #fff, which is unreadable on the light accents: Nord's cyan,
     Dracula's purple, Catppuccin Latte's blue. */
  --accent-fg: var(--bg);

  /* Status dots. docker.css hardcoded Apple system colours (#34c759/#ff9f0a/#ff453a) that ignored
     the theme entirely and had no light/dark variants. */
  --state-ok: var(--add-marker);
  --state-warn: var(--warn);
  --state-bad: var(--del-marker);

  /* A brand's own colour, pulled toward the theme's foreground until it is readable on it. A brand hex
     is authored against white: OpenAI's #412991 sits at 1.7:1 on a dark pane, well under the 3:1 a
     glyph wants, and no single authored hex can clear both directions. 45% is the most brand that
     clears 3:1 in all twelve themes; solarized dark with that purple is the tight one, at 3.03:1.
     --brand is set per element by brandStyle() in ui/brandMarks.ts, so this resolves per mark; with
     none set it is the accent, mixed the same way. See docs/ui-design/icons.md § Brand color. */
  --brand-legible: color-mix(in srgb, var(--brand, var(--accent)) 45%, var(--text));

  /* Theme-independent: a find highlight has to stay legible over syntax-coloured code in every
     theme, and the modal scrim is black in all 12 today. */
  --find-hit-bg: rgba(255, 200, 0, 0.28);
  --find-current-bg: rgba(255, 145, 0, 0.6);
  --scrim-color: #000000;

  /* Syntax colours for code GitHub already highlighted (a suggested change), where each token
     arrives as a class instead of a Shiki colour. Theme-independent for the same reason Shiki is:
     these are github-light and github-dark, the two themes infra/highlight/shiki.ts loads, so a
     suggestion matches the diff row it sits under. */
  --syntax-comment: light-dark(#6a737d, #6a737d);
  --syntax-keyword: light-dark(#d73a49, #f97583);
  --syntax-string: light-dark(#032f62, #9ecbff);
  --syntax-constant: light-dark(#005cc5, #79b8ff);
  --syntax-entity: light-dark(#6f42c1, #b392f0);
  --syntax-variable: light-dark(#e36209, #ffab70);
  --syntax-tag: light-dark(#22863a, #85e89d);
}

/* Series identity: chart marks that answer "which series is this", never "how is this doing"
   (docs/dashboards/views.md § Views are derived, not chosen from a menu; docs/ui-design/tokens.md § Token axes
   for why these are a defaults set rather than a light/dark pair). All three sit at L≈0.6 in
   oklch, clearing the 3:1 contrast floor against both the light ground (#ffffff) and the dark one
   (#121212), in hues no status tone occupies (ok green #1a7f37, warn amber #9a6700, bad red
   #cf222e, and --accent, which is near-black here). Holding one lightness for both grounds costs
   the grey-scale separation a three-step lightness ramp would have given, so the three are told
   apart by hue alone; a pack that wants better on its own ground restates them. See SERIES_TOKENS
   in ui/tokenAxes.ts. Series four onwards folds to --text-faint. */
:root {
  --viz-series-1: oklch(0.58 0.17 255);
  --viz-series-2: oklch(0.60 0.20 328);
  --viz-series-3: oklch(0.62 0.11 195);
}

/* Sequential scale: a heatmap cell's shade, from the lowest nonzero value (1) to the highest (5).
   The first series hue mixed into the card's ground in five strengths, so it never reads as a status,
   and the order survives without colour: each step moves further from the ground, darker on a light
   theme and lighter on a dark one, in every theme. Not light-dark(): the build rewrites it into
   variables nothing sets. The empty step is no token; it draws --bg-subtle. See SCALE_TOKENS in
   kit/tokens/tokenAxes.ts. */
:root {
  --viz-scale-1: color-mix(in oklch, var(--viz-series-1) 20%, var(--card-bg));
  --viz-scale-2: color-mix(in oklch, var(--viz-series-1) 40%, var(--card-bg));
  --viz-scale-3: color-mix(in oklch, var(--viz-series-1) 60%, var(--card-bg));
  --viz-scale-4: color-mix(in oklch, var(--viz-series-1) 80%, var(--card-bg));
  --viz-scale-5: var(--viz-series-1);
}

/* Dark token values, defined once as --dark-* (inert until mapped below). Two paths apply them:
   the manual data-theme="dark" toggle and the OS preference (unless forced light); the mappings
   are thin var() indirections so a dark tweak is made in exactly one place. */
:root {
  --dark-bg: #121212;        --dark-bg-subtle: #1a1a1a;   --dark-bg-hover: #202020;   --dark-bg-selected: #262626;
  --dark-border: #2a2a2a;    --dark-border-strong: #424242;
  --dark-text: #dddddd;      --dark-text-muted: #8d8d8d;  --dark-text-faint: #5d5d5d;
  --dark-accent: #dddddd;    --dark-focus: #dddddd;
  --dark-add-bg: #12261b;    --dark-add-marker: #3fb950;
  --dark-del-bg: #2a1416;    --dark-del-marker: #f85149;
  --dark-add-word-bg: #1f6f33; --dark-del-word-bg: #7a2630;
  --dark-hunk-bg: #161f2b;   --dark-hunk-text: #8b949e;
  --dark-warn: #d29922;      --dark-badge-border: #383838;
  --dark-shadow-popover: rgba(0, 0, 0, 0.42);
}

/* Manual toggle: setting data-theme="dark" on <html> wins over the media query. */
:root[data-theme="dark"] {
  --bg: var(--dark-bg);        --bg-subtle: var(--dark-bg-subtle);   --bg-hover: var(--dark-bg-hover);   --bg-selected: var(--dark-bg-selected);
  --border: var(--dark-border);    --border-strong: var(--dark-border-strong);
  --text: var(--dark-text);      --text-muted: var(--dark-text-muted);  --text-faint: var(--dark-text-faint);
  --accent: var(--dark-accent);    --focus: var(--dark-focus);
  --add-bg: var(--dark-add-bg);    --add-marker: var(--dark-add-marker);
  --del-bg: var(--dark-del-bg);    --del-marker: var(--dark-del-marker);
  --add-word-bg: var(--dark-add-word-bg); --del-word-bg: var(--dark-del-word-bg);
  --hunk-bg: var(--dark-hunk-bg);   --hunk-text: var(--dark-hunk-text);
  --warn: var(--dark-warn);      --badge-border: var(--dark-badge-border);
  --shadow-popover: var(--dark-shadow-popover);
  --is-dark: 1;  --color-scheme: dark;
}

/* docs/ui-design/tokens.md § Token axes covers why this has to work before any JavaScript runs. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: var(--dark-bg);        --bg-subtle: var(--dark-bg-subtle);   --bg-hover: var(--dark-bg-hover);   --bg-selected: var(--dark-bg-selected);
    --border: var(--dark-border);    --border-strong: var(--dark-border-strong);
    --text: var(--dark-text);      --text-muted: var(--dark-text-muted);  --text-faint: var(--dark-text-faint);
    --accent: var(--dark-accent);    --focus: var(--dark-focus);
    --add-bg: var(--dark-add-bg);    --add-marker: var(--dark-add-marker);
    --del-bg: var(--dark-del-bg);    --del-marker: var(--dark-del-marker);
    --add-word-bg: var(--dark-add-word-bg); --del-word-bg: var(--dark-del-word-bg);
    --hunk-bg: var(--dark-hunk-bg);   --hunk-text: var(--dark-hunk-text);
    --warn: var(--dark-warn);      --badge-border: var(--dark-badge-border);
    --shadow-popover: var(--dark-shadow-popover);
    --is-dark: 1;  --color-scheme: dark;
  }
}

/* Named themes (Settings → Appearance). Each restates the 21 primitives; the dark ones also flip
   the three self-description tokens. Hand-synced with settings/themes.ts: settings/themes.test.ts
   reads this file and fails if the two sets drift. */
:root[data-theme="solarized-light"] {
  --bg: #fdf6e3;        --bg-subtle: #f5eeda;   --bg-hover: #eee8d5;   --bg-selected: #e3ddc8;
  --border: #e0d9c4;    --border-strong: #cfc8b3;
  --text: #586e75;      --text-muted: #839496;  --text-faint: #93a1a1;
  --accent: #268bd2;    --focus: #268bd2;
  --add-bg: #e8ecc9;    --add-marker: #859900;
  --del-bg: #f6e0d5;    --del-marker: #dc322f;
  --add-word-bg: #cdd68f; --del-word-bg: #f0c0b5;
  --hunk-bg: #dfe9ee;   --hunk-text: #657b83;
  --warn: #b58900;      --badge-border: #d6cfba;
  --shadow-popover: rgba(0, 0, 0, 0.12);
}
:root[data-theme="solarized-dark"] {
  --bg: #002b36;        --bg-subtle: #073642;   --bg-hover: #0a3f4d;   --bg-selected: #0e4653;
  --border: #0a3d49;    --border-strong: #17505d;
  --text: #93a1a1;      --text-muted: #839496;  --text-faint: #586e75;
  --accent: #268bd2;    --focus: #268bd2;
  --add-bg: #10352b;    --add-marker: #859900;
  --del-bg: #3a1f1e;    --del-marker: #dc322f;
  --add-word-bg: #35521f; --del-word-bg: #6e2b28;
  --hunk-bg: #073642;   --hunk-text: #839496;
  --warn: #b58900;      --badge-border: #17505d;
  --shadow-popover: rgba(0, 0, 0, 0.42);
  --is-dark: 1;  --color-scheme: dark;
}
:root[data-theme="monokai"] {
  --bg: #272822;        --bg-subtle: #2d2e28;   --bg-hover: #34352f;   --bg-selected: #3e3d32;
  --border: #3c3d37;    --border-strong: #55564f;
  --text: #f8f8f2;      --text-muted: #a59f85;  --text-faint: #75715e;
  --accent: #f92672;    --focus: #f92672;
  --add-bg: #2c3321;    --add-marker: #a6e22e;
  --del-bg: #3a1f28;    --del-marker: #f92672;
  --add-word-bg: #4a5a1f; --del-word-bg: #7a2540;
  --hunk-bg: #2b2f33;   --hunk-text: #75715e;
  --warn: #e6db74;      --badge-border: #55564f;
  --shadow-popover: rgba(0, 0, 0, 0.42);
  --is-dark: 1;  --color-scheme: dark;
}
:root[data-theme="nord"] {
  --bg: #2e3440;        --bg-subtle: #323949;   --bg-hover: #3b4252;   --bg-selected: #434c5e;
  --border: #3b4252;    --border-strong: #4c566a;
  --text: #eceff4;      --text-muted: #9aa5b8;  --text-faint: #7b869c;
  --accent: #88c0d0;    --focus: #88c0d0;
  --add-bg: #333d33;    --add-marker: #a3be8c;
  --del-bg: #3a2f31;    --del-marker: #bf616a;
  --add-word-bg: #4a5a3f; --del-word-bg: #6e3f43;
  --hunk-bg: #333b49;   --hunk-text: #7b869c;
  --warn: #ebcb8b;      --badge-border: #4c566a;
  --shadow-popover: rgba(0, 0, 0, 0.42);
  --is-dark: 1;  --color-scheme: dark;
}
:root[data-theme="catppuccin-latte"] {
  --bg: #eff1f5;        --bg-subtle: #e6e9ef;   --bg-hover: #dce0e8;   --bg-selected: #ccd0da;
  --border: #ccd0da;    --border-strong: #bcc0cc;
  --text: #4c4f69;      --text-muted: #6c6f85;  --text-faint: #9ca0b0;
  --accent: #1e66f5;    --focus: #1e66f5;
  --add-bg: #dcecd7;    --add-marker: #40a02b;
  --del-bg: #f6dbdf;    --del-marker: #d20f39;
  --add-word-bg: #b8ddb0; --del-word-bg: #f0b8c0;
  --hunk-bg: #dde6f7;   --hunk-text: #6c6f85;
  --warn: #df8e1d;      --badge-border: #bcc0cc;
  --shadow-popover: rgba(0, 0, 0, 0.12);
}
:root[data-theme="catppuccin-frappe"] {
  --bg: #303446;        --bg-subtle: #292c3c;   --bg-hover: #414559;   --bg-selected: #51576d;
  --border: #414559;    --border-strong: #51576d;
  --text: #c6d0f5;      --text-muted: #a5adce;  --text-faint: #737994;
  --accent: #8caaee;    --focus: #8caaee;
  --add-bg: #333f39;    --add-marker: #a6d189;
  --del-bg: #3f2f34;    --del-marker: #e78284;
  --add-word-bg: #4c6644; --del-word-bg: #6e4247;
  --hunk-bg: #2f3a4d;   --hunk-text: #a5adce;
  --warn: #e5c890;      --badge-border: #51576d;
  --shadow-popover: rgba(0, 0, 0, 0.42);
  --is-dark: 1;  --color-scheme: dark;
}
:root[data-theme="catppuccin-macchiato"] {
  --bg: #24273a;        --bg-subtle: #1e2030;   --bg-hover: #363a4f;   --bg-selected: #494d64;
  --border: #363a4f;    --border-strong: #494d64;
  --text: #cad3f5;      --text-muted: #a5adcb;  --text-faint: #6e738d;
  --accent: #8aadf4;    --focus: #8aadf4;
  --add-bg: #283a30;    --add-marker: #a6da95;
  --del-bg: #3a2830;    --del-marker: #ed8796;
  --add-word-bg: #46613f; --del-word-bg: #6e3a45;
  --hunk-bg: #232f45;   --hunk-text: #a5adcb;
  --warn: #eed49f;      --badge-border: #494d64;
  --shadow-popover: rgba(0, 0, 0, 0.42);
  --is-dark: 1;  --color-scheme: dark;
}
:root[data-theme="catppuccin-mocha"] {
  --bg: #1e1e2e;        --bg-subtle: #181825;   --bg-hover: #313244;   --bg-selected: #45475a;
  --border: #313244;    --border-strong: #45475a;
  --text: #cdd6f4;      --text-muted: #a6adc8;  --text-faint: #6c7086;
  --accent: #89b4fa;    --focus: #89b4fa;
  --add-bg: #22352b;    --add-marker: #a6e3a1;
  --del-bg: #3a2530;    --del-marker: #f38ba8;
  --add-word-bg: #43653e; --del-word-bg: #6e3648;
  --hunk-bg: #1e2b45;   --hunk-text: #a6adc8;
  --warn: #f9e2af;      --badge-border: #45475a;
  --shadow-popover: rgba(0, 0, 0, 0.42);
  --is-dark: 1;  --color-scheme: dark;
}
:root[data-theme="one-dark"] {
  --bg: #282c34;        --bg-subtle: #2c313a;   --bg-hover: #333842;   --bg-selected: #3a3f4b;
  --border: #3a3f4b;    --border-strong: #4b5263;
  --text: #abb2bf;      --text-muted: #8b93a1;  --text-faint: #5c6370;
  --accent: #61afef;    --focus: #61afef;
  --add-bg: #2b3328;    --add-marker: #98c379;
  --del-bg: #37282c;    --del-marker: #e06c75;
  --add-word-bg: #47632f; --del-word-bg: #6e3a40;
  --hunk-bg: #24303f;   --hunk-text: #5c6370;
  --warn: #e5c07b;      --badge-border: #4b5263;
  --shadow-popover: rgba(0, 0, 0, 0.42);
  --is-dark: 1;  --color-scheme: dark;
}
:root[data-theme="dracula"] {
  --bg: #282a36;        --bg-subtle: #21222c;   --bg-hover: #343746;   --bg-selected: #44475a;
  --border: #3a3d4d;    --border-strong: #44475a;
  --text: #f8f8f2;      --text-muted: #a9abc0;  --text-faint: #6272a4;
  --accent: #bd93f9;    --focus: #bd93f9;
  --add-bg: #26362b;    --add-marker: #50fa7b;
  --del-bg: #3a2530;    --del-marker: #ff5555;
  --add-word-bg: #2f6b3f; --del-word-bg: #6e3140;
  --hunk-bg: #2b2f4a;   --hunk-text: #6272a4;
  --warn: #f1fa8c;      --badge-border: #44475a;
  --shadow-popover: rgba(0, 0, 0, 0.42);
  --is-dark: 1;  --color-scheme: dark;
}

/* The style axis: shape, typography, space, density, chrome, motion. Never colour.
 * Selected by `data-style` on <html> (persistence/appStartup.ts). See docs/ui-design/tokens.md
 * § Token axes for the contract with tokens-theme.css and for why this file is also the
 * Terminal pack.
 */
:root {
  /* ── Shape ─────────────────────────────────────────────────────────────────────────────────
     Role-scoped, not one --border-w. A single width token sounds tidy until a pack sets it to 0
     to drop row dividers and also deletes the sticky section-header rule and the topbar rule,
     which are structure every pack still wants. */
  --radius-0: 0;      --radius-xs: 2px;   --radius-sm: 3px;   --radius-md: 4px;
  --radius-lg: 8px;   --radius-xl: 12px;  --radius-pill: 999px;  --radius-circle: 50%;

  --radius-control: var(--radius-md);      /* buttons, inputs, selects */
  --radius-surface: var(--radius-0);       /* panes and cards, Terminal is square */
  --radius-popover: var(--radius-md);      /* menus, tooltips, modals */
  --radius-chip: var(--radius-md);         /* aesthetic: a chip may be square or a pill */
  --radius-pill-fixed: var(--radius-pill); /* semantic: a capsule in every pack */
  --radius-marker: var(--radius-xs);       /* word-diff spans, tiny bars */
  /* Legacy alias, still ~57 call sites. All of them are controls and chips (the floating
     surfaces were promoted to --radius-popover), so it points at --radius-control rather than
     the raw scale rung, so a pack that changes control rounding now moves them too. */
  --radius: var(--radius-control);

  /* `0px`, not `0`: a pack points `--marker-w` here, and calc() can't add a unitless 0 to a length. */
  --bw-0: 0px;  --bw: 1px;  --bw-strong: 2px;  --bw-marker: 3px;

  --divider-w: var(--bw);          /* list-row separators, a pack may zero these */
  --chrome-divider-w: var(--bw);   /* … without losing topbar / section-header structure */
  --pane-divider-w: var(--bw);     /* the .pane border-right */
  --pane-bw: var(--bw-0);          /* the other three pane edges, Modern: var(--bw) */
  --control-bw: var(--bw);
  --surface-bw: var(--bw);         /* cards, popovers, modals */
  --marker-w: var(--bw-marker);    /* active-row left rule, Modern/Cute zero this */
  /* docs/ui-design/tokens.md § Border roles covers why --stripe-w is a semantic sibling of --marker-w
     that no pack may zero. */
  --stripe-w: var(--bw-marker);
  --tab-active-w: var(--bw-strong);

  /* docs/ui-design/tokens.md § Border roles covers why these four recipes split by role. */
  --divider: var(--divider-w) solid var(--border);              /* row / list separators */
  --chrome-divider: var(--chrome-divider-w) solid var(--border); /* topbar, section headers */
  --control-border: var(--control-bw) solid var(--border-strong); /* inputs, buttons */
  --surface-border-style: solid;  /* a pack may stitch the floating surfaces with dashed or dotted */
  --surface-border: var(--surface-bw) var(--surface-border-style) var(--border-strong); /* popovers, modals, cards */

  /* ── Space ─────────────────────────────────────────────────────────────────────────────────
     Semantics are restated per pack, not derived from a --density multiplier. calc() against a
     scalar yields fractional pixels, which blur 1px borders on non-retina and make every computed
     value unreadable in devtools. Fourteen literal lines per pack is the cheaper trade. */
  --space-0: 0;    --space-1: 2px;  --space-2: 4px;  --space-3: 6px;
  --space-4: 8px;  --space-5: 10px; --space-6: 12px; --space-7: 14px;
  --space-8: 16px; --space-9: 20px; --space-10: 24px; --space-11: 32px;

  --pane-pad: var(--space-7);
  /* How wide a column of reading gets before the pane stops giving it more. A pane can be 3700px
     on a wide display, and a transcript line 480 characters long is a line nobody reads. A pack
     may raise or lower it; zeroing it is not a thing, so it carries a length rather than a switch. */
  --pane-measure: 1200px;
  /* How wide a form in a detail column gets. */
  --page-measure: 720px;
  --gap-inline: var(--space-3);
  --gap-row: var(--space-4);
  --gap-stack: var(--space-5);
  --gap-section: var(--space-7);
  /* Inline only: control height is --control-h (see .ui-btn in primitives.css). */
  --pad-control: 0 var(--space-4);
  --pad-control-lg: 0 var(--space-6);
  --pad-chip: 0 var(--space-4);
  --pad-cell: var(--space-1) var(--space-3);
  --pad-surface: var(--pane-pad);
  /* Nothing reads these two. They stay because a plugin's style pack is checked against this list, and
     a pack written when they existed would fail to load without them. */
  --pane-pad-y: var(--pane-pad);
  --pad-body: var(--pane-pad);

  /* ── Density ───────────────────────────────────────────────────────────────────────────────
     --row-h-virt is kept separate from --row-h: virtualized lists read it from JS
     (core/client/ui/metrics.ts) because they apply an inline height that beats any CSS rule, so
     the two need to be able to move independently during migration. */
  --row-h: 36px;
  --row-h-sm: 30px;
  --row-h-virt: 36px;
  /* The button height (primitives.css .ui-btn), and the square side of an icon-only one. */
  --control-h: 32px;
  --control-h-sm: 26px;
  --control-h-xs: 20px;
  --topbar-h: 48px;
  --pane-head-h: 48px;
  /* Horizontal tab strips beneath pane headers; pane-level strips use --pane-head-h instead.
     They had four different heights before this token: 40px, 48px, 30px and whatever the terminal
     drawer's line-height came to. A pack keeps it above --control-h, so a button can ride in the
     strip's actions slot, and below --pane-head-h, so a strip under a pane header stays subordinate
     to it. */
  --tab-h: 40px;
  /* Square, and the same side as the pane header the rail runs beside, so a rail button, a pane
     header and the top bar all read as one row height. A pack that moves --pane-head-h moves the
     rails with it. */
  --tabrail-w: var(--pane-head-h);
  --task-footer-h: 27px;
  /* ListDetail's two column genres. The browse list tracks the pane so a wide window gets a wider
     list; the switcher rail holds identifiers and never needs to. Four panes had four values here
     before the primitive existed. */
  --listdetail-w: clamp(200px, 22vw, 300px);
  --listdetail-w-narrow: 150px;
  /* A third genre: a column that is a document rather than a picker. github's browse puts the pull
     request itself between the list and the diff, and it needs prose width. */
  --listdetail-w-wide: clamp(360px, 23vw, 430px);
  /* The control column of a setting row and of a split field, one measure so the two line up. */
  --setting-control-w: 18rem;
  --icon-size: 14px;
  --icon-box: 18px;
  --avatar-sm: 18px;
  --avatar-md: 24px;
  /* Artwork's three presets. The rail picture fits the button's icon box and leaves its corners to the
     status markers; the thumbnail fits a row; both follow a pack that moves either height. */
  --artwork-rail: calc(var(--pane-head-h) - 28px);
  --artwork-thumbnail: calc(var(--row-h) - 8px);
  --artwork-detail: 160px;
  --diff-line-h: 20px;
  /* docs/ui-design/tokens.md § Token axes covers why --term-fs is a bridge token. */
  --term-fs: 15px;

  /* ── Typography ────────────────────────────────────────────────────────────────────────────
     docs/ui-design/tokens.md § Token axes covers why --font-mono cannot be repointed by a style pack.
     --font-glyph keeps the remaining Unicode glyphs (◆ ◇ ⊘ ◉ ▦ ⎇) on the same mono stack even when
     the chrome goes sans, since those box-drawing characters only align there. The brand marks
     that used to be on this list are path data now (ui/brandMarks.ts), but the token still earns
     its keep: pin state, dirty markers, notification and task-state glyphs are all still text. */
  --font-mono: "Berkeley Mono", ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Code", Menlo, Consolas, monospace;
  --font-ui: var(--font-mono);
  --font-glyph: var(--font-mono);
  --font-display: var(--font-ui);

  --fs-2xs: 10px;  --fs-xs: 11px;  --fs-sm: 12px;  --fs: 13px;
  --fs-md: 14px;   --fs-lg: 15px;  --fs-xl: 18px;
  /* What the text of an input is set in. A phone raises it, because iOS Safari zooms the page into an
     input set smaller than 16px (narrow.css). */
  --fs-input: var(--fs-sm);
  --lh: 1.5;  --lh-tight: 1.25;  --lh-diff: 1.45;
  --fw-normal: 400;  --fw-medium: 500;  --fw-semibold: 600;  --fw-bold: 700;

  /* The uppercase-tracked-mono label is the Terminal look. One token turns it into Modern's
     sentence case app-wide: a sweep for uppercase JSX literals finds exactly one hit, and it's
     the acronym "CPU". Everything else is text-transform. */
  --label-transform: uppercase;
  --label-tracking: 0.08em;
  --label-weight: var(--fw-semibold);
  --label-size: var(--fs-sm);
  --heading-transform: none;
  --heading-tracking: 0;
  --heading-weight: var(--fw-semibold);

  /* ── Elevation ─────────────────────────────────────────────────────────────────────────────
     Named recipes, because the six existing geometries are directional rather than a linear ramp.
     Only the colour comes from the theme (--shadow-popover). */
  --shadow-0: none;
  --shadow-1: 0 1px 2px var(--shadow-popover);
  --shadow-2: 0 4px 16px var(--shadow-popover);
  --shadow-3: 0 8px 20px var(--shadow-popover);
  --shadow-4: 0 8px 24px var(--shadow-popover);
  --shadow-5: 0 12px 28px var(--shadow-popover);
  --shadow-drawer-l: -12px 0 28px var(--shadow-popover);
  --shadow-drawer-l-sm: -4px 0 16px var(--shadow-popover);

  --elev-popover: var(--shadow-2);
  --elev-menu: var(--shadow-5);
  --elev-modal: var(--shadow-5);
  --elev-drawer: var(--shadow-drawer-l);
  --elev-panel: var(--shadow-drawer-l-sm);
  --elev-card: var(--shadow-0);      /* Terminal is flat; Modern: var(--shadow-1) */
  --elev-pane: var(--shadow-0);
  --elev-row-hover: var(--shadow-0);

  --ring: inset 0 0 0 var(--bw) var(--accent);
  /* A filled band round a settings section a search result landed on, out to its outline. */
  --ring-highlight: 0 0 0 var(--space-3) var(--bg-selected);
  --focus-ring-w: 2px;
  --focus-ring-offset: 2px;
  --focus-ring-style: solid;

  --scrim-alpha: 0.4;
  --scrim: color-mix(in srgb, var(--scrim-color) calc(var(--scrim-alpha) * 100%), transparent);
  --scrim-filter: none;

  /* Which theme slot fills a role: style-owned indirection, never a colour literal (see the file
     header, above). */
  --card-bg: var(--bg);
  --pane-bg: var(--bg);
  --popover-bg: var(--bg);
  --input-bg: var(--bg-subtle); /* the shared input surface, .integration-key-input's background */
  --chip-bg: var(--bg-subtle);

  /* Shell geometry. --shell-pad + --pane-gap are what turn the flat three-pane grid into inset
     cards with no markup change at all. */
  --shell-pad: 0;
  --pane-gap: 0;
  --pane-radius: var(--radius-surface);

  /* ── Motion ────────────────────────────────────────────────────────────────────────────────
     --hover-lift / --press-scale are applied by the null-default declarations in base.css. See
     docs/ui-design/tokens.md § Style packs for the null-default pattern this follows. */
  --dur-instant: 0ms;  --dur-short: 150ms;  --dur-med: 220ms;  --dur-long: 320ms;
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease-interactive: var(--ease-out);
  --transition-color:
    background-color var(--dur-short) var(--ease-interactive),
    border-color var(--dur-short) var(--ease-interactive),
    color var(--dur-short) var(--ease-interactive);
  --hover-lift: none;
  --press-scale: none;
}

/* Invariant tokens, owned by neither axis. See docs/ui-design/tokens.md § Token axes for why. */
:root {
  /* ── Stacking ladder ───────────────────────────────────────────────────────────────────────
     Collapsed from 28 raw z-index declarations spanning 18 distinct values. See docs/ui-design.md
     § Token axes for why the three orderings below are load-bearing rather than cosmetic. */
  --z-base: 1;
  --z-sticky: 3;
  --z-resizer: 6;
  --z-float: 15;
  --z-rail: 20;
  --z-panel: 30;
  --z-popover: 40;
  --z-drawer: 50;
  --z-drawer-menu: 60;
  --z-overlay: 70;
  --z-modal: 100;
  --z-picker: 110;
  --z-toast: 120;
  --z-tooltip: 1000;

  /* ── Brand marks ───────────────────────────────────────────────────────────────────────────
     What sits on top of a brand colour. The colours themselves ride on the mark now, as `--brand`,
     because core cannot know a third party's hex. See docs/ui-design/icons.md § Icons. */
  --brand-fg: #ffffff;

  /* Numeric alignment: load-bearing for diff gutters, line counts and timestamps in every pack. */
  --tabular: tabular-nums;
}

@media (prefers-color-scheme: dark) {
  :root[data-theme="catppuccin-latte"] {
  --bg: #1e1e2e;        --bg-subtle: #181825;   --bg-hover: #313244;   --bg-selected: #45475a;
  --border: #313244;    --border-strong: #45475a;
  --text: #cdd6f4;      --text-muted: #a6adc8;  --text-faint: #6c7086;
  --accent: #89b4fa;    --focus: #89b4fa;
  --add-bg: #22352b;    --add-marker: #a6e3a1;
  --del-bg: #3a2530;    --del-marker: #f38ba8;
  --add-word-bg: #43653e; --del-word-bg: #6e3648;
  --hunk-bg: #1e2b45;   --hunk-text: #a6adc8;
  --warn: #f9e2af;      --badge-border: #45475a;
  --shadow-popover: rgba(0, 0, 0, 0.42);
  --is-dark: 1;  --color-scheme: dark;
}
}
@font-face { font-family: 'JetBrains Mono'; src: url('/assets/fonts/JetBrainsMono-Regular.2b5410aba498858b.woff2') format('woff2'); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: 'JetBrains Mono'; src: url('/assets/fonts/JetBrainsMono-Bold.a710f827e23cd7b9.woff2') format('woff2'); font-weight: 700; font-style: normal; font-display: swap; }
html:root { --font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', 'Cascadia Code', Menlo, Consolas, monospace; }
/* The account website's own sheet. It comes after acorn's token sheets (src/web/assets.ts) and names a
   token for every color, font, space, size, and border. A value acorn has no token for is a --site-*
   token here. docs/future/ui/style-guide.md has the rules, and test/siteCss.test.ts holds the sheet to
   them. Text is only ever --text: a state shows in a glyph or a rule. */

html:root {
  --site-gutter: var(--space-8);
  --site-tap: 44px;
  --site-code: 32px;
  --site-display: 28px;
  --site-row-h: var(--row-h);
}

/* 40rem, the width below which a page is laid out for a phone. A media query can't read a token. */
@media (max-width: 40rem) {
  html:root {
    --site-gutter: var(--space-6);
    /* iOS Safari zooms the page into an input set smaller than 16 pixels. */
    --fs-input: 16px;
    --site-row-h: 48px;
  }
}

html {
  color-scheme: var(--color-scheme);
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--fs-md);
  line-height: var(--lh);
}

body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  margin: 0;
}

:focus-visible {
  outline: var(--focus-ring-w) var(--focus-ring-style) var(--focus);
  outline-offset: var(--focus-ring-offset);
}

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

h1 {
  margin: 0 0 var(--gap-section);
  font-size: var(--fs-xl);
  font-weight: var(--heading-weight);
}

/* A section label: an h2 so a screen reader still navigates by it. An input's label takes the same type. */
h2 {
  margin: calc(var(--gap-section) * 2) 0 var(--gap-row);
  padding-bottom: var(--space-3);
  border-bottom: var(--chrome-divider);
}

h2,
.site-label {
  font-size: var(--label-size);
  font-weight: var(--label-weight);
  text-transform: var(--label-transform);
  letter-spacing: var(--label-tracking);
}

/* The secondary button, which every button is until a page names another variant. */
button,
.button {
  display: inline-flex;
  align-items: center;
  box-sizing: border-box;
  min-height: var(--control-h);
  padding: var(--pad-control-lg);
  border: var(--control-border);
  border-radius: var(--radius-control);
  background: var(--bg);
  color: var(--text);
  font: inherit;
  text-decoration: none;
  cursor: pointer;
  transition: var(--transition-color);
}

button:hover,
.button:hover {
  background: var(--bg-hover);
}

/* A page's one main action: the inverse of the page, because Latte's accent fails 4.5:1 for text on it. */
.site-primary {
  border-color: transparent;
  background: var(--text);
  color: var(--bg);
}

.site-primary:hover {
  background: var(--text);
  opacity: 0.9;
}

/* A button inside a sentence, such as "Not alice? Sign out", drawn as a link. */
.site-link {
  display: inline;
  min-height: 0;
  padding: 0;
  border: none;
  background: none;
  text-decoration: underline var(--accent);
  text-underline-offset: 1px;
}

.site-link:hover {
  background: none;
}

input {
  box-sizing: border-box;
  min-height: var(--control-h);
  padding: var(--pad-control);
  border: var(--control-border);
  border-radius: var(--radius-control);
  background: var(--input-bg);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-input);
}

input[aria-invalid='true'] {
  border-color: var(--danger);
}

/* The browser sets code and pre in its own monospace size. */
code,
pre {
  font: inherit;
}

time,
code {
  font-variant-numeric: var(--tabular);
}

@media (max-width: 40rem) {
  button,
  .button,
  input {
    min-height: var(--site-tap);
  }
}

@media (prefers-reduced-motion: reduce) {
  * {
    transition: none;
  }
}

/* The frame: the top bar, the column, and the footer. Side padding grows to center a --page-measure column. */

.site-topbar,
.site-main,
.site-footer {
  padding-inline: max(var(--site-gutter), calc((100% - var(--page-measure)) / 2));
}

.site-topbar {
  display: flex;
  align-items: center;
  gap: var(--gap-row);
  min-height: var(--topbar-h);
  border-bottom: var(--chrome-divider);
  background: var(--bg-subtle);
}

.site-home {
  display: flex;
  gap: var(--gap-inline);
  margin-right: auto;
  font-weight: var(--fw-bold);
  text-decoration: none;
}

.site-main {
  flex: 1;
  padding-block: var(--space-11);
}

.site-footer {
  padding-block: var(--space-6);
  border-top: var(--chrome-divider);
  font-size: var(--fs-sm);
}

@media (max-width: 40rem) {
  .site-login {
    display: none;
  }
}

/* The parts in src/web/parts.ts, and the layout around them. */

/* A column for a page that holds one form, such as the approval page. */
.site-narrow {
  max-width: 30rem;
}

.site-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap-row);
  margin-block: var(--gap-section);
}

/* On a phone a page's actions fill the column, one under the other. */
@media (max-width: 40rem) {
  .site-actions {
    flex-direction: column;
  }

  .site-actions button,
  .site-actions .button {
    justify-content: center;
    width: 100%;
  }
}

.site-label {
  display: block;
  margin-bottom: var(--gap-row);
}

.site-detail {
  font-size: var(--fs-sm);
}

.site-field-error {
  margin-block: var(--gap-row);
}

.site-code {
  margin-block: var(--gap-section);
  padding: var(--space-8);
  border: var(--surface-border);
  background: var(--bg-subtle);
  font-size: var(--site-code);
  font-weight: var(--fw-bold);
  letter-spacing: 0.12em;
  text-align: center;
}

/* Read by a screen reader, and not drawn. */
.site-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.site-notice {
  padding: var(--space-6) var(--space-8);
  border-left: var(--stripe-w) solid var(--border);
  background: var(--bg-subtle);
}

.site-notice-ok {
  border-left-color: var(--state-ok);
}

.site-notice-warn {
  border-left-color: var(--state-warn);
}

.site-notice-danger {
  border-left-color: var(--danger);
}

/* A list of rows. A row isn't a link and doesn't change under the pointer: only its button does anything. */
.site-rows {
  margin: 0;
  padding: 0;
  list-style: none;
}

.site-row {
  box-sizing: border-box;
  min-height: var(--site-row-h);
  padding-block: var(--space-4);
  border-bottom: var(--divider);
}

.site-row-title {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--gap-row);
}

.site-row-main {
  margin-right: auto;
}

.site-row strong {
  font-weight: var(--fw-semibold);
}

.site-row .site-detail {
  margin-block: var(--space-2) 0;
}

/* The word beside a glyph names the state. In Latte the ok and warn glyphs are under 3:1 on --bg. */
.site-glyph-ok {
  color: var(--state-ok);
}

.site-glyph-warn {
  color: var(--state-warn);
}

.site-glyph-neutral {
  color: var(--border-strong);
}

/* The second press of a destructive action, which names the verb and the thing. */
.site-danger {
  border-color: transparent;
  background: var(--danger);
  color: var(--bg);
}

.site-danger:hover {
  background: var(--danger);
  opacity: 0.9;
}

.site-confirm > summary {
  list-style: none;
}

.site-confirm > summary::-webkit-details-marker {
  display: none;
}

/* Hidden until its <details> opens, then the full width of the row under the title line. */
.site-confirm-body {
  display: none;
  flex-basis: 100%;
}

.site-confirm[open] + .site-confirm-body {
  display: block;
}

.site-empty {
  padding-block: var(--gap-section);
  text-align: center;
}

.site-mark {
  margin: 0 0 var(--gap-section);
}

/* The sign-in page's heading, a step above the other pages' because it's the only thing on the page. */
.site-display {
  font-size: var(--site-display);
  font-weight: var(--fw-bold);
}

.site-empty .site-mark {
  display: inline-block;
  margin: 0;
  text-align: left;
}

.site-empty h3 {
  margin: var(--gap-section) 0 var(--gap-row);
  font-size: var(--fs-lg);
}

.site-empty ol {
  display: inline-block;
  margin: 0;
  text-align: left;
}
